Wanted to play with the code of http://www.dhteumeuleu.com/circumscrible. Drawing in 3d!
A Pen by Steve Marsh on CodePen.
Wanted to play with the code of http://www.dhteumeuleu.com/circumscrible. Drawing in 3d!
A Pen by Steve Marsh on CodePen.
<canvas id="screen">HTML5 CANVAS 3D Drawing demo</canvas> |
/* ======================================================= | |
* ---- HTML5 CANVAS 3D drawing ---- | |
* script: Gerard Ferrandez - 7 February 2013 | |
* Released under the MIT license | |
* http://www.dhteumeuleu.com/LICENSE.html | |
* ======================================================= */ | |
"use strict"; | |
(function () { | |
// ==== private variables ===== | |
var scr, ctx, pointer; | |
var shapes = []; | |
var sparks = []; | |
var sparkId = 0; | |
var fov = 650; | |
var globalZ = 0; | |
var xm = 0; | |
var ym = 0; | |
var auto = true; | |
var currentShape; | |
var start = true; | |
// ==== spark object ==== | |
var Spark = function (x, y) { | |
this.x = x; | |
this.y = y; | |
this.sx = Math.random() - 0.5; | |
this.sy = 5 + Math.random() * 10; | |
} | |
// ==== draw sparks ==== | |
Spark.prototype.draw = function () { | |
if (this.y < scr.height) { | |
this.x += this.sx; | |
this.y += this.sy; | |
ctx.moveTo(this.x, this.y - 2); | |
ctx.lineTo(this.x, this.y); | |
} | |
} | |
// ==== shape object ==== | |
var Shape = function () { | |
this.points = []; | |
this.length = 0; | |
this.filled = false; | |
this.color = "red"; | |
this.angle = 0; | |
this.fov = fov; | |
return this; | |
} | |
// ==== add point ==== | |
Shape.prototype.addPoint = function (x, y, z) { | |
this.points.push( | |
new Point(Math.round(x), Math.round(y), Math.round(z)) | |
); | |
this.length++; | |
if (Math.random() > 0.5) { | |
sparks[sparkId++] = new Spark(x + scr.width * 0.5, y + scr.height * 0.5); | |
if (sparkId == 100) sparkId = 0; | |
} | |
} | |
// ==== rotate shape ==== | |
Shape.prototype.rotate = function () { | |
// ---- increment angle ---- | |
this.angle += Math.PI / 180; | |
var ax = Math.cos(this.angle); | |
var ay = Math.sin(this.angle); | |
// ---- points rotation ---- | |
for (var i = 0; i < this.length; i++) { | |
this.points[i].rotate(ax, ay); | |
} | |
} | |
// ==== draw shape ==== | |
Shape.prototype.draw = function () { | |
// ---- 3D to 2D points projection ---- | |
for (var i = 0; i < this.length; i++) { | |
this.points[i].project(this.fov); | |
} | |
// ---- draw smooth curve through N points ---- | |
var p0 = this.points[0]; | |
var lf = scr.width * 0.5; | |
var tp = scr.height * 0.5; | |
ctx.beginPath(); | |
ctx.moveTo(Math.random() * 3 - 1.5 + p0.xp + lf, Math.random() * 3 - 1.5 + p0.yp + tp); | |
for (var i = 1, l = this.points.length; i < l; i++) { | |
var p1 = this.points[i]; | |
var xc = Math.random() * 3 - 1.5 + (p0.xp + p1.xp) / 2; | |
var yc = Math.random() * 3 - 1.5 + (p0.yp + p1.yp) / 2; | |
ctx.quadraticCurveTo(p0.xp + lf, p0.yp + tp, xc + lf, yc + tp); | |
p0 = p1; | |
} | |
// ---- paint ---- | |
ctx.strokeStyle = '#' + (function co(lor){ return (lor += [0,1,2,3,4,5,6,7,8,9,'a','b','c','d','e','f'][Math.floor(Math.random()*16)]) | |
&& (lor.length == 6) ? lor : co(lor); })(''); | |
ctx.lineWidth = 5; | |
ctx.lineCap = "round"; | |
ctx.lineJoin = "round"; | |
if (this.filled) { | |
ctx.closePath(); | |
ctx.fillStyle = this.color; | |
ctx.fill(); | |
} | |
ctx.stroke(); | |
} | |
// ==== point object ==== | |
var Point = function (x, y, z) { | |
this.x = x; | |
this.y = y; | |
this.z = z; | |
this.x0 = x; | |
this.z0 = z; | |
this.xp = 0; | |
this.yp = 0; | |
this.zp = 0; | |
} | |
// ==== 3D to 2D point projection ==== | |
Point.prototype.project = function (sfov) { | |
this.zp = sfov / (sfov + this.z); | |
this.xp = this.x * this.zp; | |
this.yp = this.y * this.zp; | |
} | |
// ==== rotate point ==== | |
Point.prototype.rotate = function (ax, ay) { | |
this.x = Math.round(this.x0 * ax + this.z0 * ay); | |
this.z = Math.round(this.x0 * -ay + this.z0 * ax); | |
} | |
// ==== painting pointer ==== | |
var movePointer = function () { | |
if (pointer.isDown) { | |
var dx = xm - pointer.X; | |
var dy = ym - pointer.Y; | |
var d = Math.sqrt(dx * dx + dy * dy); | |
if (d > 10) { | |
if (!currentShape) { | |
if (start) { | |
start = false; | |
shapes.length = 0; | |
} | |
shapes.push( | |
currentShape = new Shape() | |
); | |
} | |
var z = fov / (fov + globalZ); | |
currentShape.addPoint( | |
(pointer.X - scr.width * 0.5) / z, | |
(pointer.Y - scr.height * 0.5) / z, | |
globalZ | |
); | |
xm = pointer.X; | |
ym = pointer.Y; | |
// ---- closing shape ---- | |
currentShape.filled = false; | |
currentShape.color = ""; | |
var first = currentShape.points[0]; | |
var last = currentShape.points[currentShape.length - 1]; | |
var dx = last.x - first.x; | |
var dy = last.y - first.y; | |
var dz = last.z - first.z; | |
var d = Math.sqrt(dx * dx + dy * dy + dz * dz); | |
if (d < 15) { | |
if (currentShape.length > 4) { | |
currentShape.color = 'hsla(' + Math.round(Math.random() * 360) + ', 90%, 60%, 0.2)'; | |
currentShape.filled = true; | |
} | |
} | |
} | |
} else { | |
// ---- up ---- | |
if (currentShape) { | |
currentShape = false; | |
} | |
// ---- rotate ---- | |
if (auto) { | |
var i = 0, s; | |
while ( s = shapes[i++]) s.rotate(); | |
} | |
} | |
} | |
// ==== save drawing ==== | |
var save = function (id) { | |
// ---- clean up ---- | |
var array = shapes.slice(0); | |
for (var i = 0; i < array.length; i++) { | |
delete array[i].angle; | |
var pts = array[i].points; | |
for (var j = 0; j < pts.length; j++) { | |
var p = pts[j]; | |
for (var k in p) { | |
if (k.length != 1) delete p[k]; | |
} | |
} | |
} | |
// ---- save json to local storage ---- | |
var a = JSON.stringify(array); | |
window.localStorage.setItem(id, a); | |
// ---- re-load ---- | |
load(id); | |
} | |
// ==== load drawing ==== | |
var load = function (id) { | |
// ---- clear all ---- | |
shapes.length = 0; | |
// ---- load ---- | |
var array = JSON.parse(window.localStorage.getItem(id)); | |
// ---- rebuild objects ---- | |
build(array); | |
} | |
// ==== inject data ==== | |
var build = function(array) { | |
if (array) { | |
for (var i = 0; i < array.length; i++) { | |
shapes.push( | |
currentShape = new Shape() | |
); | |
var p = array[i].points; | |
for (var j = 0; j < p.length; j++) { | |
currentShape.points.push( | |
new Point(p[j].x, p[j].y, p[j].z) | |
); | |
} | |
currentShape.length = array[i].length; | |
currentShape.filled = array[i].filled; | |
currentShape.color = array[i].color; | |
} | |
} | |
} | |
// ==== init script ==== | |
var init = function (json) { | |
// ---- screen ---- | |
scr = new ge1doot.Screen({ | |
container: "screen", | |
resize: function () { | |
fov = Math.round(scr.width * 0.5); | |
} | |
}); | |
scr.resize(); | |
ctx = scr.ctx; | |
// ---- pointer events ---- | |
pointer = new ge1doot.Pointer({ }); | |
// ---- some key events ---- | |
document.body.onkeydown = function (e) { | |
// ---- storage detection ---- | |
var storage = typeof window.localStorage == 'object'; | |
// ---- hold/release rotation [SPACE] ---- | |
if (e.keyCode == 32) { | |
auto = !auto; | |
} | |
// ---- undo shapes [DEL] ---- | |
if (e.keyCode == 46) { | |
if (shapes.length > 0) { | |
shapes.length--; | |
} | |
} | |
// ---- switch global Z [Z] ---- | |
if (e.keyCode == 90) { | |
if (globalZ == 0) globalZ = fov * 0.35; else globalZ = 0; | |
} | |
// ---- save/load [S/L]---- | |
if (e.keyCode == 83 && storage) save("circumscrible"); | |
if (e.keyCode == 76 && storage) load("circumscrible"); | |
return false; | |
} | |
// ---- intro drawing ---- | |
build(json); | |
// ---- engine start ---- | |
run(); | |
} | |
// ======== main loop ======== | |
var run = function () { | |
ctx.clearRect(0, 0, scr.width, scr.height); | |
movePointer(); | |
// ---- draw shapes ---- | |
var i = 0, s; | |
while ( s = shapes[i++]) { | |
s.draw(); | |
} | |
// ---- sparks ---- | |
ctx.beginPath(); | |
var i = 0, s; | |
while ( s = sparks[i++]) { | |
s.draw(); | |
} | |
ctx.lineWidth = 1; | |
ctx.strokeStyle = "#fff"; | |
ctx.stroke(); | |
// ---- animation loop ---- | |
requestAnimFrame(run); | |
} | |
return { | |
// ---- onload event ---- | |
load : function (json) { | |
window.addEventListener('load', function () { | |
init(json); | |
}, false); | |
} | |
} | |
})().load([{"points":[{"x":10,"y":-18,"z":-80},{"x":10,"y":-8,"z":-82},{"x":10,"y":2,"z":-83},{"x":10,"y":12,"z":-85},{"x":11,"y":22,"z":-86},{"x":11,"y":33,"z":-87}],"length":6,"filled":false,"color":"","fov":506},{"points":[{"x":10,"y":-21,"z":-81},{"x":9,"y":-23,"z":-71},{"x":7,"y":-22,"z":-61},{"x":6,"y":-15,"z":-52},{"x":6,"y":-5,"z":-47},{"x":6,"y":6,"z":-47},{"x":6,"y":17,"z":-49},{"x":7,"y":26,"z":-57},{"x":8,"y":28,"z":-67},{"x":9,"y":29,"z":-76},{"x":11,"y":29,"z":-87}],"length":11,"filled":false,"color":"","fov":506},{"points":[{"x":4,"y":-30,"z":-29},{"x":4,"y":-18,"z":-29},{"x":4,"y":-8,"z":-30},{"x":4,"y":5,"z":-30},{"x":4,"y":16,"z":-30},{"x":4,"y":27,"z":-30}],"length":6,"filled":false,"color":"","fov":506},{"points":[{"x":4,"y":-30,"z":-31},{"x":2,"y":-33,"z":-20},{"x":1,"y":-32,"z":-9},{"x":0,"y":-25,"z":0},{"x":0,"y":-15,"z":1},{"x":1,"y":-8,"z":-9},{"x":3,"y":-8,"z":-22},{"x":2,"y":3,"z":-17},{"x":1,"y":13,"z":-10},{"x":0,"y":22,"z":-3},{"x":0,"y":32,"z":1}],"length":11,"filled":false,"color":"","fov":506},{"points":[{"x":-4,"y":-29,"z":29},{"x":-3,"y":-18,"z":25},{"x":-2,"y":-7,"z":20},{"x":-2,"y":3,"z":16},{"x":-1,"y":13,"z":12},{"x":-1,"y":23,"z":10}],"length":6,"filled":false,"color":"","fov":506},{"points":[{"x":-4,"y":-34,"z":29},{"x":-4,"y":-22,"z":33},{"x":-4,"y":-10,"z":36},{"x":-5,"y":2,"z":38},{"x":-5,"y":13,"z":41},{"x":-5,"y":23,"z":43},{"x":-6,"y":33,"z":46}],"length":7,"filled":false,"color":"","fov":506},{"points":[{"x":-2,"y":2,"z":17},{"x":-3,"y":2,"z":28}],"length":2,"filled":false,"color":"","fov":506},{"points":[{"x":-2,"y":5,"z":13},{"x":-3,"y":3,"z":24},{"x":-4,"y":3,"z":35}],"length":3,"filled":false,"color":"","fov":506},{"points":[{"x":-6,"y":-31,"z":49},{"x":-6,"y":-20,"z":52},{"x":-7,"y":-10,"z":55},{"x":-7,"y":1,"z":59},{"x":-8,"y":12,"z":62},{"x":-8,"y":22,"z":66},{"x":-9,"y":12,"z":73},{"x":-10,"y":2,"z":77},{"x":-11,"y":7,"z":86},{"x":-11,"y":18,"z":91},{"x":-12,"y":28,"z":95},{"x":-13,"y":15,"z":104},{"x":-13,"y":5,"z":108},{"x":-14,"y":-12,"z":115},{"x":-15,"y":-22,"z":119},{"x":-15,"y":-33,"z":124}],"length":16,"filled":false,"color":"","fov":506}]); |
<script src="http://www.dhteumeuleu.com/library/ge1doot.js"></script> |
html { | |
overflow: hidden; | |
-ms-touch-action: none; | |
-ms-content-zooming: none; | |
} | |
body { | |
position: absolute; | |
margin: 0; | |
padding: 0; | |
background: #000; | |
width: 100%; | |
height: 100%; | |
} | |
#screen { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
cursor: pointer; | |
} |