var neighborData = [ { "position": [-1, 0, 0], "corners": [ [0, 0, 0, 0, 0], [0, 0, 1, 1, 0], [0, 1, 0, 0, 1], [0, 1, 1, 1, 1] ] }, { "position": [1, 0, 0], "corners": [ [1, 0, 1, 0, 0], [1, 0, 0, 1, 0], [1, 1, 1, 0, 1], [1, 1, 0, 1, 1] ] }, { "position": [0, -1, 0], "corners": [ [0, 0, 0, 0, 1], [1, 0, 0, 1, 1], [0, 0, 1, 0, 0], [1, 0, 1, 1, 0] ] }, { "position": [0, 1, 0], "corners": [ [0, 1, 1, 0, 0], [1, 1, 1, 1, 0], [0, 1, 0, 0, 1], [1, 1, 0, 1, 1] ] }, { "position": [0, 0, -1], "corners": [ [1, 0, 0, 0, 0], [0, 0, 0, 1, 0], [1, 1, 0, 0, 1], [0, 1, 0, 1, 1] ] }, { "position": [0, 0, 1], "corners": [ [0, 0, 1, 0, 0], [1, 0, 1, 1, 0], [0, 1, 1, 0, 1], [1, 1, 1, 1, 1] ] } ]; var timePalette = { "21600": 0x081632, // Marine Blue (#081632) "25200": 0xFF7656, "28800": 0xBDE4F5, "61200": 0xBDE4F5, "64800": 0xFA88AF, "68400": 0x081632 }; var textureData = { "resolution": [16, 16], "size": [256, 256], "textures": { } }; class World extends global.three.Scene { constructor (parameters) { super(); this.generator = parameters.generator || "flat"; this.chunks = new global.Map(); this.chunkSize = parameters.chunkSize || [16, 16, 16]; this.background = new global.three.Color(0xBDE4F5); this.ambientLight = new global.three.AmbientLight(0xFFFFFF, 1); this.directionalLight = new global.three.DirectionalLight(0xFFFFFF, 1); this.directionalLight.position.set(1, 1, 1); this.add(this.ambientLight); this.add(this.directionalLight); this.worker = new global.Worker("./modules/world_worker.js"); this.callbackObjects = {}; global.core.on("resourcepack-load", (function (pack) { var textureData = { "resolution": pack.meta.textures["block-atlas"].size, "size": pack.meta.textures["block-atlas"].totalSize, "textures": pack.meta.textures }; this.worker.postMessage({ "command": "config", "arguments": { "textureData": textureData } }); this.chunks.forEach((chunk, chunkID) => { chunk.material.map = pack.textures.get("block-atlas").map; }); }).bind(this)); var resourcePackNames = Object.keys(global.resourcePacks); if (resourcePackNames.length > 0) { var pack = global.resourcePacks[resourcePackNames[0]]; var textureData = { "resolution": pack.meta.textures["block-atlas"].size, "size": pack.meta.textures["block-atlas"].totalSize, "textures": pack.meta.textures }; this.worker.postMessage({ "command": "config", "arguments": { "world": { "chunkSize": this.chunkSize }, "textureData": textureData } }); } this.worker.postMessage({ "command": "create-world", "arguments": { "uuid": this.uuid } }); this.worker.onmessage = (function (message) { var event = message.data["event"]; var data = message.data["data"]; var callbackID = data["callbackID"]; var callbackObject = this.callbackObjects[callbackID]; switch (event) { case "generated": var newChunk = new Chunk(this.chunkSize, this); for (var b = 0; b < data.blocks.length; b++) { var block = data.blocks[b]; if (!block) continue; newChunk.blocks[b] = block; } this.setChunk(newChunk, callbackObject.position); callbackObject.resolve(newChunk); break; case "constructed": var properties = data.properties; var chunk = callbackObject.chunk; chunk.geometry.setAttribute("position", new global.three.Float32BufferAttribute(properties["positions"], 3)); chunk.geometry.setAttribute("normal", new global.three.Float32BufferAttribute(properties["normals"], 3)); chunk.geometry.setAttribute("uv", new global.three.Float32BufferAttribute(properties["uvs"], 2)); chunk.geometry.setIndex(properties["indexes"]); chunk.geometry.computeBoundingSphere(); var resourcePackNames = Object.keys(global.resourcePacks); if (resourcePackNames.length > 0) { var pack = global.resourcePacks[resourcePackNames[0]]; chunk.material.map = pack.textures.get("block-atlas").map; } // this.worker.postMessage({ // "command": "set-body", // "arguments": { // "uuid": chunk.uuid, // "position": [ // chunk.position.x, // chunk.position.y, // chunk.position.z // ], // "shape": { // "vertices": chunk.geometry.attributes["position"].array, // "indices": chunk.geometry.index.array // }, // "type": "static" // } // }); callbackObject.resolve(chunk); break; case "simulation-stepped": for (var uuid in data.updates) { if (data.updates.hasOwnProperty(uuid)) { var meshUpdate = data.updates[uuid]; var mesh = this.children.find(function (m) { return m.uuid === uuid; }); if (!mesh) continue; mesh.position.set( meshUpdate.position[0], meshUpdate.position[1], meshUpdate.position[2] ); mesh.quaternion.set( meshUpdate.quaternion[0], meshUpdate.quaternion[1], meshUpdate.quaternion[2], meshUpdate.quaternion[3] ); } } callbackObject.resolve(data.updates); break; default: } }).bind(this); } addChunk (chunk) { if (!chunk) return new Error("First argument is not a chunk. "); if (!(chunk instanceof Chunk)) return new Error("First argument is not an instance of Chunk. "); this.add(chunk); var blockIndices = []; for (var b = 0; b < chunk.blocks.length; b++) { if (chunk.blocks[b]) blockIndices.push(b); } // this.worker.postMessage({ // "command": "put-voxels", // "arguments": { // "worldUUID": this.uuid, // "chunkUUID": chunk.uuid, // "chunkPosition": chunk.position, // "indices": blockIndices // } // }); return chunk; } setChunk (chunk, position) { if (!chunk) return new Error("First argument is not a chunk. "); if (!(chunk instanceof Chunk)) return new Error("First argument is not an instance of Chunk. "); if (!position) return new Error("Second argument is not a position. "); var worldPosition = [ this.chunkSize[0] * position[0], this.chunkSize[1] * position[1], this.chunkSize[2] * position[2] ]; if (this.chunks.has(position.join(":"))) { var oldChunk = this.chunks.get(position.join(":")); var voxelIndices = []; for (var b = 0; b < oldChunk.blocks.length; b++) { if (oldChunk.blocks[b]) voxelIndices.push(b); } // this.worker.postMessage({ // "command": "remove-voxels", // "arguments": { // "worldUUID": this.uuid, // "chunkUUID": oldChunk.uuid // } // }); } var blockIndices = []; for (var b = 0; b < chunk.blocks.length; b++) { if (chunk.blocks[b]) blockIndices.push(b); } // this.worker.postMessage({ // "command": "put-voxels", // "arguments": { // "worldUUID": this.uuid, // "chunkUUID": chunk.uuid, // "chunkPosition": position, // "indices": blockIndices // } // }); chunk.position.set( worldPosition[0], worldPosition[1], worldPosition[2] ); // this.worker.postMessage({ // "command": "set-body", // "arguments": { // "uuid": chunk.uuid, // "position": worldPosition // } // }); return this.chunks.set(position.join(":"), chunk); } addEntity (entity) { if (!entity) return new Error("First argument is not an entity. "); if (!(entity instanceof Entity)) return new Error("First argument is not an instance of Entity. "); this.add(entity); this.worker.postMessage({ "command": "add-body", "arguments": { "worldUUID": this.uuid, "bodyUUID": entity.uuid, "type": "dynamic" } }); return entity; } removeChunk (position) { if (!position) return new Error("First argument is not a position. "); if (this.chunks.has(position.join(":"))) return this.chunks.delete(position.join(":")); return false; } generateChunk (position, data) { return new Promise((function (resolve, reject) { var callbackID = "G" + Math.round(Math.random() * 65536); this.callbackObjects[callbackID] = { "position": position, "resolve": resolve, "reject": reject }; this.worker.postMessage({ "command": "generate", "arguments": { "callbackID": callbackID, "position": position, "generator": this.generator } }); }).bind(this)); } constructChunk (position) { return new Promise((function (resolve, reject) { var callbackID = "C" + Math.round(Math.random() * 65536); this.callbackObjects[callbackID] = { "chunk": this.chunks.get(position.join(":")), "position": position, "resolve": resolve, "reject": reject }; var neighbors = []; for (var n = 0; n < neighborData.length; n++) { var neighborPosition = addPositions(position, neighborData[n].position); var neighborKey = neighborPosition.join(":"); if (this.chunks.has(neighborKey)) { neighbors.push(this.chunks.get(neighborKey).toJSON()); } else { neighbors.push(null); } } this.worker.postMessage({ "command": "construct", "arguments": { "callbackID": callbackID, "position": position, "chunk": this.chunks.get(position.join(":")).toJSON(), "neighbors": neighbors } }); }).bind(this)); } stepSimulation (delta) { return new Promise((function (resolve, reject) { var callbackID = "C" + Math.round(Math.random() * 65536); this.callbackObjects[callbackID] = { "resolve": resolve, "reject": reject }; this.worker.postMessage({ "command": "step-simulation", "arguments": { "callbackID": callbackID, "uuid": this.uuid, "delta": delta } }); }).bind(this)); } toJSON () { var chunksObject = {}; this.chunks.forEach(function (value, key) { chunksObject[key] = value; }); return { "generator": this.generator, "chunks": chunksObject }; } } class Chunk extends global.three.Mesh { constructor (size = [16, 16, 16], world) { super( new global.three.BufferGeometry(), new global.three.MeshStandardMaterial({ "side": global.three.DoubleSide, "transparent": true, "alphaTest": 0.5 }) ); this._size = size; this.blocks = new global.Array(size[0] * size[1] * size[2]); if (world instanceof World) { this.world = world; // world.worker.postMessage({ // "command": "create-body", // "arguments": { // "uuid": this.uuid, // "type": "static" // } // }); } } get size () { return this._size; } setBlock (position, block) { if (!position) return new Error("First argument is not a position. "); if (!block) return new Error("Second argument is not a block. "); if (!(block instanceof Block)) return new Error("Second argument is not an instance of Block. "); var blockIndex = computeBlockIndex(this.size, position); if (this.world) { // this.world.worker.postMessage({ // "command": "remove-body", // "arguments": { // "uuid": this.uuid + ";" + position.join(":") // } // }); if (!this.blocks[blockIndex]) { this.world.worker.postMessage({ "command": "create-body", "arguments": { "uuid": this.uuid + ";" + blockIndex, "type": "static", "position": [ this.position.x + position[0], this.position.y + position[1], this.position.z + position[2], ] } }); } } this.blocks[blockIndex] = block; } removeBlock (position) { if (!position) return new Error("First argument is not a position. "); var blockIndex = computeBlockIndex(this.size, position); delete this.blocks[blockIndex]; if (this.world) { this.world.worker.postMessage({ "command": "remove-body", "arguments": { "worldUUID": this.world.uuid, "bodyUUID": this.uuid + ";" + blockIndex } }); this.world.worker.postMessage({ "command": "destroy-body", "arguments": { "uuid": this.uuid + ";" + blockIndex } }); } } toJSON () { return { "size": this.size, "blocks": this.blocks }; } } class Block extends global.Array { constructor (type, data = {}) { super(); if (!type) return new Error("Block type must be non-null. "); this[0] = type; this[1] = new global.Map(); for (var key in data) { if (data.hasOwnProperty(key)) { var value = data[key]; this[1].set(key, value); } } } get type () { return this[0]; } get tags () { return this[1]; } setTagValue (tag, value) { return this[1].set(tag, value); } removeTagValue (tag) { if (this[1].has(tag)) return this[1].delete(tag); return false; } toJSON () { var tagsObject = {}; this[1].forEach(function (value, key) { tagsObject[key] = value; }); return { "type": this[0], "tags": tagsObject }; } } class Entity extends global.three.Mesh { constructor (type, world) { super( new global.three.BoxBufferGeometry(0.8, 0.8, 0.8), new global.three.MeshStandardMaterial({ "side": global.three.DoubleSide, "transparent": true, "alphaTest": 0.5, "opacity": 0.0, "wireframe": true }) ); this.type = type; this.parts = {}; this._texture = null; for (var p = 0; p < global.resourcePacks.length; p++) { var pack = global.resourcePacks[p]; if (pack.models.has(this.type) && Object.keys(this.parts).length < 1) { this.model = pack.models.get(this.type); } if (pack.textures.has(this.type) && !this.material.map) { this.texture = pack.textures.get(this.type).map; } } if (world instanceof World) { world.worker.postMessage({ "command": "create-body", "arguments": { "uuid": this.uuid, "mass": 16, "shape": { "vertices": this.geometry.attributes["position"].array, "indices": this.geometry.index.array } } }); } } get texture () { if (this._texture) return this._texture; for (var partName in this.parts) { if (this.parts.hasOwnProperty(partName)) { var part = this.parts[partName]; if (!part.material || !part.material.map) continue; return part.material.map; } } return null; } set texture (texture) { this._texture = texture; for (var partName in this.parts) { if (this.parts.hasOwnProperty(partName)) { var part = this.parts[partName]; if (part.isGroup) continue; part.material.map = texture; } } } set model (model) { this.geometry = new global.three.BoxBufferGeometry( model.hitbox[0], model.hitbox[1], model.hitbox[2] ); for (var partName in model.parts) { if (model.parts.hasOwnProperty(partName)) { var part = model.parts[partName]; let partObject; switch (part.type) { case "group": partObject = new global.three.Group(); break; case "geometry": partObject = new global.three.Mesh( new global.three.BoxBufferGeometry( part.geometry[0], part.geometry[1], part.geometry[2] ), new global.three.MeshStandardMaterial({ "side": global.three.FrontSide, "transparent": true, "alphaTest": 0.5, "map": this.texture }) ); if ("uvMap" in part) { var uvArray = partObject.geometry.attributes["uv"].array; for (var f = 0; f < part.uvMap.length; f++) { var faceMap = part.uvMap[f]; var firstUVIndex = f * 8; uvArray[firstUVIndex + 0] = faceMap[0][0]; uvArray[firstUVIndex + 1] = faceMap[1][1]; uvArray[firstUVIndex + 2] = faceMap[1][0]; uvArray[firstUVIndex + 3] = faceMap[1][1]; uvArray[firstUVIndex + 4] = faceMap[0][0]; uvArray[firstUVIndex + 5] = faceMap[0][1]; uvArray[firstUVIndex + 6] = faceMap[1][0]; uvArray[firstUVIndex + 7] = faceMap[0][1]; } } break; default: } if (part.position) partObject.position.set( part.position[0], part.position[1], part.position[2] ); var modelPart = this.parts[partName] = partObject; } } for (var partName in model.parts) { if (model.parts.hasOwnProperty(partName)) { var part = model.parts[partName]; if (part.children) { for (var c = 0; c < part.children.length; c++) { this.parts[partName].add(this.parts[part.children[c]]); } } } } if ("root" in this.parts) this.add(this.parts["root"]); } setPosition (position) { this.position.set( position[0], position[1], position[2] ); if (this.parent instanceof World) { this.parent.worker.postMessage({ "command": "set-body", "arguments": { "uuid": this.uuid, "position": position } }); } } } var computeBlockIndex = function (chunkSize, blockPosition) { var x = blockPosition[0] % chunkSize[0]; var z = blockPosition[2] % chunkSize[2]; var y = blockPosition[1] % chunkSize[1]; return (x * (chunkSize[1] * chunkSize[2])) + y + (z * chunkSize[1]); }; var computeBlockPosition = function (chunkSize, blockIndex) { var blockPosition = [0, 0, 0]; var subtractedIndex = 0 + blockIndex; blockPosition[1] = subtractedIndex % chunkSize[1]; subtractedIndex -= blockPosition[1]; blockPosition[2] = (subtractedIndex / chunkSize[1]) % chunkSize[2]; subtractedIndex -= blockPosition[2] * chunkSize[1]; blockPosition[0] = (subtractedIndex / (chunkSize[1] * chunkSize[2])) % chunkSize[0]; subtractedIndex -= blockPosition[0] * chunkSize[0]; return blockPosition; }; var splitRGBComponents = function (hex) { var subtracted = hex; var blue = hex % 0x100; subtracted -= blue; var green = (subtracted / 0x100) % 0x100; subtracted -= subtracted * 0x100; var red = (subtracted / 0x10000) % 0x100; subtracted -= subtracted * 0x10000; return [red, green, blue]; }; var computeTimeColor = function (palette, time) { var times = Object.keys(palette); if (times.length == 1) return palette[times[0]]; var seconds = ((time[0] * 3600) + (time[1] * 60) + time[2]); var timesBefore = times.filter(function (timeFloat) { return parseFloat(timeFloat) <= seconds; }); var timesAfter = times.filter(function (timeFloat) { return parseFloat(timeFloat) > seconds; }); let startTime, startTimeFloat; let endTime, endTimeFloat; if (timesBefore.length < 1) { startTime = times[times.length - 1]; startTimeFloat = parseFloat(times[times.length - 1]) - (24 * 3600); } if (timesAfter.length < 1) { endTime = times[0]; endTimeFloat = (24 * 3600) + parseFloat(times[0]); } var startComponents = splitRGBComponents(palette[startTime]); var endComponents = splitRGBComponents(palette[endTime]); var redDiff = endComponents[0] - startComponents[0]; var greenDiff = endComponents[1] - startComponents[1]; var blueDiff = endComponents[2] - startComponents[2]; progress = (seconds - startTimeFloat) / (endTimeFloat - startTimeFloat); return (Math.round(startComponents[0] + (progress * redDiff)) * 65536) + (Math.round((startComponents[1] + (progress * greenDiff))) * 256) + Math.round(startComponents[2] + (progress * blueDiff)); }; var addPositions = function (positionA, positionB) { var position = new Array(positionA.length); for (var p = 0; p < position.length; p++) { position[p] = positionA[p] + positionB[p]; } return position; }; module.exports = { "World": World, "Chunk": Chunk, "Block": Block, "Entity": Entity, "utils": { "computeBlockIndex": computeBlockIndex, "computeBlockPosition": computeBlockPosition, "splitRGBComponents": splitRGBComponents, "computeTimeColor": computeTimeColor, "addPositions": addPositions } };