Creating Obj from Web and Bugfixing
In this second part of our journey, we focused on enhancing the functionality and user experience of the Tetrimino Tool by introducing advanced features such as auto-filling, saving/loading canvas states, and even exporting the canvas as a 3D .OBJ file for use in Blender. Let’s go through each step and the challenges we overcame to achieve this.
1. Auto-filling the Canvas with Random Shapes
The first major feature we implemented was the ability to auto-fill the canvas with random Tetrimino shapes. This involved generating shapes at random positions on the canvas, ensuring they fit within the grid and don’t overlap with each other or extend outside the boundaries.
const fillEmptySpaces = () => { const types = Object.keys(tetriminos).filter(type => type !== 'b'); // Excluding 'b' for (let y = 0; y < canvas.height; y += gridSize) { for (let x = 0; x < canvas.width; x += gridSize) { const randomType = types[Math.floor(Math.random() * types.length)]; if (!isOutsideCanvas(tetriminos[randomType].shape, x, y) && !isPositionOccupied(tetriminos[randomType].shape, x, y)) { addTetriminoAtPosition(randomType, x, y); } } } };
The challenge was to ensure that the shapes don’t overlap with existing ones and stay within the canvas limits. We solved this by writing helper functions like isPositionOccupied()
and isOutsideCanvas()
that validate each position before placing a shape.
2. Undoing Auto-fill Action
Alongside auto-filling, we also implemented an undo function, which allows the user to revert the canvas to its previous state before auto-fill was applied. This required storing the previous state of the canvas and re-applying it when necessary.
const undoLastFill = () => { if (lastFillWasEmptyCanvas) { clearCanvas(); } else { tetriminoObjects = previousState; Object.keys(tetriminoCounts).forEach(type => { tetriminoCounts[type] = previousState.filter(obj => obj.shape === tetriminos[type].shape).length; document.getElementById(`count-${type}`).textContent = tetriminoCounts[type]; }); updateCanvas(); } };
This feature required us to track the state of the canvas, which involved saving the array of shapes and their positions, and updating it every time the user auto-fills the canvas.
3. Exporting Canvas to 3D .OBJ File
One of the most exciting features added to the tool was the ability to export the canvas as a 3D .OBJ file, which can then be imported into Blender for further 3D modeling. This was particularly useful for turning our 2D canvas into a 3D grid filled with Tetrimino-shaped cubes.
We achieved this by generating the vertex and face data for each cube and then writing this data into a .OBJ file format. This was a complex process as it required understanding the 3D coordinate system and properly organizing the vertices to create 3D cubes.
function generateAndDownloadObjFile() { let objFileContent = "# 21x21 grid with boundary and user-drawn 'b' cubes "; let vertices = []; let faces = []; let vertOffset = 1; // Generate boundary cubes for (let i = 0; i < 21; i++) { for (let j = 0; j < 21; j++) { if (i === 0 || i === 20 || j === 0 || j === 20) { // Boundary cubes let cubeVertices = generateCube(i, 0, j); vertices.push(...cubeVertices); faces.push([vertOffset, vertOffset + 1, vertOffset + 2, vertOffset + 3]); // bottom face vertOffset += 8; } } } // Add user-drawn cubes tetriminoObjects.forEach(obj => { if (obj.shape === tetriminos['b'].shape) { // Only 'b' type cubes let cubeVertices = generateCube(obj.x / gridSize, 0, obj.y / gridSize); vertices.push(...cubeVertices); faces.push([vertOffset, vertOffset + 1, vertOffset + 2, vertOffset + 3]); // bottom face vertOffset += 8; } }); // Output .OBJ file format vertices.forEach(v => objFileContent += `v ${v.x} ${v.y} ${v.z} `); faces.forEach(f => objFileContent += `f ${f.join(' ')} `); downloadFile("canvas_with_cubes.obj", objFileContent); }
This feature opened up new possibilities for creating more complex designs and using the tool beyond just 2D shapes. The exported .OBJ files were tested successfully in Blender, where the cubes appeared in the correct positions and scale.
4. Saving and Loading Canvas States
One of the most requested features was the ability to save and load canvas states. This allowed users to export the current state of the canvas to a JSON format and reload it later, preserving all Tetrimino positions.
const generateCanvasState = () => { const state = tetriminoObjects.map(obj => ({ type: Object.keys(tetriminos).find(t => JSON.stringify(tetriminos[t].shape) === JSON.stringify(obj.shape)), x: obj.x, y: obj.y })); document.getElementById('canvasStateInput').value = JSON.stringify(state); }; const applyCanvasState = () => { const state = JSON.parse(document.getElementById('canvasStateInput').value); tetriminoObjects = state.map(obj => ({ shape: tetriminos[obj.type].shape, color: tetriminos[obj.type].color, x: obj.x, y: obj.y })); updateCanvas(); };
The challenge was ensuring that the shapes restored from the JSON state appeared exactly as they were before, in the correct position, and that the counters updated accordingly. This involved tweaking the logic to store the type of each shape and recreating it upon loading.
Is it the end?
Throughout this part of the project, we introduced exciting new features that extended the tool's functionality and made it more versatile. We faced challenges in managing the canvas state, preventing overlaps, and ensuring accurate 3D exports, but each problem was solved eventually.
Tetramino tool
Tool to check if pixelart canvas can be filled with tetramino blocks
Status | In development |
Category | Tool |
Author | Stonehill Games |
Genre | Puzzle |
Tags | Pixel Art, tool |
More posts
- Building a Tetramino Fitting Tool47 days ago
- Building a Tetris-like Web Game47 days ago