HTML5 Canvas + JavaScript wireframe sphere transformation issue - javascript

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.

Related

I will try to print 100 of circle and make them go anywhere on the screen

class Circle {
constructor(x, y, dx, dy, radius) {
this.x = x
this.y = y
this.dx = dx
this.dy = dy
this.radius = radius
}
draw() {
c.beginPath()
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
c.strokeStyle = "blue"
c.stroke()
}
update() {
if (this.x + this.radius > innerWidth || this.x - this.radius < 0) {
this.dx = - this.dx
}
if (this.y + this.radius > innerHeight || this.y - this.radius < 0) {
this.dy = -this.dy
}
this.x += this.dx
this.y -= this.dy
circleArray.draw()
}
}
let circleArray = []
// let circle = [];
for (let i = 0 ; i < 100; i++){
let radius = 30
let x = Math.random() * innerWidth
let y = Math.random() * innerHeight
let dx = (Math.random() - 0.5 * 5)
let dy = (Math.random() - 0.5 * 5)
// circle = new Circle(x,y,dx,dy,radius)
circleArray =(new Circle(x,y,dx,dy,radius))
console.log(circleArray)
// console.log(circleArray)
}
function animation() {
requestAnimationFrame(animation)
c.clearRect(0, 0, innerWidth, innerHeight)
for(let i = 0;i < circleArray.length; i++){
circleArray[i].update()
// console.log(circle[i])
}
}
animation()
i will try to print 100 circle by using html canvas and Class in javascript but nothing happend
then i try to push new class in empty array they give me a error and them i call dray method with empyt array variable the give a error and say this is not a function and them i try to use both
empty array and a new variable so they give me a typeerror and say this is undefind
There were two issues in your snippet:
circleArray =(new Circle(x,y,dx,dy,radius))
This assigns a single circle instance to the circleArray variable. You probably meant to write circleArray.push(new Circle(...))
circleArray.draw()
This might have worked because of the mistake in (1), but should now be this.draw() instead.
Here's a snippet that has these issues fixed:
const innerWidth = 256;
const innerHeight = 256;
const cvs = document.createElement("canvas");
cvs.width = 256;
cvs.height = 256;
const c = cvs.getContext("2d");
class Circle {
constructor(x, y, dx, dy, radius) {
this.x = x
this.y = y
this.dx = dx
this.dy = dy
this.radius = radius
}
draw() {
c.beginPath()
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
c.strokeStyle = "blue"
c.stroke()
}
update() {
if (this.x + this.radius > innerWidth || this.x - this.radius < 0) {
this.dx = -this.dx
}
if (this.y + this.radius > innerHeight || this.y - this.radius < 0) {
this.dy = -this.dy
}
this.x += this.dx
this.y -= this.dy
this.draw();
}
}
let circleArray = []
// let circle = [];
for (let i = 0; i < 100; i++) {
let radius = 30
let x = Math.random() * innerWidth
let y = Math.random() * innerHeight
let dx = (Math.random() - 0.5 * 5)
let dy = (Math.random() - 0.5 * 5)
circleArray.push(new Circle(x, y, dx, dy, radius))
}
function animation() {
requestAnimationFrame(animation)
c.clearRect(0, 0, innerWidth, innerHeight)
for (let i = 0; i < circleArray.length; i++) {
circleArray[i].update()
}
}
document.body.appendChild(cvs);
animation();

Consistent way to draw ellipsis area

I'm trying to make a simple game in Processing. The canvas consist of two ellipsis, as in the following image:
This is my code:
var angle = Math.random()*360;
var diff = 1;
var region = {};
var first = true;
var o;
centerX = 250; centerY = 250; radiusX = 150; radiusY = 200;
var score = 0;
function setup() {
createCanvas(500, 500);
}
function draw() {
o = ellipseGetPoint(250,250,150,200,angle);
angle += diff;
background(220);
stroke("black");
fill("#333333");
ellipse(250,250,300,400);
stroke("white")
drawRegion(250,250,150,200);
stroke("red");
line(250,250,o.pointX, o.pointY);
fill("#999999");
stroke(0);
ellipse(250,250,200,300);
textSize(50);
text(str(score), 20, 490);
fill(0, 102, 153);
}
function ellipseGetPoint(centerX, centerY, radiusX, radiusY, angle) {
let o = {};
let pointX = centerX + radiusX * Math.cos(angle*Math.PI / 180);
let pointY = centerY + radiusY * Math.sin(angle*Math.PI / 180);
o.pointX = Math.floor(pointX);
o.pointY = Math.floor(pointY);
o.angle = angle;
return o;
}
function drawRegion(centerX, centerY, radiusX, radiusY) {
if (first) {
first = false;
region = ellipseGetPoint(centerX, centerY, radiusX, radiusY, Math.random()*360);
}
let currentX = region.pointX;
let currentY = region.pointY;
let t = 1;
for (let i = 0; i < 1000; i++) {
line(250,250, currentX, currentY);
currentX = centerX + radiusX * Math.cos(t*region.angle*Math.PI / 180)
currentY = centerY + radiusY * Math.sin(t*region.angle*Math.PI / 180)
t+=0.00015;
}
}
function keyPressed() {
if (key == " ") {
diff*=-1;
}
let x = Math.floor(o.pointX);
let y = Math.floor(o.pointY);
let t = 1;
for (let i = 0; i < 1000; i++) {
currentX = centerX + radiusX * Math.cos(t*region.angle*Math.PI / 180)
currentY = centerY + radiusY * Math.sin(t*region.angle*Math.PI / 180)
t+=0.00015;
if (Math.floor(currentX) == x) {
first = true
}
}
if (first) ++score;
else --score;
if (diff > 0 && diff < 3) diff += 0.1;
else if (diff > -3) diff -= 0.1;
}
The problem I'm facing is that, making the white area, I'm getting a different size of area depending of where it spawns.
I did it by getting a random point and adding a little offset, and drawing that 1000 times. I know this is not a good way to do it, but I don't know how to generate that region. Any way to do it?
Maybe you're looking for arc?
void draw() {
arc(50, 50, 80, 80, 0, QUARTER_PI, PIE);
}
Here's the documentation for more details. I think you'll love this.
Use arc() to draw an elliptical arc segment.
Compute the start and end angle in ellipseGetPoint and draw the arc:
function drawRegion(centerX, centerY, radiusX, radiusY) {
if (first) {
first = false;
region = ellipseGetPoint(centerX, centerY, radiusX, radiusY, Math.random()*360);
}
let t = 1;
let start = t*region.angle*Math.PI / 180;
t+= 0.15;
let end = t*region.angle*Math.PI / 180;
arc(centerX, centerY, radiusX*2, radiusY*2, start, end)
}
Note, you have to set the fill color before drawing the arc:
stroke("white")
drawRegion(250,250,150,200);
See the example:
var angle = Math.random()*360;
var diff = 1;
var region = {};
var first = true;
var o;
centerX = 250; centerY = 250; radiusX = 150; radiusY = 200;
var score = 0;
function setup() {
createCanvas(500, 500);
}
function draw() {
o = ellipseGetPoint(250,250,150,200,angle);
angle += diff;
background(220);
stroke("black");
fill("#333333");
ellipse(250,250,300,400);
fill("white")
stroke("white")
drawRegion(250,250,150,200);
stroke("red");
line(250,250,o.pointX, o.pointY);
fill("#999999");
stroke(0);
ellipse(250,250,200,300);
textSize(50);
text(str(score), 20, 490);
fill(0, 102, 153);
}
function ellipseGetPoint(centerX, centerY, radiusX, radiusY, angle) {
let o = {};
let pointX = centerX + radiusX * Math.cos(angle*Math.PI / 180);
let pointY = centerY + radiusY * Math.sin(angle*Math.PI / 180);
o.pointX = Math.floor(pointX);
o.pointY = Math.floor(pointY);
o.angle = angle;
return o;
}
function drawRegion(centerX, centerY, radiusX, radiusY) {
if (first) {
first = false;
region = ellipseGetPoint(centerX, centerY, radiusX, radiusY, Math.random()*360);
}
let t = 1;
let start = t*region.angle*Math.PI / 180;
t+= 0.15;
let end = t*region.angle*Math.PI / 180;
arc(centerX, centerY, radiusX*2, radiusY*2, start, end)
}
function keyPressed() {
if (key == " ") {
diff*=-1;
}
let x = Math.floor(o.pointX);
let y = Math.floor(o.pointY);
let t = 1;
for (let i = 0; i < 1000; i++) {
currentX = centerX + radiusX * Math.cos(t*region.angle*Math.PI / 180)
currentY = centerY + radiusY * Math.sin(t*region.angle*Math.PI / 180)
t+=0.00015;
if (Math.floor(currentX) == x) {
first = true
}
}
if (first) ++score;
else --score;
if (diff > 0 && diff < 3) diff += 0.1;
else if (diff > -3) diff -= 0.1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.js"></script>

How to color line in canvas?

EDIT: I'm trying to get a single line colored, not the whole grid
So, what I'm trying to do is that when the canvas gets clicked, the lines selected move and get colored.
I already have the lines moving but I cannot get them colored. I know I have to use the strokeStyle property, but how do I get the specific line colored?
I tried putting the strokeStyle under the draw_each method, but that doesn't work, and I don't know why. Here's my code:
//draw each line in array
function draw_each(p1, p2, p3, p4) {
$.moveTo(p1.x, p1.y);
$.lineTo(p2.x, p2.y);
$.moveTo(p1.x, p1.y);
$.lineTo(p4.x, p4.y);
if (p1.ind_x == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p4.x, p4.y);
}
if (p1.ind_y == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p2.x, p2.y);
}
}
https://codepen.io/diazabdulm/pen/qJyrZP?editors=0010
It seems to me that what you want is actually to generate a color per segment based on the force that's been applied to it, so that the wave effect keeps its effect.
What you can do, is to save the median of each node's velocity in both axis as a mean to represent the force they're currently experiencing, and in your drawing part to get the median of all nodes that will compose each segment of your grid and compose a color from it.
But this comes with a drawback: your code was well written enough to compose the whole grid a single sub-path, thus limiting the number of drawings necessary. But now, we have to make each segment its own sub-path (since they'll got their own color), so we'll loose a lot in terms of performances...
var gnum = 90; //num grids / frame
var _x = 2265; //x width (canvas width)
var _y = 1465; //y height (canvas height)
var w = _x / gnum; //grid sq width
var h = _y / gnum; //grid sq height
var $; //context
var parts; //particles
var frm = 0; //value from
var P1 = 0.0005; //point one
var P2 = 0.01; //point two
var n = 0.98; //n value for later
var n_vel = 0.03; //velocity
var ŭ = 0; //color update
var msX = 0; //mouse x
var msY = 0; //mouse y
var msdn = false; //mouse down flag
var Part = function() {
this.x = 0; //x pos
this.y = 0; //y pos
this.vx = 0; //velocity x
this.vy = 0; //velocity y
this.ind_x = 0; //index x
this.ind_y = 0; //index y
};
Part.prototype.frame = function() {
if (this.ind_x == 0 || this.ind_x == gnum - 1 || this.ind_y == 0 || this.ind_y == gnum - 1) {
return;
}
var ax = 0; //angle x
var ay = 0; //angle y
//off_dx, off_dy = offset distance x, y
var off_dx = this.ind_x * w - this.x;
var off_dy = this.ind_y * h - this.y;
ax = P1 * off_dx;
ay = P1 * off_dy;
ax -= P2 * (this.x - parts[this.ind_x - 1][this.ind_y].x);
ay -= P2 * (this.y - parts[this.ind_x - 1][this.ind_y].y);
ax -= P2 * (this.x - parts[this.ind_x + 1][this.ind_y].x);
ay -= P2 * (this.y - parts[this.ind_x + 1][this.ind_y].y);
ax -= P2 * (this.x - parts[this.ind_x][this.ind_y - 1].x);
ay -= P2 * (this.y - parts[this.ind_x][this.ind_y - 1].y);
ax -= P2 * (this.x - parts[this.ind_x][this.ind_y + 1].x);
ay -= P2 * (this.y - parts[this.ind_x][this.ind_y + 1].y);
this.vx += (ax - this.vx * n_vel);
this.vy += (ay - this.vy * n_vel);
//EDIT\\
// store the current velocity (here base on 100 since it will be used with hsl())
this.color = (Math.abs(this.vx)+Math.abs(this.vy)) * 50;
this.x += this.vx * n;
this.y += this.vy * n;
if (msdn) {
var dx = this.x - msX;
var dy = this.y - msY;
var ɋ = Math.sqrt(dx * dx + dy * dy);
if (ɋ > 50) {
ɋ = ɋ < 10 ? 10 : ɋ;
this.x -= dx / ɋ * 5;
this.y -= dy / ɋ * 5;
}
}
};
function go() {
parts = []; //particle array
for (var i = 0; i < gnum; i++) {
parts.push([]);
for (var j = 0; j < gnum; j++) {
var p = new Part();
p.ind_x = i;
p.ind_y = j;
p.x = i * w;
p.y = j * h;
parts[i][j] = p;
}
}
}
//move particles function
function mv_part() {
for (var i = 0; i < gnum; i++) {
for (var j = 0; j < gnum; j++) {
var p = parts[i][j];
p.frame();
}
}
}
//draw grid function
function draw() {
//EDIT
// we unfortunately have to break the drawing part
// since each segment has its own color, we can't have a single sub-path anymore...
ŭ -= .5;
for (var i = 0; i < gnum - 1; i += 1) {
for (var j = 0; j < gnum - 1; j += 1) {
var p1 = parts[i][j];
var p2 = parts[i][j + 1];
var p3 = parts[i + 1][j + 1];
var p4 = parts[i + 1][j];
draw_each(p1, p2, p3, p4);
}
}
}
//draw each in array
function draw_each(p1, p2, p3, p4) {
// for each segment we set the color
$.strokeStyle = `hsl(0deg, ${(p1.color+p2.color+p3.color+p4.color) / 4}%, 50%)`;
// begin a new sub-path
$.beginPath();
$.moveTo(p1.x, p1.y);
$.lineTo(p2.x, p2.y);
$.moveTo(p1.x, p1.y);
$.lineTo(p4.x, p4.y);
if (p1.ind_x == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p4.x, p4.y);
}
if (p1.ind_y == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p2.x, p2.y);
}
// and stroke it
$.stroke();
}
//call functions to run
function calls() {
$.fillStyle = "hsla(0, 0%, 7%, 1)";
$.fillRect(0, 0, _x, _y);
mv_part();
draw();
frm++;
}
var c = document.getElementById('canv');
var $ = c.getContext('2d');
$.fillStyle = "hsla(0, 0%, 7%, 1)";
$.fillRect(0, 0, _x, _y);
function resize() {
if (c.width < window.innerWidth) {
c.width = window.innerWidth;
}
if (c.height < window.innerHeight) {
c.height = window.innerHeight;
}
}
requestAnimationFrame(go);
document.addEventListener('click', MSMV, false);
document.addEventListener('click', MSDN, false);
function MSDN(e) {
msdn = true;
window.setTimeout(function() {
msdn = false;
}, 100);
}
function MSUP(e) {
msdn = false;
}
function MSMV(e) {
var rect = e.target.getBoundingClientRect();
msX = e.clientX - rect.left;
msY = e.clientY - rect.top;
}
window.onload = function() {
run();
function run() {
requestAnimationFrame(calls);
requestAnimationFrame(run);
}
resize();
};
onresize = resize;
body {
width: 100%;
overflow: hidden;
cursor:move;
}
<canvas id="canv" width="150" height="150"></canvas>
Now, a more performant way, but less good looking, would be to draw a radial-gradient where the click happened, with a bit of compositing, you'd be able to get something cheap that might work, but it would be quite a lot of coding actually to get multiple such gradients at the same time...
That works for me:
//draw each line in array
function draw_each(p1, p2, p3, p4) {
$.moveTo(p1.x, p1.y);
$.lineTo(p2.x, p2.y);
$.moveTo(p1.x, p1.y);
$.lineTo(p4.x, p4.y);
if (p1.ind_x == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p4.x, p4.y);
}
if (p1.ind_y == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p2.x, p2.y);
}
$.strokeStyle = '#458920';
}

how to use html canvas in between 2 <div>s

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/

How to draw an infinite Hexagon Spiral

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>

Categories

Resources