skycraft: a space stuff PoC
- kepler-based elliptical orbits - with visualization - and some sort of user controls - based on wmc
This commit is contained in:
parent
0b45bfe1eb
commit
282d91468e
93
skycraft/index.html
Normal file
93
skycraft/index.html
Normal file
@ -0,0 +1,93 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>WebMinecraft</title>
|
||||
<script src="app.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: "Google Sans Text", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.params {
|
||||
background-color: cadetblue;
|
||||
width: fit-content;
|
||||
}
|
||||
.collapsible {
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
font-size: medium;
|
||||
}
|
||||
.paramContent {
|
||||
display: flex;
|
||||
overflow-y: hidden;
|
||||
height: 0px;
|
||||
}
|
||||
.paramPanel {
|
||||
padding: 10px;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="game" width="1024" height="768"></canvas>
|
||||
<div id="fps"></div>
|
||||
|
||||
<div class="params">
|
||||
<button class="collapsible">Parameters</button>
|
||||
<div class="paramContent">
|
||||
<div class="paramPanel">
|
||||
<h3>Light</h3>
|
||||
<h4>Direction</h4>
|
||||
<p>
|
||||
x:
|
||||
<input type="range" min="-100" max="100" value="50" class="slider" id="lightx" />
|
||||
</p>
|
||||
<p>
|
||||
y:
|
||||
<input type="range" min="-100" max="100" value="50" class="slider" id="lighty" />
|
||||
</p>
|
||||
<p>
|
||||
z:
|
||||
<input type="range" min="-100" max="100" value="50" class="slider" id="lightz" />
|
||||
</p>
|
||||
<p>
|
||||
<div id="lightDirVec"></div>
|
||||
</p>
|
||||
<h4>Ambiant light amount</h4>
|
||||
<p>
|
||||
<input type="range" min="0" max="100" value="50" class="slider" id="ambiant" />
|
||||
</p>
|
||||
<h4>Sky color</h4>
|
||||
<p>
|
||||
r:
|
||||
<input type="range" min="0" max="255" value="115" class="slider" id="skyr" />
|
||||
</p>
|
||||
<p>
|
||||
g:
|
||||
<input type="range" min="0" max="255" value="191" class="slider" id="skyg" />
|
||||
</p>
|
||||
<p>
|
||||
b:
|
||||
<input type="range" min="0" max="255" value="255" class="slider" id="skyb" />
|
||||
</p>
|
||||
<p>
|
||||
<div id="skyColor"></div>
|
||||
</div>
|
||||
<div class="paramPanel">
|
||||
<h3>Physics</h3>
|
||||
<p>
|
||||
Gravity: <span id="gravityValue"></span><input type="range" min="-100" max="100" value="-10" class="slider" id="gravity" />
|
||||
</p>
|
||||
<p>
|
||||
Jump force: <span id="jumpForceValue"></span><input type="range" min="-100" max="100" value="10" class="slider" id="jumpForce" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
695
skycraft/index.js
Normal file
695
skycraft/index.js
Normal file
@ -0,0 +1,695 @@
|
||||
//import { initUiListeners, setupParamPanel, tick } from './game';
|
||||
//import { initWorldGl, makeWorld } from './world';
|
||||
import * as se3 from '../se3';
|
||||
import {loadTexture, makeProgram} from '../gl';
|
||||
import {makeFace, makeBufferFromFaces} from '../geometry';
|
||||
|
||||
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;
|
||||
|
||||
varying highp vec2 vTextureCoord;
|
||||
varying lowp vec3 vLighting;
|
||||
varying lowp float vDistance;
|
||||
|
||||
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;
|
||||
|
||||
vTextureCoord = aTextureCoord;
|
||||
|
||||
vDistance = length(modelview * vec4(aPosition, 1.0));
|
||||
}
|
||||
`;
|
||||
|
||||
const FSHADER = `
|
||||
uniform sampler2D uSampler;
|
||||
uniform lowp vec3 uFogColor;
|
||||
|
||||
varying highp vec2 vTextureCoord;
|
||||
varying lowp vec3 vLighting;
|
||||
varying lowp float vDistance;
|
||||
|
||||
void main() {
|
||||
highp vec4 color = texture2D(uSampler, vTextureCoord);
|
||||
if (color.a < 0.1) {
|
||||
discard;
|
||||
}
|
||||
lowp float fogamount = smoothstep(80.0, 100.0, vDistance);
|
||||
gl_FragColor = mix(vec4(vLighting * color.rgb, color.a), vec4(uFogColor, 1.0), fogamount);
|
||||
}
|
||||
`;
|
||||
|
||||
const kEpoch = 0;
|
||||
|
||||
async function initWorldGl(gl) {
|
||||
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 fogColorLoc = gl.getUniformLocation(program, 'uFogColor');
|
||||
const lightDirectionLoc = gl.getUniformLocation(program, 'uLightDirection');
|
||||
const ambiantLoc = gl.getUniformLocation(program, 'uAmbiantLight');
|
||||
|
||||
const positionLoc = gl.getAttribLocation(program, 'aPosition');
|
||||
const normalLoc = gl.getAttribLocation(program, 'aNormal');
|
||||
const textureLoc = gl.getAttribLocation(program, 'aTextureCoord');
|
||||
|
||||
const setupScene = (sceneParams) => {
|
||||
const {
|
||||
projectionMatrix,
|
||||
viewMatrix,
|
||||
fogColor,
|
||||
lightDirection,
|
||||
ambiantLightAmount,
|
||||
} = sceneParams;
|
||||
|
||||
gl.useProgram(program);
|
||||
|
||||
gl.uniformMatrix4fv(projLoc, false, new Float32Array(projectionMatrix));
|
||||
gl.uniformMatrix4fv(viewLoc, false, new Float32Array(viewMatrix));
|
||||
gl.uniform3fv(fogColorLoc, fogColor);
|
||||
gl.uniform3fv(lightDirectionLoc, lightDirection);
|
||||
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,
|
||||
} = 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(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, 1);
|
||||
}
|
||||
`;
|
||||
|
||||
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 initUiListeners(canvas, context) {
|
||||
const canvasClickHandler = () => {
|
||||
canvas.requestPointerLock();
|
||||
canvas.onclick = null;
|
||||
// const clickListener = e => {
|
||||
// switch(e.button) {
|
||||
// case 0: // left click
|
||||
// destroySelectedBlock(context);
|
||||
// break;
|
||||
// case 2: // right click
|
||||
// makeDirBlock(context);
|
||||
// break;
|
||||
// }
|
||||
// };
|
||||
const clickListener = e => {};
|
||||
const keyListener = e => {
|
||||
if (e.type === 'keydown') {
|
||||
if (e.repeat) return;
|
||||
context.keys.add(e.code);
|
||||
|
||||
switch (e.code) {
|
||||
case 'KeyF':
|
||||
// context.flying = !context.flying;
|
||||
return false;
|
||||
case 'Space':
|
||||
if (!context.flying) {
|
||||
if (context.jumpAmount > 0) {
|
||||
const amount = context.jumpForce;
|
||||
context.camera.velocity[1] = amount;
|
||||
context.jumpAmount -= 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
context.keys.delete(e.code);
|
||||
}
|
||||
};
|
||||
const moveListener = e => {
|
||||
context.camera.orientation[1] -= e.movementX / 500;
|
||||
context.camera.orientation[0] -= e.movementY / 500;
|
||||
|
||||
context.camera.orientation[0] = Math.min(Math.max(
|
||||
context.camera.orientation[0], -Math.PI / 2
|
||||
), Math.PI / 2);
|
||||
};
|
||||
const changeListener = () => {
|
||||
if (document.pointerLockElement === canvas) {
|
||||
return;
|
||||
}
|
||||
document.removeEventListener('pointerdown', clickListener);
|
||||
document.removeEventListener('pointerlockchange', changeListener);
|
||||
document.removeEventListener('pointermove', moveListener);
|
||||
document.removeEventListener('keydown', keyListener);
|
||||
document.removeEventListener('keyup', keyListener);
|
||||
canvas.onclick = canvasClickHandler;
|
||||
};
|
||||
document.addEventListener('pointerdown', clickListener);
|
||||
document.addEventListener('pointerlockchange', changeListener);
|
||||
document.addEventListener('pointermove', moveListener);
|
||||
document.addEventListener('keydown', keyListener);
|
||||
document.addEventListener('keyup', keyListener);
|
||||
};
|
||||
canvas.onclick = canvasClickHandler;
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.repeat) return;
|
||||
switch (e.code) {
|
||||
case 'F11':
|
||||
canvas.requestFullscreen();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleInput(context) {
|
||||
const move = (forward, right) => {
|
||||
const dir = [right, 0, -forward, 1.0];
|
||||
const ori = se3.roty(context.camera.orientation[1]);
|
||||
const tf = se3.apply(ori, dir);
|
||||
const maxSpeed = 8;
|
||||
const airMovement = 0.08;
|
||||
|
||||
if (context.flying) {
|
||||
context.camera.position[0] += tf[0] / 60;
|
||||
context.camera.position[2] += tf[2] / 60;
|
||||
}
|
||||
if (context.isOnGround) {
|
||||
context.camera.velocity[0] = tf[0];
|
||||
context.camera.velocity[2] = tf[2];
|
||||
} else {
|
||||
const vel = context.camera.velocity;
|
||||
|
||||
vel[0] += tf[0] * airMovement;
|
||||
vel[2] += tf[2] * airMovement;
|
||||
|
||||
const curVel = Math.sqrt(vel[0] * vel[0] + vel[2] * vel[2]);
|
||||
|
||||
if (curVel > maxSpeed) {
|
||||
vel[0] *= maxSpeed / curVel;
|
||||
vel[2] *= maxSpeed / curVel;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
context.keys.forEach(key => {
|
||||
switch (key) {
|
||||
case 'KeyW':
|
||||
move(8, 0.0);
|
||||
return;
|
||||
case 'KeyA':
|
||||
move(0.0, -8);
|
||||
return;
|
||||
case 'KeyS':
|
||||
move(-8, 0.0);
|
||||
return;
|
||||
case 'KeyD':
|
||||
move(0.0, 8);
|
||||
return;
|
||||
|
||||
case 'Space':
|
||||
if (context.flying) {
|
||||
context.camera.position[1] += 8 / 60;
|
||||
}
|
||||
return;
|
||||
|
||||
case 'ShiftLeft':
|
||||
context.camera.position[1] -= 8 / 60;
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updatePhysics(time, context) {
|
||||
}
|
||||
|
||||
function updateGeometry(context, timeout_ms = 10) {
|
||||
}
|
||||
|
||||
function normalizeAngle(theta) {
|
||||
const twopi = 2 * Math.PI;
|
||||
return theta - twopi * Math.floor((theta + twopi) / twopi);
|
||||
}
|
||||
|
||||
/** Let's be honest I should clean this up.
|
||||
* Right now it mostly only works with elliptical orbits (e < 1),
|
||||
* which is fine for planets & stuff, but not for my spacecraft (or comets)
|
||||
*
|
||||
* This is the part that solves Kepler's equation using Newton's method.
|
||||
* For circular-ish orbits, one or two iterations are usually enough.
|
||||
* More excentric orbits can take more (6 or 7?).
|
||||
*/
|
||||
function getCartesianPosition(orbit, mu, time) {
|
||||
const {
|
||||
excentricity: e,
|
||||
semimajorAxis: a,
|
||||
inclination: i,
|
||||
ascendingNodeLongitude: Om,
|
||||
periapsisArgument: w,
|
||||
} = orbit;
|
||||
const n = Math.sqrt(mu/(a**3)); // mean motion
|
||||
const M = n * time; // mean anomaly
|
||||
// const nu = (
|
||||
// M
|
||||
// + (2 * e - e**3 / 4) * Math.sin(M)
|
||||
// + 5/4 * e**2 * Math.sin(2*M)
|
||||
// + 13/12 * e**3 * Math.sin(3*M)
|
||||
// ); // true anomaly, doesn't work :(
|
||||
|
||||
// Newton's method
|
||||
var E2;
|
||||
var E = M;
|
||||
for (var j = 1; j < 20; ++j) {
|
||||
if (e < 0.001) {
|
||||
break;
|
||||
}
|
||||
if (e < 1) {
|
||||
E2 = E - (E - e * Math.sin(E) - M) / (1 - e * Math.cos(E));
|
||||
} else if (e > 1) {
|
||||
E2 = E - (-E + e * sinh(E) - M) / (e * cosh(E) - 1);
|
||||
} else {
|
||||
E2 = E - (E + E*E*E/3 - M) / (1 + E*E);
|
||||
}
|
||||
if (Math.abs(E - E2) < 1e-10) {
|
||||
break;
|
||||
}
|
||||
E = E2;
|
||||
}
|
||||
const nu = 2 * Math.atan(Math.sqrt((1+e) / (1-e)) * Math.tan(E/2));
|
||||
const r = a * (1 - e**2) / (1 + e * Math.cos(nu));
|
||||
|
||||
const cOm = Math.cos(Om);
|
||||
const sOm = Math.sin(Om);
|
||||
const cwnu = Math.cos(w + nu);
|
||||
const swnu = Math.sin(w + nu);
|
||||
|
||||
const x = r * Math.cos(i) * (cOm * cwnu - sOm * swnu);
|
||||
const y = r * (sOm * cwnu + cOm * swnu);
|
||||
const z = r * Math.sin(i) * cwnu;
|
||||
|
||||
return [x, y, z];
|
||||
}
|
||||
|
||||
function getOrientation(body, time) {
|
||||
return se3.rotxyz(
|
||||
body.spin[0] * time,
|
||||
body.spin[1] * time,
|
||||
body.spin[2] * time,
|
||||
);
|
||||
}
|
||||
|
||||
function makeOrbitObject(context, orbit, parentPosition) {
|
||||
const {gl} = context;
|
||||
const position = parentPosition;
|
||||
const glContext = context.orbitGlContext;
|
||||
const orientation = [
|
||||
se3.rotz(orbit.ascendingNodeLongitude),
|
||||
se3.roty(-orbit.inclination),
|
||||
se3.rotz(orbit.periapsisArgument),
|
||||
].reduce(se3.product);
|
||||
|
||||
const buffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
|
||||
const a = orbit.semimajorAxis;
|
||||
const b = a * Math.sqrt(1 - orbit.excentricity**2);
|
||||
|
||||
const x = - orbit.semimajorAxis * orbit.excentricity;
|
||||
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
||||
x-a, -b, 0, -1, -1,
|
||||
x-a, +b, 0, -1, +1,
|
||||
x+a, -b, 0, +1, -1,
|
||||
x+a, +b, 0, +1, +1,
|
||||
]), gl.STATIC_DRAW);
|
||||
|
||||
const geometry = {
|
||||
glBuffer: buffer,
|
||||
numVertices: 4,
|
||||
delete: () => gl.deleteBuffer(buffer),
|
||||
};
|
||||
|
||||
return {
|
||||
geometry,
|
||||
orientation,
|
||||
position,
|
||||
glContext,
|
||||
};
|
||||
}
|
||||
|
||||
function getObjects(context, body, time, parentBody, parentPosition) {
|
||||
const kGravitationalConstant = 6.674e-11;
|
||||
const objects = [];
|
||||
const {gl, glContext} = context;
|
||||
let position = [0, 0, 0];
|
||||
if (parentBody !== undefined) {
|
||||
// const mu = kGravitationalConstant * parentBody.mass;
|
||||
const mu = 10;
|
||||
const coord = getCartesianPosition(body.orbit, mu, time);
|
||||
position = [
|
||||
parentPosition[0] + coord[0],
|
||||
parentPosition[1] + coord[1],
|
||||
parentPosition[2] + coord[2],
|
||||
];
|
||||
|
||||
objects.push(makeOrbitObject(context, body.orbit, parentPosition));
|
||||
}
|
||||
|
||||
objects.push({
|
||||
geometry: makeBufferFromFaces(gl, body.geometry),
|
||||
orientation: getOrientation(body, time),
|
||||
position,
|
||||
glContext,
|
||||
});
|
||||
|
||||
if (body.children !== undefined) {
|
||||
for (const child of body.children) {
|
||||
objects.push(...getObjects(context, child, time, body, position));
|
||||
}
|
||||
}
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
function draw(context, time) {
|
||||
const {gl, camera, universe} = context;
|
||||
const objects = getObjects(context, universe, time * 0.001);
|
||||
|
||||
gl.clearColor(...context.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 camrot = camera.orientation;
|
||||
const campos = camera.position;
|
||||
const viewMatrix = se3.product(
|
||||
se3.rotxyz(-camrot[0], -camrot[1], -camrot[2]),
|
||||
se3.translation(-campos[0], -campos[1], -campos[2])
|
||||
);
|
||||
|
||||
let lastGlContext;
|
||||
|
||||
for (const {position, orientation, geometry, glContext} of objects) {
|
||||
if (glContext !== lastGlContext) {
|
||||
glContext.setupScene({
|
||||
projectionMatrix: context.projMatrix,
|
||||
viewMatrix,
|
||||
fogColor: context.skyColor,
|
||||
lightDirection: context.lightDirection,
|
||||
ambiantLightAmount: context.ambiantLight,
|
||||
});
|
||||
}
|
||||
|
||||
lastGlContext = glContext;
|
||||
|
||||
glContext.drawObject({
|
||||
position,
|
||||
orientation,
|
||||
glBuffer: geometry.glBuffer,
|
||||
numVertices: geometry.numVertices,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function tick(time, context) {
|
||||
handleInput(context);
|
||||
updatePhysics(time, context);
|
||||
|
||||
const campos = context.camera.position;
|
||||
|
||||
// world generation / geometry update
|
||||
{
|
||||
// frame time is typically 16.7ms, so this may lag a bit
|
||||
let timeLeft = 10;
|
||||
const start = performance.now();
|
||||
updateGeometry(context, timeLeft);
|
||||
}
|
||||
|
||||
draw(context, time);
|
||||
|
||||
const dt = (time - context.lastFrameTime) * 0.001;
|
||||
context.lastFrameTime = time;
|
||||
|
||||
document.querySelector('#fps').textContent = `${1.0 / dt} fps`;
|
||||
|
||||
requestAnimationFrame(time => tick(time, context));
|
||||
}
|
||||
|
||||
function makeCube(texture) {
|
||||
return [
|
||||
makeFace('-x', texture, [-0.5, 0, 0]),
|
||||
makeFace('+x', texture, [+0.5, 0, 0]),
|
||||
makeFace('-y', texture, [0, -0.5, 0]),
|
||||
makeFace('+y', texture, [0, +0.5, 0]),
|
||||
makeFace('-z', texture, [0, 0, -0.5]),
|
||||
makeFace('+z', texture, [0, 0, +0.5]),
|
||||
];
|
||||
}
|
||||
|
||||
function makeObjects(gl) {
|
||||
const texture = [0, 4];
|
||||
const faces = [
|
||||
makeFace('-x', texture, [-0.5, 0, 0]),
|
||||
makeFace('+x', texture, [+0.5, 0, 0]),
|
||||
makeFace('-y', texture, [0, -0.5, 0]),
|
||||
makeFace('+y', texture, [0, +0.5, 0]),
|
||||
makeFace('-z', texture, [0, 0, -0.5]),
|
||||
makeFace('+z', texture, [0, 0, +0.5]),
|
||||
];
|
||||
return [
|
||||
{
|
||||
geometry: makeBufferFromFaces(gl, faces),
|
||||
orientation: [0, 0, 0],
|
||||
position: [0, 0, 0],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function makeSolarSystem(gl) {
|
||||
return {
|
||||
mass: 1.0,
|
||||
spin: [0, 1.0, 0],
|
||||
geometry: makeCube([0, 4]),
|
||||
children: [
|
||||
{
|
||||
mass: 0.1,
|
||||
spin: [0.2, 0.0, 0.0],
|
||||
geometry: makeCube([0, 8]),
|
||||
orbit: {
|
||||
excentricity: 0,
|
||||
semimajorAxis: 3,
|
||||
inclination: 0,
|
||||
ascendingNodeLongitude: 0,
|
||||
periapsisArgument: 0,
|
||||
trueAnomaly: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
mass: 0.1,
|
||||
spin: [0.2, 0.0, 0.0],
|
||||
geometry: makeCube([0, 1]),
|
||||
orbit: {
|
||||
excentricity: 0.8,
|
||||
semimajorAxis: 5,
|
||||
inclination: 0,
|
||||
ascendingNodeLongitude: 0,
|
||||
periapsisArgument: 0,
|
||||
trueAnomaly: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
mass: 0.1,
|
||||
spin: [0.0, 0.0, 1.0],
|
||||
geometry: makeCube([9, 9]),
|
||||
orbit: {
|
||||
excentricity: 0.3,
|
||||
semimajorAxis: 5,
|
||||
inclination: 1.0,
|
||||
ascendingNodeLongitude: 0,
|
||||
periapsisArgument: 0,
|
||||
trueAnomaly: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const canvas = document.querySelector('#game');
|
||||
// adjust canvas aspect ratio to that of the screen
|
||||
canvas.height = screen.height / screen.width * canvas.width;
|
||||
const gl = canvas.getContext('webgl');
|
||||
|
||||
if (gl === null) {
|
||||
console.error('webgl not available')
|
||||
return;
|
||||
}
|
||||
|
||||
const context = {
|
||||
gl,
|
||||
projMatrix: se3.perspective(Math.PI / 3, canvas.clientWidth / canvas.clientHeight, 0.1, 100.0),
|
||||
camera: {
|
||||
position: [0.0, 0.0, 2.0],
|
||||
orientation: [0.0, 0.0, 0.0],
|
||||
velocity: [0, 0, 0],
|
||||
},
|
||||
keys: new Set(),
|
||||
lightDirection: [-0.2, -0.5, 0.4],
|
||||
skyColor: [0.10, 0.15, 0.2],
|
||||
ambiantLight: 0.7,
|
||||
blockSelectDistance: 8,
|
||||
flying: true,
|
||||
isOnGround: false,
|
||||
gravity: -17,
|
||||
jumpForce: 6.5,
|
||||
// objects: makeObjects(gl),
|
||||
universe: makeSolarSystem(gl),
|
||||
};
|
||||
|
||||
context.glContext = await initWorldGl(gl);
|
||||
context.orbitGlContext = getOrbitDrawContext(gl);
|
||||
initUiListeners(canvas, context);
|
||||
|
||||
// setupParamPanel(context);
|
||||
|
||||
requestAnimationFrame(time => tick(time, context));
|
||||
}
|
||||
|
||||
window.onload = main;
|
14
skycraft/package.json
Normal file
14
skycraft/package.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "skycraft",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.14.2"
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "esbuild --outfile=app.js index.js --bundle --sourcemap=inline --watch",
|
||||
"serve": "esbuild --outfile=app.js index.js --bundle --sourcemap=inline --servedir=.",
|
||||
"build": "esbuild --outfile=app.js index.js --bundle --minify"
|
||||
}
|
||||
}
|
1
skycraft/texture.png
Symbolic link
1
skycraft/texture.png
Symbolic link
@ -0,0 +1 @@
|
||||
../texture.png
|
135
skycraft/yarn.lock
Normal file
135
skycraft/yarn.lock
Normal file
@ -0,0 +1,135 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@esbuild/linux-loong64@0.14.54":
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028"
|
||||
integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==
|
||||
|
||||
esbuild-android-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be"
|
||||
integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==
|
||||
|
||||
esbuild-android-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771"
|
||||
integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==
|
||||
|
||||
esbuild-darwin-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25"
|
||||
integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==
|
||||
|
||||
esbuild-darwin-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73"
|
||||
integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==
|
||||
|
||||
esbuild-freebsd-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d"
|
||||
integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==
|
||||
|
||||
esbuild-freebsd-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48"
|
||||
integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==
|
||||
|
||||
esbuild-linux-32@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5"
|
||||
integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==
|
||||
|
||||
esbuild-linux-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652"
|
||||
integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==
|
||||
|
||||
esbuild-linux-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b"
|
||||
integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==
|
||||
|
||||
esbuild-linux-arm@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59"
|
||||
integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==
|
||||
|
||||
esbuild-linux-mips64le@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34"
|
||||
integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==
|
||||
|
||||
esbuild-linux-ppc64le@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e"
|
||||
integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==
|
||||
|
||||
esbuild-linux-riscv64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8"
|
||||
integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==
|
||||
|
||||
esbuild-linux-s390x@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6"
|
||||
integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==
|
||||
|
||||
esbuild-netbsd-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81"
|
||||
integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==
|
||||
|
||||
esbuild-openbsd-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b"
|
||||
integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==
|
||||
|
||||
esbuild-sunos-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da"
|
||||
integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==
|
||||
|
||||
esbuild-windows-32@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31"
|
||||
integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==
|
||||
|
||||
esbuild-windows-64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4"
|
||||
integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==
|
||||
|
||||
esbuild-windows-arm64@0.14.54:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982"
|
||||
integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==
|
||||
|
||||
esbuild@^0.14.2:
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2"
|
||||
integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==
|
||||
optionalDependencies:
|
||||
"@esbuild/linux-loong64" "0.14.54"
|
||||
esbuild-android-64 "0.14.54"
|
||||
esbuild-android-arm64 "0.14.54"
|
||||
esbuild-darwin-64 "0.14.54"
|
||||
esbuild-darwin-arm64 "0.14.54"
|
||||
esbuild-freebsd-64 "0.14.54"
|
||||
esbuild-freebsd-arm64 "0.14.54"
|
||||
esbuild-linux-32 "0.14.54"
|
||||
esbuild-linux-64 "0.14.54"
|
||||
esbuild-linux-arm "0.14.54"
|
||||
esbuild-linux-arm64 "0.14.54"
|
||||
esbuild-linux-mips64le "0.14.54"
|
||||
esbuild-linux-ppc64le "0.14.54"
|
||||
esbuild-linux-riscv64 "0.14.54"
|
||||
esbuild-linux-s390x "0.14.54"
|
||||
esbuild-netbsd-64 "0.14.54"
|
||||
esbuild-openbsd-64 "0.14.54"
|
||||
esbuild-sunos-64 "0.14.54"
|
||||
esbuild-windows-32 "0.14.54"
|
||||
esbuild-windows-64 "0.14.54"
|
||||
esbuild-windows-arm64 "0.14.54"
|
Loading…
Reference in New Issue
Block a user