Useful 2D geometry rendered to look 3D cylinders are useful to tie sysems and moels together
A Pen by Andi Smithers on CodePen.
Useful 2D geometry rendered to look 3D cylinders are useful to tie sysems and moels together
A Pen by Andi Smithers on CodePen.
| <body> | |
| <canvas id='cone-cylinder'></canvas> | |
| </body> |
| // conceptualized and written by andi smithers | |
| // copyright andi smithers. | |
| // freely distributable in whole or in partial | |
| // please retain credit and comment if distributed | |
| // thank you. | |
| // constant options | |
| const focalDepth = 80; | |
| const focalPoint = 256; | |
| // variables | |
| var centreX; | |
| var centreY; | |
| var mouseX; | |
| var mouseY; | |
| var spawnX; | |
| var spawnY; | |
| var frameCount=0; | |
| // test multiple groups | |
| // initialization | |
| function init() | |
| { | |
| // setup canvas and context | |
| canvas = document.getElementById('cone-cylinder'); | |
| context = canvas.getContext('2d'); | |
| // set canvas to be window dimensions | |
| resize(); | |
| // create event listeners | |
| canvas.addEventListener('mousemove', mouseMove); | |
| canvas.addEventListener('click', mouseClick); | |
| window.addEventListener('resize', resize); | |
| // initialze variables | |
| } | |
| // input functions | |
| function mouseMove(event) | |
| { | |
| var rect = canvas.getBoundingClientRect(); | |
| mouseX = event.clientX - rect.left, | |
| mouseY = event.clientY - rect.top | |
| } | |
| function mouseClick() | |
| { | |
| } | |
| function resize() | |
| { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| // compute centre of screen | |
| centreX = canvas.width/2; | |
| centreY = canvas.height/2; | |
| } | |
| // rendering functions | |
| var demoAngle = Math.PI*1.44; | |
| function render() | |
| { | |
| context.fillStyle = 'black'; | |
| context.clearRect(0, 0, canvas.width, canvas.height); | |
| var x = centreX; | |
| var y = centreY; | |
| var r = 200; | |
| demoAngle=(demoAngle+0.01)%(Math.PI*2); | |
| RenderBasestar(x, y, r, demoAngle); | |
| context.globalAlpha = 1.0; | |
| context.font = '20pt Calibri'; | |
| context.fillStyle = 'rgb(255,255,255)'; | |
| context.textAlign = "center"; | |
| context.fillText('3D Cylinder/Cone rendering on a 2D canvas', canvas.width/2, 40); | |
| context.fillText('Basestar for star-raiders', canvas.width/2, canvas.height-80); | |
| context.fillText('combines 2 hemispheres an inverted cylinder with r, -r', canvas.width/2, canvas.height-60); context.fillText('plus 2 side cylinders at the side to show well cylinders', canvas.width/2, canvas.height-40); | |
| } | |
| // star base construction - eer well it was but its a cool blue marble demo for now | |
| // to rotate horizontally switch out the x/y registers. I should probably make this generic .. however just want to use this for the starbase construction in star-raiders | |
| var lazerLength = 50; | |
| var lazerAngle = 0; | |
| function RenderBasestar(x, y, r, baseAngle) | |
| { | |
| var len = 20; | |
| r = 100; | |
| var r2 = -100; | |
| // compute y | |
| var y1 = y+len*2*Math.cos(baseAngle+Math.PI*0.5); | |
| var y2 = y-len*2*Math.cos(baseAngle+Math.PI*0.5); | |
| if (baseAngle<Math.PI*0.5 || baseAngle>Math.PI*1.5) | |
| { | |
| RenderHemisphere(x, y2, r, baseAngle, '#808080', '#808080'); | |
| RenderCylinder(x, y, r2, r, len*2, baseAngle, '#808080', '#808080', 'darkred'); | |
| RenderLazers(x,y,baseAngle); | |
| RenderHemisphere(x, y1, r, (baseAngle+Math.PI)%(Math.PI*2),'#808080', '#808080'); | |
| } | |
| else | |
| { | |
| RenderHemisphere(x, y1, r, (baseAngle+Math.PI)%(Math.PI*2), '#808080', '#808080'); | |
| RenderCylinder(x, y, r2, r, len*2, baseAngle, '#808080', '#808080', 'darkred'); | |
| RenderLazers(x,y,baseAngle); | |
| RenderHemisphere(x, y2, r, baseAngle, '#808080', '#808080'); | |
| } | |
| } | |
| function RenderLazers(x,y, baseAngle) | |
| { | |
| // context.rotate(Math.PI*0.5); | |
| // context.translate(-x+y,-y-x); | |
| var r = 10; | |
| var r2 = 10; | |
| var y3 = y; | |
| RenderCylinder(x-150, y3, r2, r, lazerLength, baseAngle+Math.PI*0.5, 'rgba(255,255,0,1)','rgba(255,128,0,1)','rgba(255,0,0,1)'); | |
| RenderCylinder(x+150, y3, r2, r, lazerLength, baseAngle+Math.PI*0.5, 'rgba(255,255,0,1)','rgba(255,128,0,1)','rgba(255,0,0,1)'); | |
| } | |
| function RenderHemisphere(x, y, r, angle, colTop, colBot) | |
| { | |
| // rotation | |
| m = 0.5522848; | |
| tr =r*Math.cos(angle)*0.5; // top hemisphere angle *rad | |
| br =r*Math.cos(angle)*0.5; // bottom angle *rad | |
| xkr = m*r; // kappa * r | |
| tkr = m*tr; // top height * kappa | |
| bkr = m*br; // bottom height * kappa | |
| context.beginPath() | |
| context.moveTo(x+r, y) | |
| context.bezierCurveTo(x+r, y-tkr, x+xkr, y-tr, x, y-tr) // top r | |
| context.bezierCurveTo(x-xkr, y-tr, x-r, y-tkr, x-r, y) // top l | |
| context.bezierCurveTo(x-r, y+bkr, x-xkr, y+br, x, y+br) // bot l | |
| context.bezierCurveTo(x+xkr, y+br, x+r, y+bkr, x+r, y) // bot r | |
| context.closePath(); | |
| context.lineWidth = 1; | |
| context.fillStyle = colBot; | |
| context.fill(); | |
| // drow the blue surface | |
| tr =r*Math.cos(angle+Math.PI); // top hemisphere angle *rad | |
| br =r*Math.cos(angle+Math.PI); // bottom angle *rad | |
| if (angle<Math.PI) br = r*0.5; | |
| if (angle>Math.PI) tr = r*0.5; | |
| xkr = m*r; // kappa * r | |
| tkr = m*tr; // top height * kappa | |
| bkr = m*br; // bottom height * kappa | |
| context.beginPath() | |
| context.moveTo(x+r, y) | |
| context.bezierCurveTo(x+r, y-tkr, x+xkr, y-tr, x, y-tr) // top r | |
| context.bezierCurveTo(x-xkr, y-tr, x-r, y-tkr, x-r, y) // top l | |
| context.bezierCurveTo(x-r, y+bkr, x-xkr, y+br, x, y+br) // bot l | |
| context.bezierCurveTo(x+xkr, y+br, x+r, y+bkr, x+r, y) // bot r | |
| context.closePath(); | |
| context.lineWidth = 1; | |
| context.fillStyle = colTop; | |
| context.fill(); | |
| } | |
| function RenderCylinder(x, y, r1, r2, side, angle, colTop, colBot, colSide) | |
| { | |
| // rotation | |
| var m = 0.5522848; | |
| var tr =r1*Math.cos(angle); // top hemisphere angle *rad | |
| var br =r1*Math.cos(angle); // bottom angle *rad | |
| var xkr = m*r1; // kappa * r | |
| var tkr = m*tr; // top height * kappa | |
| var bkr = m*br; // bottom height * kappa | |
| var y1 = y - side* Math.sin(angle); | |
| // drow the blue surface | |
| var tr2 =r2*Math.cos(angle); // top hemisphere angle *rad | |
| var br2 =r2*Math.cos(angle); // bottom angle *rad | |
| var xkr2 = m*r2; // kappa * r | |
| var tkr2 = m*tr2; // top height * kappa | |
| var bkr2 = m*br2; // bottom height * kappa | |
| // compute x, y | |
| var y2 = y + side*Math.sin(angle); | |
| // render front side | |
| context.beginPath(); | |
| context.moveTo(x+r1, y1) | |
| context.bezierCurveTo(x+r1, y1-tkr, x+xkr, y1-tr, x, y1-tr) // top r | |
| context.bezierCurveTo(x-xkr, y1-tr, x-r1, y1-tkr, x-r1, y1) // top l | |
| context.lineTo(x-r2, y2); | |
| context.bezierCurveTo(x-r2, y2-bkr2, x-xkr2, y2-br2, x, y2-br2) // bot l | |
| context.bezierCurveTo(x+xkr2, y2-br2, x+r2, y2-bkr2, x+r2, y2) // bot r | |
| context.closePath(); | |
| // context.fillStyle = colSide; | |
| var gradient = context.createRadialGradient(x, y, 0, x, y, Math.abs(r1)); | |
| gradient.addColorStop(0, 'red'); | |
| gradient.addColorStop(1, '#404040'); | |
| context.fillStyle = gradient; | |
| context.fill(); | |
| context.beginPath(); | |
| context.moveTo(x+r1, y1) | |
| context.bezierCurveTo(x+r1, y1+tkr, x+xkr, y1+tr, x, y1+tr) // top r | |
| context.bezierCurveTo(x-xkr, y1+tr, x-r1, y1+tkr, x-r1, y1) // top l | |
| context.lineTo(x-r2, y2); | |
| context.bezierCurveTo(x-r2, y2+bkr2, x-xkr2, y2+br2, x, y2+br2) // bot l | |
| context.bezierCurveTo(x+xkr2, y2+br2, x+r2, y2+bkr2, x+r2, y2) // bot r | |
| context.closePath(); | |
| // context.fillStyle = colSide; | |
| context.fillStyle = gradient; | |
| context.fill(); | |
| if (angle>Math.PI*0.5 && angle<Math.PI*1.5) | |
| { | |
| context.beginPath() | |
| context.moveTo(x+r2, y2) | |
| context.bezierCurveTo(x+r2, y2-tkr2, x+xkr2, y2-tr2, x, y2-tr2) // top r | |
| context.bezierCurveTo(x-xkr2, y2-tr2, x-r2, y2-tkr2, x-r2, y2) // top l | |
| context.bezierCurveTo(x-r2, y2+bkr2, x-xkr2, y2+br2, x, y2+br2) // bot l | |
| context.bezierCurveTo(x+xkr2, y2+br2, x+r2, y2+bkr2, x+r2, y2) // bot r | |
| context.closePath(); | |
| context.lineWidth = 0; | |
| context.fillStyle = colTop; | |
| context.fill(); | |
| } | |
| else | |
| { | |
| context.beginPath() | |
| context.moveTo(x+r1, y1) | |
| context.bezierCurveTo(x+r1, y1-tkr, x+xkr, y1-tr, x, y1-tr) // top r | |
| context.bezierCurveTo(x-xkr, y1-tr, x-r1, y1-tkr, x-r1, y1) // top l | |
| context.bezierCurveTo(x-r1, y1+bkr, x-xkr, y1+br, x, y1+br) // bot l | |
| context.bezierCurveTo(x+xkr, y1+br, x+r1, y1+bkr, x+r1, y1) // bot r | |
| context.closePath(); | |
| context.lineWidth = 1; | |
| context.fillStyle = colBot; | |
| context.fill(); | |
| } | |
| } | |
| // movement functions | |
| function update() | |
| { | |
| } | |
| // per frame tick functions | |
| function animate() | |
| { | |
| frameCount++; | |
| // movement update | |
| update(); | |
| // render update | |
| render(); | |
| // trigger next frame | |
| requestAnimationFrame(animate); | |
| } | |
| // entry point | |
| init(); | |
| animate(); |
| body{ | |
| background:#000000; | |
| margin: 0px; | |
| padding: 0px; | |
| } |