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; | |
| } |