mirror of
https://github.com/Doodle3D/doodle3d-client.git
synced 2024-12-12 17:24:57 +01:00
374 lines
12 KiB
JavaScript
Executable File
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,
|
|
}
|
|
} |