Skip to content

Instantly share code, notes, and snippets.

@EndingCredits
Last active September 3, 2022 22:27
Show Gist options
  • Save EndingCredits/9891678fccbf51636b243787afee8b91 to your computer and use it in GitHub Desktop.
Save EndingCredits/9891678fccbf51636b243787afee8b91 to your computer and use it in GitHub Desktop.
Demo using deepslate to render litematic files.
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/deepslate@0.9.0"></script>
<script src="https://unpkg.com/gl-matrix@3.3.0/gl-matrix-min.js"></script>
<!--<script src="./assets.js"></script>
<script src="./opaque.js"></script>-->
<script src="https://rawcdn.githack.com/misode/deepslate-demo/023617d9bac846b3f1ad02fc52123df3ddc623aa/assets.js"></script>
<script src="https://rawcdn.githack.com/misode/deepslate-demo/023617d9bac846b3f1ad02fc52123df3ddc623aa/opaque.js"></script>
</head>
<body>
<script>
function readFile(input) {
let file = input.files[0];
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function(evt) {
const nbtdata = deepslate.read(new Uint8Array(reader.result)).result; // Don't care about .compressed
//console.log(nbtdata);
console.log(nbtdata.value);
readLitematic(nbtdata);
};
reader.onerror = function() {
console.log(reader.error);
};
}
function readLitematic(nbtdata) {
var regions = nbtdata.value.Regions.value;
for (let regionName in regions) {
var region = regions[regionName].value;
var blockPalette = region.BlockStatePalette.value.value;
//nbits = max(ceil(log(len(palette), 2)), 2)
nbits = Math.ceil(Math.log2(blockPalette.length));
width = region.Size.value.x.value;
height = region.Size.value.y.value;
depth = region.Size.value.z.value;
var blockData = region.BlockStates.value;
var blocks = processBlockData(blockData, nbits, width, height, depth);
//console.log(blocks);
renderStructure(blocks, blockPalette);
}
}
function processBlockData(blockData, nbits, width, height, depth) {
mask = (1 << nbits) - 1;
y_shift = Math.abs(width * depth);
z_shift = Math.abs(width);
var blocks = new Array();
for (let x=0; x < Math.abs(width); x++) {
blocks[x] = new Array();
for (let y=0; y < Math.abs(height); y++) {
blocks[x][y] = new Array();
for (let z=0; z < Math.abs(depth); z++) {
index = y * y_shift + z * z_shift + x;
start_offset = index * nbits;
start_arr_index = start_offset >>> 5; /// divide by 32
end_arr_index = ((index + 1) * nbits - 1) >>> 5;
start_bit_offset = start_offset & 0x1F; // % 32
half_ind = start_arr_index >>> 1;
if ((start_arr_index & 0x1) == 0) {
blockStart = blockData[half_ind][1];
blockEnd = blockData[half_ind][0];
} else {
blockStart = blockData[half_ind][0];
blockEnd = blockData[half_ind+1][1];
}
if (start_arr_index == end_arr_index) {
blocks[x][y][z] = (blockStart >>> start_bit_offset) & mask;
} else {
end_offset = 32 - start_bit_offset; // num curtailed bits
val = ((blockStart >>> start_bit_offset) & mask) | ((blockEnd << end_offset) & mask);
blocks[x][y][z] = val;// & mask;
}
}
}
}
return blocks;
}
function __stripNBTTyping(nbtData) {
if (nbtData.hasOwnProperty("type")) {
switch(nbtData.type) {
case "compound":
var newDict = {}
for (const [k, v] of Object.entries(nbtData.value)) {
newDict[k] = __stripNBTTyping(v);
}
return newDict;
break;
case "list":
var newList = [];
for (const [k, v] of Object.entries(nbtData.value.value)) {
newList[k] = __stripNBTTyping(v);
}
return newList;
break;
default:
return nbtData.value;
}
} else {
return nbtData;
}
}
function renderStructure(blocks, palette) {
console.log( blocks );
width = blocks.length;
height = blocks[0].length;
depth = blocks[0][0].length;
console.log(width, height, depth);
const structure = new deepslate.Structure([width, height, depth]);
const size = structure.getSize();
structure.addBlock([1, 0, 0], "minecraft:stone")
structure.addBlock([2, 0, 0], "minecraft:grass_block", { "snowy": "false" });
structure.addBlock([1, 1, 0], "minecraft:cake", { "bites": "3" })
structure.addBlock([0, 0, 0], "minecraft:wall_torch", { "facing": "west" });
structure.addBlock([2, 1, 0], "minecraft:scaffolding", { "bottom": "false", "waterlogged": "false", "distance": "0" });
var blockCount = 0
console.log("Building blocks...");
for (let x=0; x < width; x++) {
for (let y=0; y < height; y++) {
for (let z=0; z < depth; z++) {
blockID = blocks[x][y][z];
if (blockID > 0) {
if(blockID < palette.length) {
blockInfo = palette[blockID];
blockName = blockInfo.Name.value;
blockCount++;
if (blockInfo.hasOwnProperty("Properties")) {
blockProperties = __stripNBTTyping(blockInfo.Properties);
structure.addBlock([x, y, z], blockName, blockProperties);
} else {
structure.addBlock([x, y, z], blockName);
}
} else {
structure.addBlock([x, y, z], "minecraft:stone")
}
}
}
}
}
console.log("Done!", blockCount);
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');
const renderer = new deepslate.StructureRenderer(gl, structure, deepslateResources);
let viewDist = 4;
let xRotation = 0.8;
let yRotation = 0.5;
function render() {
yRotation = yRotation % (Math.PI * 2);
xRotation = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, xRotation));
viewDist = Math.max(1, Math.min(20, viewDist));
const view = mat4.create();
mat4.translate(view, view, [0, 0, -viewDist]);
mat4.rotate(view, view, xRotation, [1, 0, 0]);
mat4.rotate(view, view, yRotation, [0, 1, 0]);
mat4.translate(view, view, [-size[0] / 2, -size[1] / 2, -size[2] / 2]);
renderer.drawStructure(view);
}
requestAnimationFrame(render);
let dragPos = null;
canvas.addEventListener('mousedown', evt => {
if (evt.button === 0) {
dragPos = [evt.clientX, evt.clientY];
}
})
canvas.addEventListener('mousemove', evt => {
if (dragPos) {
yRotation += (evt.clientX - dragPos[0]) / 100;
xRotation += (evt.clientY - dragPos[1]) / 100;
dragPos = [evt.clientX, evt.clientY];
requestAnimationFrame(render);
}
})
canvas.addEventListener('mouseup', () => {
dragPos = null;
})
canvas.addEventListener('wheel', evt => {
evt.preventDefault();
viewDist += evt.deltaY / 100;
requestAnimationFrame(render);
})
}
/*fetch('./Dark_Scaffolding_Shulker_Farm_v1.litematic')
.then(res => res.arrayBuffer())
.then(data => {
console.log(new Uint8Array(data));
const { result, compressed } = deepslate.read(new Uint8Array(data))
console.log(result, compressed)
//deepslate.write(result, compressed)
})*/ //Turn off security.fileuri.strict_origin_policy in about:config
</script>
<canvas id="canvas" width="800" height="600"></canvas>
<img id="atlas" src="https://raw.githubusercontent.com/misode/deepslate-demo/main/atlas.png" alt="Texture atlas" crossorigin="anonymous" hidden>
<script>
var deepslateResources;
const { mat4 } = glMatrix;
const image = document.getElementById('atlas');
if (image.complete) {
loadResources();
} else {
image.addEventListener('load', loadResources);
}
function loadResources() {
const blockDefinitions = {};
Object.keys(assets.blockstates).forEach(id => {
blockDefinitions['minecraft:' + id] = deepslate.BlockDefinition.fromJson(id, assets.blockstates[id]);
})
const blockModels = {};
Object.keys(assets.models).forEach(id => {
blockModels['minecraft:' + id] = deepslate.BlockModel.fromJson(id, assets.models[id]);
})
Object.values(blockModels).forEach(m => m.flatten({ getBlockModel: id => blockModels[id] }));
const atlasCanvas = document.createElement('canvas');
atlasCanvas.width = image.width;
atlasCanvas.height = image.height;
const atlasCtx = atlasCanvas.getContext('2d');
atlasCtx.drawImage(image, 0, 0);
const atlasData = atlasCtx.getImageData(0, 0, atlasCanvas.width, atlasCanvas.height);
const part = 16 / atlasData.width;
const idMap = {};
Object.keys(assets.textures).forEach(id => {
const [u, v] = assets.textures[id];
idMap['minecraft:' + id] = [u, v, u + part, v + part];
})
const textureAtlas = new deepslate.TextureAtlas(atlasData, idMap);
deepslateResources = {
getBlockDefinition(id) { return blockDefinitions[id] },
getBlockModel(id) { return blockModels[id] },
getTextureUV(id) { return textureAtlas.getTextureUV(id) },
getTextureAtlas() { return textureAtlas.getTextureAtlas() },
getBlockFlags(id) { return { opaque: opaqueBlocks.has(id) } },
getBlockProperties(id) { return null },
getDefaultBlockProperties(id) { return null },
}
}
</script>
<input type="file" onchange="readFile(this)">
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment