Related
I am trying to draw lissajous curves using the canvas and javascript. I have put the formula's in but instead of drawing nice curves, the canvas looks like a complete mess. The code im currently using is:
<canvas id="myCanvas" width="720" height="720" style="border:1px solid #d3d3d3;">
<script src="canvas.js"> </script>
And for my javascript:
var canvas = document.querySelector('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctxt = canvas.getContext('2d');
var x;
var y;
var STARTi = 0;
var MAXi = 100;
var yOffset = canvas.height / 2;
var xOffset = canvas.width / 2;
var xAmp = 2;
var yAmp = 3;
var xFreq = 6;
var yFreq = 3;
var phaseDiff = Math.PI / 2;
//X-Axis
ctxt.beginPath();
ctxt.setLineDash([5, 15]);
ctxt.moveTo(0, canvas.height / 2);
ctxt.lineTo(canvas.width, canvas.height / 2);
ctxt.strokeStyle = "rgba(169,169,169)"
ctxt.stroke();
//y-Axis
ctxt.beginPath();
ctxt.setLineDash([5, 15]);
ctxt.moveTo(canvas.width / 2, 0);
ctxt.lineTo(canvas.width / 2, canvas.height);
ctxt.strokeStyle = "rgba(169,169,169)"
ctxt.stroke();
ctxt.setLineDash([0, 0]);
function xCoord(a) {
return 100 * (xAmp * Math.sin(xFreq * (0.4 * a) + phaseDiff));
}
function yCoord(a) {
return -100 * (yAmp * Math.sin(yFreq * (0.4 * a)));
}
x = xCoord(STARTi);
y = yCoord(x);
ctxt.beginPath();
ctxt.moveTo(x + xOffset, y + yOffset)
for (i = STARTi; i < MAXi; i++) {
ctxt.moveTo(x + xOffset, y + yOffset)
x = xCoord(i);
y = yCoord(x);
ctxt.lineTo(x + xOffset, y + yOffset)
ctxt.strokeStyle = "black"
ctxt.stroke();
}
Im trying to get these curved and i have tried things such as making the increase of the value so small that it will eventually look smooth: didn't work, just crashed my browser eventually.
I also tried to draw these lines using the Arc property. This gave me some smooth lines, but not ones that looked like lisajous curves. I believe this mainly is because i did not know what values or variables in needed to put in for the other required parameters.
I hope it is a little bit clear what i have problems with.
Thanks in advance for all the help. ;)
All angle operations in JavaScript uses radians [0, 2 × π>, but the xCoord() and yCoord() functions uses degrees [0, 360>.
Simple use radians instead or convert inside the functions:
function xCoord(a) {
// "a" converted from degree to radian _________________
return 100 * (xAmp * Math.sin(xFreq * (0.4 * a * Math.PI / 180) + phaseDiff));
}
function yCoord(a) {
return -100 * (yAmp * Math.sin(yFreq * (0.4 * a * Math.PI / 180)));
}
var canvas = document.querySelector('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctxt = canvas.getContext('2d');
var x;
var y;
var STARTi = 0;
var MAXi = 100;
var yOffset = canvas.height / 2;
var xOffset = canvas.width / 2;
var xAmp = 2;
var yAmp = 3;
var xFreq = 6;
var yFreq = 3;
var phaseDiff = Math.PI / 2;
//X-Axis
ctxt.beginPath();
ctxt.setLineDash([5, 15]);
ctxt.moveTo(0, canvas.height / 2);
ctxt.lineTo(canvas.width, canvas.height / 2);
ctxt.strokeStyle = "rgba(169,169,169)"
ctxt.stroke();
//y-Axis
ctxt.beginPath();
ctxt.setLineDash([5, 15]);
ctxt.moveTo(canvas.width / 2, 0);
ctxt.lineTo(canvas.width / 2, canvas.height);
ctxt.strokeStyle = "rgba(169,169,169)"
ctxt.stroke();
ctxt.setLineDash([0, 0]);
function xCoord(a) {
return 100 * (xAmp * Math.sin(xFreq * (0.4 * a * Math.PI / 180) + phaseDiff));
}
function yCoord(a) {
return -100 * (yAmp * Math.sin(yFreq * (0.4 * a * Math.PI / 180)));
}
x = xCoord(STARTi);
y = yCoord(x);
ctxt.beginPath();
ctxt.moveTo(x + xOffset, y + yOffset)
for (i = STARTi; i < MAXi; i++) {
ctxt.moveTo(x + xOffset, y + yOffset)
x = xCoord(i);
y = yCoord(x);
ctxt.lineTo(x + xOffset, y + yOffset)
}
ctxt.strokeStyle = "black"; // can be set right before stroked.
ctxt.stroke(); // call this outside the loop, otherwise the lines will
// overdraw eachother per iteration making it look pixelated
<canvas id="myCanvas" width="720" height="720" style="border:1px solid #d3d3d3;">
I also found an error in the argument for the ordinate Y. I believe this would be correct:
x = xCoord(i);
y = yCoord(i); // NO y = yCoord(x);
Similarly, the above
x = xCoord(STARTi);
y = yCoord(STARTi);
I tried to use canvas in middle of my html page.but it does't work fine. I want to use ribbon effect in div. it works fine when the there is no div and when there is div or other element it doesn't work. I want use canvas in between two div. I used ribbon pen in codepen and will post the code below. I want to how to use it in my html page.
var TWO_PI = Math.PI * 2;
var HALF_PI = Math.PI * 0.5;
var THICKNESS = 12;
var LENGTH = 10;
var STEP = 0.1;
var FPS = 1000 / 60;
function Particle(x, y, mass) {
this.x = x || 0;
this.y = y || 0;
this.ox = this.x;
this.oy = this.y;
this.mass = mass || 1.0;
this.massInv = 1.0 / this.mass;
this.fixed = false;
this.update = function (dt) {
if (!this.fixed) {
var fx = 0.0000;
var fy = 0.0000;
var tx = this.x,
ty = this.y;
this.x += (this.x - this.ox) + fx * this.massInv * dt * dt;
this.y += (this.y - this.oy) + fy * this.massInv * dt * dt;
this.ox = tx;
this.oy = ty;
}
};
};
function Spring(p1, p2, restLength, strength) {
this.p1 = p1;
this.p2 = p2;
this.restLength = restLength || 10;
this.strength = strength || 1.0;
this.update = function (dt) {
// Compute desired force
var dx = p2.x - p1.x,
dy = p2.y - p1.y,
dd = Math.sqrt(dx * dx + dy * dy) + 0.0001,
tf = (dd - this.restLength) / (dd * (p1.massInv + p2.massInv)) * this.strength,
f;
// Apply forces
if (!p1.fixed) {
f = tf * p1.massInv;
p1.x += dx * f;
p1.y += dy * f;
}
if (!p2.fixed) {
f = -tf * p2.massInv;
p2.x += dx * f;
p2.y += dy * f;
}
}
};
function Sim() {
this.particles = [];
this.springs = [];
this.tick = function (dt) {
var i, n;
for (i = 0, n = this.springs.length; i < n; ++i) {
this.springs[i].update(dt);
}
for (i = 0, n = this.particles.length; i < n; ++i) {
this.particles[i].update(dt);
}
}
};
// Create a new system
var sim = new Sim(),
old = new Date().getTime(),
canvas = document.getElementById('world'),
context = canvas.getContext('2d');
function init() {
var np,
op,
mouse,
anchor,
step = STEP,
length = LENGTH,
count = length / step;
var sx = canvas.width * 0.5;
var sy = canvas.height * 0.5;
for (var i = 0; i < count; ++i) {
//np = new Particle(i*8,i*8,0.1+Math.random()*0.01);
np = new Particle(sx + (Math.random() - 0.5) * 200, sy + (Math.random() - 0.5) * 200, 0.1 + Math.random() * 0.01);
sim.particles.push(np);
if (i > 0) {
s = new Spring(np, op, step, 0.95);
sim.springs.push(s);
}
op = np;
}
// Fix the first particle
anchor = sim.particles[0];
//anchor.fixed = true;
anchor.x = 50;
anchor.y = 50;
// Move last particle with mouse
mouse = sim.particles[count - 1];
mouse.fixed = true;
canvas.addEventListener('mousemove', function (event) {
mouse.x = event.clientX;
mouse.y = event.clientY;
});
};
function step() {
var now = new Date().getTime(),
delta = now - old;
sim.tick(delta);
// Clear canvas
canvas.width = canvas.width;
var points = []; // Midpoints
var angles = []; // Delta angles
var i, n, p1, p2, dx, dy, mx, my, sin, cos, theta;
// Compute midpoints and angles
for (i = 0, n = sim.particles.length - 1; i < n; ++i) {
p1 = sim.particles[i];
p2 = sim.particles[i + 1];
dx = p2.x - p1.x;
dy = p2.y - p1.y;
mx = p1.x + dx * 0.5;
my = p1.y + dy * 0.5;
points[i] = {
x: mx,
y: my
};
angles[i] = Math.atan2(dy, dx);
}
// Render
context.beginPath();
for (i = 0, n = points.length; i < n; ++i) {
p1 = sim.particles[i];
p2 = points[i];
theta = angles[i];
r = Math.sin((i / n) * Math.PI) * THICKNESS;
sin = Math.sin(theta - HALF_PI) * r;
cos = Math.cos(theta - HALF_PI) * r;
context.quadraticCurveTo(
p1.x + cos,
p1.y + sin,
p2.x + cos,
p2.y + sin);
}
for (i = points.length - 1; i >= 0; --i) {
p1 = sim.particles[i + 1];
p2 = points[i];
theta = angles[i];
r = Math.sin((i / n) * Math.PI) * THICKNESS;
sin = Math.sin(theta + HALF_PI) * r;
cos = Math.cos(theta + HALF_PI) * r;
context.quadraticCurveTo(
p1.x + cos,
p1.y + sin,
p2.x + cos,
p2.y + sin);
}
context.strokeStyle = 'rgba(255,255,255,0.1)';
context.lineWidth = 8;
context.stroke();
context.strokeStyle = 'rgba(0,0,0,0.8)';
context.lineWidth = 0.5;
context.stroke();
context.fillStyle = 'rgba(255,255,255,0.9)';
context.fill();
old = now;
setTimeout(step, FPS);
};
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener("resize", resize);
resize();
init();
step();
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,600,700' rel='stylesheet' type='text/css'>
<canvas id='world' width='500' height='500'></canvas>
<header><h1>Wiggle your mouse...</h1></header>
Here is one way:
HTML:
<div class="top">
top
</div>
<div class="middle">
<canvas id='world' width='500' height='500'></canvas>
<header><h1>Wiggle your mouse...</h1></header>
</div>
<div class="bottom">
bottom
</div>
CSS:
div {
border: 1px solid black
}
.top, .bottom {
height: 200px
}
The js remains the same. The CSS gives the top and bottom divs some height. The canvas is in the middle div. Here is a jsfiddle: https://jsfiddle.net/av902pcs/
I'm trying to create a Structure as shown in the Screenshot below. Is there a way to build an Algorithm for this in JavaScript to get the X and Y Coordinates of each red Point in chronological order to generate an infinite Spiral depending on a specific amount?
Screenshot of how it should look and work
This Code generates me a regular Hexagon:
function hexagon(centerX, centerY) {
var ctx = canvas.getContext('2d');
var x = centerX + Math.cos(Math.PI * 2 / 6) * 50;
var y = centerY + Math.sin(Math.PI * 2 / 6) * 50;
ctx.beginPath();
ctx.moveTo(x, y);
for (var i = 1; i < 7; i++) {
x = centerX + Math.cos(Math.PI * 2 / 6 * i) * 50;
y = centerY + Math.sin(Math.PI * 2 / 6 * i) * 50;
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.stroke();
}
And this is the Cluster-function so far:
function cluster(centerX, centerY, count) {
var ctx = canvas.getContext('2d');
for (var i = 0; i < count; i++) {
if (i == 0) {
var x = centerX;
var y = centerY;
} else {
var x = centerX + Math.cos(-Math.PI / 2) * (100 * (Math.sqrt(3) / 2));
var y = centerY + Math.sin(-Math.PI / 2) * (100 * (Math.sqrt(3) / 2));
}
hexagon(x, y);
}
}
Thank you!
Your cluster function could be like this:
function cluster(centerX, centerY, count) {
var x = centerX,
y = centerY,
angle = Math.PI / 3,
dist = Math.sin(angle) * 100,
i = 1,
side = 0;
hexagon(x, y);
count--;
while (count > 0) {
for (var t = 0; t < Math.floor((side+4)/6)+(side%6==0) && count; t++) {
y = y - dist * Math.cos(side * angle);
x = x - dist * Math.sin(side * angle);
hexagon(x, y);
count--;
}
side++;
}
}
function hexagon(centerX, centerY) {
var ctx = canvas.getContext('2d');
var x = centerX + Math.cos(Math.PI * 2 / 6) * 50;
var y = centerY + Math.sin(Math.PI * 2 / 6) * 50;
ctx.beginPath();
ctx.moveTo(x, y);
for (var i = 1; i < 7; i++) {
x = centerX + Math.cos(Math.PI * 2 / 6 * i) * 50;
y = centerY + Math.sin(Math.PI * 2 / 6 * i) * 50;
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.stroke();
}
function cluster(centerX, centerY, count) {
var x = centerX,
y = centerY,
angle = Math.PI / 3,
dist = Math.sin(angle) * 100,
i = 1,
side = 0;
hexagon(x, y);
count--;
while (count > 0) {
for (var t = 0; t < Math.floor((side+4)/6)+(side%6==0) && count; t++) {
y = y - dist * Math.cos(side * angle);
x = x - dist * Math.sin(side * angle);
hexagon(x, y);
count--;
}
side++;
}
}
cluster(200,230,9);
<canvas id="canvas" width="400" height="400"></canvas>
I have written some JS code to plot a 3D wireframe sphere into a HTML5 canvas.
I started from this post and improved it by using Qt3D vertices generation for a sphere mesh. The JS code does 2 passes on the vertices: the first to display rings, and the second to display slices. Normally OpenGL would connect all the vertices automatically with triangles.
I kept the slices/rings configurable but I have issues with the transformation code, for example when I rotate the sphere along the X axis.
So, starting from the basics. Here's a 1-pass, 4 rings, 4 slices, no transformation:
Seems all good. Now 2-passes, 10 rings, 10 slices, no transformation:
Still good, but if I rotate it 30° on the X Axis, the top and bottom vertices (Y position only apparently) get messed up.
I suspect there is something wrong in the rotation functions, or in the projection function.
Can someone please help me to figure out what's going on here ?
(Note that I don't want to use Three.js cause my goal is to port this in a QML application)
Here's the full code.
var sphere = new Sphere3D();
var rotation = new Point3D();
var distance = 1000;
var lastX = -1;
var lastY = -1;
function Point3D() {
this.x = 0;
this.y = 0;
this.z = 0;
}
function Sphere3D(radius) {
this.vertices = new Array();
this.radius = (typeof(radius) == "undefined" || typeof(radius) != "number") ? 20.0 : radius;
this.rings = 10;
this.slices = 10;
this.numberOfVertices = 0;
var M_PI_2 = Math.PI / 2;
var dTheta = (Math.PI * 2) / this.slices;
var dPhi = Math.PI / this.rings;
// Iterate over latitudes (rings)
for (var lat = 0; lat < this.rings + 1; ++lat) {
var phi = M_PI_2 - lat * dPhi;
var cosPhi = Math.cos(phi);
var sinPhi = Math.sin(phi);
// Iterate over longitudes (slices)
for (var lon = 0; lon < this.slices + 1; ++lon) {
var theta = lon * dTheta;
var cosTheta = Math.cos(theta);
var sinTheta = Math.sin(theta);
p = this.vertices[this.numberOfVertices] = new Point3D();
p.x = this.radius * cosTheta * cosPhi;
p.y = this.radius * sinPhi;
p.z = this.radius * sinTheta * cosPhi;
this.numberOfVertices++;
}
}
}
function rotateX(point, radians) {
var y = point.y;
point.y = (y * Math.cos(radians)) + (point.z * Math.sin(radians) * -1.0);
point.z = (y * Math.sin(radians)) + (point.z * Math.cos(radians));
}
function rotateY(point, radians) {
var x = point.x;
point.x = (x * Math.cos(radians)) + (point.z * Math.sin(radians) * -1.0);
point.z = (x * Math.sin(radians)) + (point.z * Math.cos(radians));
}
function rotateZ(point, radians) {
var x = point.x;
point.x = (x * Math.cos(radians)) + (point.y * Math.sin(radians) * -1.0);
point.y = (x * Math.sin(radians)) + (point.y * Math.cos(radians));
}
function projection(xy, z, xyOffset, zOffset, distance) {
return ((distance * xy) / (z - zOffset)) + xyOffset;
}
function strokeSegment(index, ctx, width, height) {
var x, y;
var p = sphere.vertices[index];
rotateX(p, rotation.x);
rotateY(p, rotation.y);
rotateZ(p, rotation.z);
x = projection(p.x, p.z, width / 2.0, 100.0, distance);
y = projection(p.y, p.z, height / 2.0, 100.0, distance);
if (lastX == -1 && lastY == -1) {
lastX = x;
lastY = y;
return;
}
if (x >= 0 && x < width && y >= 0 && y < height) {
if (p.z < 0) {
ctx.strokeStyle = "gray";
} else {
ctx.strokeStyle = "white";
}
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(x, y);
ctx.stroke();
ctx.closePath();
lastX = x;
lastY = y;
}
}
function render() {
var canvas = document.getElementById("sphere3d");
var width = canvas.getAttribute("width");
var height = canvas.getAttribute("height");
var ctx = canvas.getContext('2d');
var p = new Point3D();
ctx.fillStyle = "black";
ctx.clearRect(0, 0, width, height);
ctx.fillRect(0, 0, width, height);
// draw each vertex to get the first sphere skeleton
for (i = 0; i < sphere.numberOfVertices; i++) {
strokeSegment(i, ctx, width, height);
}
// now walk through rings to draw the slices
for (i = 0; i < sphere.slices + 1; i++) {
for (var j = 0; j < sphere.rings + 1; j++) {
strokeSegment(i + (j * (sphere.slices + 1)), ctx, width, height);
}
}
}
function init() {
rotation.x = Math.PI / 6;
render();
}
canvas {
background: black;
display: block;
}
<body onLoad="init();">
<canvas id="sphere3d" width="500" height="500">
Your browser does not support HTML5 canvas.
</canvas>
</body>
Your problem is that the contents of your sphere.vertices[] array is being modified inside your strokeSegment() call so the rotation gets applied twice when you invoke it the second time on each point. So, in strokeSegment() you need to replace:
var p = sphere.vertices[index];
with:
var p = new Point3D();
p.x = sphere.vertices[index].x;
p.y = sphere.vertices[index].y;
p.z = sphere.vertices[index].z;
Then it works perfectly as shown below:
var sphere = new Sphere3D();
var rotation = new Point3D();
var distance = 1000;
var lastX = -1;
var lastY = -1;
function Point3D() {
this.x = 0;
this.y = 0;
this.z = 0;
}
function Sphere3D(radius) {
this.vertices = new Array();
this.radius = (typeof(radius) == "undefined" || typeof(radius) != "number") ? 20.0 : radius;
this.rings = 10;
this.slices = 10;
this.numberOfVertices = 0;
var M_PI_2 = Math.PI / 2;
var dTheta = (Math.PI * 2) / this.slices;
var dPhi = Math.PI / this.rings;
// Iterate over latitudes (rings)
for (var lat = 0; lat < this.rings + 1; ++lat) {
var phi = M_PI_2 - lat * dPhi;
var cosPhi = Math.cos(phi);
var sinPhi = Math.sin(phi);
// Iterate over longitudes (slices)
for (var lon = 0; lon < this.slices + 1; ++lon) {
var theta = lon * dTheta;
var cosTheta = Math.cos(theta);
var sinTheta = Math.sin(theta);
p = this.vertices[this.numberOfVertices] = new Point3D();
p.x = this.radius * cosTheta * cosPhi;
p.y = this.radius * sinPhi;
p.z = this.radius * sinTheta * cosPhi;
this.numberOfVertices++;
}
}
}
function rotateX(point, radians) {
var y = point.y;
point.y = (y * Math.cos(radians)) + (point.z * Math.sin(radians) * -1.0);
point.z = (y * Math.sin(radians)) + (point.z * Math.cos(radians));
}
function rotateY(point, radians) {
var x = point.x;
point.x = (x * Math.cos(radians)) + (point.z * Math.sin(radians) * -1.0);
point.z = (x * Math.sin(radians)) + (point.z * Math.cos(radians));
}
function rotateZ(point, radians) {
var x = point.x;
point.x = (x * Math.cos(radians)) + (point.y * Math.sin(radians) * -1.0);
point.y = (x * Math.sin(radians)) + (point.y * Math.cos(radians));
}
function projection(xy, z, xyOffset, zOffset, distance) {
return ((distance * xy) / (z - zOffset)) + xyOffset;
}
function strokeSegment(index, ctx, width, height) {
var x, y;
var p = new Point3D();
p.x = sphere.vertices[index].x;
p.y = sphere.vertices[index].y;
p.z = sphere.vertices[index].z;
rotateX(p, rotation.x);
rotateY(p, rotation.y);
rotateZ(p, rotation.z);
x = projection(p.x, p.z, width / 2.0, 100.0, distance);
y = projection(p.y, p.z, height / 2.0, 100.0, distance);
if (lastX == -1 && lastY == -1) {
lastX = x;
lastY = y;
return;
}
if (x >= 0 && x < width && y >= 0 && y < height) {
if (p.z < 0) {
ctx.strokeStyle = "gray";
} else {
ctx.strokeStyle = "white";
}
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(x, y);
ctx.stroke();
ctx.closePath();
lastX = x;
lastY = y;
}
}
function render() {
var canvas = document.getElementById("sphere3d");
var width = canvas.getAttribute("width");
var height = canvas.getAttribute("height");
var ctx = canvas.getContext('2d');
var p = new Point3D();
ctx.fillStyle = "black";
ctx.clearRect(0, 0, width, height);
ctx.fillRect(0, 0, width, height);
// draw each vertex to get the first sphere skeleton
for (i = 0; i < sphere.numberOfVertices; i++) {
strokeSegment(i, ctx, width, height);
}
// now walk through rings to draw the slices
for (i = 0; i < sphere.slices + 1; i++) {
for (var j = 0; j < sphere.rings + 1; j++) {
strokeSegment(i + (j * (sphere.slices + 1)), ctx, width, height);
}
}
}
function init() {
rotation.x = Math.PI / 3;
render();
}
canvas {
background: black;
display: block;
}
<body onLoad="init();">
<canvas id="sphere3d" width="500" height="500">
Your browser does not support HTML5 canvas.
</canvas>
</body>
Short answer
The bug is in strokeSegment function
function strokeSegment(index, ctx, width, height) {
var x, y;
var p = sphere.vertices[index];
rotateX(p, rotation.x);
rotateY(p, rotation.y);
rotateZ(p, rotation.z);
...
The bug is that all the rotate functions modify p inplace and thus the modify the value stored in sphere.vertices! So the way to fix it is simply to clone the point:
function strokeSegment(index, ctx, width, height) {
var x, y;
var p0 = sphere.vertices[index];
var p = new Point3D();
p.x = p0.x;
p.y = p0.y;
p.z = p0.z;
rotateX(p, rotation.x);
rotateY(p, rotation.y);
rotateZ(p, rotation.z);
...
You may find demo with fixed code at https://plnkr.co/edit/zs5ZxbglFxo9cbwA6MI5?p=preview
Longer addition
Before I found this issue I played with your code a bit and I think improved it. Improved version is available at https://plnkr.co/edit/tpTZ8GH9eByVARUIYZBi?p=preview
var sphere = new Sphere3D();
var rotation = new Point3D(0, 0, 0);
var distance = 1000;
var EMPTY_VALUE = Number.MIN_VALUE;
function Point3D(x, y, z) {
if (arguments.length == 3) {
this.x = x;
this.y = y;
this.z = z;
}
else if (arguments.length == 1) {
fillPointFromPoint(this, x); // 1 argument means point
}
else {
clearPoint(this); // no arguments mean creat empty
}
}
function fillPointFromPoint(target, src) {
target.x = src.x;
target.y = src.y;
target.z = src.z;
}
function clearPoint(p) {
p.x = EMPTY_VALUE;
p.y = EMPTY_VALUE;
p.z = EMPTY_VALUE;
}
function Sphere3D(radius) {
this.radius = (typeof(radius) == "undefined" || typeof(radius) != "number") ? 20.0 : radius;
this.innerRingsCount = 9; // better be odd so we have explicit Equator
this.slicesCount = 8;
var M_PI_2 = Math.PI / 2;
var dTheta = (Math.PI * 2) / this.slicesCount;
var dPhi = Math.PI / this.innerRingsCount;
this.rings = [];
// always add both poles
this.rings.push([new Point3D(0, this.radius, 0)]);
// Iterate over latitudes (rings)
for (var lat = 0; lat < this.innerRingsCount; ++lat) {
var phi = M_PI_2 - lat * dPhi - dPhi / 2;
var cosPhi = Math.cos(phi);
var sinPhi = Math.sin(phi);
console.log("lat = " + lat + " phi = " + (phi / Math.PI) + " sinPhi = " + sinPhi);
var vertices = [];
// Iterate over longitudes (slices)
for (var lon = 0; lon < this.slicesCount; ++lon) {
var theta = lon * dTheta;
var cosTheta = Math.cos(theta);
var sinTheta = Math.sin(theta);
var p = new Point3D();
p.x = this.radius * cosTheta * cosPhi;
p.y = this.radius * sinPhi;
p.z = this.radius * sinTheta * cosPhi;
vertices.push(p);
}
this.rings.push(vertices);
}
// always add both poles
this.rings.push([new Point3D(0, -this.radius, 0)]);
}
function rotateX(point, radians) {
var y = point.y;
point.y = (y * Math.cos(radians)) + (point.z * Math.sin(radians) * -1.0);
point.z = (y * Math.sin(radians)) + (point.z * Math.cos(radians));
}
function rotateY(point, radians) {
var x = point.x;
point.x = (x * Math.cos(radians)) + (point.z * Math.sin(radians) * -1.0);
point.z = (x * Math.sin(radians)) + (point.z * Math.cos(radians));
}
function rotateZ(point, radians) {
var x = point.x;
point.x = (x * Math.cos(radians)) + (point.y * Math.sin(radians) * -1.0);
point.y = (x * Math.sin(radians)) + (point.y * Math.cos(radians));
}
function projection(xy, z, xyOffset, zOffset, distance) {
return ((distance * xy) / (z - zOffset)) + xyOffset;
}
var lastP = new Point3D();
var firstP = new Point3D();
function startRenderingPortion() {
clearPoint(lastP);
clearPoint(firstP);
}
function closeRenderingPortion(ctx, width, height) {
strokeSegmentImpl(ctx, firstP.x, firstP.y, firstP.z, width, height);
clearPoint(lastP);
clearPoint(firstP);
}
function strokeSegmentImpl(ctx, x, y, z, width, height) {
if (x >= 0 && x < width && y >= 0 && y < height) {
// as we work with floating point numbers, there might near zero that != 0
// choose gray if one of two points is definitely (z < 0) and other has (z <= 0)
// Note also that in term of visibility this is a wrong logic! Line is invisible
// only if it is shadowed by another polygon and this depends on relative "Z" not
// absolute values
var eps = 0.01;
if (((z < -eps) && (lastP.z < eps))
|| ((z < eps) && (lastP.z < -eps))) {
ctx.strokeStyle = "gray";
} else {
ctx.strokeStyle = "white";
}
if ((x === lastP.x) && (y == lastP.y)) {
ctx.beginPath();
// draw single point
ctx.moveTo(x, y);
ctx.lineTo(x + 1, y + 1);
ctx.stroke();
ctx.closePath();
} else {
ctx.beginPath();
ctx.moveTo(lastP.x, lastP.y);
ctx.lineTo(x, y);
ctx.stroke();
ctx.closePath();
}
lastP.x = x;
lastP.y = y;
lastP.z = z;
}
}
function strokeSegment(p0, ctx, width, height) {
var p = new Point3D(p0); // clone original point to not mess it up with rotation!
rotateX(p, rotation.x);
rotateY(p, rotation.y);
rotateZ(p, rotation.z);
var x, y;
x = projection(p.x, p.z, width / 2.0, 100.0, distance);
y = projection(p.y, p.z, height / 2.0, 100.0, distance);
if (lastP.x === EMPTY_VALUE && lastP.y === EMPTY_VALUE) {
lastP = new Point3D(x, y, p.z);
fillPointFromPoint(firstP, lastP);
return;
}
strokeSegmentImpl(ctx, x, y, p.z, width, height);
}
function renderSphere(ctx, width, height, sphere) {
var i, j;
var vertices;
// draw each vertex to get the first sphere skeleton
for (i = 0; i < sphere.rings.length; i++) {
startRenderingPortion();
vertices = sphere.rings[i];
for (j = 0; j < vertices.length; j++) {
strokeSegment(vertices[j], ctx, width, height);
}
closeRenderingPortion(ctx, width, height);
}
// now walk through rings to draw the slices
for (i = 0; i < sphere.slicesCount; i++) {
startRenderingPortion();
for (j = 0; j < sphere.rings.length; j++) {
vertices = sphere.rings[j];
var p = vertices[i % vertices.length];// for top and bottom vertices.length = 1
strokeSegment(p, ctx, width, height);
}
//closeRenderingPortion(ctx, width, height); // don't close back!
}
}
function render() {
var canvas = document.getElementById("sphere3d");
var width = canvas.getAttribute("width");
var height = canvas.getAttribute("height");
var ctx = canvas.getContext('2d');
ctx.fillStyle = "black";
ctx.clearRect(0, 0, width, height);
ctx.fillRect(0, 0, width, height);
renderSphere(ctx, width, height, sphere);
}
function init() {
rotation.x = Math.PI / 6;
//rotation.y = Math.PI / 6;
rotation.z = Math.PI / 6;
render();
}
Main changes are:
I explicitly separated plain array vertices in array of arrays rings and also explicitly add both poles to it.
Separation of rings allowed me to clear lastX/Y more often to avoid some spurious lines by introducing startRenderingPortion.
I also introduced closeRenderingPortion that is logically similar to closePath. Using this method I was able to remove duplication of points that you needed.
Generally I tried to avoid more-OOP-ish style as you do in your code (see renderSphere or clearPoint) but I changed Point3D constructor to support 3 modes: (x,y,z), point, empty.
Use more explicit marker value for empty lastX/Y var EMPTY_VALUE = Number.MIN_VALUE;. -1 is a possible value
Note also that there is a potential bug with your gray/white color selection that I didn't fix. I assume your color should reflect "invisible" lines and simple logic of Z > 0 vs Z < 0 doesn't solve this issue properly. Actually single line might be visible only partially if it is obscured by other things in the scene.
How To Get The Value Of A Canvas . I have wheel which is rotating on mouse over the wheel stops now i want to echo out the value on which it was stopped. It is printing the whole array . Not the one on which the wheel stop.
$("#canvas").mouseover(function(){
backup= ctx;
alert(myData);
ctx = null;
});
this is the fiddle: https://jsfiddle.net/z61n9ccx/3/
Here is the full code:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
var PI2 = Math.PI * 2;
var myData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
var cx = 150;
var cy = 150;
var radius = 150;
var wheel = document.createElement('canvas');
var wheelCtx = wheel.getContext('2d');
var indicator = document.createElement('canvas');
var indicatorCtx = indicator.getContext('2d');
var angle = PI2 - PI2 / 4;
var myColor = [];
for (var i = 0; i < myData.length; i++) {
myColor.push(randomColor());
}
makeWheel();
makeIndicator();
requestAnimationFrame(animate);
function makeWheel() {
wheel.width = wheel.height = radius * 2 + 2;
wheelCtx.lineWidth = 1;
wheelCtx.font = '40px Pacifico, cursive';
wheelCtx.textAlign = 'center';
wheelCtx.textBaseline = 'middle';
var cx = wheel.width / 2;
var cy = wheel.height / 2;
var sweepAngle = PI2 / myData.length;
var startAngle = 0;
for (var i = 0; i < myData.length; i++) {
// calc ending angle based on starting angle
var endAngle = startAngle + sweepAngle;
// draw the wedge
wheelCtx.beginPath();
wheelCtx.moveTo(cx, cy);
wheelCtx.arc(cx, cy, radius, startAngle, endAngle, false);
wheelCtx.closePath();
wheelCtx.fillStyle = myColor[i];
wheelCtx.strokeStyle = 'black';
wheelCtx.fill();
wheelCtx.stroke();
// draw the label
var midAngle = startAngle + (endAngle - startAngle) / 2;
var labelRadius = radius * .85;
var x = cx + (labelRadius) * Math.cos(midAngle);
var y = cy + (labelRadius) * Math.sin(midAngle);
wheelCtx.fillStyle = 'gold';
wheelCtx.fillText(myData[i], x, y);
wheelCtx.strokeText(myData[i], x, y);
// increment angle
startAngle += sweepAngle;
}
}
function makeIndicator() {
indicator.width = indicator.height = radius + radius / 10;
indicatorCtx.font = '40px Georgia';
indicatorCtx.textAlign = 'center';
indicatorCtx.textBaseline = 'middle';
indicatorCtx.fillStyle = 'skyblue';
indicatorCtx.strokeStyle = 'blue';
indicatorCtx.lineWidth = 1;
var cx = indicator.width / 2;
var cy = indicator.height / 2;
indicatorCtx.beginPath();
indicatorCtx.moveTo(cx - radius / 8, cy);
indicatorCtx.lineTo(cx, cy - indicator.height / 2);
indicatorCtx.lineTo(cx + radius / 8, cy);
indicatorCtx.closePath();
indicatorCtx.fillStyle = 'skyblue'
indicatorCtx.fill();
indicatorCtx.stroke();
indicatorCtx.beginPath();
indicatorCtx.arc(cx, cy, radius / 3, 0, PI2);
indicatorCtx.closePath();
indicatorCtx.fill();
indicatorCtx.stroke();
indicatorCtx.fillStyle = 'blue';
indicatorCtx.fillText('Prizes', cx, cy);
}
function animate(time) {
ctx.clearRect(0, 0, cw, ch);
ctx.translate(cw / 2, ch / 2);
ctx.rotate(angle);
ctx.drawImage(wheel, -wheel.width / 2, -wheel.height / 2);
ctx.rotate(-angle);
ctx.translate(-cw / 2, -ch / 2);
ctx.drawImage(indicator, cw / 2 - indicator.width / 2, ch / 2 - indicator.height / 2)
angle += PI2 / 360;
requestAnimationFrame(animate);
}
function randomColor() {
return ('#' + Math.floor(Math.random() * 16777215).toString(16));
}
var backup = null;
$("#canvas").mouseover(function() {
backup = ctx;
alert(myData);
ctx = null;
});
$("#canvas").mouseout(function() {
// backup= ctx;
ctx = backup;
animate();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<canvas id="canvas" width="600" height="600" style="background-color:#ffff">
</canvas>
I added a counter, and then use that as a index: https://jsfiddle.net/Twisty/L6nws9yz/2/
HTML
<canvas id="canvas" width="310" height="310" style="background-color:#ffff">
</canvas>
<div id="counterBox">
<label>Counter:</label>
<span></span>
</div>
<div id="countBox">
<label>Index:</label>
<span></span>
</div>
JS
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
var PI2 = Math.PI * 2;
var myData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
var cx = 150;
var cy = 150;
var radius = 150;
var wheel = document.createElement('canvas');
var wheelCtx = wheel.getContext('2d');
var indicator = document.createElement('canvas');
var indicatorCtx = indicator.getContext('2d');
var currentSelection = 12;
var counter = 360;
var angle = PI2 - PI2 / 4;
var myColor = [];
for (var i = 0; i < myData.length; i++) {
myColor.push(randomColor());
}
makeWheel();
makeIndicator();
requestAnimationFrame(animate);
function makeWheel() {
wheel.width = wheel.height = radius * 2 + 2;
wheelCtx.lineWidth = 1;
wheelCtx.font = '40px Pacifico, cursive';
wheelCtx.textAlign = 'center';
wheelCtx.textBaseline = 'middle';
var cx = wheel.width / 2;
var cy = wheel.height / 2;
var sweepAngle = PI2 / myData.length;
var startAngle = 0;
for (var i = 0; i < myData.length; i++) {
// calc ending angle based on starting angle
var endAngle = startAngle + sweepAngle;
// draw the wedge
wheelCtx.beginPath();
wheelCtx.moveTo(cx, cy);
wheelCtx.arc(cx, cy, radius, startAngle, endAngle, false);
wheelCtx.closePath();
wheelCtx.fillStyle = myColor[i];
wheelCtx.strokeStyle = 'black';
wheelCtx.fill();
wheelCtx.stroke();
// draw the label
var midAngle = startAngle + (endAngle - startAngle) / 2;
var labelRadius = radius * .85;
var x = cx + (labelRadius) * Math.cos(midAngle);
var y = cy + (labelRadius) * Math.sin(midAngle);
wheelCtx.fillStyle = 'gold';
wheelCtx.fillText(myData[i], x, y);
wheelCtx.strokeText(myData[i], x, y);
// increment angle
startAngle += sweepAngle;
}
}
function makeIndicator() {
indicator.width = indicator.height = radius + radius / 10;
indicatorCtx.font = '40px Georgia';
indicatorCtx.textAlign = 'center';
indicatorCtx.textBaseline = 'middle';
indicatorCtx.fillStyle = 'skyblue';
indicatorCtx.strokeStyle = 'blue';
indicatorCtx.lineWidth = 1;
var cx = indicator.width / 2;
var cy = indicator.height / 2;
indicatorCtx.beginPath();
indicatorCtx.moveTo(cx - radius / 8, cy);
indicatorCtx.lineTo(cx, cy - indicator.height / 2);
indicatorCtx.lineTo(cx + radius / 8, cy);
indicatorCtx.closePath();
indicatorCtx.fillStyle = 'skyblue'
indicatorCtx.fill();
indicatorCtx.stroke();
indicatorCtx.beginPath();
indicatorCtx.arc(cx, cy, radius / 3, 0, PI2);
indicatorCtx.closePath();
indicatorCtx.fill();
indicatorCtx.stroke();
indicatorCtx.fillStyle = 'blue';
indicatorCtx.fillText('Prizes', cx, cy);
}
var lastloop = new Date;
var thisloop = new Date;
var fps = 0;
function animate(time) {
ctx.clearRect(0, 0, cw, ch);
ctx.translate(cw / 2, ch / 2);
ctx.rotate(angle);
ctx.drawImage(wheel, -wheel.width / 2, -wheel.height / 2);
ctx.rotate(-angle);
ctx.translate(-cw / 2, -ch / 2);
ctx.drawImage(indicator, cw / 2 - indicator.width / 2, ch / 2 - indicator.height / 2)
angle += PI2 / 360;
thisloop = new Date;
fps = 1000 / (thisloop - lastloop);
lastloop = thisloop;
counter--;
if (counter < 1) {
counter = 360;
}
$("#counterBox span").html(counter);
var index = counter / 30;
$("#countBox span").html(Math.round(index));
//$("#fpsBox span").html(fps);
requestAnimationFrame(animate);
}
function randomColor() {
return ('#' + Math.floor(Math.random() * 16777215).toString(16));
}
var backup = null;
$("#canvas").mouseover(function() {
backup = ctx;
alert(myData[Math.round(counter / 30)-1]);
ctx = null;
});
$("#canvas").mouseout(function() {
// backup= ctx;
ctx = backup;
animate();
});
Counter is set to 360 and then each frame decreases it. Take that and divide by 30 (360 / 12), and you can count each wedge. I round up and now I have 0 - 11 count.
Update
I moved the Index into a global space. To make it more precise, I used the % operator like so:
counter--;
if (counter == 0) {
counter = 360;
}
$("#counterBox span").html(counter);
if (counter % 30 === 0) {
index--;
}
$("#countBox span").html(Math.round(index));
if (index === 0) {
index = 12;
}
When you mouse over, you get the selection:
$("#canvas").mouseover(function() {
backup = ctx;
alert(index);
ctx = null;
});
I wrapped everything in an IIFE so that there aren't any global variables.
Updated Example
It's important to note that the angle calculation is:
angle = degree * Math.PI / 180;
With that being said, you can calculate the current degree and normalize it using:
(angle * (180 / Math.PI)) % 360
I added a function called getValue which takes an angle parameter:
function getValue(angle) {
var degree = (angle * (180 / Math.PI)) % 360,
offsetIndex = (Math.floor(degree / sweepDegree) + offset) % myData.length,
normalizedIndex = Math.abs(offsetIndex - (myData.length - 1));
return myData[normalizedIndex];
}
It essentially calculates the current degree, normalizes it taking into account what the initial degree was when the animation was initialized (which is the offset). Then it divides the degree by the sweep degree, which is 30 in this case since there are 12 items (i.e., 360/12 === 30) and rounds down.
var sweepDegree = 360 / myData.length;
var offset = (360 - (angle * (180 / Math.PI)) % 360) / sweepDegree;
This should work for a varying number of array items. In other words, nothing is hardcoded for a set length of 12 items (like in your case), so it should work for any given number of items.
Then you can simply use the getValue function in the mouseover event listener:
Updated Example
$("#canvas").mouseover(function() {
// ...
alert(getValue(angle));
});
(function() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
var PI2 = Math.PI * 2;
var myData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
var cx = 150;
var cy = 150;
var radius = 150;
var wheel = document.createElement('canvas');
var wheelCtx = wheel.getContext('2d');
var indicator = document.createElement('canvas');
var indicatorCtx = indicator.getContext('2d');
var angle = PI2 - PI2 / 4;
var sweepDegree = 360 / myData.length;
var offset = (360 - (angle * (180 / Math.PI)) % 360) / sweepDegree;
var myColor = [];
for (var i = 0; i < myData.length; i++) {
myColor.push(randomColor());
}
makeWheel();
makeIndicator();
requestAnimationFrame(animate);
function makeWheel() {
wheel.width = wheel.height = radius * 2 + 2;
wheelCtx.lineWidth = 1;
wheelCtx.font = '40px Pacifico, cursive';
wheelCtx.textAlign = 'center';
wheelCtx.textBaseline = 'middle';
var cx = wheel.width / 2;
var cy = wheel.height / 2;
var sweepAngle = PI2 / myData.length;
var startAngle = 0;
for (var i = 0; i < myData.length; i++) {
// calc ending angle based on starting angle
var endAngle = startAngle + sweepAngle;
// draw the wedge
wheelCtx.beginPath();
wheelCtx.moveTo(cx, cy);
wheelCtx.arc(cx, cy, radius, startAngle, endAngle, false);
wheelCtx.closePath();
wheelCtx.fillStyle = myColor[i];
wheelCtx.strokeStyle = 'black';
wheelCtx.fill();
wheelCtx.stroke();
// draw the label
var midAngle = startAngle + (endAngle - startAngle) / 2;
var labelRadius = radius * .85;
var x = cx + (labelRadius) * Math.cos(midAngle);
var y = cy + (labelRadius) * Math.sin(midAngle);
wheelCtx.fillStyle = 'gold';
wheelCtx.fillText(myData[i], x, y);
wheelCtx.strokeText(myData[i], x, y);
// increment angle
startAngle += sweepAngle;
}
}
function makeIndicator() {
indicator.width = indicator.height = radius + radius / 10;
indicatorCtx.font = '40px Georgia';
indicatorCtx.textAlign = 'center';
indicatorCtx.textBaseline = 'middle';
indicatorCtx.fillStyle = 'skyblue';
indicatorCtx.strokeStyle = 'blue';
indicatorCtx.lineWidth = 1;
var cx = indicator.width / 2;
var cy = indicator.height / 2;
indicatorCtx.beginPath();
indicatorCtx.moveTo(cx - radius / 8, cy);
indicatorCtx.lineTo(cx, cy - indicator.height / 2);
indicatorCtx.lineTo(cx + radius / 8, cy);
indicatorCtx.closePath();
indicatorCtx.fillStyle = 'skyblue'
indicatorCtx.fill();
indicatorCtx.stroke();
indicatorCtx.beginPath();
indicatorCtx.arc(cx, cy, radius / 3, 0, PI2);
indicatorCtx.closePath();
indicatorCtx.fill();
indicatorCtx.stroke();
indicatorCtx.fillStyle = 'blue';
indicatorCtx.fillText('Prizes', cx, cy);
}
function animate(time) {
if (ctx === null) {
return
}
ctx.clearRect(0, 0, cw, ch);
ctx.translate(cw / 2, ch / 2);
ctx.rotate(angle);
ctx.drawImage(wheel, -wheel.width / 2, -wheel.height / 2);
ctx.rotate(-angle);
ctx.translate(-cw / 2, -ch / 2);
ctx.drawImage(indicator, cw / 2 - indicator.width / 2, ch / 2 - indicator.height / 2)
angle += PI2 / 360;
requestAnimationFrame(animate);
}
function randomColor() {
return ('#' + Math.floor(Math.random() * 16777215).toString(16));
}
var backup = null;
$("#canvas").mouseover(function() {
backup = ctx;
ctx = null;
alert(getValue(angle));
});
$("#canvas").mouseout(function() {
ctx = backup;
animate();
});
function getValue(angle) {
var degree = (angle * (180 / Math.PI)) % 360,
offsetIndex = (Math.floor(degree / sweepDegree) + offset) % myData.length,
normalizedIndex = Math.abs(offsetIndex - (myData.length - 1));
return myData[normalizedIndex];
}
})();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<canvas id="canvas" width="600" height="600" style="background-color:#ffff">
</canvas>