Add files from before the Git repository
This commit is contained in:
141
modules/input.js
Normal file
141
modules/input.js
Normal file
@ -0,0 +1,141 @@
|
||||
var controllerTypes = [
|
||||
"desktop",
|
||||
"mobile",
|
||||
"console"
|
||||
];
|
||||
|
||||
class Controller {
|
||||
constructor (type, options = {}) {
|
||||
if (controllerTypes.indexOf(type) < 0) return new Error(
|
||||
"Controller type must be \"desktop\", \"mobile\" or \"console\". "
|
||||
);
|
||||
this.type = type;
|
||||
|
||||
this.mappings = {};
|
||||
this.actions = {};
|
||||
|
||||
switch (this.type) {
|
||||
case "desktop":
|
||||
if (options.domElement) {
|
||||
this.domElement = options.domElement;
|
||||
} else {
|
||||
this.domElement = document.querySelector(":root");
|
||||
}
|
||||
break;
|
||||
case "mobile":
|
||||
if (options.domElements) {
|
||||
this.domElements = options.domElements;
|
||||
} else {
|
||||
this.domElements = {
|
||||
"main": document.querySelector(":root")
|
||||
};
|
||||
}
|
||||
break;
|
||||
case "console":
|
||||
if (!options.gamepad) return new Error("A gamepad object must be passed. ");
|
||||
this.gamepad = options.gamepad;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
registerMapping (mapping, action) {
|
||||
this.mappings[mapping] = action;
|
||||
}
|
||||
registerAction (action, callback) {
|
||||
this.actions[action] = callback;
|
||||
}
|
||||
unregisterMapping (mapping) {
|
||||
delete this.mappings[mapping];
|
||||
}
|
||||
unregisterAction (action) {
|
||||
delete this.actions[action];
|
||||
}
|
||||
grab () {
|
||||
switch (this.type) {
|
||||
case "desktop":
|
||||
this.domElement.requestPointerLock();
|
||||
|
||||
this.domElement.onkeyup = this.domElement.onkeydown = processKeyEvent.bind(this);
|
||||
|
||||
this.domElement.onmousemove = this.domElement.onmousedown = this.domElement.onmouseup = processMouseEvent.bind(this);
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
release () {
|
||||
switch (this.type) {
|
||||
case "desktop":
|
||||
document.exitPointerLock();
|
||||
|
||||
this.domElement.onkeyup = this.domElement.onkeydown = null;
|
||||
|
||||
this.domElement.onmousemove = null;
|
||||
this.domElement.onmousedown = this.domElement.onmouseup = null;
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var processKeyEvent = function (event) {
|
||||
var mapping = this.mappings[event.code];
|
||||
if (!mapping) return;
|
||||
|
||||
let eventType;
|
||||
|
||||
switch (event.type) {
|
||||
case "keydown":
|
||||
eventType = "down";
|
||||
break;
|
||||
case "keyup":
|
||||
eventType = "up";
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
|
||||
var action = this.actions[mapping];
|
||||
if (typeof action == "function") action(eventType);
|
||||
};
|
||||
|
||||
var processMouseEvent = function (event) {
|
||||
let eventType;
|
||||
let mappingName;
|
||||
let movement;
|
||||
|
||||
switch (eventType) {
|
||||
case "mousedown":
|
||||
eventType = "down";
|
||||
mappingName = "MouseDown" + event.which;
|
||||
break;
|
||||
case "mousemove":
|
||||
eventType = "move";
|
||||
mappingName = "MouseMove";
|
||||
|
||||
movement = [event.movementX, event.movementY];
|
||||
break;
|
||||
case "mouseup":
|
||||
eventType = "up";
|
||||
mappingName = "MouseUp" + event.which;
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
|
||||
var mapping = this.mappings[mappingName];
|
||||
if (!mapping) return;
|
||||
|
||||
var action = this.actions[mapping];
|
||||
if (typeof action == "function") action(((eventType == "move") ? movement : eventType));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
"Controller": Controller
|
||||
};
|
||||
|
||||
global.core.on("pre-init", function () {
|
||||
global.core.emit("input-init");
|
||||
});
|
358
modules/network.js
Normal file
358
modules/network.js
Normal file
@ -0,0 +1,358 @@
|
||||
const networkClock = 1000 / 16;
|
||||
|
||||
var allowedIDCharacters = "0123456789ABCDEF";
|
||||
var tokenCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._-";
|
||||
|
||||
var generateAccessToken = function () {
|
||||
var token = "";
|
||||
|
||||
for (var c = 0; c < 64; c++) {
|
||||
token += tokenCharacters[
|
||||
Math.floor(
|
||||
Math.random() * tokenCharacters.length
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
return token;
|
||||
};
|
||||
|
||||
var initMatrixClient = function (baseURL, mxID, withCrypto = false) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var matrixAccessToken = localStorage.getItem("matrix_access_token");
|
||||
if (!matrixAccessToken) {
|
||||
matrixAccessToken = generateAccessToken();
|
||||
localStorage.setItem("matrix_access_token", matrixAccessToken);
|
||||
}
|
||||
|
||||
var matrixClient = global.matrixcs.createClient({
|
||||
"baseUrl": baseURL,
|
||||
"accessToken": matrixAccessToken,
|
||||
"userId": mxID,
|
||||
"deviceId": "XVoxel"
|
||||
});
|
||||
|
||||
module.exports.matrixClient = matrixClient;
|
||||
|
||||
resolve(matrixClient);
|
||||
|
||||
if (withCrypto) {
|
||||
matrixClient.initCrypto().then(function () {
|
||||
|
||||
}).catch(console.error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var loginToMatrix = function (username, password) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (!("matrixClient" in module.exports)) return reject(new Error("Matrix client hasn't been initialized yet"));
|
||||
module.exports.matrixClient.loginWithPassword(username, password).then(function (result) {
|
||||
var matrixAccessToken = module.exports.matrixClient.getAccessToken();
|
||||
localStorage.setItem("matrix_access_token", matrixAccessToken);
|
||||
resolve(result);
|
||||
}).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
var startMatrixClient = function () {
|
||||
module.exports.matrixClient.startClient();
|
||||
|
||||
module.exports.matrixClient.on("sync", function (state, previousState, response) {
|
||||
if (state === "PREPARED") {
|
||||
var buttonReadyInterval = setInterval(function () {
|
||||
var mxWJBtn = document.querySelector("#matrixworldjoin");
|
||||
|
||||
if (!mxWJBtn) return;
|
||||
|
||||
mxWJBtn.disabled = false;
|
||||
clearInterval(buttonReadyInterval);
|
||||
}, 256);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var createWorldRoom = function (name) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
module.exports.matrixClient.createRoom({
|
||||
"name": name,
|
||||
"visibility": "private",
|
||||
"topic": "A Voxel world",
|
||||
"creation_content": {
|
||||
"type": "m.world.voxel"
|
||||
}
|
||||
}).then(resolve).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
var getWorldRooms = function () {
|
||||
var rooms = module.exports.matrixClient.getRooms();
|
||||
|
||||
if (!rooms) return [];
|
||||
|
||||
return rooms.filter(room => room.getType() == "m.world.voxel");
|
||||
};
|
||||
|
||||
var syncWorldFromMatrixRoom = function (room) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
module.exports.matrixClient.getStateEvent(room.roomId, "m.world.voxel", "save").then(function (event) {
|
||||
fetch(module.exports.matrixClient.mxcUrlToHttp(event["save_uri"])).then(function (response) {
|
||||
response.arrayBuffer().then(function (arrayBuffer) {
|
||||
global.openSave(arrayBuffer).then(function (loadedWorlds) {
|
||||
for (var worldName in loadedWorlds) {
|
||||
if (loadedWorlds.hasOwnProperty(worldName)) {
|
||||
global.worlds[worldName] = loadedWorlds[worldName];
|
||||
}
|
||||
}
|
||||
|
||||
resolve(global.worlds[room.name]);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
var syncWorldToMatrixRoom = function (room) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
global.makeSave().then(function (archive) {
|
||||
module.exports.matrixClient.uploadContent(archive, {
|
||||
"name": room.name + ".zip",
|
||||
"type": "application/zip"
|
||||
}).then(function (response) {
|
||||
module.exports.matrixClient.sendStateEvent(room.roomId, "m.world.voxel", {
|
||||
"save_uri": response.content_uri
|
||||
}, "save").then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
var connectMatrix = function (room) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var groupCall = module.exports.matrixClient.getGroupCallForRoom(room.roomId);
|
||||
if (!groupCall) {
|
||||
return module.exports.matrixClient.createGroupCall(
|
||||
room.roomId, global.matrixcs.GroupCallType["Voice"],
|
||||
false, global.matrixcs.GroupCallIntent["Room"], true
|
||||
).then(function (call) {
|
||||
call.initWithAudioMuted = true;
|
||||
call.initWithVideoMuted = true;
|
||||
call.setMicrophoneMuted(true);
|
||||
call.enter();
|
||||
resolve(call);
|
||||
}).catch(console.error);
|
||||
}
|
||||
groupCall.initWithAudioMuted = true;
|
||||
groupCall.initWithVideoMuted = true;
|
||||
groupCall.setMicrophoneMuted(true);
|
||||
groupCall.enter();
|
||||
resolve(groupCall);
|
||||
});
|
||||
};
|
||||
|
||||
if (localStorage.getItem("matrix_is_connected")) {
|
||||
initMatrixClient(
|
||||
localStorage.getItem("matrix_baseurl"),
|
||||
localStorage.getItem("matrix_mxid")
|
||||
).then(function (matrixClient) {
|
||||
module.exports.matrixClient = matrixClient;
|
||||
startMatrixClient();
|
||||
}).catch(function (error) {
|
||||
console.error(error);
|
||||
localStorage.removeItem("matrix_is_connected");
|
||||
});
|
||||
}
|
||||
|
||||
var connectRTC = function (options) {
|
||||
var randomID = (options.username || "Guest") + "-";
|
||||
for (var c = 0; c < 16; c++) {
|
||||
randomID += allowedIDCharacters[
|
||||
Math.floor(
|
||||
Math.random() * allowedIDCharacters.length
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
var peerNetwork = new global.peer.default(randomID, {
|
||||
"host": options.host,
|
||||
"port": options.port,
|
||||
"path": options.path
|
||||
});
|
||||
|
||||
return peerNetwork;
|
||||
};
|
||||
|
||||
var connect = function (url) {
|
||||
try {
|
||||
var connectionURL = new URL(url);
|
||||
} catch (error) {
|
||||
global.throwError(error);
|
||||
}
|
||||
|
||||
var splitPathName = connectionURL.pathname.split(/[\/]/);
|
||||
|
||||
var pathNameParts = splitPathName[2].split("@");
|
||||
|
||||
if (pathNameParts.length > 1) {
|
||||
var pathNameCredentials = pathNameParts.split(":");
|
||||
|
||||
var pathNameUsername = pathNameCredentials[0];
|
||||
|
||||
if (pathNameCredentials.length > 1) {
|
||||
var pathNamePassword = pathNameCredentials[1];
|
||||
}
|
||||
|
||||
var pathNameConnectionDataIndex = 1;
|
||||
} else {
|
||||
var pathNameConnectionDataIndex = 0;
|
||||
}
|
||||
|
||||
var pathNameConnectionData = pathNameParts[pathNameConnectionDataIndex].split(":");
|
||||
|
||||
var pathNameAddress = pathNameConnectionData[0];
|
||||
|
||||
if (pathNameConnectionData.length > 1) {
|
||||
var pathNamePort = pathNameConnectionData[1];
|
||||
}
|
||||
|
||||
var pathNamePath = "/" + splitPathName.slice(3).join("/");
|
||||
|
||||
switch (connectionURL.protocol) {
|
||||
case "rtc:":
|
||||
return connectRTC({
|
||||
"host": pathNameAddress,
|
||||
"port": pathNamePort || 443,
|
||||
"path": pathNamePath || "/",
|
||||
"username": pathNameUsername || "Guest",
|
||||
"password": pathNamePassword || null
|
||||
});
|
||||
break;
|
||||
case "ws:":
|
||||
|
||||
break;
|
||||
case "wss:":
|
||||
|
||||
break;
|
||||
case "http:":
|
||||
|
||||
break;
|
||||
case "https:":
|
||||
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
"initMatrixClient": initMatrixClient,
|
||||
"loginToMatrix": loginToMatrix,
|
||||
"startMatrixClient": startMatrixClient,
|
||||
"createWorldRoom": createWorldRoom,
|
||||
"getWorldRooms": getWorldRooms,
|
||||
"connectMatrix": connectMatrix,
|
||||
"connect": connect,
|
||||
"menus": {
|
||||
"play": {
|
||||
"elements": [
|
||||
{
|
||||
"type": "button",
|
||||
"label": "Multiplayer",
|
||||
"action": "open-menu",
|
||||
"action-args": ["serversel", 2]
|
||||
}
|
||||
]
|
||||
},
|
||||
"serversel": {
|
||||
"elements": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "serveraddressinput",
|
||||
"label": global.locales.getRawString(global.currentLanguage, "play.multiplayer.serveraddressinput.label"),
|
||||
"placeholder": global.locales.getRawString(global.currentLanguage, "play.multiplayer.serveraddressinput.placeholder"),
|
||||
"value": "",
|
||||
"action": "run",
|
||||
"runnable": function (event) {
|
||||
var serverConnectButton = global.document.getElementById("serverconnectbutton");
|
||||
|
||||
if (event.path[0].value.length < 1) {
|
||||
serverConnectButton.disabled = true;
|
||||
} else {
|
||||
try {
|
||||
new URL(event.path[0].value);
|
||||
|
||||
serverConnectButton.disabled = false;
|
||||
} catch (e) {
|
||||
serverConnectButton.disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"id": "serverconnectbutton",
|
||||
"label": global.locales.getRawString(global.currentLanguage, "play.multiplayer.serverconnectbutton.label"),
|
||||
"disabled": true,
|
||||
"action": "run",
|
||||
"runnable": function (event) {
|
||||
var serverAddressInputBox = global.document.getElementById("serveraddressinput");
|
||||
|
||||
connect(serverAddressInputBox.value);
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"id": "matrixworldjoin",
|
||||
"label": global.locales.getRawString(global.currentLanguage, "play.multiplayer.loadmatrixworldsbutton.label"),
|
||||
"disabled": true,
|
||||
"action": "run",
|
||||
"runnable": function (event) {
|
||||
for (var e = module.exports.menus["serversel"].elements.length - 1; e >= 0; e--) {
|
||||
var menuElement = module.exports.menus["serversel"].elements[e];
|
||||
|
||||
if (("classes" in menuElement) && menuElement.classes.indexOf("mxworldjoinbtn") > -1) module.exports.menus["serversel"].elements.splice(e, 1);
|
||||
}
|
||||
|
||||
var matrixWorldRooms = getWorldRooms();
|
||||
|
||||
for (var r = 0; r < matrixWorldRooms.length; r++) {
|
||||
var matrixWorldRoom = matrixWorldRooms[r];
|
||||
|
||||
var menuEntry = {
|
||||
"type": "button",
|
||||
"label": global.locales.getFormattedString(
|
||||
global.currentLanguage,
|
||||
"play.singleplayer.openworld.label",
|
||||
{
|
||||
"WORLD_NAME": matrixWorldRoom.name
|
||||
}
|
||||
),
|
||||
"action": "run",
|
||||
"runnable": (function (event) {
|
||||
syncWorldFromMatrixRoom(this).then((function (world) {
|
||||
global.worlds[this.name] = world;
|
||||
global.startGame(0, this.name);
|
||||
}).bind(this)).catch(global.throwError);
|
||||
|
||||
global.menu.hideMenus(0);
|
||||
}).bind(matrixWorldRoom),
|
||||
"classes": [
|
||||
"mxworldjoinbtn"
|
||||
]
|
||||
};
|
||||
|
||||
module.exports.menus["serversel"].elements.push(menuEntry);
|
||||
}
|
||||
|
||||
global.menu.hideMenus(2);
|
||||
|
||||
global.menu.menus["serversel"] = global.menu.constructMenu("serversel", module.exports.menus["serversel"]);
|
||||
|
||||
global.menu.showMenu("serversel", 2);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
37
modules/render.js
Normal file
37
modules/render.js
Normal file
@ -0,0 +1,37 @@
|
||||
class Renderer extends global.three.WebGLRenderer {
|
||||
constructor (options = {}) {
|
||||
super(options);
|
||||
|
||||
// if (options.autoResize) this.autoResize = options.autoResize;
|
||||
// if (options.noAutoResize) this.autoResize = false;
|
||||
//
|
||||
// if (this.domElement && this.domElement.parentElement) {
|
||||
// this.domElement.parentElement.addEventListener("resize", function (event) {
|
||||
// if (this.autoResize) this.setSize(this.domElement.clientWidth, this.domElement.clientHeight);
|
||||
// });
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
class PerspectiveCamera extends global.three.PerspectiveCamera {
|
||||
constructor (...options) {
|
||||
super(...options);
|
||||
}
|
||||
get fieldOfView () {
|
||||
return this.fov;
|
||||
}
|
||||
set fieldOfView (value) {
|
||||
this.fov = value;
|
||||
|
||||
this.updateProjectionMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
"Renderer": Renderer,
|
||||
"PerspectiveCamera": PerspectiveCamera
|
||||
};
|
||||
|
||||
global.core.on("pre-init", function () {
|
||||
global.core.emit("render-init");
|
||||
});
|
169
modules/resources.js
Normal file
169
modules/resources.js
Normal file
@ -0,0 +1,169 @@
|
||||
class ResourcePack {
|
||||
constructor (data) {
|
||||
this.name = data.name;
|
||||
this.description = data.description;
|
||||
|
||||
this.meta = data.meta;
|
||||
|
||||
this.models = data.models || new global.Map();
|
||||
this.textures = data.textures || new global.Map();
|
||||
this.sounds = new global.Map();
|
||||
}
|
||||
static loadFromZIP (zipBuffer) {
|
||||
if (!zipBuffer) return Promise.reject(new Error("Missing argument zipBuffer. "));
|
||||
return new Promise(function (resolve, reject) {
|
||||
global.fflate.unzip(new Uint8Array(zipBuffer), {}, function (error, data) {
|
||||
if (error) return reject(error);
|
||||
if (!data["pack.json"]) return reject(new Error("pack.json not found in zip file. "));
|
||||
|
||||
let packMeta;
|
||||
try {
|
||||
packMeta = JSON.parse(global.fflate.strFromU8(data["pack.json"]));
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
var packData = readPackMeta(packMeta, data);
|
||||
|
||||
packData.meta = packMeta;
|
||||
|
||||
var resourceLoadings = [];
|
||||
|
||||
resourceLoadings.push(loadU8Textures(packMeta, data).then(function (textureMap) {
|
||||
packData.textures = textureMap;
|
||||
}).catch(reject));
|
||||
|
||||
resourceLoadings.push(loadU8Models(packMeta, data).then(function (modelMap) {
|
||||
packData.models = modelMap;
|
||||
}).catch(reject));
|
||||
|
||||
Promise.all(resourceLoadings).then(function () {
|
||||
var resourcePack = new ResourcePack(packData);
|
||||
|
||||
global.core.emit("resourcepack-load", resourcePack);
|
||||
|
||||
resolve(resourcePack);
|
||||
}).catch(reject);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var readPackMeta = function (packMeta, packArchive) {
|
||||
var pack = {
|
||||
"name": packMeta.name,
|
||||
"description": packMeta.description
|
||||
};
|
||||
|
||||
return pack;
|
||||
};
|
||||
|
||||
var loadU8Textures = function (packMeta, packData) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var textureMap = new global.Map();
|
||||
|
||||
var textureLoadings = [];
|
||||
|
||||
var textureMeta = packMeta.textures;
|
||||
|
||||
for (var textureName in textureMeta) {
|
||||
if (textureMeta.hasOwnProperty(textureName)) {
|
||||
var textureData = textureMeta[textureName];
|
||||
|
||||
if (textureData.path) {
|
||||
var splitPath = textureData.path.split(/[\/\.]+/g);
|
||||
|
||||
var extension = splitPath[splitPath.length - 1].toLowerCase();
|
||||
if (extension == "jpg") extension = "jpeg";
|
||||
|
||||
if ((["png", "jpeg", "webp", "gif", "apng", "avif"]).indexOf(extension) > -1) {
|
||||
var type = "image";
|
||||
} else if ((["mp4", "mkv", "ogg", "webm"]).indexOf(extension) > -1) {
|
||||
var type = "video";
|
||||
}
|
||||
|
||||
var mimeType = type + "/" + extension;
|
||||
|
||||
if (!packData[textureData.path]) reject(new Error("Texture doesn't exist at " + textureData.path + ". "));
|
||||
|
||||
var textureBind = {
|
||||
"name": textureName
|
||||
};
|
||||
|
||||
textureLoadings.push(new Promise((function(res, rej) {
|
||||
var imageData = packData[textureData.path];
|
||||
|
||||
var imageBlob = new Blob([imageData.buffer], {"type": mimeType});
|
||||
|
||||
var imageObjectURL = URL.createObjectURL(imageBlob);
|
||||
|
||||
var texture = new global.three.Texture();
|
||||
var image = new global.Image();
|
||||
|
||||
texture.minFilter = global.three.NearestFilter;
|
||||
texture.magFilter = global.three.NearestFilter;
|
||||
|
||||
image.onload = (function () {
|
||||
texture.image = image;
|
||||
texture.needsUpdate = true;
|
||||
|
||||
var data = {
|
||||
"map": texture
|
||||
};
|
||||
|
||||
textureMap.set(this.name, data);
|
||||
|
||||
res(data);
|
||||
}).bind(textureBind);
|
||||
|
||||
image.src = imageObjectURL;
|
||||
}).bind(textureBind)));
|
||||
} else if (textureData.texture) {
|
||||
var data = {
|
||||
|
||||
};
|
||||
|
||||
textureMap.set(textureName, data);
|
||||
|
||||
textureLoadings.push(Promise.resolve(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(textureLoadings).then(function (textures) {
|
||||
resolve(textureMap);
|
||||
}).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
var loadU8Models = function (packMeta, packData) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var modelMap = new global.Map();
|
||||
|
||||
var modelLoadings = [];
|
||||
|
||||
var modelMeta = packMeta.models;
|
||||
|
||||
for (var modelName in modelMeta) {
|
||||
if (modelMeta.hasOwnProperty(modelName)) {
|
||||
var modelData = modelMeta[modelName];
|
||||
|
||||
var modelString = global.fflate.strFromU8(packData[modelData.path]);
|
||||
|
||||
var modelJSON = JSON.parse(modelString);
|
||||
|
||||
modelMap.set(modelName, modelJSON);
|
||||
|
||||
modelLoadings.push(Promise.resolve(modelJSON));
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(modelLoadings).then(function (models) {
|
||||
resolve(modelMap);
|
||||
}).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
"ResourcePack": ResourcePack
|
||||
};
|
832
modules/world.js
Normal file
832
modules/world.js
Normal file
@ -0,0 +1,832 @@
|
||||
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
|
||||
}
|
||||
};
|
772
modules/world_worker.js
Normal file
772
modules/world_worker.js
Normal file
@ -0,0 +1,772 @@
|
||||
importScripts("../lib/improved-noise.js");
|
||||
importScripts("../lib/cannon-es.js");
|
||||
importScripts("../lib/ammo.js");
|
||||
|
||||
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]
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
let worldConfig = {
|
||||
"chunkSize": [16, 16, 16]
|
||||
};
|
||||
|
||||
let textureData = {
|
||||
"resolution": [16, 16],
|
||||
"size": [256, 256],
|
||||
"blocks": {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
var tasks = {
|
||||
"physics": {
|
||||
"running": false,
|
||||
"interval": setInterval(async function () {
|
||||
if (!tasks["physics"].running && (tasks["physics"].queue.length > 0)) {
|
||||
tasks["physics"].running = true;
|
||||
while (tasks["physics"].queue.length > 0) {
|
||||
tasks["physics"].queue[0].run(...tasks["physics"].queue[0].arguments);
|
||||
tasks["physics"].queue.splice(0, 1);
|
||||
}
|
||||
tasks["physics"].running = false;
|
||||
}
|
||||
}, 256),
|
||||
"queue": []
|
||||
}
|
||||
};
|
||||
|
||||
var ammoIsReady = false;
|
||||
|
||||
let world;
|
||||
|
||||
var worlds = {};
|
||||
|
||||
var bodies = {};
|
||||
|
||||
var onmessage = function (event) {
|
||||
var command = event.data["command"];
|
||||
var arguments = event.data["arguments"];
|
||||
|
||||
var callbackID = arguments["callbackID"];
|
||||
|
||||
switch (command) {
|
||||
case "config":
|
||||
if (arguments["world"]) worldConfig = arguments["world"];
|
||||
textureData = arguments["textureData"];
|
||||
break;
|
||||
case "generate":
|
||||
var generator = self.generators[arguments["generator"]];
|
||||
var data = arguments["data"];
|
||||
|
||||
if (!generator) return postMessage({
|
||||
"event": "generated",
|
||||
"data": {
|
||||
"callbackID": callbackID,
|
||||
"blocks": []
|
||||
}
|
||||
});
|
||||
|
||||
var position = arguments["position"];
|
||||
|
||||
var blocks = [];
|
||||
|
||||
var chunkWidth = worldConfig.chunkSize[0];
|
||||
var chunkHeight = worldConfig.chunkSize[1];
|
||||
var chunkDepth = worldConfig.chunkSize[2];
|
||||
|
||||
switch (generator.type) {
|
||||
case "stepped":
|
||||
for (var x = 0; x < chunkWidth; x++) {
|
||||
for (var z = 0; z < chunkDepth; z++) {
|
||||
for (var y = 0; y < chunkHeight; y++) {
|
||||
var block = generator.run([
|
||||
(position[0] * chunkWidth) + x,
|
||||
(position[1] * chunkHeight) + y,
|
||||
(position[2] * chunkDepth) + z
|
||||
], data, worldConfig.chunkSize);
|
||||
var blockIndex = computeBlockIndex(
|
||||
worldConfig.chunkSize,
|
||||
[
|
||||
x,
|
||||
y,
|
||||
z
|
||||
]
|
||||
);
|
||||
|
||||
if (block) blocks[blockIndex] = block;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "full":
|
||||
var blocks = generator.run(position, data, worldConfig.chunkSize);
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
|
||||
postMessage({
|
||||
"event": "generated",
|
||||
"data": {
|
||||
"callbackID": callbackID,
|
||||
"blocks": blocks
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "construct":
|
||||
var chunk = arguments["chunk"];
|
||||
var neighbors = arguments["neighbors"];
|
||||
|
||||
postMessage({
|
||||
"event": "constructed",
|
||||
"data": {
|
||||
"callbackID": callbackID,
|
||||
"properties": construct(chunk, neighbors)
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "create-world":
|
||||
// worlds[arguments["uuid"]] = new CANNON.World({
|
||||
// "allowSleep": arguments["allowSleep"] || true,
|
||||
// "gravity": ("gravity" in arguments) ? new CANNON.Vec3(
|
||||
// arguments["gravity"][0],
|
||||
// arguments["gravity"][1],
|
||||
// arguments["gravity"][2]
|
||||
// ) : new CANNON.Vec3(0, -9.82, 0),
|
||||
// "solver": new CANNON.GSSolver()
|
||||
// });
|
||||
|
||||
var collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
|
||||
var dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
|
||||
var overlappingPairCache = new Ammo.btDbvtBroadphase();
|
||||
var solver = new Ammo.btSequentialImpulseConstraintSolver();
|
||||
|
||||
world = new Ammo.btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
|
||||
world.setGravity(new Ammo.btVector3(0, -9.82, 0));
|
||||
|
||||
postMessage({
|
||||
"event": "world-created",
|
||||
"data": {
|
||||
"callbackID": callbackID
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "create-body":
|
||||
let bodyType;
|
||||
|
||||
switch (arguments["type"]) {
|
||||
case "dynamic":
|
||||
bodyType = CANNON.Body.DYNAMIC;
|
||||
break;
|
||||
case "static":
|
||||
bodyType = CANNON.Body.STATIC;
|
||||
break;
|
||||
case "kinematic":
|
||||
bodyType = CANNON.Body.KINEMATIC;
|
||||
break;
|
||||
default:
|
||||
bodyType = CANNON.Body.DYNAMIC;
|
||||
}
|
||||
|
||||
var bodyOptions = {
|
||||
"allowSleep": arguments["allowSleep"] || true,
|
||||
"fixedRotation": arguments["fixedRotation"] || false,
|
||||
"mass": arguments["mass"] || 0,
|
||||
"position": ("position" in arguments) ? new CANNON.Vec3(
|
||||
arguments["position"][0],
|
||||
arguments["position"][1],
|
||||
arguments["position"][2]
|
||||
) : new CANNON.Vec3(0, 0, 0),
|
||||
"quaternion": ("quaternion" in arguments) ? new CANNON.Quaternion(
|
||||
arguments["quaternion"][0],
|
||||
arguments["quaternion"][1],
|
||||
arguments["quaternion"][2],
|
||||
arguments["quaternion"][3]
|
||||
) : new CANNON.Quaternion(0, 0, 0, 1),
|
||||
"type": bodyType,
|
||||
"material": new CANNON.Material()
|
||||
/*"shape": ("shape" in arguments) ? new CANNON.ConvexPolyhedron({
|
||||
"vertices": (function (positions) {
|
||||
var vertices = [];
|
||||
|
||||
for (var v = 0; v < (positions.length / 3); v++) {
|
||||
var vertexOffset = v * 3;
|
||||
|
||||
vertices[v] = new CANNON.Vec3(
|
||||
positions[vertexOffset + 0],
|
||||
positions[vertexOffset + 1],
|
||||
positions[vertexOffset + 2]
|
||||
);
|
||||
}
|
||||
|
||||
return vertices;
|
||||
})(arguments["shape"].vertices),
|
||||
"faces": (function (indices) {
|
||||
var faces = [];
|
||||
|
||||
for (var f = 0; f < (indices.length / 3); f++) {
|
||||
var faceOffset = f * 3;
|
||||
|
||||
faces[f] = indices.slice(faceOffset, faceOffset + 3);
|
||||
}
|
||||
|
||||
return faces;
|
||||
})(arguments["shape"].indices)
|
||||
}) : new CANNON.Box(new CANNON.Vec3(1, 1, 1))*/
|
||||
};
|
||||
|
||||
var shape = ("shape" in arguments) ? new CANNON.Trimesh(
|
||||
arguments["shape"].vertices,
|
||||
arguments["shape"].indices
|
||||
) : new CANNON.Box(new CANNON.Vec3(1, 1, 1));
|
||||
|
||||
postMessage({
|
||||
"event": "body-created",
|
||||
"data": {
|
||||
"callbackID": callbackID
|
||||
}
|
||||
});
|
||||
|
||||
bodies[arguments["uuid"]] = (shape instanceof CANNON.Trimesh) ? trimeshToPolyhedron(bodyOptions, shape) : new CANNON.Body(bodyOptions);
|
||||
|
||||
if (shape instanceof CANNON.Box) bodies[arguments["uuid"]].addShape(shape);
|
||||
|
||||
bodies[arguments["uuid"]].uuid = arguments["uuid"];
|
||||
|
||||
break;
|
||||
case "set-body":
|
||||
if ("shape" in arguments) {
|
||||
var convexPolyhedronBody = trimeshToPolyhedron({}, new CANNON.Trimesh(
|
||||
arguments["shape"].vertices,
|
||||
arguments["shape"].indices
|
||||
));
|
||||
|
||||
for (var s = bodies[arguments["uuid"]].shapes.length - 1; s >= 0; s--) {
|
||||
bodies[arguments["uuid"]].removeShape(bodies[arguments["uuid"]].shapes[s]);
|
||||
}
|
||||
|
||||
for (var s = 0; s < convexPolyhedronBody.shapes.length; s++) {
|
||||
bodies[arguments["uuid"]].addShape(convexPolyhedronBody.shapes[s]);
|
||||
}
|
||||
|
||||
bodies[arguments["uuid"]].updateBoundingRadius();
|
||||
bodies[arguments["uuid"]].updateAABB();
|
||||
/*bodies[arguments["uuid"]].shapes[0] = new CANNON.ConvexPolyhedron({
|
||||
"vertices": (function (positions) {
|
||||
var vertices = [];
|
||||
|
||||
for (var v = 0; v < (positions.length / 3); v++) {
|
||||
var vertexOffset = v * 3;
|
||||
|
||||
vertices[v] = new CANNON.Vec3(
|
||||
positions[vertexOffset + 0],
|
||||
positions[vertexOffset + 1],
|
||||
positions[vertexOffset + 2]
|
||||
);
|
||||
}
|
||||
|
||||
return vertices;
|
||||
})(arguments["shape"].vertices),
|
||||
"faces": (function (indices) {
|
||||
var faces = [];
|
||||
|
||||
for (var f = 0; f < (indices.length / 3); f++) {
|
||||
var faceOffset = f * 3;
|
||||
|
||||
faces[f] = indices.slice(faceOffset, faceOffset + 3);
|
||||
}
|
||||
|
||||
return faces;
|
||||
})(arguments["shape"].indices)
|
||||
});*/
|
||||
}
|
||||
|
||||
if ("position" in arguments) {
|
||||
bodies[arguments["uuid"]].position.set(
|
||||
arguments["position"][0],
|
||||
arguments["position"][1],
|
||||
arguments["position"][2]
|
||||
);
|
||||
bodies[arguments["uuid"]].aabbNeedsUpdate = true;
|
||||
}
|
||||
|
||||
if ("quaternion" in arguments) {
|
||||
bodies[arguments["uuid"]].quaternion.set(
|
||||
arguments["quaternion"][0],
|
||||
arguments["quaternion"][1],
|
||||
arguments["quaternion"][2],
|
||||
arguments["quaternion"][3]
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "add-body":
|
||||
worlds[arguments["worldUUID"]].addBody(bodies[arguments["bodyUUID"]]);
|
||||
|
||||
postMessage({
|
||||
"event": "body-added",
|
||||
"data": {
|
||||
"callbackID": callbackID
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "step-simulation":
|
||||
worlds[arguments["uuid"]].step(arguments["delta"]);
|
||||
|
||||
var updates = {};
|
||||
|
||||
for (var b = 0; b < worlds[arguments["uuid"]].bodies.length; b++) {
|
||||
var body = worlds[arguments["uuid"]].bodies[b];
|
||||
|
||||
updates[body.uuid] = {
|
||||
"position": body.position.toArray(),
|
||||
"quaternion": body.quaternion.toArray()
|
||||
}
|
||||
}
|
||||
|
||||
postMessage({
|
||||
"event": "simulation-stepped",
|
||||
"data": {
|
||||
"callbackID": callbackID,
|
||||
"updates": updates
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "remove-body":
|
||||
worlds[arguments["worldUUID"]].removeBody(bodies[arguments["bodyUUID"]]);
|
||||
|
||||
postMessage({
|
||||
"event": "body-removed",
|
||||
"data": {
|
||||
"callbackID": callbackID
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "destroy-body":
|
||||
delete bodies[arguments["uuid"]];
|
||||
|
||||
postMessage({
|
||||
"event": "body-destroyed",
|
||||
"data": {
|
||||
"callbackID": callbackID
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "put-voxels":
|
||||
tasks["physics"].queue.push({
|
||||
"run": function (args) {
|
||||
var worldUUID = args["worldUUID"];
|
||||
var chunkUUID = args["chunkUUID"];
|
||||
var chunkPosition = args["chunkPosition"];
|
||||
var voxelIndices = args["indices"];
|
||||
|
||||
var bodyOptions = {
|
||||
"mass": 0,
|
||||
"type": CANNON.Body.STATIC,
|
||||
"material": new CANNON.Material(),
|
||||
"position": new CANNON.Vec3(
|
||||
chunkPosition[0] * worldConfig.chunkSize[0],
|
||||
chunkPosition[1] * worldConfig.chunkSize[1],
|
||||
chunkPosition[2] * worldConfig.chunkSize[2]
|
||||
)
|
||||
};
|
||||
|
||||
var chunk = bodies[chunkUUID] = new CANNON.Body(bodyOptions);
|
||||
|
||||
var shapes = [];
|
||||
|
||||
for (var i = 0; i < voxelIndices.length; i++) {
|
||||
var shape = shapes[i] = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
|
||||
var shapePosition = computeBlockPosition(worldConfig.chunkSize, voxelIndices[i]);
|
||||
|
||||
chunk.addShape(shape, new CANNON.Vec3(
|
||||
shapePosition[0],
|
||||
shapePosition[1],
|
||||
shapePosition[2]
|
||||
));
|
||||
}
|
||||
|
||||
worlds[worldUUID].addBody(chunk);
|
||||
},
|
||||
"arguments": [arguments]
|
||||
});
|
||||
break;
|
||||
case "remove-voxels":
|
||||
tasks["physics"].queue.push({
|
||||
"run": function (args) {
|
||||
var worldUUID = args["worldUUID"];
|
||||
var chunkUUID = args["chunkUUID"];
|
||||
|
||||
worlds[worldUUID].removeBody(bodies[chunkUUID]);
|
||||
|
||||
delete bodies[chunkUUID];
|
||||
},
|
||||
"arguments": [arguments]
|
||||
});
|
||||
break;
|
||||
case "destroy-world":
|
||||
delete worlds[arguments["uuid"]];
|
||||
|
||||
postMessage({
|
||||
"event": "world-destroyed",
|
||||
"data": {
|
||||
"callbackID": callbackID
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
self.generators = {
|
||||
"flat": {
|
||||
"type": "stepped",
|
||||
"run": function (position, data = {}) {
|
||||
if (position[1] >= 0 && position[1] <= 5) return ["glitch", {}];
|
||||
if (position[1] >= 6 && position[1] <= 57) return ["stone", {}];
|
||||
if (position[1] >= 58 && position[1] <= 62) return ["dirt", {}];
|
||||
if (position[1] == 63) return ["grass", {}];
|
||||
}
|
||||
},
|
||||
"sphere": {
|
||||
"type": "stepped",
|
||||
"run": function (position, data = {}) {
|
||||
var mustGenerate = ((position[0] ** 2) + (position[1] ** 2) + (position[2] ** 2)) <= 16 * 16;
|
||||
|
||||
if (mustGenerate) return ["stone", {}];
|
||||
}
|
||||
},
|
||||
"xr-flat": {
|
||||
"type": "stepped",
|
||||
"run": function (position, data = {}) {
|
||||
if (position[1] >= 0 && position[1] <= 1) return ["glitch", {}];
|
||||
if (position[1] >= 2 && position[1] <= 4) return ["stone", {}];
|
||||
if (position[1] >= 5 && position[1] <= 6) return ["dirt", {}];
|
||||
if (position[1] == 7) return ["grass", {}];
|
||||
}
|
||||
},
|
||||
"terrain": {
|
||||
"type": "full",
|
||||
"run": function (position, data = {}, chunkSize) {
|
||||
var chunkWidth = chunkSize[0];
|
||||
var chunkHeight = chunkSize[1];
|
||||
var chunkDepth = chunkSize[2];
|
||||
|
||||
var blocks = new Array(chunkSize[0] * chunkSize[1] * chunkSize[2]),
|
||||
perlin = new ImprovedNoise(),
|
||||
averageY = data["averageY"] || 64;
|
||||
|
||||
let quality = 25;
|
||||
|
||||
for (var x = 0; x < chunkWidth; x++) {
|
||||
for (var z = 0; z < chunkDepth; z++) {
|
||||
var absoluteCoords = [
|
||||
(position[0] * chunkWidth) + x,
|
||||
(position[2] * chunkDepth) + z
|
||||
];
|
||||
|
||||
var maxY = Math.round(
|
||||
perlin.noise(
|
||||
absoluteCoords[0] / quality,
|
||||
averageY / quality,
|
||||
absoluteCoords[1] / quality
|
||||
) * quality
|
||||
) + averageY;
|
||||
|
||||
for (var y = 0; y < chunkHeight; y++) {
|
||||
var absoluteY = (position[1] * chunkHeight) + y;
|
||||
|
||||
if (absoluteY >= (position[1] * chunkHeight)
|
||||
&& absoluteY <= ((position[1] * chunkHeight) + chunkHeight)) {
|
||||
if (absoluteY >= 0 && absoluteY <= 3) {
|
||||
blocks[computeBlockIndex(worldConfig.chunkSize, [
|
||||
x, y, z
|
||||
])] = ["glitch", {}];
|
||||
} else if (absoluteY >= 4 && absoluteY <= (maxY - 4)) {
|
||||
blocks[computeBlockIndex(worldConfig.chunkSize, [
|
||||
x, y, z
|
||||
])] = ["stone", {}];
|
||||
} else if (absoluteY >= (maxY - 3) && absoluteY <= (maxY - 1)) {
|
||||
blocks[computeBlockIndex(worldConfig.chunkSize, [
|
||||
x, y, z
|
||||
])] = ["dirt", {}];
|
||||
} else if (absoluteY == maxY) {
|
||||
blocks[computeBlockIndex(worldConfig.chunkSize, [
|
||||
x, y, z
|
||||
])] = ["grass", {}];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var construct = function (chunk, neighbors, chunkSize) {
|
||||
var positions = [];
|
||||
var normals = [];
|
||||
var uvs = [];
|
||||
var indexes = [];
|
||||
let texUV = [];
|
||||
|
||||
for (var b = 0; b < chunk.blocks.length; b++) {
|
||||
var blockPosition = computeBlockPosition(worldConfig.chunkSize, b);
|
||||
|
||||
if (chunk.blocks[b]) {
|
||||
var type = chunk.blocks[b][0];
|
||||
|
||||
var blockTexData = textureData.textures[type];
|
||||
|
||||
for (var n = 0; n < neighborData.length; n++) {
|
||||
var neighbor = neighborData[n];
|
||||
|
||||
var adjacentPosition = addPositions(blockPosition, neighbor.position);
|
||||
|
||||
let adjacentInNeighbor = false;
|
||||
if (
|
||||
adjacentPosition[0] < 0 || adjacentPosition[0] >= worldConfig.chunkSize[0] ||
|
||||
adjacentPosition[1] < 0 || adjacentPosition[1] >= worldConfig.chunkSize[1] ||
|
||||
adjacentPosition[2] < 0 || adjacentPosition[2] >= worldConfig.chunkSize[2]
|
||||
) adjacentInNeighbor = true;
|
||||
|
||||
let hasAdjacent = true;
|
||||
if (adjacentInNeighbor) {
|
||||
if (!neighbors[n]) hasAdjacent = false;
|
||||
|
||||
var positionInNeighbor = neighborPosition(adjacentPosition, worldConfig.chunkSize);
|
||||
var indexInNeighbor = computeBlockIndex(worldConfig.chunkSize, positionInNeighbor);
|
||||
|
||||
if (!neighbors[n] || !neighbors[n].blocks[indexInNeighbor]) hasAdjacent = false;
|
||||
} else {
|
||||
var blockIndex = computeBlockIndex(worldConfig.chunkSize, adjacentPosition);
|
||||
if (!chunk.blocks[blockIndex]) hasAdjacent = false;
|
||||
}
|
||||
|
||||
if (!hasAdjacent) {
|
||||
var index = positions.length / 3;
|
||||
|
||||
if (blockTexData.sided) {
|
||||
let sideMap;
|
||||
|
||||
for (var sm = 0; sm < blockTexData.sideMaps.length; sm++) {
|
||||
if (blockTexData.sideMaps[sm].includes(n)) sideMap = sm;
|
||||
}
|
||||
|
||||
texUV[0] = blockTexData.positions[sideMap][0] / blockTexData.sizes[sideMap][0];
|
||||
texUV[1] = blockTexData.positions[sideMap][1] / blockTexData.sizes[sideMap][1];
|
||||
} else {
|
||||
texUV[0] = blockTexData.position[0] / blockTexData.size[0];
|
||||
texUV[1] = blockTexData.position[1] / blockTexData.size[1];
|
||||
}
|
||||
var sideTexData = blockTexData[n];
|
||||
|
||||
// texUV[0] = sideTexData.end[0] - sideTexData.start[0];
|
||||
// texUV[1] = sideTexData.end[1] - sideTexData.start[1];
|
||||
|
||||
for (var c = 0; c < neighbor.corners.length; c++) {
|
||||
var corner = neighbor.corners[c];
|
||||
|
||||
var position = addPositions(blockPosition, corner);
|
||||
|
||||
positions.push(
|
||||
position[0],
|
||||
position[1],
|
||||
position[2]
|
||||
);
|
||||
normals.push(
|
||||
neighbor.position[0],
|
||||
neighbor.position[1],
|
||||
neighbor.position[2]
|
||||
);
|
||||
|
||||
var uv = corner.slice(3, 5);
|
||||
|
||||
uvs.push(
|
||||
(texUV[0] + uv[0]) *
|
||||
textureData.resolution[0] /
|
||||
textureData.size[0],
|
||||
1 - (texUV[1] + 1 - uv[1]) *
|
||||
textureData.resolution[1] /
|
||||
textureData.size[1]
|
||||
);
|
||||
}
|
||||
|
||||
indexes.push(
|
||||
index + 0, index + 1, index + 2,
|
||||
index + 2, index + 1, index + 3
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {positions, normals, uvs, indexes};
|
||||
};
|
||||
|
||||
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 neighborPosition = function (position, chunkSize) {
|
||||
var chunkPosition = position.slice(0, 3);
|
||||
|
||||
if (position[0] < 0) chunkPosition[0] = chunkSize[0] + position[0];
|
||||
if (position[0] >= chunkSize[0]) chunkPosition[0] = position[0] - chunkSize[0];
|
||||
if (position[1] < 0) chunkPosition[1] = chunkSize[1] + position[1];
|
||||
if (position[1] >= chunkSize[1]) chunkPosition[1] = position[1] - chunkSize[1];
|
||||
if (position[2] < 0) chunkPosition[2] = chunkSize[2] + position[2];
|
||||
if (position[2] >= chunkSize[2]) chunkPosition[2] = position[2] - chunkSize[2];
|
||||
|
||||
return chunkPosition;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
// Thanks to https://github.com/pmndrs/cannon-es/issues/21 (I slightly modified it)
|
||||
var trimeshToPolyhedron = function (bodyOptions, trimesh, upvector) {
|
||||
let p1 = new CANNON.Vec3(),
|
||||
p2 = new CANNON.Vec3(),
|
||||
p3 = new CANNON.Vec3(),
|
||||
p4 = new CANNON.Vec3(),
|
||||
mp = new CANNON.Vec3(),
|
||||
tmp = new CANNON.Vec3(),
|
||||
e1 = new CANNON.Vec3(),
|
||||
e2 = new CANNON.Vec3();
|
||||
|
||||
const body = new CANNON.Body(bodyOptions);
|
||||
|
||||
for (let i = 0; i < trimesh.indices.length / 3; i++) {
|
||||
mp.set(0, 0, 0);
|
||||
trimesh.getTriangleVertices(i, p1, p2, p3);
|
||||
trimesh.getNormal(i, p4);
|
||||
// if (upvector && p4.dot(upvector) < 0) p4.scale(-1, p4);
|
||||
p4.normalize();
|
||||
mp = mp.vadd(p1).vadd(p2).vadd(p3).scale(1/3);
|
||||
const vertices = [
|
||||
new CANNON.Vec3().copy(p1),
|
||||
new CANNON.Vec3().copy(p2),
|
||||
new CANNON.Vec3().copy(p3),
|
||||
mp.vadd(p4.scale(-1 / 2))
|
||||
];
|
||||
const faces = [
|
||||
[0, 1, 2],
|
||||
[0, 3, 1],
|
||||
[1, 3, 2],
|
||||
[2, 3, 0]
|
||||
];
|
||||
const normals = [
|
||||
new CANNON.Vec3().copy(p4)
|
||||
];
|
||||
for (let j = 0; j < 3; j++) {
|
||||
vertices[j].vsub(vertices[(j + 1) % 3], e1);
|
||||
vertices[(j + 1) % 3].vsub(p4, e2);
|
||||
tmp.set(0, 0, 0);
|
||||
const points = faces[j + 1];
|
||||
for (let p = 0; p < points.length; p++) {
|
||||
tmp.vadd(vertices[points[p]], tmp);
|
||||
}
|
||||
tmp.scale(1 / points.length, tmp);
|
||||
const normal = e1.cross(e2);
|
||||
normal.normalize();
|
||||
normal.scale(-1, normal);
|
||||
const angle = normal.dot(tmp);
|
||||
|
||||
if(angle < 0) normal.scale(-1, normal);
|
||||
normals.push(normal);
|
||||
}
|
||||
const polyhedron = new CANNON.ConvexPolyhedron({vertices, faces, normals});
|
||||
body.addShape(polyhedron);
|
||||
}
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
Ammo().then(function () {
|
||||
ammoIsReady = true;
|
||||
}).catch(function (error) {
|
||||
|
||||
});
|
Reference in New Issue
Block a user