DEbug not working so moving pcs
This commit is contained in:
335
tools/tileset-creator.html
Normal file
335
tools/tileset-creator.html
Normal file
@@ -0,0 +1,335 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Dusk Tools / Tileset Creator</title>
|
||||
|
||||
<style type="text/css">
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
canvas {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Dusk Tileset Creator</h1>
|
||||
<p>
|
||||
Tool to create tilesets for textures. Currently only supports well sliced
|
||||
tilesets (those with fixed dimensions essentially). In the future, may
|
||||
support more freeform tilesets.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<h2>Tileset Settings</h2>
|
||||
<div>
|
||||
<label>Tile Width:</label>
|
||||
<input type="number" value="8" data-tile-width min="1" step="1" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Tile Height:</label>
|
||||
<input type="number" value="8" data-tile-height min="1" step="1" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Column Count:</label>
|
||||
<input type="number" value="10" data-column-count min="1" step="1" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Row Count:</label>
|
||||
<input type="number" value="10" data-row-count min="1" step="1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Preview</h2>
|
||||
<div>
|
||||
<input type="file" data-texture-input />
|
||||
</div>
|
||||
<div data-output-error style="color:red;display:none;"></div>
|
||||
<div>
|
||||
<label>
|
||||
Preview Scale:
|
||||
<input type="number" value="4" data-indexed-preview-scale min="1" step="1" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Preview Background:
|
||||
<button data-page-bg-white>White</button>
|
||||
<button data-page-bg-transparent>Black</button>
|
||||
<button data-page-bg-checkerboard>Checkerboard</button>
|
||||
<button data-page-bg-magenta>Magenta</button>
|
||||
<button data-page-bg-blue>Blue</button>
|
||||
<button data-page-bg-green>Green</button>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<canvas data-output-preview style="border:1px solid black;"></canvas>
|
||||
</div>
|
||||
<div>
|
||||
<textarea data-output-information rows="15" style="width: 500px;"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<button data-tileset-download>Download Tileset</button>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
const elTileWidth = document.querySelector('[data-tile-width]');
|
||||
const elTileHeight = document.querySelector('[data-tile-height]');
|
||||
const elColumnCount = document.querySelector('[data-column-count]');
|
||||
const elRowCount = document.querySelector('[data-row-count]');
|
||||
const elFileInput = document.querySelector('[data-texture-input]');
|
||||
const elOutputError = document.querySelector('[data-output-error]');
|
||||
const elOutputInformation = document.querySelector('[data-output-information]');
|
||||
const elOutputPreview = document.querySelector('[data-output-preview]');
|
||||
const elScale = document.querySelector('[data-indexed-preview-scale]');
|
||||
const btnDownloadTileset = document.querySelector('[data-tileset-download]');
|
||||
const btnBackgroundWhite = document.querySelector('[data-page-bg-white]');
|
||||
const btnBackgroundTransparent = document.querySelector('[data-page-bg-transparent]');
|
||||
const btnBackgroundCheckerboard = document.querySelector('[data-page-bg-checkerboard]');
|
||||
const btnBackgroundMagenta = document.querySelector('[data-page-bg-magenta]');
|
||||
const btnBackgroundBlue = document.querySelector('[data-page-bg-blue]');
|
||||
const btnBackgroundGreen = document.querySelector('[data-page-bg-green]');
|
||||
const btnDownloadPalette = document.querySelector('[data-palette-download]');
|
||||
const btnDownloadImage = document.querySelector('[data-indexed-download]');
|
||||
|
||||
let imageWidth = 0;
|
||||
let imageHeight = 0;
|
||||
let image = null;
|
||||
let hoveredX = -1;
|
||||
let hoveredY = -1;
|
||||
|
||||
const updatePreview = () => {
|
||||
if(!image) return;
|
||||
|
||||
const scale = parseInt(elScale.value) || 1;
|
||||
elOutputPreview.width = imageWidth * scale;
|
||||
elOutputPreview.height = imageHeight * scale;
|
||||
const ctx = elOutputPreview.getContext('2d');
|
||||
ctx.clearRect(0, 0, elOutputPreview.width, elOutputPreview.height);
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
ctx.drawImage(image, 0, 0, elOutputPreview.width, elOutputPreview.height);
|
||||
|
||||
// Draw grid lines
|
||||
const tileWidth = parseInt(elTileWidth.value) || 0;
|
||||
const tileHeight = parseInt(elTileHeight.value) || 0;
|
||||
const scaledTileWidth = tileWidth * scale;
|
||||
const scaledTileHeight = tileHeight * scale;
|
||||
const columnCount = parseInt(elColumnCount.value) || 0;
|
||||
const rowCount = parseInt(elRowCount.value) || 0;
|
||||
ctx.strokeStyle = 'rgba(255,0,0,1)';
|
||||
for(let x = scaledTileWidth; x < elOutputPreview.width; x += scaledTileWidth) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, 0);
|
||||
ctx.lineTo(x, elOutputPreview.height);
|
||||
ctx.stroke();
|
||||
}
|
||||
for(let y = scaledTileHeight; y < elOutputPreview.height; y += scaledTileHeight) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, y);
|
||||
ctx.lineTo(elOutputPreview.width, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
const u0 = tileWidth / imageWidth;
|
||||
const v0 = tileHeight / imageHeight;
|
||||
const hoveredU0 = hoveredX != -1 && hoveredY != -1 ? (hoveredX * tileWidth / imageWidth) : 0;
|
||||
const hoveredV0 = hoveredX != -1 && hoveredY != -1 ? (hoveredY * tileHeight / imageHeight) : 0;
|
||||
const hoveredU1 = hoveredU0 + u0;
|
||||
const hoveredV1 = hoveredV0 + v0;
|
||||
|
||||
elOutputInformation.value = [
|
||||
hoveredX != -1 ? `Hovered Tile: ${hoveredX}, ${hoveredY} (${hoveredY * columnCount + hoveredX})` : 'Hovered Tile: None',
|
||||
hoveredX != -1 ? `Hovered UV: ${(hoveredU0).toFixed(4)}, ${(hoveredV0).toFixed(4)} -> ${(hoveredU1).toFixed(4)}, ${(hoveredV1).toFixed(4)}` : 'Hovered UV: None',
|
||||
`Image Width: ${imageWidth}`,
|
||||
`Image Height: ${imageHeight}`,
|
||||
`Tile Width: ${elTileWidth.value}`,
|
||||
`Tile Height: ${elTileHeight.value}`,
|
||||
`Column Count: ${columnCount}`,
|
||||
`uv: ${u0.toFixed(4)}, ${v0.toFixed(4)}`,
|
||||
`Row Count: ${rowCount}`,
|
||||
`Tile count: ${columnCount * rowCount}`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
elTileWidth.addEventListener('input', () => {
|
||||
if(imageWidth) {
|
||||
const tileWidth = parseInt(elTileWidth.value);
|
||||
const columnCount = Math.floor(imageWidth / tileWidth);
|
||||
elColumnCount.value = columnCount;
|
||||
}
|
||||
updatePreview();
|
||||
});
|
||||
|
||||
elTileHeight.addEventListener('input', () => {
|
||||
if(imageHeight) {
|
||||
const tileHeight = parseInt(elTileHeight.value);
|
||||
const rowCount = Math.floor(imageHeight / tileHeight);
|
||||
elRowCount.value = rowCount;
|
||||
}
|
||||
updatePreview();
|
||||
});
|
||||
|
||||
elColumnCount.addEventListener('input', () => {
|
||||
if(!imageWidth) {
|
||||
alert('Set an image first to calculate tile width from column count');
|
||||
return;
|
||||
}
|
||||
|
||||
const columnCount = parseInt(elColumnCount.value);
|
||||
const tileWidth = Math.floor(imageWidth / columnCount);
|
||||
elTileWidth.value = tileWidth;
|
||||
updatePreview();
|
||||
});
|
||||
|
||||
elRowCount.addEventListener('input', () => {
|
||||
if(!imageHeight) {
|
||||
alert('Set an image first to calculate tile height from row count');
|
||||
return;
|
||||
}
|
||||
|
||||
const rowCount = parseInt(elRowCount.value);
|
||||
const tileHeight = Math.floor(imageHeight / rowCount);
|
||||
elTileHeight.value = tileHeight;
|
||||
updatePreview();
|
||||
});
|
||||
|
||||
elScale.addEventListener('input', () => {
|
||||
updatePreview();
|
||||
});
|
||||
|
||||
elOutputPreview.addEventListener('mousemove', (e) => {
|
||||
if(!image) return;
|
||||
|
||||
const scale = parseInt(elScale.value) || 1;
|
||||
const tileWidth = parseInt(elTileWidth.value) || 0;
|
||||
const tileHeight = parseInt(elTileHeight.value) || 0;
|
||||
const columnCount = parseInt(elColumnCount.value) || 0;
|
||||
const rowCount = parseInt(elRowCount.value) || 0;
|
||||
|
||||
const rect = elOutputPreview.getBoundingClientRect();
|
||||
const x = Math.floor((e.clientX - rect.left) / scale);
|
||||
const y = Math.floor((e.clientY - rect.top) / scale);
|
||||
hoveredX = Math.floor(x / tileWidth);
|
||||
hoveredY = Math.floor(y / tileHeight);
|
||||
|
||||
if(hoveredX < 0 || hoveredX >= columnCount || hoveredY < 0 || hoveredY >= rowCount) {
|
||||
hoveredX = -1;
|
||||
hoveredY = -1;
|
||||
}
|
||||
|
||||
updatePreview();
|
||||
});
|
||||
|
||||
elOutputPreview.addEventListener('mouseleave', () => {
|
||||
hoveredX = -1;
|
||||
hoveredY = -1;
|
||||
updatePreview();
|
||||
});
|
||||
|
||||
// File
|
||||
elFileInput.addEventListener('change', (e) => {
|
||||
elOutputError.style.display = 'none';
|
||||
|
||||
if(!elFileInput.files.length) {
|
||||
elOutputError.textContent = 'No file selected';
|
||||
elOutputError.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
const file = elFileInput.files[0];
|
||||
image = new Image();
|
||||
image.onload = () => {
|
||||
imageWidth = image.width;
|
||||
imageHeight = image.height;
|
||||
const tileWidth = parseInt(elTileWidth.value);
|
||||
const tileHeight = parseInt(elTileHeight.value);
|
||||
const columnCount = Math.floor(imageWidth / tileWidth);
|
||||
const rowCount = Math.floor(imageHeight / tileHeight);
|
||||
elColumnCount.value = columnCount;
|
||||
elRowCount.value = rowCount;
|
||||
updatePreview();
|
||||
};
|
||||
image.onerror = () => {
|
||||
image = null;
|
||||
elOutputError.textContent = 'Failed to load image';
|
||||
elOutputError.style.display = 'block';
|
||||
updatePreview();
|
||||
};
|
||||
image.src = URL.createObjectURL(file);
|
||||
});
|
||||
|
||||
btnDownloadTileset.addEventListener('click', () => {
|
||||
if(!image) {
|
||||
alert('No image loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
const tileWidth = parseInt(elTileWidth.value);
|
||||
const tileHeight = parseInt(elTileHeight.value);
|
||||
const columnCount = parseInt(elColumnCount.value);
|
||||
const rowCount = parseInt(elRowCount.value);
|
||||
const u0 = tileWidth / imageWidth;
|
||||
const v0 = tileHeight / imageHeight;
|
||||
const tileCount = columnCount * rowCount;
|
||||
|
||||
const headerBytes = new Uint8Array([
|
||||
'D'.charCodeAt(0),// Dusk
|
||||
'T'.charCodeAt(0),// Tileset
|
||||
'F'.charCodeAt(0),// File/Format
|
||||
0x00, // version
|
||||
tileWidth & 0xFF,// Tile width (uint16_t)
|
||||
(tileWidth >> 8) & 0xFF,
|
||||
tileHeight & 0xFF,// Tile height (uint16_t)
|
||||
(tileHeight >> 8) & 0xFF,
|
||||
columnCount & 0xFF,// Column count (uint16_t)
|
||||
(columnCount >> 8) & 0xFF,
|
||||
rowCount & 0xFF,// Row count (uint16_t)
|
||||
(rowCount >> 8) & 0xFF,
|
||||
// Float32_t UV step (u0, v0)
|
||||
...new Uint8Array(new Float32Array([u0]).buffer),
|
||||
...new Uint8Array(new Float32Array([v0]).buffer),
|
||||
]);
|
||||
|
||||
// Download file
|
||||
const blob = new Blob([headerBytes], { type: 'application/octet-stream' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'tileset.dtf';
|
||||
a.click();
|
||||
});
|
||||
|
||||
btnBackgroundWhite.addEventListener('click', () => {
|
||||
document.body.style.background = 'white';
|
||||
});
|
||||
btnBackgroundTransparent.addEventListener('click', () => {
|
||||
document.body.style.background = 'black';
|
||||
});
|
||||
btnBackgroundCheckerboard.addEventListener('click', () => {
|
||||
document.body.style.background = 'repeating-conic-gradient(#ccc 0% 25%, #eee 0% 50%) 50% / 20px 20px';
|
||||
});
|
||||
btnBackgroundMagenta.addEventListener('click', () => {
|
||||
document.body.style.background = 'magenta';
|
||||
});
|
||||
btnBackgroundBlue.addEventListener('click', () => {
|
||||
document.body.style.background = 'blue';
|
||||
});
|
||||
btnBackgroundGreen.addEventListener('click', () => {
|
||||
document.body.style.background = 'green';
|
||||
});
|
||||
|
||||
// Init
|
||||
btnBackgroundCheckerboard.click();
|
||||
updatePreview();
|
||||
</script>
|
||||
</html>
|
||||
Reference in New Issue
Block a user