BangleApps/apps/viewstl/viewstl.app.js

417 lines
13 KiB
JavaScript
Raw Normal View History

2020-08-13 17:54:33 +00:00
// globals
var u = [0, 1, 0]; // axis of rotation
var aux = new Float32Array(12);
var p_aux = E.getAddressOf(aux, true);
var a = 0.5; // angle of rotation
var light = new Float32Array(12);
var p_light = E.getAddressOf(light, true);
light[0] = 1/Math.sqrt(2);
light[1] = 1/Math.sqrt(2);
light[2] = 0;
var filex = new Float32Array(6);
var p_filex = E.getAddressOf(filex, true);
var addr = new Int32Array(12);
var p_addr = E.getAddressOf(addr, true);
var polyp = new Int32Array(6);
var p_polyp = E.getAddressOf(polyp, true);
var polyedge = new Int32Array(8);
var p_polyedge = E.getAddressOf(polyedge, true);
var edges;
var p_edges;
var zbuf;
var p_zbuf;
var zDist;
var points;
var p_points;
var rpoints;
var p_rpoints;
var npoints = 0;
var faces;
var p_faces;
var vpoints;
var p_vpoints;
var normals;
var p_normals;
var rnormals;
var p_rnormals;
var lastTime;
var nFrames = 0;
var interv;
var qZrot = 0;
2020-08-13 17:54:33 +00:00
var qWireframe = 0;
var zBeta = 0;
const c05 = Math.cos(0.05);
const s05 = Math.sin(0.05);
var c = E.compiledC(`
// void rotatePoints(int, int, int, int)
// void projectPoints(int, int, int, int)
// void popZBuf(int, int, int, int)
// int processFace(int, int, int)
// void initEdges(int, int)
// int findSlot(int, int, int)
void rotatePoints(float *p, float *res, int len, float *u) {
float c = u[3];
float s = u[4];
for (int i=0; i<len; ++i) {
res[3*i ] = p[3*i]*(c+u[0]*u[0]*(1-c)) + p[3*i+1]*(u[0]*u[1]*(1-c)-u[2]*s) + p[3*i+2]*(u[0]*u[2]*(1-c)+u[1]*s);
res[3*i+1] = p[3*i]*(u[1]*u[0]*(1-c)+u[2]*s) + p[3*i+1]*(c+u[1]*u[1]*(1-c)) + p[3*i+2]*(u[1]*u[2]*(1-c)-u[0]*s);
res[3*i+2] = p[3*i]*(u[2]*u[0]*(1-c)-u[1]*s) + p[3*i+1]*(u[2]*u[1]*(1-c)+u[0]*s) + p[3*i+2]*(c+u[2]*u[2]*(1-c));
}
}
void projectPoints(float *p, short *res, int len, int zd) {
float zdist = zd/100.0;
for (int i=0; i<len; ++i) {
float dz = p[3*i+2]-zdist;
res[2*i+0] = (short)(120.0+320.0*p[3*i+0]/dz);
res[2*i+1] = (short)(80.0+320.0*p[3*i+1]/dz);
}
}
void quick_sort(int *base, unsigned int nel)
{
int *pivot;
int tmp;
unsigned int left, right;
if (nel < 2) return;
pivot = base;
left = 0;
right = nel-1;
while (left < right) {
while (*(base+left)>*pivot && left<right) left++;
while (*(base+right)<*pivot && left<right) right--;
if (left<right && *(base+left)!=*(base+right)) {
if (base+left==pivot) {
pivot = base+right;
} else if (base+right==pivot) {
pivot = base+left;
}
tmp = *(base+left); *(base+left) = *(base+right); *(base+right) = tmp;
} else {
left++;
}
}
quick_sort(base, left-1);
quick_sort(base+left, nel-left);
}
void popZBuf(float *p, unsigned short *f, int *zb, int len) {
for (int i=0; i<len; ++i) {
int z = int(1000.0*(p[f[i*3]*3+2]+p[f[i*3+1]*3+2]+p[f[i*3+2]*3+2]));
zb[i] = (z<<10) | i;
}
quick_sort(zb, len);
}
int processFace(int *p, int z, int qWire) {
short *faces = (short *)p[0];
float *rnormals = (float *)p[1];
short *vpoints = (short *)p[2];
int *polyp = (int *)p[3];
float *light = (float *)p[4];
char *edges = (char *)p[5];
int *polyedge = (int *)p[6];
int shade;
z = z & 0x3ff;
if (rnormals[z*3+2]>0) {
for (int j=0; j<3; ++j) {
polyp[j*2+0] = vpoints[faces[z*3+j]*2];
polyp[j*2+1] = vpoints[faces[z*3+j]*2+1];
}
if (qWire) {
char e = edges[z];
switch(e) {
case 1:
polyedge[0]=polyp[0];polyedge[1]=polyp[1];
polyedge[2]=polyp[2];polyedge[3]=polyp[3];
polyedge[4]=polyp[0];polyedge[5]=polyp[1];
polyedge[6]=polyp[2];polyedge[7]=polyp[3];
break;
case 2:
polyedge[0]=polyp[2];polyedge[1]=polyp[3];
polyedge[2]=polyp[4];polyedge[3]=polyp[5];
polyedge[4]=polyp[2];polyedge[5]=polyp[3];
polyedge[6]=polyp[4];polyedge[7]=polyp[5];
break;
case 4:
polyedge[0]=polyp[4];polyedge[1]=polyp[5];
polyedge[2]=polyp[0];polyedge[3]=polyp[1];
polyedge[4]=polyp[4];polyedge[5]=polyp[5];
polyedge[6]=polyp[0];polyedge[7]=polyp[1];
break;
case 7:
for (int k=0; k<6; ++k) polyedge[k] = polyp[k];
polyedge[6] = polyp[0]; polyedge[7] = polyp[1];
break;
case 3:
for (int k=0; k<6; ++k) polyedge[k] = polyp[k];
polyedge[6]=polyp[4]; polyedge[7]=polyp[5];
break;
case 5:
polyedge[0]=polyp[4];polyedge[1]=polyp[5];
polyedge[2]=polyp[0];polyedge[3]=polyp[1];
polyedge[4]=polyp[2];polyedge[5]=polyp[3];
polyedge[6]=polyp[2];polyedge[7]=polyp[3];
break;
case 6:
polyedge[0]=polyp[2];polyedge[1]=polyp[3];
polyedge[2]=polyp[4];polyedge[3]=polyp[5];
polyedge[4]=polyp[0];polyedge[5]=polyp[1];
polyedge[6]=polyp[0];polyedge[7]=polyp[1];
break;
default:
for (int k=0; k<8; ++k) polyedge[k]=-1;
}
}
float s = 0.2+(1.0+rnormals[3*z+0]*light[0]+rnormals[3*z+1]*light[1]+rnormals[3*z+2]*light[2])/2.9;
shade = int(s*31) | (int(s*63)<<5) | (int(s*31)<<11);
return shade;
}
else return 0;
}
void initEdges(int *p, int len) {
short *faces = (short *)p[0];
char *edges = (char *)p[5];
float *normals = (float *)p[7];
for (int i=0; i<len; ++i) {
char r=0;
for (int j=0; j<3; ++j) {
short e1=faces[j+i*3];
short e2=faces[((j+1) % 3)+i*3];
int k = len;
while (k--) {
if (i!=k && ((faces[k*3+1]==e1 && faces[k*3]==e2) || (faces[k*3+2]==e1 && faces[k*3+1]==e2) || (faces[k*3]==e1 && faces[k*3+2]==e2))) {
if (normals[k*3]*normals[i*3]+normals[k*3+1]*normals[i*3+1]+normals[k*3+2]*normals[i*3+2] < 0.95) r |= (1<<j);
k=0;
}
}
}
edges[i] = r;
}
}
int findSlot(float *p, float *x, int len) {
for (int i=0; i<len; ++i)
if (p[3*i]-x[0]<0.0001 && p[3*i]-x[0]>-0.0001 && p[3*i+1]-x[1]<0.0001 && p[3*i+1]-x[1]>-0.0001 && p[3*i+2]-x[2]<0.0001 && p[3*i+2]-x[2]>-0.0001) return i;
p[3*len] = x[0];
p[3*len+1] = x[1];
p[3*len+2] = x[2];
return len;
}
`);
function initNormals() {
var i = faces.length/3;
while (i--) {
normals[i*3+0] = (points[faces[3*i+1]*3+1]-points[faces[3*i+0]*3+1])*(points[faces[3*i+2]*3+2]-points[faces[3*i+0]*3+2]) - (points[faces[3*i+1]*3+2]-points[faces[3*i+0]*3+2])*(points[faces[3*i+2]*3+1]-points[faces[3*i+0]*3+1]);
normals[i*3+1] = (points[faces[3*i+1]*3+2]-points[faces[3*i+0]*3+2])*(points[faces[3*i+2]*3+0]-points[faces[3*i+0]*3+0]) - (points[faces[3*i+1]*3+0]-points[faces[3*i+0]*3+0])*(points[faces[3*i+2]*3+2]-points[faces[3*i+0]*3+2]);
normals[i*3+2] = (points[faces[3*i+1]*3+0]-points[faces[3*i+0]*3+0])*(points[faces[3*i+2]*3+1]-points[faces[3*i+0]*3+1]) - (points[faces[3*i+1]*3+1]-points[faces[3*i+0]*3+1])*(points[faces[3*i+2]*3+0]-points[faces[3*i+0]*3+0]);
var n = Math.sqrt(normals[i*3]*normals[i*3]+normals[i*3+1]*normals[i*3+1]+normals[i*3+2]*normals[i*3+2]);
if (n>0) {
normals[i*3] /= -n;
normals[i*3+1] /= -n;
normals[i*3+2] /= -n;
}
}
}
function readSTL(fn) {
var fb = require("Storage").read(fn);
2020-08-22 02:52:54 +00:00
var nverts=0,i=0; while((i=fb.indexOf("vertex",i)+1)!=0) nverts++;
2020-08-13 17:54:33 +00:00
points = new Float32Array(nverts);
p_points = E.getAddressOf(points, true);
faces = new Uint16Array(nverts);
p_faces = E.getAddressOf(faces, true);
edges = new Uint8Array(Math.max(faces.length/3,24))
p_edges = E.getAddressOf(edges, true);
var fp=0, p=0;
g.setColor(0.9, 0.9, 0.9);
g.drawRect(20, 140, 220, 160);
g.setColor(0.6, 0.6, 0.9);
while (p<fb.length) {
var line = '';
while (fb[p]!="\n") line += fb[p++];
p++;
if (line.toLowerCase().includes("vertex")) {
var v = line.trim().split(/\s+/);
filex[0] = v[1];
filex[1] = v[3];
filex[2] = v[2];
var slot = 0|c.findSlot(p_points, p_filex, npoints);
if (slot==npoints) npoints++;
faces[fp++] = slot;
if (fp%3 == 0) g.fillRect(21, 141, 21+198*fp/nverts, 159);
}
}
vpoints = new Uint16Array(Math.max(12,2*npoints));
p_vpoints = E.getAddressOf(vpoints, true);
normals = new Float32Array(faces.length);
p_normals = E.getAddressOf(normals, true);
initNormals();
rnormals = new Float32Array(faces.length);
p_rnormals = E.getAddressOf(rnormals, true);
rpoints = new Float32Array(points.length);
p_rpoints = E.getAddressOf(rpoints, true);
zbuf = new Int32Array(Math.max(12,faces.length/3));
p_zbuf = E.getAddressOf(zbuf, true);
addr[0] = p_faces;
addr[1] = p_rnormals;
addr[2] = p_vpoints;
addr[3] = p_polyp;
addr[4] = p_light;
addr[5] = p_edges;
addr[6] = p_polyedge;
addr[7] = p_normals;
c.initEdges(p_addr, faces.length/3);
}
function rotV(v, u, c, s) {
"ram"
return [v[0]*(c+u[0]*u[0]*(1-c)) + v[1]*(u[0]*u[1]*(1-c)-u[2]*s) + v[2]*(u[0]*u[2]*(1-c)+u[1]*s),
v[0]*(u[1]*u[0]*(1-c)+u[2]*s) + v[1]*(c+u[1]*u[1]*(1-c)) + v[2]*(u[1]*u[2]*(1-c)-u[0]*s),
v[0]*(u[2]*u[0]*(1-c)-u[1]*s) + v[1]*(u[2]*u[1]*(1-c)+u[0]*s) + v[2]*(c+u[2]*u[2]*(1-c))];
}
function largestExtent(pts) {
var x = 0;
var i = pts.length/3;
while (i--) {
var e = pts[3*i]*pts[3*i]+pts[3*i+1]*pts[3*i+1]+pts[3*i+2]*pts[3*i+2];
if (e>x) x = e;
}
return Math.sqrt(x);
}
function draw() {
"ram"
const n = [1, 0, 0];
if (qZrot>0) {
var ca, sa, cb, sb;
if (qZrot==2) {
var acc = Bangle.getAccel();
zBeta = -Math.atan2(acc.z, -acc.y);
var comp = Bangle.getCompass();
if (!isNaN(comp.heading)) {
var m = [comp.dx, comp.dy, comp.dz];
//console.log(m);
var rm = rotV(m, [1, 0, 0], Math.cos(zBeta), Math.sin(zBeta));
a = -Math.atan2(rm[0], rm[2]);
//console.log("heading="+a*180/Math.PI, "zBeta="+zBeta);
}
else a = 0;
}
ca=Math.cos(a); sa=Math.sin(a); cb=Math.cos(zBeta); sb=Math.sin(zBeta);
2020-08-13 17:54:33 +00:00
var ul = Math.sqrt(sb*sb+ca*ca*sb*sb+2*sa*sa*cb+2*ca*sb*sb+2*sa*sa);
u = [(sb+ca*sb)/ul, (-sa-sa*cb)/ul, (-sa*sb)/ul];
var ra = Math.acos((ca+cb+ca*cb-1)/2);
if (ra<0) ra += Math.PI;
aux[3] = Math.cos(ra);
aux[4] = Math.sin(ra);
}
else{
2020-08-13 17:54:33 +00:00
u = rotV(u, n, c05, s05);
aux[3] = Math.cos(a);
aux[4] = Math.sin(a);
}
a += 0.08;
aux[0] = u[0]; aux[1]=u[1]; aux[2]=u[2];
c.rotatePoints(p_points, p_rpoints, npoints, p_aux);
c.rotatePoints(p_normals, p_rnormals, faces.length/3, p_aux);
c.projectPoints(p_rpoints, p_vpoints, npoints, 0|zDist*100);
c.popZBuf(p_rpoints, p_faces, p_zbuf, faces.length/3);
g.clear();
var z, shade;
if (qWireframe>0) {
var i = faces.length/3;
while (i--) {
z = zbuf[i];
shade = 0|c.processFace(p_addr, z, 1);
if (shade > 0) {
if (qWireframe==1) g.setColor(shade).fillPoly(polyp).setColor(0).drawPoly(polyedge);
else {
g.setColor(0).fillPoly(polyp).setColor(shade);
if (qWireframe==2) g.drawPoly(polyedge);
else g.drawPoly(polyp, true);
}
}
}
}
else {
var i = faces.length/3;
while (i--) {
z = zbuf[i];
shade = 0|c.processFace(p_addr, z, 0);
if (shade > 0) g.setColor(shade).fillPoly(polyp);
}
}
nFrames++;
var fps = Math.round(nFrames*100000/(Date.now()-lastTime))/100;
g.setColor(0.7, 0.7, 0.7);
g.setFont("6x8", 1);
g.drawString("fps:"+fps.toString(), 20, 0);
g.flip();
}
function loadFile(fn) {
Bangle.setLCDMode("direct");
g.clear();
E.showMenu();
E.showMessage(/*LANG*/"Loading...", fn);
2020-08-13 17:54:33 +00:00
readSTL(fn);
zDist = 5*largestExtent(points);
g.clear();
g.setColor(1, 1, 1);
g.setFont("6x8", 2);
g.setFontAlign(-1, -1);
g.setColor(1, 0.5, 0.5);
g.drawString("Model info",15, 40);
g.setColor(1, 1, 1);
g.drawString("# faces: "+faces.length/3, 15, 80);
g.drawString("# vertices: "+npoints, 15, 110);
g.drawString("max extent: "+Math.round(100*(zDist/5))/100, 15, 140);
g.flip();
setWatch(function() {
if (interv) {
interv = clearInterval(interv);
load();
}
else {
Bangle.setLCDMode("doublebuffered");
lastTime = Date.now();
nFrames = 0;
interv = setInterval(function() { draw();}, 30);
} }, BTN2, {repeat:true, debounce:50});
setWatch(function() {
if (qZrot==1) {
if (zBeta<2*Math.PI/2-0.08) zBeta += 0.08;
} else zDist *= 0.9;
2020-08-13 17:54:33 +00:00
}, BTN1, {repeat:true});
setWatch(function() {
if (qZrot==1) {
if (zBeta>-2*Math.PI/2-0.08) zBeta -= 0.08;
} else zDist /= 0.9;
2020-08-13 17:54:33 +00:00
}, BTN3, {repeat:true});
Bangle.on('swipe', function(direction){
switch(direction){
case 1:
qZrot = (qZrot+1)%3;
2020-08-13 17:54:33 +00:00
break;
case -1:
qWireframe = (qWireframe+1)%4;
break;
}});
}
function drawMenu() {
const menu = {
'': { 'title': 'STL files' }
};
var files = require("Storage").list(".stl");
for (var i=0; i<files.length; ++i) {
menu[files[i]] = loadFile.bind(null, files[i]);
}
menu['Exit'] = function() { load(); };
E.showMenu(menu);
}
Bangle.on('kill',()=>{Bangle.setCompassPower(0);});
Bangle.setCompassPower(1);
2020-08-13 17:54:33 +00:00
drawMenu();