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