XVoxel-ASync/modules/world.js

833 lines
19 KiB
JavaScript

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
}
};