// Cloned by Zoey2 on 25 Sep 2024 from World "Paint By Blocks v2 (Three.js) (clone by Vanya Cadogan)" by Vanya Cadogan
// Please leave this clone trail here.
// Cloned by Vanya Cadogan on 24 Feb 2023 from World "Paint By Blocks v1 (Three.js)" by Vanya Cadogan
// Please leave this clone trail here.
//AB.socketStart();
// Load CSS
AB.loadCSS("/uploads/cadogav2/app-styles.css");
$.getScript("/uploads/cadogav2/three.r149.min.js", function(){
startApp();
});
let rubikFont;
const jsonPath = "uploads/stewalsh/big-ben.json";
class UI {
constructor(){
this.selectedMaterialID = 0;
this.selectedItem = null;
this.isExporting = false;
this.selectedToolBtn = null;
this.glbExporter = new GLTFExporter();
this.objExporter = new OBJExporter();
}
init(){
window.addEventListener("contextmenu", e => e.preventDefault());
document.getElementById('ab-runheaderbox').remove()
document.body.innerHTML += `<header class="menu-header">
<nav class="menu-nav">
<ul>
<li class="no-select">
<span>File</span>
<ul class="submenu">
<div class="submenu-content">
<li id="load_csv">Load Blocks from CSV</li>
<li id="save_csv">Save Blocks as CSV</li>
<li id="export_glb">Export Model as GLB</li>
<li id="export_obj">Export Model as OBJ</li>
</div>
</ul>
</li>
<li class="no-select"><span>Views</span></li>
<li class="no-select"><span>Help</span></li>
</ul>
<ul>
<i id="undo_i_btn"
class="fa-solid fa-arrow-rotate-left disabled"
title="Undo"
></i>
<i id="redo_i_btn"
class="fa-solid fa-arrow-rotate-right disabled"
title="Redo"
></i>
</ul>
<ul>
<i id="save_i_btn" class="fa-regular fa-floppy-disk" title="Save"></i>
<i class="fa-solid fa-download" title="Download Model"></i>
</ul>
<ul>
<i id="draw_i_btn" class="fa-solid fa-pen btn-active" title="Draw Block"></i>
<i id="paint_i_btn" class="fa-solid fa-brush" title="Paint Blocks"></i>
<i id="delete_i_btn" class="fa-solid fa-eraser" title="Delete Blocks"></i>
</ul>
<ul class="i-menu">
<i class="fa-solid fa-palette" title="Materials"></i>
<ul class="submenu">
<div class="submenu-content">
<div class="grid-container">
<div class="item" title="Grass">
<img src="/uploads/cadogav2/mc-grass-side.png" />
</div>
<div class="item" title="Snow Grass">
<img src="/uploads/cadogav2/mc_grass_block_snow.png" />
</div>
<div class="item" title="Snow">
<img src="/uploads/cadogav2/mc_snow.png" />
</div>
<div class="item" title="Barrel">
<img src="/uploads/cadogav2/mc_barrel_side.png" />
</div>
<div class="item" title="Cobblestone">
<img src="/uploads/cadogav2/mc_cobblestone.png" />
</div>
<div class="item" title="Dark Diamond">
<img src="/uploads/cadogav2/mc_diamond_block.png" />
</div>
<div class="item" title="Sandstone Brick">
<img src="/uploads/cadogav2/mc_sandstone_bricks_side.png" />
</div>
<div class="item" title="Chiseled Sandstone 1">
<img
src="/uploads/cadogav2/mc_sandstone_smooth.png"
/>
</div>
<div class="item" title="Chiseled Sandstone 2">
<img src="/uploads/cadogav2/mc_sandstone_carved.png" />
</div>
<div class="item" title="Sand">
<img src="/uploads/cadogav2/mc_sand.png" />
</div>
<div class="item" title="Deepslate Bricks">
<img src="/uploads/cadogav2/mc_deepslate_bricks.png" />
</div>
<div class="item" title="Oak Log">
<img src="/uploads/cadogav2/mc_oak_log.png" />
</div>
<div class="item" title="Dark Oak Log">
<img src="/uploads/cadogav2/mc_dark_oak_log.png" />
</div>
<div class="item" title="Spruce Log">
<img src="/uploads/cadogav2/mc_spruce_log.png" />
</div>
<div class="item" title="Stripped Oak Log">
<img src="/uploads/cadogav2/mc_stripped_oak_log.png" />
</div>
<div class="item" title="Acacia Log">
<img src="/uploads/cadogav2/mc_acacia_log.png" />
</div>
<div class="item" title="Stripped Acacia Log">
<img src="/uploads/cadogav2/mc_stripped_acacia_log.png" />
</div>
<div class="item" title="Quartz">
<img src="/uploads/cadogav2/mc_quartz_block_side.png" />
</div>
<div class="item" title="Quartz Bricks">
<img src="/uploads/cadogav2/mc_quartz_bricks.png" />
</div>
<div class="item" title="Quartz Pillar">
<img src="/uploads/cadogav2/mc_quartz_pillar.png" />
</div>
<div class="item" title="Bricks">
<img src="/uploads/cadogav2/mc_bricks.png" />
</div>
<div class="item" title="Cracked Stone Bricks">
<img src="/uploads/cadogav2/mc_cracked_stone_bricks.png" />
</div>
<div class="item" title="Mossy Stone Bricks">
<img src="/uploads/cadogav2/mc_mossy_stone_bricks.png" />
</div>
<div class="item" title="Nether Bricks">
<img src="/uploads/cadogav2/mc_nether_bricks.png" />
</div>
<div class="item" title="Mud Bricks">
<img src="/uploads/cadogav2/mc_mud_bricks.png" />
</div>
<div class="item" title="Gold">
<img src="/uploads/cadogav2/mc_gold_block.png" />
</div>
<div class="item" title="Copper">
<img src="/uploads/cadogav2/mc_copper_block.png" />
</div>
<div class="item" title="Cut Copper">
<img src="/uploads/cadogav2/mc_cut_copper.png" />
</div>
<div class="item" title="Oxidized Copper">
<img src="/uploads/cadogav2/mc_oxidized_copper.png" />
</div>
<div class="item" title="Cut Oxidized Copper">
<img src="/uploads/cadogav2/mc_oxidized_cut_copper.png" />
</div>
<div class="item" title="Bamboo Planks">
<img src="/uploads/cadogav2/mc_bamboo_planks.png" />
</div>
<div class="item" title="Crimson Planks">
<img src="/uploads/cadogav2/mc_crimson_planks.png" />
</div>
<div class="item" title="Jungle Planks">
<img src="/uploads/cadogav2/mc_jungle_planks.png" />
</div>
<div class="item" title="Mangrove Planks">
<img src="/uploads/cadogav2/mc_mangrove_planks.png" />
</div>
<div class="item" title="Oak Planks">
<img src="/uploads/cadogav2/mc_oak_planks.png" />
</div>
<div class="item" title="Spruce Planks">
<img src="/uploads/cadogav2/mc_spruce_planks.png" />
</div>
<div class="item" title="Granite">
<img src="/uploads/cadogav2/mc_granite.png" />
</div>
<div class="item" title="Polished Granite">
<img src="/uploads/cadogav2/mc_polished_granite.png" />
</div>
</div>
</div>
</ul>
</ul>
<ul>
<i id="camera_views_i_btn" class="fa-solid fa-camera" title="Camera Views"></i>
<i id="camera_reset_btn"
class="fa-solid fa-house"
title="Center Camera to Origin"
></i>
</ul>
<ul class="i-menu">
<i
class="fa-solid fa-circle-half-stroke"
title="Reference Opacity"
>
</i>
<ul class="submenu">
<div class="submenu-content">
<input
type="range"
min="0"
max="1"
value="0.05"
step="0.05"
class="slider"
id="opacity-slider"
/>
</div>
</ul>
</ul>
<ul>
<i class="fa-solid fa-computer-mouse"></i>
</ul>
</nav>
</header>`;
document.body.innerHTML += `<div id="load-display" class="dialog-box">
<p>Loading Model...</p>
<div id="progress-bar">
<div id="progress"></div>
</div>
<div class="btn">Close</div>
</div>`
this.setupListeners();
}
changeSelectedTool(event){
if (event.target !== this.selectedToolBtn){
this.selectedToolBtn.classList.remove("btn-active");
this.selectedToolBtn = event.target;
this.selectedToolBtn.classList.add("btn-active");
}
}
setupListeners(){
console.log(document.getElementById("draw_i_btn"));
this.selectedToolBtn = document.getElementById("draw_i_btn");
console.log(this.selectedToolBtn);
document.getElementById("draw_i_btn").addEventListener('click',(event) => {
this.changeSelectedTool(event);
game.currentTool = Tools.DRAW;
});
document.getElementById("paint_i_btn").addEventListener('click',(event) => {
this.changeSelectedTool(event);
game.currentTool = Tools.PAINT;
});
document.getElementById("delete_i_btn").addEventListener('click',(event) => {
this.changeSelectedTool(event);
game.currentTool = Tools.ERASE;
});
// Reset Camera View
document.getElementById("camera_reset_btn").addEventListener('click',function(event){
if (controls){
controls.enableDamping = false;
controls.reset();
controls.enableDamping = true;
}
});
// Export as OBJ
document.getElementById("export_obj").addEventListener('click',(event) =>{
if (blocksMap.size === 0) {
console.log('There are no blocks to export');
return;
}
// Check if export is already in progress
if (this.isExporting) {
console.log('Export is already in progress');
return;
}
// Set the export flag
this.isExporting = true;
const group = new THREE.Group();
// Clone and add the meshes to the group
for (const mesh of blocksMap.values()) {
const clone = mesh.clone();
group.add(clone);
}
// Parse group to OBJ
const result = this.objExporter.parse(group);
// Download OBJ
const blob = new Blob([result], { type: 'text/plain' });
saveAs(blob, 'objects.obj');
// Remove the clones from the group
group.remove(...group.children);
this.isExporting = false;
});
// Export as GLB
document.getElementById("export_glb").addEventListener('click',(event) =>{
console.log('this.glbExporter')
console.log(this.glbExporter)
if (blocksMap.size === 0) {
console.log('There are no blocks to export');
return;
}
// Check if export is already in progress
if (this.isExporting) {
console.log('Export is already in progress');
return;
}
// Set the export flag
this.isExporting = true;
const objectsToExport = [...blocksMap.values()]
console.log('objectsToExport')
console.log(objectsToExport)
// Export the objects as GLB
this.glbExporter.parse(objectsToExport, (result) => {
// Save the GLB file
console.log('parse')
const blob = new Blob([result], { type: 'application/json' });
console.log('saveAs')
saveAs(blob, 'export.glb');
// Reset the export flag
this.isExporting = false;
}, (error) => {
console.error(error);
});
});
document.getElementById("save_csv").addEventListener('click',function(event){
if (blocksMap.size === 0) {
console.log('There are no blocks to export');
return;
}
fs.saveToCSV();
});
// load_csv
document.getElementById("load_csv").addEventListener('click',function(event){
// Check if the File System Access API is available
if ('showOpenFilePicker' in window) {
const options = {
types: [{
description: 'CSV Files',
accept: { 'text/csv': ['.csv'] },
}],
};
window.showOpenFilePicker(options).then((fileHandles) => {
const fileHandle = fileHandles[0];
// Use the file handle to read the file
fileHandle.getFile().then((file) => {
fs.loadFromCSV(file);
});
}).catch((error) => {
console.error(error);
});
} else {
// No File System Access API available
console.error('No File System Access API available');
}
});
document.getElementById("opacity-slider").addEventListener('input', function() {
fs.refMaterial.opacity = parseFloat(this.value);
});
document.querySelectorAll(".item").forEach((item, index) => {
item.addEventListener("click", () => {
// unselect previously selected item
if (this.selectedItem !== null) {
this.selectedItem.classList.remove("selected");
}
// select clicked item
item.classList.add("selected");
this.selectedItem = item;
this.selectedMaterialID = index;
// log selected item's number
console.log(`Selected item number: ${index + 1}`);
});
});
}
}
class Utils {
static updateMousePosition(mouse, event){
mouse.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1);
}
static getMouseGridPosition(mouse, raycaster, floor){
raycaster.setFromCamera(mouse, camera);
let intersects = raycaster.intersectObjects([floor]);
if (intersects.length > 0) {
const point = intersects[0].point;
const sqPosition = new THREE.Vector3(Math.floor(point.x), 0, Math.floor(point.z));
return sqPosition;
}
}
static getPreviewCubePosition(mouse, raycaster, floor){
raycaster.setFromCamera(mouse, camera);
let intersects = raycaster.intersectObjects([floor, ...blocksMap.values()]);
if (intersects.length > 0) {
let intersect = intersects[0];
if (intersect.object.userData.type === 'block'){
// Get face normal
const norm = intersect.face.normal;
const blockPosition = intersect.object.position;
const rayPosition = new THREE.Vector3(Math.floor(blockPosition.x), Math.floor(blockPosition.y), Math.floor(blockPosition.z));
return rayPosition.add(norm);
} else if (intersect.object.userData.type === 'floor') {
const point = intersect.point;
const sqPosition = new THREE.Vector3(Math.floor(point.x), 0, Math.floor(point.z));
return sqPosition;
} else {
return undefined;
}
}
}
static getMouseIntersect(mouse, raycaster, floor){
raycaster.setFromCamera(mouse, camera);
let intersects = raycaster.intersectObjects([floor,...blocksMap.values()]);
if (intersects.length > 0) {
return intersects[0];
}
}
static fitCameraToModel(model, camera){
const distance = 2;
const box = new THREE.Box3().setFromObject(model);
const size = box.getSize(new THREE.Vector3()).length();
const center = box.getCenter(new THREE.Vector3());
const direction = camera.position.clone().sub(center).normalize();
camera.position.copy(direction.multiplyScalar(-distance * size).add(center));
camera.lookAt(center);
}
}
const Tools = {
DRAW: "DRAW",
PAINT: "PAINT",
ERASE: "ERASE",
}
class Game {
constructor(ui, textureManager){
this.currentBlockMaterial = 1;
this.currentTool = Tools.DRAW;
this.mouse = new THREE.Vector2();
this.raycaster = new THREE.Raycaster();
this.previewBlock;
this.floor;
this.gridSize = 10;
this.gridHelper;
this.ui = ui;
this.tm = textureManager;
}
init(){
// Create grid
this.gridHelper = new THREE.GridHelper(this.gridSize, this.gridSize, 0x7d8085, 0x4f5154);
scene.add(this.gridHelper);
this.createFloor();
this.createPreviewCube();
this.tm.init();
}
createFloor(){
let geometry = new THREE.PlaneGeometry(this.gridSize, this.gridSize, this.gridSize, this.gridSize);
let material = new THREE.ShadowMaterial();
material.opacity = 0.2;
this.floor = new THREE.Mesh(geometry, material);
this.floor.rotation.x = -Math.PI / 2;
this.floor.userData.type = 'floor';
this.floor.receiveShadow = true;
scene.add(this.floor);
}
createPreviewCube(){
const geometry = new THREE.BoxGeometry(1,1,1);
const material = new THREE.MeshPhysicalMaterial({
color: 0x39e0fa, //0x40b8ff,
emissive: 0x134359,//0x134359,
roughness: 0.5,
transmission: 0.75,
thickness: 1,
reflectivity: 0.5,
specularIntensity: 0,
transparent: true,
//encoding: THREE.sRGBEncoding
});
this.previewBlock = new THREE.Mesh(geometry, material);
this.previewBlock.position.set(0.5, 0.5, 0.5);
scene.add(this.previewBlock);
}
movePreviewBlock(){
const sqPosition = Utils.getPreviewCubePosition(this.mouse, this.raycaster, this.floor);
if (sqPosition !== undefined){
this.previewBlock.visible = true;
this.previewBlock.position.copy(sqPosition.clone().add(new THREE.Vector3(0.5, 0.5, 0.5)));
} else {
this.previewBlock.visible = false;
}
}
addBlock(){
const previewBlockPosition = this.previewBlock.position;
const key = `${previewBlockPosition.x},${previewBlockPosition.y},${previewBlockPosition.z}`;
const isBlockOverlapping = blocksMap.has(key);
if (!isBlockOverlapping && this.previewBlock.visible === true){
const geometry = new THREE.BoxGeometry(1, 1, 1);
let block;
if (this.tm.materials.has(this.ui.selectedMaterialID)){
block = new THREE.Mesh(geometry, this.tm.materials.get(this.ui.selectedMaterialID));
block.userData.materialID = this.ui.selectedMaterialID;
} else {
block = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color: "#FF9933" }));
block.userData.materialID = -1;
}
block.position.copy(this.previewBlock.position);
block.userData.type = 'block';
block.castShadow = true;
block.receiveShadow = true;
block.updateMatrixWorld(); // updates for next intersect
blocksMap.set(key, block);
scene.add(block);
// Update preview cube position
this.movePreviewBlock();
} else {
console.log('A cube already exists at this position or is out of range!')
}
}
removeBlock(){
if (blocksMap.size === 0) return;
// Get intersected object
const intersect = Utils.getMouseIntersect(this.mouse, this.raycaster, this.floor);
if (intersect !== undefined) {
if (intersect.object.userData.type === 'block'){
const block = intersect.object;
const key = `${block.position.x},${block.position.y},${block.position.z}`;
scene.remove(block);
blocksMap.delete(key);
}
}
}
updateGridHelper(newSize){
scene.remove(this.gridHelper);
this.gridSize = newSize;
this.gridHelper = new THREE.GridHelper(this.gridSize, this.gridSize, 0x7d8085, 0x4f5154);
scene.add(this.gridHelper);
// Adjust the floor scale
const scaleMultiplier = newSize / 10;
this.floor.scale.set(scaleMultiplier, scaleMultiplier);
}
paintSingleBlock(){
console.log('paint one');
if (blocksMap.size === 0) return;
// Get intersected object
const intersect = Utils.getMouseIntersect(this.mouse, this.raycaster, this.floor);
if (intersect !== undefined) {
if (intersect.object.userData.type === 'block'){
const block = intersect.object;
const key = `${block.position.x},${block.position.y},${block.position.z}`;
if (block.userData.materialID !== this.ui.selectedMaterialID){
block.material = this.tm.materials.get(this.ui.selectedMaterialID);
block.userData.materialID = this.ui.selectedMaterialID;
}
}
}
}
paintAllSimilarBlocks(){
console.log('paint all');
if (blocksMap.size === 0) return;
// Get intersected object
const intersect = Utils.getMouseIntersect(this.mouse, this.raycaster, this.floor);
if (intersect !== undefined) {
if (intersect.object.userData.type === 'block'){
const selectedBlock = intersect.object;
const key = `${selectedBlock.position.x},${selectedBlock.position.y},${selectedBlock.position.z}`;
if (selectedBlock.userData.materialID !== this.ui.selectedMaterialID){
console.log('paint all - 1');
const selectedMaterialID = selectedBlock.userData.materialID;
for (const block of blocksMap.values()){
console.log('paint all', block.userData.materialID, selectedMaterialID);
// if block has the same material as block
if (block.userData.materialID == selectedMaterialID){
console.log('paint all - same');
block.material = this.tm.materials.get(this.ui.selectedMaterialID);
block.userData.materialID = this.ui.selectedMaterialID;
}
}
}
}
}
}
}
class TextureManager {
constructor(){
this.loader = new THREE.TextureLoader();
this.materials = new Map();
this.materialList = [];
}
init(){
this.createMaterialFromTextures([
"/uploads/cadogav2/mc-grass-side.png",
"/uploads/cadogav2/mc-grass-top.png",
"/uploads/cadogav2/mc-grass-bottom.png"], 0);
this.createMaterialFromTextures([
"/uploads/cadogav2/mc_grass_block_snow.png",
"/uploads/cadogav2/mc_snow.png",
"/uploads/cadogav2/mc-grass-bottom.png"], 1);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_snow.png"], 2);
this.createMaterialFromTextures([
"/uploads/cadogav2/mc_barrel_side.png",
"/uploads/cadogav2/mc_barrel_top_closed.png",
"/uploads/cadogav2/mc_barrel_bottom.png"], 3);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_cobblestone.png"], 4);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_diamond_block.png"], 5);
this.createMaterialFromTextures([
"/uploads/cadogav2/mc_sandstone_bricks_side.png",
"/uploads/cadogav2/mc_sandstone_top.png"], 6);
this.createMaterialFromTextures([
"/uploads/cadogav2/mc_sandstone_smooth.png",
"/uploads/cadogav2/mc_sandstone_top.png"], 7);
this.createMaterialFromTextures([
"/uploads/cadogav2/mc_sandstone_carved.png",
"/uploads/cadogav2/mc_sandstone_top.png"], 8);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_sand.png"], 9);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_deepslate_bricks.png"], 10);
this.createMaterialFromTextures([
"/uploads/cadogav2/mc_oak_log.png",
"/uploads/cadogav2/mc_oak_log_top.png"], 11);
this.createMaterialFromTextures([
"/uploads/cadogav2/mc_dark_oak_log.png",
"/uploads/cadogav2/mc_dark_oak_log_top.png"], 12);
this.createMaterialFromTextures([
"/uploads/cadogav2/mc_spruce_log.png",
"/uploads/cadogav2/mc_spruce_log_top.png"], 13);
this.createMaterialFromTextures([
"/uploads/cadogav2/mc_stripped_oak_log.png",
"/uploads/cadogav2/mc_stripped_oak_log_top.png"], 14);
this.createMaterialFromTextures([
"/uploads/cadogav2/mc_acacia_log.png",
"/uploads/cadogav2/mc_acacia_log_top.png"], 15);
// mc_stripped_acacia_log.png
this.createMaterialFromTextures([
"/uploads/cadogav2/mc_stripped_acacia_log.png",
"/uploads/cadogav2/mc_stripped_acacia_log_top.png"], 16);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_quartz_block_side.png"], 17);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_quartz_bricks.png"], 18);
this.createMaterialFromTextures([
"/uploads/cadogav2/mc_quartz_pillar.png",
"/uploads/cadogav2/mc_quartz_pillar_top.png"], 19);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_bricks.png"], 20);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_cracked_stone_bricks.png"], 21);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_mossy_stone_bricks.png"], 22);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_nether_bricks.png"], 23);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_mud_bricks.png"], 24);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_gold_block.png"], 25);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_copper_block.png"], 26);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_cut_copper.png"], 27);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_oxidized_copper.png"], 28);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_oxidized_cut_copper.png"], 29);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_bamboo_planks.png"], 30);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_crimson_planks.png"], 31);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_jungle_planks.png"], 32);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_mangrove_planks.png"], 33);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_oak_planks.png"], 34);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_spruce_planks.png"], 35);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_granite.png"], 36);
this.createMaterialFromTextures(["/uploads/cadogav2/mc_polished_granite.png"], 37);
}
createMaterialFromTextures(textureFilesArray, index){
const materialsArray = [];
let sideMaterial;
if (textureFilesArray.length === 1){
const texture = this.loader.load(textureFilesArray[0], (t) => {
t.minFilter = THREE.NearestFilter;
t.magFilter = THREE.NearestFilter;
});
const material = new THREE.MeshStandardMaterial({ map: texture });
materialsArray.push(material);
materialsArray.push(material);
materialsArray.push(material);
materialsArray.push(material);
materialsArray.push(material);
materialsArray.push(material);
} else if (textureFilesArray.length === 2){
const texture1 = this.loader.load(textureFilesArray[0], (t) => {
t.minFilter = THREE.NearestFilter;
t.magFilter = THREE.NearestFilter;
});
const texture2 = this.loader.load(textureFilesArray[1], (t) => {
t.minFilter = THREE.NearestFilter;
t.magFilter = THREE.NearestFilter;
});
const material1 = new THREE.MeshStandardMaterial({ map: texture1 });
const material2 = new THREE.MeshStandardMaterial({ map: texture2 });
materialsArray.push(material1);
materialsArray.push(material1);
materialsArray.push(material2);
materialsArray.push(material2);
materialsArray.push(material1);
materialsArray.push(material1);
} else if (textureFilesArray.length === 3){
textureFilesArray.forEach((path, i) => {
const texture = this.loader.load(path, (t) => {
t.minFilter = THREE.NearestFilter;
t.magFilter = THREE.NearestFilter;
});
const material = new THREE.MeshStandardMaterial({ map: texture });
materialsArray.push(material);
if (i === 0) {
sideMaterial = material;
materialsArray.push(material); // push additional side material
} else if (i === 2) {
// push 2 more additional side materials for the last faces
materialsArray.push(sideMaterial);
materialsArray.push(sideMaterial);
}
});
}
if (materialsArray.length > 0){
this.materials.set(index, materialsArray);
}
}
}
class FileSystem {
constructor(){
this.fileLoader = new THREE.FileLoader();
this.fileReader = new FileReader();
this.glbLoader = new GLTFLoader();
this.xhr = new XMLHttpRequest();
this.refMaterial = new THREE.MeshStandardMaterial({
color: 0xffffff,
opacity: 0.05,
transparent: true,
flatShading: true,
});
this.colors = {
0: "#B8255F",
1: "#FF9933",
2: "#7ECC49",
3: "#6ACCBC",
4: "#4073FF",
5: "#884DFF",
6: "#EB96EB",
7: "#FF8D85",
8: "#FF5733",
9: "#DAF7A6",
10: "#FFEC59",
11: "#C05780",
12: "#6C88C4",
13: "#74737A",
14: "#CCCCFF",
15: "#9FE2BF",
16: "#8A3186",
17: "#4057A7",
18: "#042838",
19: "#2DCED9",
20: "#CBD6E2",
21: "#4FB06D",
22: "#BE398D",
23: "#FF3B30",
24: "#FFFFFF"
};
this.colorsLength = Object.keys(this.colors).length;
this.importGLBModel = this.importGLBModel.bind(this);
}
importMineCraftJson(){
let jsonData;
this.fileLoader.load(jsonPath, (data) => {
jsonData = JSON.parse(data);
// Create blocks from json
this.createAllBlocks(jsonData, 0.5);
});
}
createBlockFromBlockData(blockData) {
const from = blockData.from.map(x => Math.round(x));
const to = blockData.to.map(x => Math.round(x));
const width = to[0] - from[0];
const height = to[1] - from[1];
const depth = to[2] - from[2];
const geometry = new THREE.BoxGeometry(width, height, depth);
const colorIndex = blockData.color !== undefined? blockData.color % (this.colorsLength - 1) : Math.floor(Math.random() * (this.colorsLength - 1));
const material = new THREE.MeshLambertMaterial({ color: this.colors[colorIndex] });
const cube = new THREE.Mesh(geometry, material);
// x, y, z
cube.position.set(from[0] + width / 2, from[1] + height / 2, from[2] + depth / 2);
return cube;
}
createAllBlocks(data, scaleFactor) {
const group = new THREE.Group();
data.elements.forEach(element => {
const cube = this.createBlockFromBlockData(element);
group.add(cube);
});
// Scale group
group.scale.set(scaleFactor, scaleFactor, scaleFactor);
// Transfer new scale directly to the children
let position = new THREE.Vector3()
let scale = new THREE.Vector3()
group.children.forEach(child => {
child.getWorldPosition(position)
child.position.copy(position)
child.getWorldScale(scale)
child.scale.copy(scale);
});
// Add children to the scene
scene.add(...group.children);
// Clear the group
group.children = [];
}
// IMPORT GLB
importGLBModel(path, byteSize=0){
$('#load-display').css('display', 'flex');
// "uploads/stewalsh/mc-wh.glb"
this.glbLoader.load("uploads/stewalsh/mc-sol.glb", (gltf) => {
$('#load-display').css('display', 'none');
// Get model
const model = gltf.scene;
this.centerToGrid(model)
model.traverse((child) => {
if (child instanceof THREE.Mesh) {
// Use the child's own texture map for the material
if (child.material.map) {
this.refMaterial.map = child.material.map;
//this.refMaterial.needsUpdate = true;
//this.refMaterial.normalScale = child.material.normalScale;
}
// Set the material on the mesh
child.material = this.refMaterial;
}
});
Utils.fitCameraToModel(model, camera)
// const box = new THREE.BoxHelper( model, 0xffff00 );
// scene.add( box );
// Add the model to the scene
scene.add(model);
},
// Loading progress
function (xhr) {
let progress;
if (byteSize === 0){
if (xhr.total > 0) {
progress = (xhr.loaded / xhr.total) * 100;
}
} else {
progress = (xhr.loaded / byteSize) * 100;
}
console.log("progress", progress)
$('#progress').width(progress + '%');
},
// Load failed
function (error) {
console.error("Error loading GLB file:", error);
}
);
}
saveToCSV() {
// CSV header
const header = "x_pos,y_pos,z_pos,x_rot,y_rot,z_rot,material_id\n";
let csvData = header;
for (const block of blocksMap.values()) {
// Get the position and rotation values
const pos = block.position;
const rot = block.rotation;
// Create a new row
const row = `${pos.x},${pos.y},${pos.z},${rot.x},${rot.y},${rot.z},${block.userData.materialID}\n`;
// Add row
csvData += row;
}
const blob = new Blob([csvData], { type: "text/csv" });
// Save the Blob as CSV file
saveAs(blob, "block_data.csv");
}
loadFromCSV(file) {
this.fileReader.readAsText(file);
this.fileReader.onload = () => {
const csvData = this.fileReader.result;
const lines = csvData.split(/\r?\n/);
// Skip Header, i = 1
for (let i = 1; i < lines.length; i++) {
// Get all values of the csv line
const values = lines[i].split(',');
if (values.length < 7) continue;
const position = new THREE.Vector3(parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]));
const rotation = new THREE.Vector3(parseFloat(values[3]), parseFloat(values[4]), parseFloat(values[5]));
const materialID = parseInt(values[6]);
const key = `${position.x},${position.y},${position.z}`;
const geometry = new THREE.BoxGeometry(1, 1, 1);
let block;
if (materialID >= 0){
block = new THREE.Mesh(geometry, game.tm.materials.get(materialID));
} else {
block = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color: "#FF9933" }));
}
block.position.copy(position);
block.rotation.set(rotation.x, rotation.y, rotation.z);
block.userData.materialID = materialID;
block.userData.type = 'block';
block.castShadow = true;
block.receiveShadow = true;
block.updateMatrixWorld(); // updates for next intersect
if (!blocksMap.has(key)){
console.log("add",key)
blocksMap.set(key, block);
scene.add(block);
} else {
console.log("skip",key)
}
}
};
}
centerToGrid(model) {
// Get the bounding box of the model
const box = new THREE.Box3().setFromObject(model);
// Get the size of the bounding box
const size = box.getSize(new THREE.Vector3());
// Get center of the bounding box
const center = box.getCenter(new THREE.Vector3());
// Get the maximum dimension of the bounding box
const maxDimension = Math.max(size.x, size.z);
const gridSize = Math.ceil(maxDimension / 2) * 2;
game.updateGridHelper(gridSize);
// Center model
model.position.x -= center.x;
model.position.y -= box.min.y;
model.position.z -= center.z;
console.log("box",box)
console.log("size",size)
// Calculate the top-left corner of the rectangle
const topLeftX = center.x - size.x / 2;
const topLeftZ = center.z - size.z / 2;
// Calculate the nearest rounded point and update the new rounded center coordinates
const newCenterX = Math.round(topLeftX) + size.x / 2;
const newCenterZ = Math.round(topLeftZ) + size.z / 2;
const offsetX = gridSize % 2 === 0 && size.x % 2 === 0? 0 : 0.5;
const offsetZ = gridSize % 2 === 0 && size.z % 2 === 0? 0 : 0.5;
model.position.x += offsetX;
model.position.z += offsetZ;
}
}
function addEventListeners(){
canvas.addEventListener('mousedown', onMouseDown);
canvas.addEventListener('mousemove', onMouseMove);
canvas.addEventListener('mouseup', onMouseUp);
window.addEventListener("resize", () => {
// Update sizes
sizes.width = window.innerWidth;
sizes.height = window.innerHeight;
// Update camera
camera.aspect = sizes.width / sizes.height;
camera.updateProjectionMatrix();
// Update renderer
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});
}
function onMouseDown(event){
console.log('DOWN');
mouseDownPos = {
x: event.clientX,
y: event.clientY
};
controls.enableRotate = false;
cameraStartPosition.copy(camera.position);
startDragging = true;
isOrbiting = false;
lastButton = event.button;
}
function onMouseMove(event){
console.log('MOVE')
Utils.updateMousePosition(game.mouse, event);
game.movePreviewBlock();
if (startDragging){
const distanceX = Math.abs(event.clientX - mouseDownPos.x);
const distanceY = Math.abs(event.clientY - mouseDownPos.y);
if (lastButton === 0 && !isOrbiting && (distanceX > mouseMoveThreshold || distanceY > mouseMoveThreshold)) {
console.log('TRUE')
isOrbiting = true;
mouseDownPos = {
x: event.clientX,
y: event.clientY
};
controls.enableRotate = true;
// force pointer event for orbit controls
const pointerEvent = new PointerEvent('pointerdown', {
bubbles: true,
cancelable: true,
pointerType: 'mouse',
button: event.button,
clientX: event.clientX,
clientY: event.clientY,
});
controls.domElement.dispatchEvent(pointerEvent);
}
}
}
function onMouseUp(event){
console.log('UP');
startDragging = false;
// event.button, 0 = left, 1 = middle, 2 = right
// console.log(event.button)
Utils.updateMousePosition(game.mouse, event);
// if camera position has been moved since mousedown, then exit function
// to ignore add or remove block functions
let startRounded = cameraStartPosition.clone().round();
let cameraRounded = camera.position.clone().round();
if (!startRounded.equals(cameraRounded)){
return;
}
if (game.currentTool === Tools.DRAW){
if (event.button === 0){
game.addBlock();
} else if (event.button === 2){
game.removeBlock();
}
} else if (game.currentTool === Tools.PAINT){
if (event.button === 0){
game.paintSingleBlock();
} else if (event.button === 2){
game.paintAllSimilarBlocks();
}
}
}
const sizes = {
width: window.innerWidth,
height: window.innerHeight,
};
let canvas;
let camera;
let scene;
let renderer;
let controls;
let ui;
// TextureManager
let tm;
let game;
// FileSystem
let fs;
const blocksMap = new Map();
let glbLoader;
let mouseDownPos = null;
const mouseMoveThreshold = 25;
let cameraStartPosition = new THREE.Vector3();
let startDragging = false;
let isOrbiting = false;
let lastButton = 0;
function startApp(){
console.log('AB')
console.log(AB)
fs = new FileSystem();
ui = new UI();
ui.init();
tm = new TextureManager();
game = new Game(ui, tm);
const container = document.getElementById("ab-runcanvas");
// Create a canvas element
canvas = document.createElement("canvas");
container.appendChild(canvas);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, sizes.width / sizes.height, 0.1, 1000 );
camera.position.x = -10;
camera.position.y = 7.5;
camera.position.z = 10;
scene.add(camera);
renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true,
alpha: true,
preserveDrawingBuffer : true
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setClearColor(0xff0000, 0);
//renderer.outputEncoding = THREE.sRGBEncoding;
//renderer.shadowMap.enabled = true;
//renderer.sortObjects = false;
//renderer.shadowMap.type = THREE.BasicShadowMap;
// Lights
let directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(-5, 10, 10);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.radius = 1;
scene.add(directionalLight);
let ambientLight = new THREE.AmbientLight(0xd3def5, 0.55)//(0x3258a8);
scene.add(ambientLight);
game.init();
addEventListeners();
controls = new OrbitControls(camera, canvas);
controls.mouseButtons = {LEFT: 0, MIDDLE: 2};
controls.dampingFactor = 0.15;
controls.enableDamping = true;
// controls.addEventListener('start', function(e) {
// console.log('c start')
// });
// controls.addEventListener('change', function(e) {
// console.log('c change')
// });
// controls.addEventListener('end', function(e) {
// console.log('c end')
// });
// AxesHelper
const axesHelper = new THREE.AxesHelper();
scene.add(axesHelper)
// Import Model
// fs.importMineCraftJson()
fs.importGLBModel();
const animate = () => {
// const elapsedTime = clock.getElapsedTime();
// Update controls
controls.update();
// Render
renderer.render(scene, camera);
//composer.render()
// Call tick again on the next frame
requestAnimationFrame(animate);
};
animate();
}