256 lines
6.2 KiB
HTML
256 lines
6.2 KiB
HTML
|
<!--
|
||
|
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>
|