initial rendering test
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
*.iml
|
||||||
|
.idea
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Minecraft</title>
|
||||||
|
<link rel="stylesheet" href="style.css"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="canvas-container"></div>
|
||||||
|
<span id="pre-status">Loading page...</span>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script src="src/start.js"></script>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
Vendored
+6
File diff suppressed because one or more lines are too long
@@ -0,0 +1,30 @@
|
|||||||
|
window.GameWindow = class {
|
||||||
|
|
||||||
|
constructor(renderer, canvasWrapperId) {
|
||||||
|
this.renderer = renderer;
|
||||||
|
this.canvasWrapperId = canvasWrapperId;
|
||||||
|
|
||||||
|
// Add web renderer canvas to wrapper
|
||||||
|
document.getElementById(this.canvasWrapperId).appendChild(renderer.canvasElement);
|
||||||
|
|
||||||
|
// Init
|
||||||
|
this.initialize();
|
||||||
|
|
||||||
|
// On resize
|
||||||
|
let scope = this;
|
||||||
|
window.addEventListener('resize', _ => scope.initialize(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
// Get canvas size
|
||||||
|
let canvasElement = document.getElementById(this.canvasWrapperId);
|
||||||
|
this.canvasWidth = canvasElement.offsetWidth;
|
||||||
|
this.canvasHeight = canvasElement.offsetHeight;
|
||||||
|
|
||||||
|
// Adjust camera
|
||||||
|
this.renderer.camera.aspect = this.canvasWidth / this.canvasHeight;
|
||||||
|
this.renderer.camera.updateProjectionMatrix();
|
||||||
|
this.renderer.webRenderer.setSize(this.canvasWidth, this.canvasHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
window.Minecraft = class {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Minecraft instance and render it on a canvas
|
||||||
|
*/
|
||||||
|
constructor(canvasWrapperId) {
|
||||||
|
this.worldRenderer = new WorldRenderer(this);
|
||||||
|
this.window = new GameWindow(this.worldRenderer, canvasWrapperId);
|
||||||
|
this.timer = new Timer(20);
|
||||||
|
|
||||||
|
this.frames = 0;
|
||||||
|
this.lastTime = Date.now();
|
||||||
|
|
||||||
|
this.world = new World();
|
||||||
|
this.worldRenderer.scene.add(this.world.group);
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.running = true;
|
||||||
|
this.requestNextFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
requestNextFrame() {
|
||||||
|
let scope = this;
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
if (scope.running) {
|
||||||
|
scope.requestNextFrame();
|
||||||
|
scope.onRender();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onRender() {
|
||||||
|
// Update the timer
|
||||||
|
this.timer.advanceTime();
|
||||||
|
|
||||||
|
// Call the tick to reach updates 20 per seconds
|
||||||
|
for (let i = 0; i < this.timer.ticks; i++) {
|
||||||
|
this.onTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the game
|
||||||
|
this.worldRenderer.render(this.timer.partialTicks);
|
||||||
|
|
||||||
|
// Increase rendered frame
|
||||||
|
this.frames++;
|
||||||
|
|
||||||
|
// Loop if a second passed
|
||||||
|
while (Date.now() >= this.lastTime + 1000) {
|
||||||
|
console.log(this.frames + " fps");
|
||||||
|
|
||||||
|
this.lastTime += 1000;
|
||||||
|
this.frames = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTick() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
window.WorldRenderer = class {
|
||||||
|
|
||||||
|
constructor(minecraft) {
|
||||||
|
this.minecraft = minecraft;
|
||||||
|
|
||||||
|
this.supportWebGL = !!WebGLRenderingContext
|
||||||
|
&& (!!document.createElement('canvas').getContext('experimental-webgl')
|
||||||
|
|| !!document.createElement('canvas').getContext('webgl'));
|
||||||
|
|
||||||
|
// Create cameras
|
||||||
|
this.camera = new THREE.PerspectiveCamera(85, 1, 1, 10000);
|
||||||
|
this.camera.position.set(0, 3, 0);
|
||||||
|
this.camera.up = new THREE.Vector3(0, 0, 1);
|
||||||
|
|
||||||
|
// Create scene
|
||||||
|
this.scene = new THREE.Scene();
|
||||||
|
|
||||||
|
// Create web renderer
|
||||||
|
this.canvasElement = document.createElement('canvas')
|
||||||
|
this.webRenderer = this.supportWebGL ? new THREE.WebGLRenderer({
|
||||||
|
canvas: this.canvasElement,
|
||||||
|
antialias: true
|
||||||
|
}) : new THREE.CanvasRenderer({
|
||||||
|
canvas: this.canvasElement,
|
||||||
|
antialias: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this.webRenderer.shadowMap.enabled = true;
|
||||||
|
this.webRenderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
|
||||||
|
this.webRenderer.autoClear = false;
|
||||||
|
this.webRenderer.setClearColor(0x000000, 0);
|
||||||
|
this.webRenderer.clear();
|
||||||
|
|
||||||
|
const nightLight = new THREE.AmbientLight(0x888888, 1.0);
|
||||||
|
this.scene.add(nightLight);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(partialTicks) {
|
||||||
|
let world = this.minecraft.world;
|
||||||
|
|
||||||
|
const xKeys = Object.keys(world.chunks)
|
||||||
|
for (let x = 0; x < xKeys.length; x++) {
|
||||||
|
|
||||||
|
let zArray = world.chunks[xKeys[x]];
|
||||||
|
const zKeys = Object.keys(zArray)
|
||||||
|
|
||||||
|
for (let z = 0; z < zKeys.length; z++) {
|
||||||
|
let chunk = zArray[zKeys[z]];
|
||||||
|
|
||||||
|
for (let y = 0; y < chunk.sections.length; y++) {
|
||||||
|
let section = chunk.sections[y];
|
||||||
|
|
||||||
|
if (section.dirty) {
|
||||||
|
section.rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render window
|
||||||
|
this.webRenderer.render(this.scene, this.camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
window.Chunk = class {
|
||||||
|
|
||||||
|
constructor(x, z) {
|
||||||
|
this.group = new THREE.Object3D();
|
||||||
|
|
||||||
|
this.x = x;
|
||||||
|
this.z = z;
|
||||||
|
|
||||||
|
// Initialize sections
|
||||||
|
this.sections = [];
|
||||||
|
for (let y = 0; y < 16; y++) {
|
||||||
|
let section = new ChunkSection(x, y, z);
|
||||||
|
|
||||||
|
this.sections[y] = section;
|
||||||
|
this.group.add(section.group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSection(y) {
|
||||||
|
return this.sections[y];
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuild() {
|
||||||
|
for (let y = 0; y < this.sections.length; y++) {
|
||||||
|
this.sections[y].rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queueForRebuild() {
|
||||||
|
for (let y = 0; y < this.sections.length; y++) {
|
||||||
|
this.sections[y].queueForRebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
window.ChunkSection = class {
|
||||||
|
|
||||||
|
static get SIZE() {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(x, y, z) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
|
||||||
|
this.group = new THREE.Object3D();
|
||||||
|
this.dirty = true;
|
||||||
|
|
||||||
|
this.blocks = [];
|
||||||
|
for (let x = 0; x < ChunkSection.SIZE; x++) {
|
||||||
|
for (let y = 0; y < ChunkSection.SIZE; y++) {
|
||||||
|
for (let z = 0; z < ChunkSection.SIZE; z++) {
|
||||||
|
this.setBlockAt(x, y, z, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuild() {
|
||||||
|
this.dirty = false;
|
||||||
|
this.group.clear();
|
||||||
|
|
||||||
|
for (let x = 0; x < ChunkSection.SIZE; x++) {
|
||||||
|
for (let y = 0; y < ChunkSection.SIZE; y++) {
|
||||||
|
for (let z = 0; z < ChunkSection.SIZE; z++) {
|
||||||
|
let typeId = this.getBlockAt(x, y, z);
|
||||||
|
|
||||||
|
if (typeId !== 0) {
|
||||||
|
let absoluteX = this.x * ChunkSection.SIZE + x;
|
||||||
|
let absoluteY = this.y * ChunkSection.SIZE + y;
|
||||||
|
let absoluteZ = this.z * ChunkSection.SIZE + z;
|
||||||
|
|
||||||
|
// Debug stuff
|
||||||
|
let color = 0x888888 | (Math.random() * 223);
|
||||||
|
|
||||||
|
let geometry = new THREE.BoxGeometry(1, 1, 1);
|
||||||
|
let material = new THREE.MeshBasicMaterial({
|
||||||
|
color: color
|
||||||
|
});
|
||||||
|
|
||||||
|
let cube = new THREE.Mesh(geometry, material);
|
||||||
|
cube.position.set(absoluteX - 0.5, absoluteY - 0.5, absoluteZ - 0.5);
|
||||||
|
|
||||||
|
this.group.add(cube);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getBlockAt(x, y, z) {
|
||||||
|
let index = y << 8 | z << 4 | x;
|
||||||
|
return this.blocks[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
setBlockAt(x, y, z, typeId) {
|
||||||
|
let index = y << 8 | z << 4 | x;
|
||||||
|
this.blocks[index] = typeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
queueForRebuild() {
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
window.World = class {
|
||||||
|
|
||||||
|
static get TOTAL_HEIGHT() {
|
||||||
|
return ChunkSection.SIZE * 16 - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.group = new THREE.Object3D();
|
||||||
|
this.chunks = [];
|
||||||
|
|
||||||
|
for (let x = -16; x < 16; x++) {
|
||||||
|
for (let z = -16; z < 16; z++) {
|
||||||
|
this.setBlockAt(x, 0, z, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setBlockAt(x, y, z, type) {
|
||||||
|
let chunkSection = this.getChunkAtBlock(x, y, z);
|
||||||
|
if (chunkSection != null) {
|
||||||
|
chunkSection.setBlockAt(x & 15, y & 15, z & 15, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.blockChanged(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
getChunkAt(x, z) {
|
||||||
|
let zArray = this.chunks[x];
|
||||||
|
if (typeof zArray === 'undefined') {
|
||||||
|
zArray = this.chunks[x] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk = zArray[z];
|
||||||
|
if (typeof chunk === 'undefined') {
|
||||||
|
chunk = new Chunk(x, z);
|
||||||
|
this.chunks[x][z] = chunk;
|
||||||
|
this.group.add(chunk.group);
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockChanged(x, y, z) {
|
||||||
|
this.setDirty(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
setDirty(minX, minY, minZ, maxX, maxY, maxZ) {
|
||||||
|
// To chunk coordinates
|
||||||
|
minX = minX >> 4;
|
||||||
|
maxX = maxX >> 4;
|
||||||
|
minY = minY >> 4;
|
||||||
|
maxY = maxY >> 4;
|
||||||
|
minZ = minZ >> 4;
|
||||||
|
maxZ = maxZ >> 4;
|
||||||
|
|
||||||
|
// Minimum and maximum y
|
||||||
|
minY = Math.max(0, minY);
|
||||||
|
maxY = Math.min(15, maxY);
|
||||||
|
|
||||||
|
for (let x = minX; x <= maxX; x++) {
|
||||||
|
for (let y = minY; y <= maxY; y++) {
|
||||||
|
for (let z = minZ; z <= maxZ; z++) {
|
||||||
|
this.getChunkAt(x, y, z).queueForRebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getChunkAtBlock(x, y, z) {
|
||||||
|
let chunk = this.getChunkAt(x >> 4, z >> 4);
|
||||||
|
return y < 0 || y > World.TOTAL_HEIGHT ? null : chunk.getSection(y >> 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
window.Timer = class {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timer to control the tick speed independently of the framerate
|
||||||
|
*
|
||||||
|
* @param ticksPerSecond Amount of ticks per second
|
||||||
|
*/
|
||||||
|
constructor(ticksPerSecond) {
|
||||||
|
this.MS_PER_SECOND = 1000;
|
||||||
|
this.MAX_MS_PER_UPDATE = 1000;
|
||||||
|
this.MAX_TICKS_PER_UPDATE = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amount of ticks per second
|
||||||
|
*/
|
||||||
|
this.ticksPerSecond = ticksPerSecond;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last time updated in nano seconds
|
||||||
|
*/
|
||||||
|
this.lastTime = this._nanoTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scale the tick speed
|
||||||
|
*/
|
||||||
|
this.timeScale = 1.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Framerate of the advanceTime update
|
||||||
|
*/
|
||||||
|
this.fps = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passed time since last game update
|
||||||
|
*/
|
||||||
|
this.passedTime = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of ticks for the current game update.
|
||||||
|
* It's the passed time as an integer
|
||||||
|
*/
|
||||||
|
this.ticks = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The overflow of the current tick, caused by casting the passed time to an integer
|
||||||
|
*/
|
||||||
|
this.partialTicks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function calculates the amount of ticks required to reach the ticksPerSecond.
|
||||||
|
* Call this function in the main render loop of the game
|
||||||
|
*/
|
||||||
|
advanceTime() {
|
||||||
|
let now = this._nanoTime();
|
||||||
|
let passedMs = now - this.lastTime;
|
||||||
|
|
||||||
|
// Store nano time of this update
|
||||||
|
this.lastTime = now;
|
||||||
|
|
||||||
|
// Maximum and minimum
|
||||||
|
passedMs = Math.max(0, passedMs);
|
||||||
|
passedMs = Math.min(this.MAX_MS_PER_UPDATE, passedMs);
|
||||||
|
|
||||||
|
// Calculate fps
|
||||||
|
this.fps = this.MS_PER_SECOND / passedMs;
|
||||||
|
|
||||||
|
// Calculate passed time and ticks
|
||||||
|
this.passedTime += passedMs * this.timeScale * this.ticksPerSecond / this.MS_PER_SECOND;
|
||||||
|
this.ticks = parseInt(this.passedTime);
|
||||||
|
|
||||||
|
// Maximum ticks per update
|
||||||
|
this.ticks = Math.min(this.MAX_TICKS_PER_UPDATE, this.ticks);
|
||||||
|
|
||||||
|
// Calculate the overflow of the current tick
|
||||||
|
this.passedTime -= this.ticks;
|
||||||
|
this.partialTicks = this.passedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
_nanoTime() {
|
||||||
|
return Date.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Browser test function
|
||||||
|
function isES6() {
|
||||||
|
try {
|
||||||
|
Function("() => {};");
|
||||||
|
return true;
|
||||||
|
} catch (exception) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for browser support
|
||||||
|
if (!isES6()) {
|
||||||
|
alert("Your browser isn't supported! Please use another one.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Script loader
|
||||||
|
function loadScripts(scripts) {
|
||||||
|
let total = scripts.length;
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
return scripts.reduce((currentPromise, scriptUrl) => {
|
||||||
|
return currentPromise.then(() => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Update status message
|
||||||
|
updatePreStatus("Loading scripts... " + index + "/" + total);
|
||||||
|
|
||||||
|
// Load script
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.async = true;
|
||||||
|
script.src = scriptUrl;
|
||||||
|
script.onload = () => resolve();
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(script);
|
||||||
|
|
||||||
|
index++;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, Promise.resolve());
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePreStatus(message) {
|
||||||
|
document.getElementById("pre-status").innerText = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load scripts
|
||||||
|
loadScripts([
|
||||||
|
// Dependencies
|
||||||
|
"libraries/three.min.js",
|
||||||
|
|
||||||
|
// Minecraft Source
|
||||||
|
"src/net/minecraft/util/Timer.js",
|
||||||
|
"src/net/minecraft/client/GameWindow.js",
|
||||||
|
"src/net/minecraft/client/world/ChunkSection.js",
|
||||||
|
"src/net/minecraft/client/world/Chunk.js",
|
||||||
|
"src/net/minecraft/client/world/World.js",
|
||||||
|
"src/net/minecraft/client/Minecraft.js",
|
||||||
|
"src/net/minecraft/client/render/WorldRenderer.js"
|
||||||
|
]).then(() => {
|
||||||
|
// Remove pre status
|
||||||
|
document.getElementById("pre-status").remove();
|
||||||
|
|
||||||
|
// Start Minecraft
|
||||||
|
window.app = new Minecraft("canvas-container");
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pre-status {
|
||||||
|
/* Position */
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
|
/* No select */
|
||||||
|
-webkit-touch-callout: none; /* iOS Safari */
|
||||||
|
-webkit-user-select: none; /* Safari */
|
||||||
|
-khtml-user-select: none; /* Konqueror HTML */
|
||||||
|
-moz-user-select: none; /* Old versions of Firefox */
|
||||||
|
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||||
|
user-select: none; /* Non-prefixed version, currently
|
||||||
|
supported by Chrome, Edge, Opera and Firefox */
|
||||||
|
/* No overflow */
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
/* Font */
|
||||||
|
font-size: 25px;
|
||||||
|
font-family: "FoundryGridnik", serif;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas-container {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user