0
0
mirror of https://github.com/Doodle3D/doodle3d-client.git synced 2024-11-11 05:33:23 +01:00
doodle3d-client/www/printmanager/js/PrintPreview.js
2015-04-27 23:38:01 +02:00

374 lines
12 KiB
JavaScript
Executable File

var PrintPreview = function(jqCanvas) {
var canvas = jqCanvas[0];
// === basic wheel reinvention stuff ===
// function $(id) { return document.getElementById(id) }
// comparison function using a key, to pass to .sort()
function keycomp(key) {
return function(a, b) {
var ka = key(a)
var kb = key(b)
if (ka < kb) return -1
if (ka > kb) return 1
return 0
}
}
// return a list transformed by a function
function map(f, list) {
var rv = []
for (var ii = 0; ii < list.length; ii++) rv.push(f(list[ii]))
return rv
}
// === 3d transforms ===
// We represent transforms as a 3x4 list of lists (ahem, array of arrays):
// [[x_from_x, x_from_y, x_from_z, x_off],
// [y_from_x, y_from_y, y_from_z, y_off],
// [z_from_x, z_from_y, z_from_z, z_off]]
// And we only actually multiply points through them in xform.
function translate(x, y, z) {
return [[1, 0, 0, x], [0, 1, 0, y], [0, 0, 1, z]]
}
function identity() { return translate(0, 0, 0) }
// rotation around the Z-axis
function rotate(theta) {
var s = Math.sin(theta)
var c = Math.cos(theta)
return [[c, -s, 0, 0], [s, c, 0, 0], [0, 0, 1, 0]]
}
// exchange two of the X, Y, Z axes --- useful for making rotate() go around
// another axis :)
function transpose_axes(a, b) {
var rv = identity()
var tmp = rv[a]
rv[a] = rv[b]
rv[b] = tmp
return rv
}
// you'd think we'd have a scale() function too, but I haven't needed it yet.
// concatenate two transforms --- the magic that makes it all possible
function concat(x1, x2) {
var rv = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
for (var ii = 0; ii < 3; ii++) {
rv[ii][3] = x2[ii][3]
for (var jj = 0; jj < 3; jj++) {
rv[ii][3] += x1[jj][3] * x2[ii][jj]
for (var kk = 0; kk < 3; kk++) {
rv[ii][jj] += x1[kk][jj] * x2[ii][kk]
}
}
}
return rv
}
// concatenate N transforms. I'd insert a special case for 0 transforms,
// but amusingly this function isn't all that performance-critical.
function concat_n(xforms) {
var rv = identity()
for (var ii = 0; ii < xforms.length; ii++) rv = concat(rv, xforms[ii])
return rv
}
// transform a point.
function xform(xform, p) {
var result_vec = []
for (var ii = 0; ii < 3; ii++) {
var rv = xform[ii][3]
for (var jj = 0; jj < 3; jj++) rv += xform[ii][jj] * p[jj]
result_vec.push(rv)
}
return result_vec
}
// transform multiple points.
function xform_points(xf, points) {
var xp = []
for (var ii = 0; ii < points.length; ii++) {
xp.push(xform(xf, points[ii]))
}
return xp
}
// perspective-transform a point --- into 2d.
function persp(p) { return [p[0] / p[2], p[1] / p[2]] }
// perspective-transform multiple points
function persp_points(points) {
return map(persp, points)
}
// return the normal of a triangle defined by three points.
function normal(p1, p2, p3) {
var v1 = [p1[0]-p2[0], p1[1]-p2[1], p1[2]-p2[2]]
var v2 = [p2[0]-p3[0], p2[1]-p3[1], p2[2]-p3[2]]
var n = [v1[1]*v2[2]-v1[2]*v2[1],
v1[2]*v2[0]-v1[0]*v2[2],
v1[0]*v2[1]-v1[1]*v2[0]]
var mag = Math.sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2])
return [n[0]/mag, n[1]/mag, n[2]/mag]
}
// === 3d shapes ===
// We represent these as an array of three arrays
// [points, lines, polies] where each line is two indices into the points array
// and each poly is three indices into the points array
function dup(array) {
var newarray = new Array(array.length)
for (var ii = 0; ii < array.length; ii++) newarray[ii] = array[ii]
return newarray
}
// transform a shape, returning a new shape
function xform_shape(xf, shape) {
// de-alias new lines and polies
return [xform_points(xf, shape[0]), dup(shape[1]), dup(shape[2])]
}
// add a new shape onto an old shape, mutating the old one
function augment(shape1, shape2) {
var s1p = shape1[0]
var off = s1p.length
for (var ii = 0; ii < shape2[0].length; ii++) s1p.push(shape2[0][ii])
var s2ll = shape2[1].length // in case of aliasing
for (var ii = 0; ii < s2ll; ii++)
shape1[1].push([shape2[1][ii][0] + off, shape2[1][ii][1] + off])
var s2pl = shape2[2].length
for (var ii = 0; ii < s2pl; ii++) {
var tri = shape2[2][ii]
shape1[2].push([tri[0]+off, tri[1]+off, tri[2]+off])
}
}
// given a shape, make a more complicated shape by copying it through transform
// xf n times, and connecting the corresponding points. This is more powerful
// than the usual kind of extrusion, and can be used to create fairly
// interesting shapes --- a snail shell from a circle, for instance.
function extrude_shape(xf, shape, n) {
if (n == null) n = 1
var new_part = shape
var old_line_base = 0 // where the lines to attach the triangles start
for (var ii = 0; ii < n; ii++) {
var new_part = xform_shape(xf, new_part)
var shape_length = shape[0].length
var new_line_base = shape[1].length // for triangles later
augment(shape, new_part)
var new_part_length = new_part[0].length
// connect corresponding points
for (var jj = 0; jj < new_part_length; jj++) {
shape[1].push([shape_length + jj - new_part_length, shape_length + jj])
}
// make triangles
var nlines = new_part[1].length
// var old_line_base = new_line_base - nlines
for (var jj = 0; jj < nlines; jj++) {
var old_line = shape[1][old_line_base + jj]
var new_line = shape[1][new_line_base + jj]
shape[2].push([old_line[0], old_line[1], new_line[0]])
shape[2].push([new_line[1], new_line[0], old_line[1]])
}
old_line_base = new_line_base
}
}
// a shape consisting of a single point
function point_shape(x, y, z) { return [[[x, y, z]], [], []] }
// approximate a circle in the x-y plane around the origin; radius r and n points
function circle(r, n) {
var shape = point_shape(r, 0, 0)
extrude_shape(rotate(Math.atan(1)*8/n), shape, n)
return shape
}
// approximate a torus with major radius r2 and minor radius r1,
// with correspondingly n2 and n1 points around each axis
function make_torus(r1, r2, n1, n2) {
var c = xform_shape(translate(r2, 0, 0), circle(r1, n1))
extrude_shape(concat_n([transpose_axes(1, 2),
rotate(Math.atan(1)*8/n2),
transpose_axes(1, 2)]),
c, n2)
return c
}
// === drawing code ===
// draw a 3d shape on a canvas
// 95% of the run time is in this function and its kids
function draw_shape(canvas, xf, shape, alpha) {
var ctx = canvas.getContext('2d')
var w = canvas.width
var h = canvas.height
// set up coordinate system so canvas is (-1, -1) to (1, 1)
ctx.save()
ctx.translate(w/2, h/2)
ctx.scale(w/2, h/2)
// 1/3 of the time is in these two lines (when not doing polies)
var points3d = xform_points(xf, shape[0])
var points = persp_points(points3d)
var lines = shape[1]
// 2/3 of the time is in this loop (when we're not doing polies)
if (alpha == null) {
ctx.strokeStyle = 'grey'
ctx.lineWidth = 1/(w/2)
ctx.beginPath()
var p1, p2
for (var ii = 0; ii < lines.length; ii++) {
p1 = points[lines[ii][0]]
p2 = points[lines[ii][1]]
ctx.moveTo(p1[0], p1[1])
ctx.lineTo(p2[0], p2[1])
}
ctx.stroke()
}
// when we're doing polies, 90% of our time is spent doing polies
if (alpha != null) {
// Sort polygons by depth so we draw the farthest-away stuff first
// ("painter's algorithm")
var minusdepth = function(p) {
return [-(points3d[p[0]][2] + points3d[p[1]][2] + points3d[p[2]][2]), p]
}
var polies = map(minusdepth, shape[2])
polies.sort(keycomp(function(p) { return p[0] }))
// draw all the polygons
var tri, p1, p2, p3, n, bright
for (var ii = 0; ii < polies.length; ii++) {
tri = polies[ii][1]
if (alpha == '1') {
// light surface
n = normal(points3d[tri[0]], points3d[tri[1]], points3d[tri[2]])
// I'm not sure how to make backface removal work with perspective:
// if (n[2] > 0 && alpha == '1') continue // backface removal
// lighting from (1, -1, -1) direction
bright = parseInt(((n[0]-n[1]-n[2]) / Math.sqrt(3) * 255))
if (bright < 20) bright = 20
} else {
// lighting doesn't make sense if the object is transparent,
// so we color by depth to have some variation in color...
var maxd = polies[polies.length-1][0]
var mind = polies[0][0]
var d = polies[ii][0]
bright = parseInt((d-mind)/(maxd-mind) * 255)
}
ctx.fillStyle = 'rgba(' + bright + ',' + bright + ',' + bright + ',' + alpha + ')';
ctx.beginPath()
p1 = points[tri[0]]
p2 = points[tri[1]]
p3 = points[tri[2]]
ctx.moveTo(p1[0], p1[1])
ctx.lineTo(p2[0], p2[1])
ctx.lineTo(p3[0], p3[1])
// ctx.closePath() seems to be unnecessary
ctx.fill()
}
}
ctx.restore()
}
// clear a canvas
function cls(canvas) {
var ctx = canvas.getContext('2d')
ctx.fillStyle = 'black'
ctx.fillRect(0, 0, canvas.width, canvas.height)
}
// // === drawing of particular shapes. also DOM. ===
// angle = 0
// function unit_cube() {
// var shape = point_shape(0, 0, 0)
// extrude_shape(translate(0,0,1), shape)
// extrude_shape(translate(0,1,0), shape)
// extrude_shape(translate(1,0,0), shape)
// return shape
// }
// // this was where I tested stuff as I wrote this
// function make_some_junk() {
// // make a unit cube centered on the origin
// var shape = xform_shape(translate(-0.5, -0.5, -0.5), unit_cube())
// // add some circles
// augment(shape, circle(0.707, 16))
// augment(shape, xform_shape(transpose_axes(0, 2), circle(0.707, 16)))
// augment(shape, xform_shape(transpose_axes(1, 2), circle(0.707, 16)))
// augment(shape, circle(1, 15))
// // add a disc
// var big_disc = circle(2, 20)
// extrude_shape(translate(0, 0, 0.5), big_disc, 2)
// augment(shape, big_disc)
// return shape
// }
// var some_junk = make_some_junk()
// function draw_some_junk(canvas) {
// var xf = concat_n([transpose_axes(1, 2),
// rotate(angle),
// transpose_axes(1, 2),
// rotate(angle*1.618),
// translate(0, 0, 2.5)])
// draw_shape(canvas, xf, some_junk)
// }
var torus = make_torus(1, 3, 12, 12)
console.log('torus',torus);
function draw() {
// var start = new Date()
// var alpha = null
// if ($('fill').checked) {
// alpha = ($('translucent').checked ? '0.5' : 1)
// }
// if ($('trails').checked) {
// $('canvas').getContext('2d').globalAlpha = 0.33
// } else {
// $('canvas').getContext('2d').globalAlpha = 1
// }
var angle = 0;
var alpha = 1;
draw_shape(canvas,
concat_n([
rotate(0),
transpose_axes(1, 2),
rotate(0), // to reduce periodicity
transpose_axes(1, 2),
translate(0, 0, 6),
// transpose_axes(1, 2),
// rotate(1),
]),
torus, alpha);
// var end = new Date()
// var ms = $('ms')
// if (ms) {
// var msvalue = ms.value + ' ' + (end.getTime() - start.getTime())
// if (msvalue.length > 25) msvalue = msvalue.substr(msvalue.length - 25)
// ms.value = msvalue
// }
}
// function update() {
// if (!$('go').checked) return
// angle += 3.14159 / 30
// cls($('canvas'))
// draw_torus($('canvas'))
// }
// function init(ev) {
// setInterval(update, 100)
// // this doesn't work: $('fill').addEventListener('change', update, true)
// // how do you do what I want to do there?
// cls($('canvas'))
// draw_torus($('canvas'))
// }
// window.addEventListener('load', init, true)
return {
draw: draw,
}
}