Add Library
This commit is contained in:
parent
e365b9dbd9
commit
3c47103b39
318 changed files with 56465 additions and 0 deletions
624
libraries/bitluni_ESP32Lib/Utilities/SpriteEditor.html
Normal file
624
libraries/bitluni_ESP32Lib/Utilities/SpriteEditor.html
Normal file
|
@ -0,0 +1,624 @@
|
|||
<!--
|
||||
Author: bitluni 2019
|
||||
License:
|
||||
Creative Commons Attribution ShareAlike 4.0
|
||||
https://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
For further details check out:
|
||||
https://youtube.com/bitlunislab
|
||||
https://github.com/bitluni
|
||||
http://bitluni.net
|
||||
-->
|
||||
<head>
|
||||
<title>bitluni's Sprite Editor</title>
|
||||
<script>
|
||||
|
||||
var project = {
|
||||
name: "sprites",
|
||||
zoom: 4,
|
||||
sprites: []
|
||||
};
|
||||
|
||||
function objFromId(id)
|
||||
{
|
||||
for(var i = 0; i < project.sprites.length; i++)
|
||||
if(project.sprites[i].id === id)
|
||||
return project.sprites[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
function upSprite(id)
|
||||
{
|
||||
var sprites = project.sprites;
|
||||
for(var i = 1; i < sprites.length; i++)
|
||||
if(sprites[i].id === id)
|
||||
{
|
||||
var w = sprites[i - 1];
|
||||
sprites[i - 1] = sprites[i];
|
||||
sprites[i] = w;
|
||||
update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function downSprite(id)
|
||||
{
|
||||
var sprites = project.sprites;
|
||||
for(var i = 0; i < sprites.length - 1; i++)
|
||||
if(sprites[i].id === id)
|
||||
{
|
||||
var w = sprites[i + 1];
|
||||
sprites[i + 1] = sprites[i];
|
||||
sprites[i] = w;
|
||||
update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function deleteSprite(id)
|
||||
{
|
||||
var sprites = project.sprites;
|
||||
for(var i = 0; i < sprites.length; i++)
|
||||
if(sprites[i].id === id)
|
||||
{
|
||||
sprites.splice(i, 1);
|
||||
update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function newButton(text, cb)
|
||||
{
|
||||
var b = document.createElement("button");
|
||||
b.innerHTML = text;
|
||||
b.onclick = cb;
|
||||
return b;
|
||||
}
|
||||
|
||||
function checker(ctx, xres, yres)
|
||||
{
|
||||
for(var y = 0; y < yres; y++)
|
||||
for(var x = 0; x < xres; x++)
|
||||
{
|
||||
ctx.fillStyle = (x + y) & 1 ? "#eeeeee" : "#ffffff";
|
||||
ctx.fillRect(x * 10, y * 10, 10, 10);
|
||||
}
|
||||
}
|
||||
|
||||
function draw(obj)
|
||||
{
|
||||
checker(obj.ctx, obj.xres, obj.yres);
|
||||
var img = obj.ctx.getImageData(0, 0, obj.xres, obj.yres);
|
||||
var p = 0;
|
||||
for(var y = 0; y < obj.yres; y++)
|
||||
for(var x = 0; x < obj.xres; x++)
|
||||
{
|
||||
var a = obj.source[p + 3];
|
||||
var ra = 255 - a;
|
||||
img.data[p + 0] = Math.round((img.data[p + 0] * ra + obj.source[p + 0] * a) / 255.);
|
||||
img.data[p + 1] = Math.round((img.data[p + 1] * ra + obj.source[p + 1] * a) / 255.);
|
||||
img.data[p + 2] = Math.round((img.data[p + 2] * ra + obj.source[p + 2] * a) / 255.);
|
||||
img.data[p + 3] = 255;
|
||||
p += 4;
|
||||
}
|
||||
obj.ctx.putImageData(img, 0, 0);
|
||||
obj.ctx.fillStyle = "rgba(0, 255, 0, 0.7)";
|
||||
for(var i = 0; i < obj.points.length; i++)
|
||||
{
|
||||
var p = obj.points[i];
|
||||
obj.ctx.fillRect(p[0] - 3, p[1], 7, 1);
|
||||
obj.ctx.fillRect(p[0], p[1] - 3, 1, 7);
|
||||
}
|
||||
}
|
||||
|
||||
function getXY(obj, e)
|
||||
{
|
||||
var r = obj.canvas.getBoundingClientRect();
|
||||
return [Math.floor(e.offsetX / project.zoom), Math.floor(e.offsetY / project.zoom)];
|
||||
}
|
||||
|
||||
function canvasClick(obj, e)
|
||||
{
|
||||
var pos = getXY(obj, e);
|
||||
obj.points.push(pos);
|
||||
draw(obj);
|
||||
updatePoints(obj);
|
||||
}
|
||||
|
||||
function canvasMove(obj, e)
|
||||
{
|
||||
var pos = getXY(obj, e);
|
||||
draw(obj);
|
||||
obj.ctx.fillStyle = "rgba(255, 0, 0, 0.7)";
|
||||
obj.ctx.fillRect(0, pos[1], pos[0], 1);
|
||||
obj.ctx.fillRect(pos[0] + 1, pos[1], obj.xres - pos[0] - 1, 1);
|
||||
obj.ctx.fillRect(pos[0], 0, 1, pos[1]);
|
||||
obj.ctx.fillRect(pos[0], pos[1] + 1, 1, obj.yres - pos[1] - 1);
|
||||
}
|
||||
|
||||
function canvasOut(obj)
|
||||
{
|
||||
draw(obj);
|
||||
}
|
||||
|
||||
function createCanvas(obj)
|
||||
{
|
||||
var canvas = obj.canvas = document.createElement('canvas');
|
||||
var ctx = obj.ctx = canvas.getContext('2d');
|
||||
canvas.width = obj.xres;
|
||||
canvas.height = obj.yres;
|
||||
canvas.addEventListener("mousedown", function(e){ canvasClick(obj, e); }, false);
|
||||
canvas.addEventListener("mouseout", function(){ canvasOut(obj); }, false);
|
||||
canvas.addEventListener("mousemove", function(e){ canvasMove(obj, e); }, false);
|
||||
}
|
||||
|
||||
function addFile(file, id)
|
||||
{
|
||||
var obj = {"id": id, "name": file.name, "type": file.type, "xres": 0, "yres": 0, "points":[]};
|
||||
project.sprites.push(obj);
|
||||
var reader = new FileReader();
|
||||
reader.onload = function()
|
||||
{
|
||||
var img = document.createElement('img');
|
||||
img.onload = function()
|
||||
{
|
||||
obj.xres = img.width;
|
||||
obj.yres = img.height;
|
||||
createCanvas(obj);
|
||||
obj.ctx.drawImage(img, 0, 0);
|
||||
obj.source = Array.from(obj.ctx.getImageData(0, 0, obj.xres, obj.yres).data);
|
||||
obj.points.push([Math.floor(obj.xres * 0.5), Math.floor(obj.yres * 0.5)]);
|
||||
update();
|
||||
};
|
||||
img.src = reader.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
function addFiles(event)
|
||||
{
|
||||
for(var i = 0; i < event.target.files.length; i++)
|
||||
{
|
||||
var file = event.target.files[i];
|
||||
addFile(file, "f" + file.name.replace(/[^A-Z0-9]/ig, "_") + Math.floor(Math.random() * 1000000))
|
||||
}
|
||||
event.target.value = "";
|
||||
}
|
||||
|
||||
function spritesToHeader(pixelformat)
|
||||
{
|
||||
var name = project.name;
|
||||
var sprites = project.sprites;
|
||||
var offsets = [0];
|
||||
var pointOffsets = [0];
|
||||
var rf, rshift, gf, gshift, bf, bshift, af, ashift, type, bytesPerPixel;
|
||||
switch(pixelformat)
|
||||
{
|
||||
case "R2G2B2A2":
|
||||
{
|
||||
rf = gf = bf = af = 3.;
|
||||
rshift = 0;
|
||||
gshift = 2;
|
||||
bshift = 4;
|
||||
ashift = 6;
|
||||
type = "unsigned char";
|
||||
bytesPerPixel = 1;
|
||||
break;
|
||||
}
|
||||
case "R4G4B4A4":
|
||||
{
|
||||
rf = gf = bf = af = 15.;
|
||||
rshift = 0;
|
||||
gshift = 4;
|
||||
bshift = 8;
|
||||
ashift = 12;
|
||||
type = "unsigned short";
|
||||
bytesPerPixel = 2;
|
||||
break;
|
||||
}
|
||||
case "R5G5B4A2":
|
||||
{
|
||||
rf = gf = 31.;
|
||||
bf = 15.;
|
||||
af = 3.;
|
||||
rshift = 0;
|
||||
gshift = 5;
|
||||
bshift = 10;
|
||||
ashift = 14;
|
||||
type = "unsigned short";
|
||||
bytesPerPixel = 2;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
alert("Unsupported pixel format " + pixelformat);
|
||||
return;
|
||||
}
|
||||
|
||||
for(var i = 0; i < sprites.length; i++)
|
||||
{
|
||||
offsets.push(offsets[i] + (sprites[i].source.length / 4) * bytesPerPixel);
|
||||
pointOffsets.push(pointOffsets[i] + sprites[i].points.length);
|
||||
}
|
||||
var text = "const int " + name + "Offsets[] = {"
|
||||
for(var i = 0; i < offsets.length; i++)
|
||||
text += offsets[i] + ", ";
|
||||
text += "};\r\n";
|
||||
text += "const short " + name + "PointOffsets[] = {"
|
||||
for(var i = 0; i < pointOffsets.length; i++)
|
||||
text += pointOffsets[i] + ", ";
|
||||
text += "};\r\n";
|
||||
|
||||
text += "const unsigned short " + name + "Res[][2] = {"
|
||||
for(var i = 0; i < sprites.length; i++)
|
||||
{
|
||||
text += "{" + sprites[i].xres + ", " + sprites[i].yres + "}, ";
|
||||
}
|
||||
text += "};\r\n";
|
||||
|
||||
text += "const signed short " + name + "Points[][2] = {"
|
||||
for(var i = 0; i < sprites.length; i++)
|
||||
for(var j = 0; j < sprites[i].points.length; j++)
|
||||
text += "{" + sprites[i].points[j][0] + ", " + sprites[i].points[j][1] + "}, ";
|
||||
text += "};\r\n";
|
||||
|
||||
var k = 0;
|
||||
text += "const " + type + " " + name + "Pixels[] = {"
|
||||
for(var i = 0; i < sprites.length; i++)
|
||||
{
|
||||
if((i & 63) == 0) text += "\r\n";
|
||||
var s = sprites[i].source;
|
||||
for(var j = 0; j < s.length; j+=4)
|
||||
{
|
||||
var r = s[j + 0];
|
||||
var g = s[j + 1];
|
||||
var b = s[j + 2];
|
||||
var a = s[j + 3];
|
||||
var c = 0;
|
||||
c = (Math.round(r / 255. * rf) << rshift) + (Math.round(g / 255. * gf) << gshift) + (Math.round(b / 255. * bf) << bshift) + (Math.round(a / 255. * af) << ashift);
|
||||
text += c + ", ";
|
||||
k++;
|
||||
if((k & 31) == 0)
|
||||
text += "\n";
|
||||
}
|
||||
}
|
||||
text += "};\r\n";
|
||||
text += "Sprites " + name + "(" + sprites.length + ", " + name + "Pixels, " + name + "Offsets, " + name + "Res, " + name + "Points, " + name + "PointOffsets, Sprites::PixelFormat::" + pixelformat + ");\r\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
function cropSprite(obj)
|
||||
{
|
||||
var bx = [obj.xres, 0];
|
||||
var by = [obj.yres, 0];
|
||||
var p = 3;
|
||||
for(var y = 0; y < obj.yres; y++)
|
||||
for(var x = 0; x < obj.xres; x++)
|
||||
{
|
||||
if(obj.source[p] > 0)
|
||||
{
|
||||
bx[0] = Math.min(bx[0], x);
|
||||
bx[1] = Math.max(bx[1], x);
|
||||
by[0] = Math.min(by[0], y);
|
||||
by[1] = Math.max(by[1], y);
|
||||
}
|
||||
p += 4;
|
||||
}
|
||||
if(bx[0] > bx[1])
|
||||
{
|
||||
bx = [0, 0];
|
||||
by = [0, 0];
|
||||
}
|
||||
var xres = bx[1] - bx[0] + 1;
|
||||
var yres = by[1] - by[0] + 1;
|
||||
var source = new Array(xres * yres * 4);
|
||||
var p = 0;
|
||||
for(var y = by[0]; y <= by[1]; y++)
|
||||
for(var x = bx[0]; x <= bx[1]; x++)
|
||||
{
|
||||
var p0 = (y * obj.xres + x) * 4;
|
||||
source[p++] = obj.source[p0++];
|
||||
source[p++] = obj.source[p0++];
|
||||
source[p++] = obj.source[p0++];
|
||||
source[p++] = obj.source[p0++];
|
||||
}
|
||||
for(var i = 0; i < obj.points.length; i++)
|
||||
{
|
||||
obj.points[i][0] -= bx[0];
|
||||
obj.points[i][1] -= by[0];
|
||||
}
|
||||
obj.source = source;
|
||||
obj.xres = xres;
|
||||
obj.yres = yres;
|
||||
obj.canvas.width = xres;
|
||||
obj.canvas.height = yres;
|
||||
}
|
||||
|
||||
function crop()
|
||||
{
|
||||
for(var i = 0; i < project.sprites.length; i++)
|
||||
cropSprite(project.sprites[i]);
|
||||
update();
|
||||
}
|
||||
|
||||
function getMeta()
|
||||
{
|
||||
var text = "";
|
||||
for(var i = 0; i < project.sprites.length; i++)
|
||||
text += i + ", " + project.sprites[i].name + "\r\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
function deletePoint(id, i)
|
||||
{
|
||||
var obj = objFromId(id);
|
||||
obj.points.splice(i, 1);
|
||||
draw(obj);
|
||||
updatePoints(obj);
|
||||
}
|
||||
|
||||
function changePointX(id, i, value)
|
||||
{
|
||||
var obj = objFromId(id);
|
||||
obj.points[i][0] = value;
|
||||
draw(obj);
|
||||
}
|
||||
|
||||
function changePointY(id, i, value)
|
||||
{
|
||||
var obj = objFromId(id);
|
||||
obj.points[i][1] = value;
|
||||
draw(obj);
|
||||
}
|
||||
|
||||
function addPointItem(parent, id, i, point)
|
||||
{
|
||||
var pointElement = document.createElement("div");
|
||||
pointElement.className = "point";
|
||||
var coord = document.createElement("span");
|
||||
coord.innerHTML =
|
||||
'<input type="number" value="' + point[0] + '" style="width: 50px" onchange="changePointX(\'' + id + '\', ' + i + ', this.value)">' +
|
||||
'<input type="number" value="' + point[1] + '" style="width: 50px" onchange="changePointY(\'' + id + '\', ' + i + ', this.value)">';
|
||||
pointElement.appendChild(coord);
|
||||
pointElement.appendChild(newButton("✘", function(){ deletePoint(id, i)}));
|
||||
parent.appendChild(pointElement);
|
||||
}
|
||||
|
||||
function updatePoints(obj)
|
||||
{
|
||||
var pointList = document.querySelector("#" + obj.id + " .points");
|
||||
pointList.innerHTML = "";
|
||||
for(var j = 0; j < obj.points.length; j++)
|
||||
addPointItem(pointList, obj.id, j, obj.points[j]);
|
||||
}
|
||||
|
||||
function addListItem(i, id, name, canvas)
|
||||
{
|
||||
var spriteDiv = document.createElement("div");
|
||||
spriteDiv.id = id;
|
||||
var index = document.createElement("span");
|
||||
index.className = "num";
|
||||
index.innerHTML = i;
|
||||
spriteDiv.appendChild(index);
|
||||
spriteDiv.appendChild(canvas);
|
||||
canvas.style.zoom = project.zoom;
|
||||
spriteDiv.appendChild(newButton("✘", function(){ deleteSprite(id)}));
|
||||
spriteDiv.appendChild(newButton("⬇", function(){ downSprite(id)}));
|
||||
spriteDiv.appendChild(newButton("⬆", function(){ upSprite(id)}));
|
||||
var points = document.createElement("span");
|
||||
points.className = "points block right";
|
||||
spriteDiv.appendChild(points);
|
||||
var span = document.createElement("span");
|
||||
span.className = "right";
|
||||
span.innerHTML = name;
|
||||
spriteDiv.appendChild(span);
|
||||
document.getElementById("sprites").appendChild(spriteDiv);
|
||||
}
|
||||
|
||||
function loadProject(event)
|
||||
{
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e){
|
||||
project = JSON.parse(e.target.result);
|
||||
document.getElementById("name").value = project.name;
|
||||
document.getElementById("zoom").value = project.zoom;
|
||||
for(var i = 0; i < project.sprites.length; i++)
|
||||
createCanvas(project.sprites[i]);
|
||||
update();
|
||||
};
|
||||
reader.readAsText(event.target.files[0]);
|
||||
document.getElementById("files").className = "hidden";
|
||||
document.getElementById("filearea").innerHTML = "";
|
||||
}
|
||||
|
||||
function saveProject()
|
||||
{
|
||||
document.getElementById("filearea").innerHTML = "";
|
||||
var fileArea = document.getElementById("filearea");
|
||||
var file = document.createElement("a");
|
||||
file.className = "block file";
|
||||
file.download = file.innerHTML = document.getElementById("name").value + ".json";
|
||||
for(var i = 0; i < project.sprites.length; i++)
|
||||
if(project.sprites[i].bs)
|
||||
delete project.sprites[i].bs;
|
||||
file.href = URL.createObjectURL(new Blob([JSON.stringify(project)], {type: "application/json"}));
|
||||
fileArea.appendChild(file);
|
||||
document.getElementById("files").className = "menu";
|
||||
}
|
||||
|
||||
function saveHeader()
|
||||
{
|
||||
document.getElementById("filearea").innerHTML = "";
|
||||
var fileArea = document.getElementById("filearea");
|
||||
var file = document.createElement("a");
|
||||
var meta = document.createElement("a");
|
||||
meta.className = file.className = "block file";
|
||||
file.download = file.innerHTML = project.name + ".h";
|
||||
meta.download = meta.innerHTML = project.name + ".txt";
|
||||
var e = document.getElementById("pixelformat");
|
||||
var pixelformat = e.options[e.selectedIndex].value;
|
||||
file.href = URL.createObjectURL(new Blob([spritesToHeader(pixelformat)], {type: "text/plain"}));
|
||||
meta.href = URL.createObjectURL(new Blob([getMeta()], {type: "text/plain"}));
|
||||
fileArea.appendChild(file);
|
||||
fileArea.appendChild(meta);
|
||||
document.getElementById("files").className = "menu";
|
||||
}
|
||||
|
||||
function update()
|
||||
{
|
||||
for(var i = 0; i < project.sprites.length; i++)
|
||||
if(!project.sprites[i].canvas)
|
||||
return;
|
||||
project.name = document.getElementById("name").value;
|
||||
project.zoom = document.getElementById("zoom").value;
|
||||
|
||||
document.getElementById("sprites").innerHTML = "";
|
||||
for(var i = 0; i < project.sprites.length; i++)
|
||||
{
|
||||
addListItem(i, project.sprites[i].id, project.sprites[i].name, project.sprites[i].canvas);
|
||||
updatePoints(project.sprites[i]);
|
||||
draw(project.sprites[i]);
|
||||
}
|
||||
document.getElementById("files").className = "hidden";
|
||||
document.getElementById("filearea").innerHTML = "";
|
||||
}
|
||||
|
||||
</script>
|
||||
<style>
|
||||
#sprites
|
||||
{
|
||||
}
|
||||
.num
|
||||
{
|
||||
width: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
.option
|
||||
{
|
||||
margin: 3px;
|
||||
}
|
||||
.options
|
||||
{
|
||||
background-color: #aeaeee;
|
||||
}
|
||||
.spritefiles
|
||||
{
|
||||
background-color: #aeeeee;
|
||||
}
|
||||
.menu
|
||||
{
|
||||
background-color: #eeeeee;
|
||||
padding: 1px;
|
||||
display: block;
|
||||
margin: 3px;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
#sprites > div
|
||||
{
|
||||
margin: 2px;
|
||||
border-radius: 3px;
|
||||
background-color: #aeeeae;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#sprites > div > *
|
||||
{
|
||||
margin-right: 5px;
|
||||
vertical-align: top;
|
||||
}
|
||||
#sprites button
|
||||
{
|
||||
float: right;
|
||||
}
|
||||
.right
|
||||
{
|
||||
float: right;
|
||||
}
|
||||
h1
|
||||
{
|
||||
color: white;
|
||||
background: #800000;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.block
|
||||
{
|
||||
padding: 5px;
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.project
|
||||
{
|
||||
background-color: #eeeeae;
|
||||
}
|
||||
.hidden
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
.file
|
||||
{
|
||||
background-color: #ffdddd;
|
||||
margin-right: 5px;
|
||||
}
|
||||
h1
|
||||
{
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
canvas
|
||||
{
|
||||
cursor: none;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.points
|
||||
{
|
||||
background-color: #eeddcc;
|
||||
}
|
||||
.point
|
||||
{
|
||||
background-color: #ccddee;
|
||||
margin: 2px;
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.point input
|
||||
{
|
||||
height: 23px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="font-family: arial">
|
||||
<h1>bitluni's Sprite Editor</h1>
|
||||
<div style="max-width: 800px">
|
||||
<div class="menu">
|
||||
<span class="project block">
|
||||
<button title="Open project file" onclick="document.getElementById('openproject').click()">📂</button>
|
||||
<input id="openproject" type="file" onchange="loadProject(event)" accept=".json" hidden>
|
||||
<button title="Generate project file" onclick="saveProject();">💾</button>
|
||||
</span>
|
||||
<span class="block spritefiles">
|
||||
<button title="Add image files" onclick="document.getElementById('addimages').click()">🌆</button>
|
||||
<input id="addimages" type="file" onchange="addFiles(event)" accept="image/*" multiple hidden>
|
||||
<button title="Export sprite header" onclick="saveHeader();" style="font-size: 15px; font-weight:bold">.h</button>
|
||||
<select id="pixelformat" title="Export pixel format">
|
||||
<option vlaue="R2G2B2A2">R2G2B2A2</option>
|
||||
<option vlaue="R5G5B4A2">R5G5B4A2</option>
|
||||
<option vlaue="R4G4B4A4">R4G4B4A4</option>
|
||||
</select>
|
||||
</span>
|
||||
<span class="block options">
|
||||
<span class="option">name <input id="name" type="text" value="sprites" style="width: 80px" onchange="update()"></span>
|
||||
<span class="option">🔎<input id="zoom" type="number" value="4" style="width: 50px" onchange="update()"></span>
|
||||
<span class="option"><button title="Crop sprites" onclick="crop();">⌗</button></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="hidden" id="files">
|
||||
Files
|
||||
<div id="filearea">
|
||||
</div>
|
||||
</div>
|
||||
<div id="sprites"></div>
|
||||
</div>
|
||||
|
||||
<small>check out <a href="https://youtube.com/bitlunislab">bitluni's lab</a></small>
|
||||
</body></html>
|
255
libraries/bitluni_ESP32Lib/Utilities/StlConverter.html
Normal file
255
libraries/bitluni_ESP32Lib/Utilities/StlConverter.html
Normal file
|
@ -0,0 +1,255 @@
|
|||
<!--
|
||||
Author: bitluni 2019
|
||||
License:
|
||||
Creative Commons Attribution ShareAlike 4.0
|
||||
https://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
For further details check out:
|
||||
https://youtube.com/bitlunislab
|
||||
https://github.com/bitluni
|
||||
http://bitluni.net
|
||||
-->
|
||||
<head>
|
||||
<script>
|
||||
function readStl(stl, removeDouble)
|
||||
{
|
||||
//digital high five to @tonylukasavage for his https://github.com/tonylukasavage/jsstl
|
||||
var data = {
|
||||
vertexCount: 0,
|
||||
triangleCount: 0,
|
||||
edgeCount: 0,
|
||||
triangles: [],
|
||||
edges: [],
|
||||
vertices: [],
|
||||
triangleNormals: []
|
||||
}
|
||||
var map = [];
|
||||
|
||||
var o = 80;
|
||||
var dv = new DataView(stl);
|
||||
var triangleCount = dv.getUint32(o, true);
|
||||
o += 4;
|
||||
for (var j = 0; j < triangleCount; j++)
|
||||
{
|
||||
var t = [];
|
||||
data.triangleNormals.push([dv.getFloat32(o, true), dv.getFloat32(o + 4, true), dv.getFloat32(o + 8, true)]);
|
||||
o += 12;
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
var v = [dv.getFloat32(o, true), dv.getFloat32(o + 4, true), dv.getFloat32(o + 8, true)];
|
||||
if(removeDouble)
|
||||
{
|
||||
var newv = false;
|
||||
if(map["f"+v[0]] === undefined)
|
||||
map["f"+v[0]] = [];
|
||||
if(map["f"+v[0]]["f"+v[1]] === undefined)
|
||||
map["f"+v[0]]["f"+v[1]] = [];
|
||||
if(map["f"+v[0]]["f"+v[1]]["f"+v[2]] === undefined)
|
||||
{
|
||||
map["f"+v[0]]["f"+v[1]]["f"+v[2]] = data.vertices.length;
|
||||
t.push(data.vertices.length);
|
||||
data.vertices.push(v);
|
||||
}
|
||||
else
|
||||
t.push(map["f"+v[0]]["f"+v[1]]["f"+v[2]]);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
t.push(data.vertices.length);
|
||||
data.vertices.push(v);
|
||||
}
|
||||
o += 12;
|
||||
}
|
||||
data.triangles.push(t);
|
||||
o += 2;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
function scaleMesh(mesh)
|
||||
{
|
||||
if(!mesh.vertices.length) return;
|
||||
var min = [mesh.vertices[0][0], mesh.vertices[0][1], mesh.vertices[0][2]];
|
||||
var max = [min[0], min[1], min[2]];
|
||||
for(var i = 0; i < mesh.vertices.length; i++)
|
||||
{
|
||||
var v = mesh.vertices[i];
|
||||
min[0] = Math.min(min[0], v[0]);
|
||||
min[1] = Math.min(min[1], v[1]);
|
||||
min[2] = Math.min(min[2], v[2]);
|
||||
max[0] = Math.max(max[0], v[0]);
|
||||
max[1] = Math.max(max[1], v[1]);
|
||||
max[2] = Math.max(max[2], v[2]);
|
||||
}
|
||||
size = Math.max(Math.max(max[0] - min[0], max[1] - min[1]), max[2] - min[2]);
|
||||
for(var i = 0; i < mesh.vertices.length; i++)
|
||||
{
|
||||
var v = mesh.vertices[i];
|
||||
v[0] = (v[0] - (max[0] + min[0]) * 0.5) / size;
|
||||
v[1] = (v[1] - (max[1] + min[1]) * 0.5) / size;
|
||||
v[2] = (v[2] - (max[2] + min[2]) * 0.5) / size;
|
||||
}
|
||||
}
|
||||
|
||||
function createEdges(mesh)
|
||||
{
|
||||
var map = [];
|
||||
mesh.edges = [];
|
||||
|
||||
for(var i = 0; i < mesh.triangles.length; i++)
|
||||
{
|
||||
var t = mesh.triangles[i];
|
||||
for(var j = 0; j < 3; j++)
|
||||
{
|
||||
var v0 = t[j];
|
||||
var v1 = t[(j + 1) % 3];
|
||||
if(v0 > v1)
|
||||
{
|
||||
var v = v0; v0 = v1; v1 = v;
|
||||
}
|
||||
if(map["v" + v0] === undefined)
|
||||
map["v" + v0] = [];
|
||||
if(map["v" + v0]["v" + v1] === undefined)
|
||||
{
|
||||
mesh.edges.push([v0, v1]);
|
||||
map["v" + v0]["v" + v1] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function meshToText(mesh, digits, edges, triangles, normals, name)
|
||||
{
|
||||
var text = "namespace " + name + "\r\n{\r\n";
|
||||
text += "const int vertexCount = " + mesh.vertices.length + ";\r\n";
|
||||
if(edges)
|
||||
text += "const int edgeCount = " + mesh.edges.length + ";\r\n";
|
||||
if(triangles)
|
||||
text += "const int triangleCount = " + mesh.triangles.length + ";\r\n";
|
||||
|
||||
text += "const float vertices[][3] = {"
|
||||
for(var i = 0; i < mesh.vertices.length; i++)
|
||||
{
|
||||
if((i & 15) == 0) text += "\r\n";
|
||||
text += mesh.vertices[i][0].toFixed(digits) + ", " + mesh.vertices[i][1].toFixed(digits) + ", " + mesh.vertices[i][2].toFixed(digits) + ", ";
|
||||
}
|
||||
text += "};\r\n";
|
||||
|
||||
if(triangles)
|
||||
{
|
||||
text += "const unsigned short triangles[][3] = {";
|
||||
for(var i = 0; i < mesh.triangles.length; i++)
|
||||
{
|
||||
if((i & 15) == 0) text += "\r\n";
|
||||
text += mesh.triangles[i][0] + ", " + mesh.triangles[i][1] + ", " + mesh.triangles[i][2] + ", ";
|
||||
}
|
||||
text += "};\r\n";
|
||||
}
|
||||
|
||||
if(edges)
|
||||
{
|
||||
text += "const unsigned short edges[][2] = {";
|
||||
for(var i = 0; i < mesh.edges.length; i++)
|
||||
{
|
||||
if((i & 15) == 0) text += "\r\n";
|
||||
text += mesh.edges[i][0] + ", " + mesh.edges[i][1] + ", ";
|
||||
}
|
||||
text += "};\r\n";
|
||||
}
|
||||
|
||||
if(normals)
|
||||
{
|
||||
text += "const float triangleNormals[][3] = {";
|
||||
for(var i = 0; i < mesh.triangles.length; i++)
|
||||
{
|
||||
if((i & 15) == 0) text += "\r\n";
|
||||
text += mesh.triangleNormals[i][0].toFixed(digits) + ", " + mesh.triangleNormals[i][1].toFixed(digits) + ", " + mesh.triangleNormals[i][2].toFixed(digits) + ", ";
|
||||
}
|
||||
text += "};\r\n";
|
||||
}
|
||||
text += "};\r\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
function convert(event)
|
||||
{
|
||||
var reader = new FileReader();
|
||||
var file = event.target.files[0];
|
||||
reader.onload = function(){
|
||||
mesh = readStl(reader.result, true);
|
||||
scaleMesh(mesh);
|
||||
createEdges(mesh);
|
||||
var link = document.createElement("a");
|
||||
var name = file.name.split('.', 1)[0];
|
||||
link.download = name + ".h";
|
||||
link.href = URL.createObjectURL(new Blob(
|
||||
[meshToText(mesh, 4,
|
||||
document.getElementById("edges").checked,
|
||||
document.getElementById("tris").checked,
|
||||
document.getElementById("triNorms").checked, name)], {type: "text/plain"}));
|
||||
document.body.appendChild(document.createElement("br"));
|
||||
document.body.appendChild(link);
|
||||
link.innerHTML = link.download;
|
||||
link.click();
|
||||
//document.body.removeChild(link);
|
||||
event.target.value = "";
|
||||
}
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
</script><style>
|
||||
.menu
|
||||
{
|
||||
background-color: #eeeeee;
|
||||
padding: 1px;
|
||||
display: block;
|
||||
margin: 3px;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
h1
|
||||
{
|
||||
color: white;
|
||||
background: #800000;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.file
|
||||
{
|
||||
background-color: #ffdddd;
|
||||
margin-right: 5px;
|
||||
}
|
||||
h1
|
||||
{
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.block
|
||||
{
|
||||
padding: 5px;
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.options
|
||||
{
|
||||
background-color: #aeeeee;
|
||||
}
|
||||
.fileselection
|
||||
{
|
||||
background-color: #eeeeae;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="font-family: arial">
|
||||
<h1>bitluni's STL Converter</h1>
|
||||
<div style="max-width: 800px">
|
||||
<div class="menu">
|
||||
Choose data structures to export and open the binary STL file to convert it to a c++ header file.<br>
|
||||
Normals are needed to calculate lighting.<br>
|
||||
<span class="options block">
|
||||
Export: <input id="edges" type="checkbox">edges <input id="tris" type="checkbox" checked>triangles <input id="triNorms" type="checkbox" checked>triangle normals<br>
|
||||
</span>
|
||||
<span class="fileselection block">
|
||||
<input type="file" onchange="convert(event)" accept=".stl">
|
||||
</span>
|
||||
</div>
|
||||
</body></html>
|
Loading…
Add table
Add a link
Reference in a new issue