I have an Angular 7 app with a home page containing a large coloured block (enough to fill the page) at the top with a header and some images. I want to put some lava effect animations into the background similar to this
code in case link is removed:
HTML:
<canvas id="lamp-anim" class="lamp-anim" width="1034" height="613"></canvas>
CSS:
body {
background: #f857a6; /* fallback for old browsers */
background: -webkit-linear-gradient(to top, #ff5858, #f857a6); /* Chrome
10-25, Safari 5.1-6 */
background: linear-gradient(to top, #ff5858, #f857a6); /* W3C, IE 10+/
Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
JS:
window.lavaAnimation = function() {
"use strict";
var t, i = {
screen: {
elem: null,
callback: null,
ctx: null,
width: 0,
height: 0,
left: 0,
top: 0,
init: function(t, i, s) {
return this.elem = document.getElementById(t), this.callback = i || null, "CANVAS" == this.elem.tagName && (this.ctx = this.elem.getContext("2d")), window.addEventListener("resize", function() {
this.resize()
}.bind(this), !1), this.elem.onselectstart = function() {
return !1
}, this.elem.ondrag = function() {
return !1
}, s && this.resize(), this
},
resize: function() {
var t = this.elem;
for (this.width = t.offsetWidth, this.height = t.offsetHeight, this.left = 0, this.top = 0; null != t; t = t.offsetParent) this.left += t.offsetLeft, this.top += t.offsetTop;
this.ctx && (this.elem.width = this.width, this.elem.height = this.height), this.callback && this.callback()
}
}
},
s = function(t, i) {
this.x = t, this.y = i, this.magnitude = t * t + i * i, this.computed = 0, this.force = 0
};
s.prototype.add = function(t) {
return new s(this.x + t.x, this.y + t.y)
};
var h = function(t) {
var i = .1,
h = 1.5;
this.vel = new s((Math.random() > .5 ? 1 : -1) * (.2 + .25 * Math.random()), (Math.random() > .5 ? 1 : -1) * (.2 + Math.random())), this.pos = new s(.2 * t.width + Math.random() * t.width * .6, .2 * t.height + Math.random() * t.height * .6), this.size = t.wh / 15 + (Math.random() * (h - i) + i) * (t.wh / 15), this.width = t.width, this.height = t.height
};
h.prototype.move = function() {
this.pos.x >= this.width - this.size ? (this.vel.x > 0 && (this.vel.x = -this.vel.x), this.pos.x = this.width - this.size) : this.pos.x <= this.size && (this.vel.x < 0 && (this.vel.x = -this.vel.x), this.pos.x = this.size), this.pos.y >= this.height - this.size ? (this.vel.y > 0 && (this.vel.y = -this.vel.y), this.pos.y = this.height - this.size) : this.pos.y <= this.size && (this.vel.y < 0 && (this.vel.y = -this.vel.y), this.pos.y = this.size), this.pos = this.pos.add(this.vel)
};
var e = function(t, i, e, n, a) {
this.step = 5, this.width = t, this.height = i, this.wh = Math.min(t, i), this.sx = Math.floor(this.width / this.step), this.sy = Math.floor(this.height / this.step), this.paint = !1, this.metaFill = r(t, i, t, n, a), this.plx = [0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0], this.ply = [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1], this.mscases = [0, 3, 0, 3, 1, 3, 0, 3, 2, 2, 0, 2, 1, 1, 0], this.ix = [1, 0, -1, 0, 0, 1, 0, -1, -1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1], this.grid = [], this.balls = [], this.iter = 0, this.sign = 1;
for (var o = 0; o < (this.sx + 2) * (this.sy + 2); o++) this.grid[o] = new s(o % (this.sx + 2) * this.step, Math.floor(o / (this.sx + 2)) * this.step);
for (var l = 0; e > l; l++) this.balls[l] = new h(this)
};
e.prototype.computeForce = function(t, i, s) {
var h, e = s || t + i * (this.sx + 2);
if (0 === t || 0 === i || t === this.sx || i === this.sy) h = .6 * this.sign;
else {
h = 0;
for (var r, n = this.grid[e], a = 0; r = this.balls[a++];) h += r.size * r.size / (-2 * n.x * r.pos.x - 2 * n.y * r.pos.y + r.pos.magnitude + n.magnitude);
h *= this.sign
}
return this.grid[e].force = h, h
}, e.prototype.marchingSquares = function(t) {
var i = t[0],
s = t[1],
h = t[2],
e = i + s * (this.sx + 2);
if (this.grid[e].computed === this.iter) return !1;
for (var r, n = 0, a = 0; 4 > a; a++) {
var l = i + this.ix[a + 12] + (s + this.ix[a + 16]) * (this.sx + 2),
d = this.grid[l].force;
(d > 0 && this.sign < 0 || 0 > d && this.sign > 0 || !d) && (d = this.computeForce(i + this.ix[a + 12], s + this.ix[a + 16], l)), Math.abs(d) > 1 && (n += Math.pow(2, a))
}
if (15 === n) return [i, s - 1, !1];
5 === n ? r = 2 === h ? 3 : 1 : 10 === n ? r = 3 === h ? 0 : 2 : (r = this.mscases[n], this.grid[e].computed = this.iter);
var p = this.step / (Math.abs(Math.abs(this.grid[i + this.plx[4 * r + 2] + (s + this.ply[4 * r + 2]) * (this.sx + 2)].force) - 1) / Math.abs(Math.abs(this.grid[i + this.plx[4 * r + 3] + (s + this.ply[4 * r + 3]) * (this.sx + 2)].force) - 1) + 1);
return o.lineTo(this.grid[i + this.plx[4 * r] + (s + this.ply[4 * r]) * (this.sx + 2)].x + this.ix[r] * p, this.grid[i + this.plx[4 * r + 1] + (s + this.ply[4 * r + 1]) * (this.sx + 2)].y + this.ix[r + 4] * p), this.paint = !0, [i + this.ix[r + 4], s + this.ix[r + 8], r]
}, e.prototype.renderMetaballs = function() {
for (var t, i = 0; t = this.balls[i++];) t.move();
for (this.iter++, this.sign = -this.sign, this.paint = !1, o.fillStyle = this.metaFill, o.beginPath(), i = 0; t = this.balls[i++];) {
var s = [Math.round(t.pos.x / this.step), Math.round(t.pos.y / this.step), !1];
do s = this.marchingSquares(s); while (s);
this.paint && (o.fill(), o.closePath(), o.beginPath(), this.paint = !1)
}
};
var r = function(t, i, s, h, e) {
var r = o.createRadialGradient(t / 1, i / 1, 0, t / 1, i / 1, s);
return r.addColorStop(0, h), r.addColorStop(1, e), r
};
if (document.getElementById("lamp-anim")) {
var n = function() {
requestAnimationFrame(n), o.clearRect(0, 0, a.width, a.height), t.renderMetaballs()
},
a = i.screen.init("lamp-anim", null, !0),
o = a.ctx;
a.resize(), t = new e(a.width, a.height, 6, "#3494E6", "#EC6EAD")
}
return {
run: n
}
}();
if (document.getElementById('lamp-anim')) {
lavaAnimation.run();
}
setTimeout(function() {
$('.js-works-d-list').addClass('is-loaded');
}, 150);
Is it possible to convert/do this in angular animations? Are they flexible enough to do this sort of (what id call advanced) animation?
I think the question of 'can I convert this to Angular' is a bit off because Angular runs on Typescript, which is a language built from javascript. So, yes you can do all this in Angular or rather using Typescript within an Angular app.
We're always here to help once you get some code written in an Angular app! But in general, we are here to help you were you get stuck in code and help you solve the problem. It's a bit more challenging to say 'yes it will work' without seeing how you implement it in your project and can't really guide or help you until we see how your angular components are written.
Short answer: Yeah, I think it can work. But it also depends how you implement this code into your Angular app.
Related
I have a grid. Which should be in 3d space. The grid must be dimensionless. That is, when the camera is rotated 90 degrees in x, the edge of the grid should not be visible. To solve the problem, before multiplying by the look at and perspective matrix, I increase it in scale, that is:
scale grid
matrix representation:
typedef std::array<std::array<double, 4>, 4> M4x4;
I implemented the code for working with matrices:
M4x4 Matrix::multiply(const M4x4 &a, const M4x4 &b)
{
M4x4 m;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
m[i][j] = a[i][0] * b[0][j] +
a[i][1] * b[1][j] +
a[i][2] * b[2][j] +
a[i][3] * b[3][j];
}
}
return m;
}
M4x4 Matrix::getTranslation(double dx, double dy, double dz)
{
return {
std::array<double, 4>{1, 0, 0, dx},
std::array<double, 4>{0, 1, 0, dy},
std::array<double, 4>{0, 0, 1, dz},
std::array<double, 4>{0, 0, 0, 1}
};
}
M4x4 Matrix::getLookAt(const Vector &eye, const Vector &target, const Vector &up)
{
Vector vz = Vector::substruct(eye, target).normalize();
Vector vx = Vector::crossProduct(up, vz).normalize();
Vector vy = Vector::crossProduct(vz, vx).normalize();
CoorVector eyeCoor = eye.getCoorVector();
CoorVector vzCoor = vz.getCoorVector();
CoorVector vxCoor = vx.getCoorVector();
CoorVector vyCoor = vy.getCoorVector();
M4x4 m = {
std::array<double, 4>{vxCoor.x, vxCoor.y, vxCoor.z, 0},
std::array<double, 4>{vyCoor.x, vyCoor.y, vyCoor.z, 0},
std::array<double, 4>{vzCoor.x, vzCoor.y, vzCoor.z, 0},
std::array<double, 4>{0, 0, 0, 1}
};
return Matrix::multiply(
Matrix::getTranslation(-eyeCoor.x, -eyeCoor.y, -eyeCoor.z),
m
);
}
M4x4 Matrix::getGeneralScale(double val)
{
return {
std::array<double, 4>{1, 0, 0, 0},
std::array<double, 4>{0, 1, 0, 0},
std::array<double, 4>{0, 0, 1, 0},
std::array<double, 4>{0, 0, 0, 1 / val}
};
}
M4x4 Matrix::getScale(double sx, double sy, double sz)
{
return {
std::array<double, 4>{sx, 0, 0, 0},
std::array<double, 4>{0, sy, 0, 0},
std::array<double, 4>{0, 0, sz, 0},
std::array<double, 4>{0, 0, 0, 1}
};
}
M4x4 Matrix::getRotationZ(double angle)
{
return {
std::array<double, 4>{std::cos(angle), -std::sin(angle), 0, 0},
std::array<double, 4>{std::sin(angle), std::cos(angle), 0, 0},
std::array<double, 4>{0, 0, 1, 0},
std::array<double, 4>{0, 0, 0, 1}
};
}
Vector Matrix::multiplyVector(const M4x4 &m, const Vector &v)
{
CoorVector coorV = v.getCoorVector();
return Vector({
m[0][0] * coorV.x + m[0][1] * coorV.y + m[0][2] * coorV.z + m[0][3] * coorV.w,
m[1][0] * coorV.x + m[1][1] * coorV.y + m[1][2] * coorV.z + m[1][3] * coorV.w,
m[2][0] * coorV.x + m[2][1] * coorV.y + m[2][2] * coorV.z + m[2][3] * coorV.w,
m[3][0] * coorV.x + m[3][1] * coorV.y + m[3][2] * coorV.z + m[3][3] * coorV.w
});
}
M4x4 Matrix::getPerspectiveProjection(double fovy, double aspect, double n, double f)
{
const double PI = 3.141592653589793238463;
const double radians = PI / 180 * fovy;
const double sx = (1 / std::tan(radians / 2)) / aspect;
const double sy = (1 / std::tan(radians / 2));
const double sz = (f + n) / (f - n);
const double dz = (-2 * f * n) / (f - n);
return {
std::array<double, 4>{sx, 0, 0, 0},
std::array<double, 4>{0, sy, 0, 0},
std::array<double, 4>{0, 0, sz, dz},
std::array<double, 4>{0, 0, -1, 0}
};
}
M4x4 Matrix::getFrustum(double left, double right, double bottom, double top, double near, double far)
{
return {
std::array<double, 4>{2 * near / (right - left), 0, (right + left) / (right - left), 0},
std::array<double, 4>{0, 2 * near / (top - bottom), (top + bottom) / (top - bottom), 0},
std::array<double, 4>{0, 0, -(far + near) / (far - near), -2*far*near/(far- near)},
std::array<double, 4>{0, 0, -1, 0}
};
}
vector representation:
struct CoorVector {
double x = 0;
double y = 0;
double z = 0;
double w = 1;
};
Working with vectors:
Vector Vector::substruct(const Vector &v1, const Vector &v2)
{
CoorVector coorV1 = v1.getCoorVector();
CoorVector coorV2 = v2.getCoorVector();
return Vector({coorV1.x - coorV2.x, coorV1.y - coorV2.y, coorV1.z - coorV2.z, 1});
}
Vector Vector::crossProduct(const Vector &v1, const Vector &v2)
{
CoorVector coorV1 = v1.getCoorVector();
CoorVector coorV2 = v2.getCoorVector();
return Vector({coorV1.y * coorV2.z - coorV1.z * coorV2.y,
coorV1.z * coorV2.x - coorV1.x * coorV2.z,
coorV1.x * coorV2.y - coorV1.y * coorV2.x});
}
double Vector::getLength()
{
return std::sqrt(
coorV.x * coorV.x + coorV.y * coorV.y + coorV.z * coorV.z
);
}
Vector Vector::normalize()
{
const double length = getLength();
coorV.x /= length;
coorV.y /= length;
coorV.z /= length;
return *this;
}
CoorVector Vector::getCoorVector() const
{
return coorV;
}
And I use matrices to multiply the vertices when drawing:
void Grid::customMatrixFrustum(QPainter &p)
{
M4x4 m = Matrix::getRotationZ(degreesToRadians(gridData.rotationZ));
//does not work with getPerspectiveProjection function
double scale = gridData.scale;
m = Matrix::multiply(
Matrix::getScale(scale, scale, scale),
m);
//or
// m = Matrix::multiply(
// Matrix::getGeneralScale(scale),
// m);
m = Matrix::multiply(
Matrix::getTranslation(0, 0, gridData.height),
m);
m = Matrix::multiply(
Matrix::getLookAt(
Vector({0, 0, 0, 1}), // where is the observer
Vector({-degreesToRadians(gridData.x), degreesToRadians(gridData.y), -1, 1}), // where to look
Vector({0, 1, 0, 1})
),
m);
m = Matrix::multiply(
Matrix::getPerspectiveProjection(
90, 1, -1, -1000),
m);
m = Matrix::multiply(
Matrix::getFrustum(-1, 1, -1, 1, -1, -1000),
m
);
//doesn't work as it should. the camera's view goes beyond the grid.
// m = Matrix::multiply(
// Matrix::getScale(scale, scale, scale),
// m);
QList<Vector> sceneVertices;
for (int i = 0; i < vertices.size(); i++) {
Vector vertex = Matrix::multiplyVector(
m,
vertices[i]);
CoorVector coorVertex = vertex.getCoorVector();
coorVertex.x = coorVertex.x / coorVertex.w * (this->width() / 2);
coorVertex.y = coorVertex.y / coorVertex.w * (this->width() / 2);
sceneVertices.append(Vector({coorVertex}));
}
for (int i = 0, l = edges.size(); i < l; i++) {
std::array<int, 2> e = edges[i];
CoorVector coorP1 = sceneVertices[e[0]].getCoorVector();
CoorVector coorP2 = sceneVertices[e[1]].getCoorVector();
if (e[0] == 2 && e[1] == 3) {
qDebug() << "points:" << e[0] << e[1]
<< coorP1.x << coorP1.y << coorP1.w
<< coorP2.x << coorP2.y << coorP2.w;
qDebug() << "lines:" << e[0] << e[1]
<< QPoint(coorP1.x + this->width() / 2, -(coorP1.y - this->height() / 2)) << coorP1.w
<< QPoint(coorP2.x + this->width() / 2, -(coorP2.y - this->height() / 2)) << coorP2.w;
}
p.drawLine(coorP1.x + this->width() / 2, coorP1.y + (this->height() / 2),
coorP2.x + this->width() / 2, coorP2.y + (this->height() / 2));
}
}
I create mesh vertices using the following algorithm:
void Grid::init()
{
//create vertices - points
countHorizontal = 100;//number of cells horizontally (counting from top to bottom)
countVertiacal = 100;//number of cells vertically (counting from left to right)
const int pointHorizontal = countHorizontal * 2 + 2;
const int pointVertiacal = countVertiacal * 2 - 2;
const int pointCount = pointHorizontal + pointVertiacal;
const double intervalHorizontal = 2 / (double)countHorizontal;
const double intervalVertical = 2 / (double)countVertiacal;
double currentIntHor = 1;// Descent from top to bottom
double currentIntVer = -1 + intervalVertical;//descent from left to right
for (int i = 0; i < pointCount; ++i) {
if (i < pointHorizontal) {
if (i % 2) {
vertices.append(Vector({1, currentIntHor, 0, 1}));//performed second
currentIntHor -= intervalHorizontal;
} else {
vertices.append(Vector({-1, currentIntHor, 0, 1}));//runs first
}
} else {
if (i % 2) {
vertices.append(Vector({currentIntVer, -1, 0, 1}));//performed second
currentIntVer += intervalVertical;
} else {
vertices.append(Vector({currentIntVer, 1, 0, 1})); //runs first
}
}
}
//Create pairs of vertex indices for lines
const int vertical = countHorizontal + 1;
const int horizontal = countVertiacal + 1;
for (int i = 0; i < vertical; ++i) {
edges.append({i*2, i*2 + 1});
}
const int lastHorIndex = edges.size() - 1;
std::array<int, 2> lastHorVal = edges[lastHorIndex];
for (int i = 0; i < horizontal; ++i){
if (i == 0) {//first line
edges.append({0, lastHorVal[0]});
} else if (i == horizontal - 1) {//last line
edges.append({1, lastHorVal[1]});
} else {
const int magic = 2 * i;
edges.append({lastHorVal[1] + magic - 1, lastHorVal[1] + magic});
}
}
}
In the figure it looks like this:
create grid
With a scale of 3 and a camera angle of 90 degrees in x, the grid looks like this:
norm grid
When you increase the camera rotation in x by 1 degree, the grid already breaks:
broken grid
And if you increase the scale of the grid, then the angle of rotation of the camera when drawing the grid becomes smaller. I assume that the problem is with the w factor, since it does not change for the better when the grid is scaled up. And I don't know how to change it so that the grid after scaling and perspective is drawn normally.
I posted the problem code on github:
grid project
P.S. I tried QMatrix4x4 but when passing the values from the example, perspective didn't work. If someone solves my problem through QMatrix4x4 I will be glad.
I am making a mechanical system animation on OpenGL and having a little trouble calculating the rotations angle of the connecting rods based on a known rotation angle A and the position of the point D.
I need to calculate the angle CDE and CBG as well as the position of point E based on angle A and the position of D. But my high school is failing me right now. I have tried several ways but they all lead to nothing.
The length of segment DA is also known.
Do you have any ideas on how to do that? What should I do?
I have had to make a few assumptions, and while I was finding a solution I forgot to check the labels so below is an image to clarify point and line name, including in red the geometry used to solve.
Assumptions.
Points A and B are fixed.
Lines BC, FC, and DC are all the same length L
Point D is constrained to the line EG
Angle not labeled is the angle you refer to in the question.
The point F is on the circle centered at A. I forgot to label the radius and angle.
Point A is at the origin {x: 0, y: 0}
I also assume that you know the basics of vector math and that the problem is not finding the angle between lines or vectors, but rather solving to find the points C and D that is giving you troubles (hope so as this is going to be a long answer for me).
Solving
Depending on the value of L and the position of the constraining line EG there may not be a solution for all positions of F. The method below will result in either some values being NaN or the position of D will be incorrect.
Find C
Easy start. As A is at the origin then F is at F.x = cos(angle) * radius, F.y = sin(angle) * radius
Now find the mid m point on the line FB and the length of the line Bm as b
This forms the right triangle mBC and we know the length of BC === L and just calculated length of line Bm === b thus the length of the line mC is (L * L - b * b) ** 0.5
Create a unit vector (normalized) from F to B, rotate it clockwise 90 deg and scale it by the calculated length of mC. Add that vector to the point m and you have C
// vector
nx = B.x - F.x;
ny = B.y - F.y;
// Normalize, scale, rotate and add to m to get C. shorthand
// mC len of line mC
s = mC / (nx * nx + ny * ny) ** 0.5;
C.x = m.x - ny * s;
C.y = m.y + nx * s;
// OR in steps
// normalize
len = (nx * nx + ny * ny) ** 0.5;
nx /= len;
ny /= len;
// scale to length of mC
nx *= mC;
ny *= mC;
// rotated 90CW and add to m to get C
C.x = m.x - ny;
C.y = m.y + nx;
Find D
Now that we have the point C we know that the point D is on the constraining line EG. Thus we know that the point D is at the point where a circle at C or radius L intercepts the line EG
However there are two solutions to the intercept of circle and line, the point B is at one of these points if B is on the line EG. If B is not on the line EG then you will have to pick which of the two solutions you want. Likely the point D is the furthest from B
There are several methods to find the intercepts of a line and a circle. The following is a little more complex but will help when picking which point to use
// line EG as vec
vxA = G.x - E.x;
vyA = G.y - E.y;
// square of length line EG
lenA = vxA * vxA + vyA * vyA;
// vector from E to C
vxB = C.x - E.x;
vyB = C.y - E.y;
// square of length line EC
lenB = vxB * vxB + vyB * vyB;
// dot product A.B * - 2
b = -2 * (vxB * vxA + vyB * vyA);
// Stuff I forget what its called
d = (b * b - 4 * lenA * (lenB - L * L)) ** 0.5; // L is length of CD
// is there a solution if not we are done
if (isNaN(d)) { return }
// there are two solution (even if the same point)
// Solutions as unit distances along line EG
u1 = (b - d) / (2 * lenA);
u2 = (b + d) / (2 * lenA); // this is the one we want
The second unit distance is the one that will fit your layout example. So now we just find the point at u2 on the line EG and we have the final point D
D.x = E.x + u2 * (G.x - E.x);
D.y = E.y + u2 * (G.y - E.y);
The angles
In your question it is a little ambiguous to me which angles you want. So I will just give you a method to find the angle between to lines. Eg CB and CD
Convert both lines to vectors. The cross product of these vectors divided by the square root of the product of the squared lengths gives us the sin of the angle. However we still need the quadrant. We workout which quadrant by checking the sign of the dot product of the two vectors.
Note this method will find the the smallest angle between the two lines and is invariant to the order of the lines
Note the angle is in radians
// vector CB
xA = B.x - C.x;
yA = B.y - C.y;
// vector CD
xB = D.x - C.x;
yB = D.y - C.y;
// square root of the product of the squared lengths
l = ((xa * xa + ya * ya) * (xb * xb + yb * yb)) ** 0.5;
// if this is 0 then angle between lines is 0
if (l === 0) { return 0 } // return angle
angle = Math.asin((xa * yb - ya * xb) / l); // get angle quadrant undefined
// if dot of the vectors is < 0 then angle is in quadrants 2 or 3. get angle and return
if (xa * xb + ya * yb < 0) {
return (angle< 0 ? -Math.PI: Math.PI) - angle;
}
// else the angle is in quads 1 or 4 so just return the angle
return angle;
DONE
To make sure it all worked I have created an interactive diagram. The code of interest is at the top. Variables names are as in my diagram at top of answer. Most of the code is just cut and paste vector libs and UI stuff unrelated to the answer.
To use
The diagram will scale to fit the page so click full page if needed.
Use the mouse to drag points with white circles around. For example to rotate F around A click and drag it.
The white line segment El sets the length of the lines CF, CB, CD. The radius of circle at A is set by moving the white circle point to the right of it.
Move mouse out of form to animate.
Mouse only interface.
Overkill but its done.
setTimeout(() => {
// points and lines as in diagram of answer
const A = new Vec2(-100,100);
const B = new Vec2(-240, - 100);
const C = new Vec2();
const D = new Vec2();
const E = new Vec2(-300, -100);
const F = new Vec2();
const G = new Vec2(200, -100);
const AF = new Line2(A, F), FA = new Line2(F, A);
const BC = new Line2(B, C), CB = new Line2(C, B);
const CD = new Line2(C, D), DC = new Line2(D, C);
const EG = new Line2(E, G), GE = new Line2(G, E);
const FB = new Line2(F, B), BF = new Line2(B, F);
const FC = new Line2(F, C), CF = new Line2(C, F);
// Math to find points C and D
function findCandD() {
F.initPolar(angle, radius).add(A) // Get position of F
FB.unitDistOn(0.5, m); // Find point midway between F, B, store as m
// Using right triangle m, B, C the hypot BC length is L
var c = (FB.length * 0.5) ** 2; // Half the length of FB squared
const clLen = (L * L - c) ** 0.5 // Length of line mC
FB.asVec(v1).rotate90CW().length = clLen; // Create vector v1 at 90 from FB and length clLen
C.init(m).add(v1); // Add v1 to m to get point C
const I = EG.unitInterceptsCircle(C, L, cI); // Point D is L dist from
if (EG.unitInterceptsCircle(C, L, cI)) { // Point D is L dist from C. thus us the intercept of corcle radius L and constraining line EG
EG.unitDistanceOn(cI.y, D) // Use second intercept as first could be at point B
} else { C.x = NaN } // C is too far from constraining line EG for a solution
// At this point, the line CD may be the wrong length. Check the length CD is correct
blk = Math.isSmall(CD.length - L) ? black : red; // Mark all in red if no solution
}
// Here on down UI, and all the support code
requestAnimationFrame(update);
const refRes = 512;
var scale = 1;
const mousePos = new Vec2();
var w = 0, h = 0, cw = 0, ch = 0;
var frame = 0;
const m = new Vec2(); // holds mid point on line BF
const m1 = new Vec2();
const v1 = new Vec2(); // temp vector
const v2 = new Vec2(); // temp vector
const cI = new Vec2(); // circle intercepts
var radius = 100;
var L = 200
var angle = 1;
const aa = new Vec2(A.x + radius, A.y);
const al = new Vec2(E.x + L, E.y);
const rad = new Line2(A, aa);
const cl = new Line2(m, C)
const armLen = new Line2(E, al);
var blk = "#000"
const wht = "#FFF"
const red = "#F00"
const black = "#000"
const ui = Vecs2([A, B, aa, E, G, al, F])
function update(timer){
frame ++;
ctx.setTransform(1,0,0,1,0,0); // reset transform
if (w !== innerWidth || h !== innerHeight){
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
scale = Math.min(w / refRes, h / refRes);
} else {
ctx.clearRect(0, 0, w, h);
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
mousePos.init(mouse);
mousePos.x = (mousePos.x - canvas.width / 2) / scale;
mousePos.y = (mousePos.y -canvas.height / 2) / scale;
mousePos.button = mouse.button;
ctx.font = "24px Arial black"
ctx.textAlign = "center";
ctx.setTransform(scale,0,0,scale,canvas.width / 2, canvas.height / 2);
const nearest = ui.dragable(mousePos, 20);
if (nearest === A) {
aa.y = A.y
aa.x = A.x + radius;
} else if(nearest === F){
angle = A.directionTo(F);
} else if(nearest === aa){
aa.y = A.y
radius = rad.length;
} else if (nearest === E) {
EG.distanceAlong(L, al)
} else if (nearest === G || nearest === al) {
EG.nearestOnLine(al, al)
L = armLen.length;
}
if (nearest) {
canvas.style.cursor = ui.dragging ? "none" : "move";
nearest.draw(ctx, "#F00", 2, 4);
if (nearest.isLine2) {
nearest.nearestOnLine(mousePos, onLine).draw(ctx, "#FFF", 2, 2)
}
} else {
canvas.style.cursor = "default";
}
angle += SPEED;
findCandD();
ui.mark(ctx, wht, 1, 4);
ui.mark(ctx, wht, 1, 14);
armLen.draw(ctx,wht,2)
EG.draw(ctx, wht, 1)
ctx.fillStyle = wht;
ctx.fillText("E", E.x, E.y - 16)
ctx.fillText("G", G.x, G.y - 16)
ctx.fillText("l", armLen.p2.x, armLen.p2.y - 16)
FC.draw(ctx, blk, 4)
BC.draw(ctx, blk, 4)
CD.draw(ctx, blk, 4)
A.draw(ctx, blk, 2, radius);
C.draw(ctx, blk, 4, 4)
F.draw(ctx, blk, 4, 4)
B.draw(ctx, blk, 4, 4);
D.draw(ctx, blk, 4, 4)
ctx.fillStyle = blk;
ctx.fillText("B", B.x, B.y - 16)
ctx.fillText("A", A.x, A.y - 16)
ctx.fillText("F", F.x, F.y + 26)
ctx.fillText("D", D.x, D.y - 16)
ctx.fillText("C", C.x, C.y - 16)
ctx.font = "16px Arial";
drawAngle(C, CD, CB, 40, B.add(Vec2.Vec(60, -50), Vec2.Vec()), ctx, blk, 2);
drawAngle(C, CF, CB, 50, A.add(Vec2.Vec(-160, 0), Vec2.Vec()), ctx, blk, 2);
drawAngle(C, CD, CF, 60, A.add(Vec2.Vec(300, 20), Vec2.Vec()), ctx, blk, 2);
blk = Math.isSmall(CD.length - L) ? black : red;
requestAnimationFrame(update);
}
}, 0);
const ctx = canvas.getContext("2d");
const mouse = {x: 0, y: 0, ox: 0, oy: 0, button: false, callback: undefined}
function mouseEvents(e) {
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
var SPEED = 0.05;
canvas.addEventListener("mouseover",() => SPEED = 0);
canvas.addEventListener("mouseout",() => SPEED = 0.05);
Math.EPSILON = 1e-6;
Math.isSmall = val => Math.abs(val) < Math.EPSILON;
Math.isUnit = u => !(u < 0 || u > 1);
Math.uClamp = u => u <= 0 ? 0 : u >= 1 ? 1 : u; // almost 2* faster than Math.min, Math.Max method
Math.TAU = Math.PI * 2;
Math.rand = (m, M) => Math.random() * (M - m) + m;
Math.randI = (m, M) => Math.random() * (M - m) + m | 0;
Math.rad2Deg = r => r * 180 / Math.PI;
Math.symbols = {};
Math.symbols.degrees = "°";
/* export {Vec2, Line2} */ // this should be a module
var temp;
function Vec2(x = 0, y = (temp = x, x === 0 ? (x = 0 , 0) : (x = x.x, temp.y))) { this.x = x; this.y = y }
Vec2.Vec = (x, y) => ({x, y}); // Vec2 like
Vec2.prototype = {
isVec2: true,
init(x, y = (temp = x, x = x.x, temp.y)) { this.x = x; this.y = y; return this }, // assumes x is a Vec2 if y is undefined
initPolar(dir, length = (temp = dir, dir = dir.x, temp.y)) { this.x = Math.cos(dir) * length; this.y = Math.sin(dir) * length; return this },
toPolar(res = this) {
const dir = this.direction, len = this.length;
res.x = dir;
res.y = length;
return res;
},
zero() { this.x = this.y = 0; return this },
initUnit(dir) { this.x = Math.cos(dir); this.y = Math.sin(dir); return this },
copy() { return new Vec2(this) },
equal(v) { return (this.x - v.x) === 0 && (this.y - v.y) === 0 },
isUnits() { return Math.isUnit(this.x) && Math.isUnit(this.y) },
add(v, res = this) { res.x = this.x + v.x; res.y = this.y + v.y; return res },
addScaled(v, scale, res = this) { res.x = this.x + v.x * scale; res.y = this.y + v.y * scale; return res },
sub(v, res = this) { res.x = this.x - v.x; res.y = this.y - v.y; return res },
scale(val, res = this) { res.x = this.x * val; res.y = this.y * val; return res },
invScale(val, res = this) { res.x = this.x / val; res.y = this.y / val; return res },
dot(v) { return this.x * v.x + this.y * v.y },
uDot(v, div) { return (this.x * v.x + this.y * v.y) / div },
cross(v) { return this.x * v.y - this.y * v.x },
uCross(v, div) { return (this.x * v.y - this.y * v.x) / div },
get direction() { return Math.atan2(this.y, this.x) },
set direction(dir) { this.initPolar(dir, this.length) },
get length() { return this.lengthSqr ** 0.5 },
set length(l) { this.scale(l / this.length) },
get lengthSqr() { return this.x * this.x + this.y * this.y },
set lengthSqr(lSqr) { this.scale(lSqr ** 0.5 / this.length) },
distanceFrom(vec) { return ((this.x - vec.x) ** 2 + (this.y - vec.y) ** 2) ** 0.5 },
distanceSqrFrom(vec) { return ((this.x - vec.x) ** 2 + (this.y - vec.y) ** 2) },
directionTo(vec) { return Math.atan2(vec.y - this.y, vec.x - this.x) },
normalize(res = this) { return this.invScale(this.length, res) },
rotate90CW(res = this) {
const y = this.x;
res.x = -this.y;
res.y = y;
return res;
},
angleTo(vec) {
const xa = this.x, ya = this.y;
const xb = vec.x, yb = vec.y;
const l = ((xa * xa + ya * ya) * (xb * xb + yb * yb)) ** 0.5;
var ang = 0;
if (l !== 0) {
ang = Math.asin((xa * yb - ya * xb) / l);
if (xa * xb + ya * yb < 0) { return (ang < 0 ? -Math.PI: Math.PI) - ang }
}
return ang;
},
drawFrom(v, ctx, col = ctx.strokeStyle, lw = ctx.lineWidth, scale = 1) {
ctx.strokeStyle = col;
ctx.lineWidth = lw;
ctx.beginPath();
ctx.lineTo(v.x, v.y);
ctx.lineTo(v.x + this.x * scale, v.y + this.y * scale);
ctx.stroke();
},
draw(ctx, col = ctx.strokeStyle, lw = ctx.lineWidth, size = 4) {
ctx.strokeStyle = col;
ctx.lineWidth = lw;
ctx.beginPath();
ctx.arc(this.x, this.y, size, 0, Math.TAU);
ctx.stroke();
},
path(ctx, size) {
ctx.moveTo(this.x + size, this.y);
ctx.arc(this.x, this.y, size, 0, Math.TAU);
},
toString(digits = 3) { return "{x: " + this.x.toFixed(digits) + ", y: " + this.y.toFixed(digits) + "}" },
};
function Vecs2(vecsOrLength) {
const vecs2 = Object.assign([], Vecs2.prototype);
if (Array.isArray(vecsOrLength)) { vecs2.push(...vecsOrLength) }
else if (vecsOrLength && vecsOrLength >= 1) {
while (vecsOrLength-- > 0) { vecs2.push(new Vec2()) }
}
return vecs2;
}
Vecs2.prototype = {
isVecs2: true,
nearest(vec, maxDist = Infinity, tolerance = 1) { // max for argument semantic, used as semantic min in function
var found;
for (const v of this) {
const dist = v.distanceFrom(vec);
if (dist < maxDist) {
if (dist <= tolerance) { return v }
maxDist = dist;
found = v;
}
}
return found;
},
copy() {
var idx = 0;
const copy = Vecs2(this.length);
for(const p of this) { copy[idx++].init(p) }
return copy;
},
uniformTransform(rMat, pMat, res = this) {
var idx = 0;
for(const p of this) { p.uniformTransform(rMat, pMat, res[idx++]) }
},
mark(ctx, col = ctx.strokeStyle, lw = ctx.lineWidth, size = 4) {
ctx.strokeStyle = col;
ctx.lineWidth = lw;
ctx.beginPath();
for (const p of this) { p.path(ctx, size) }
ctx.stroke();
},
draw(ctx, close = false, col = ctx.strokeStyle, lw = ctx.lineWidth) {
ctx.strokeStyle = col;
ctx.lineWidth = lw;
ctx.beginPath();
for (const p of this) { ctx.lineTo(p.x, p.y) }
close && ctx.closePath();
ctx.stroke();
},
path(ctx, first = true) {
for (const p of this) {
if (first) {
first = false;
ctx.moveTo(p.x, p.y);
} else { ctx.lineTo(p.x, p.y) }
}
},
dragable(mouse, maxDist = Infinity, tolerance = 1) {
var near;
if (this.length) {
if (!this.dragging) {
if (!this.offset) { this.offset = new Vec2() }
near = this.nearest(this.offset.init(mouse), maxDist, tolerance); // mouse may not be a Vec2
if (near && mouse.button) {
this.dragging = near;
this.offset.init(near).sub(mouse);
}
}
if (this.dragging) {
near = this.dragging;
if (mouse.button) { this.dragging.init(mouse).add(this.offset) }
else { this.dragging = undefined }
}
}
return near;
}
}
function Line2(p1 = new Vec2(), p2 = (temp = p1, p1 = p1.p1 ? p1.p1 : p1, temp.p2 ? temp.p2 : new Vec2())) {
this.p1 = p1;
this.p2 = p2;
}
Line2.prototype = {
isLine2: true,
init(p1, p2 = (temp = p1, p1 = p1.p1, temp.p2)) { this.p1.init(p1); this.p2.init(p2) },
copy() { return new Line2(this) },
asVec(res = new Vec2()) { return this.p2.sub(this.p1, res) },
unitDistOn(u, res = new Vec2()) { return this.p2.sub(this.p1, res).scale(u).add(this.p1) },
unitDistanceOn(u, res = new Vec2()) { return this.p2.sub(this.p1, res).scale(u).add(this.p1) },
distAlong(dist, res = new Vec2()) { return this.p2.sub(this.p1, res).uDot(res, res.length).add(this.p1) },
distanceAlong(dist, res = new Vec2()) { return this.p2.sub(this.p1, res).scale(dist / res.length).add(this.p1) },
get length() { return this.lengthSqr ** 0.5 },
get lengthSqr() { return (this.p1.x - this.p2.x) ** 2 + (this.p1.y - this.p2.y) ** 2 },
get direction() { return this.asVec(wV2).direction },
translate(vec, res = this) {
this.p1.add(vec, res.p1);
this.p2.add(vec, res.p2);
return res;
},
reflect(line, u, res = line) {
this.asVec(wV2).normalize();
line.asVec(wV1);
line.unitDistOn(u, res.p1);
const d = wV1.uDot(wV2, 0.5);
wV3.init(wV2.x * d - wV1.x, wV2.y * d - wV1.y);
res.p1.add(wV3.scale(1 - u), res.p2);
return res;
},
reflectAsUnitVec(line, u, res = new Vec2()) {
this.asVec(res).normalize();
line.asVec(wV1);
return res.scale(wV1.uDot(res, 0.5)).sub(wV1).normalize()
},
angleTo(line) { return this.asVec(wV1).angleTo(line.asVec(wV2)) },
translateNormal(amount, res = this) {
this.asVec(wV1).rot90CW().length = -amount;
this.translate(wV1, res);
return res;
},
distanceNearestVec(vec) { // WARNING!! distanceLineFromVec is (and others are) dependent on vars used in this function
return this.asVec(wV1).uDot(vec.sub(this.p1, wV2), wV1.length);
},
unitNearestVec(vec) { // WARNING!! distanceLineFromVec is (and others are) dependent on vars used in this function
return this.asVec(wV1).uDot(vec.sub(this.p1, wV2), wV1.lengthSqr);
},
nearestOnLine(vec, res = new Vec2()) { return this.p1.addScaled(wV1, this.unitNearestVec(vec), res) },
nearestOnSegment(vec, res = new Vec2()) { return this.p1.addScaled(wV1, Math.uClamp(this.unitNearestVec(vec)), res) },
distanceLineFromVec(vec) { return this.nearestOnLine(vec, wV1).sub(vec).length },
distanceSegmentFromVec(vec) { return this.nearestOnSegment(vec, wV1).sub(vec).length },
unitInterceptsLine(line, res = new Vec2()) { // segments
this.asVec(wV1);
line.asVec(wV2);
const c = wV1.cross(wV2);
if (Math.isSmall(c)) { return }
wV3.init(this.p1).sub(line.p1);
res.init(wV1.uCross(wV3, c), wV2.uCross(wV3, c));
return res;
},
unitInterceptsCircle(point, radius, res = new Vec2()) {
this.asVec(wV1);
var b = -2 * this.p1.sub(point, wV2).dot(wV1);
const c = 2 * wV1.lengthSqr;
const d = (b * b - 2 * c * (wV2.lengthSqr - radius * radius)) ** 0.5
if (isNaN(d)) { return }
return res.init((b - d) / c, (b + d) / c);
},
draw(ctx, col = ctx.strokeStyle, lw = ctx.lineWidth) {
ctx.strokeStyle = col;
ctx.lineWidth = lw;
ctx.beginPath();
ctx.lineTo(this.p1.x, this.p1.y);
ctx.lineTo(this.p2.x, this.p2.y);
ctx.stroke();
},
path(ctx) {
ctx.moveTo(this.p1.x, this.p1.y);
ctx.lineTo(this.p2.x, this.p2.y);
},
toString(digits = 3) { return "{ p1: " + this.p1.toString(digits) + ", p2: " + this.p2.toString(digits) + "}" },
};
const wV1 = new Vec2(), wV2 = new Vec2(), wV3 = new Vec2(); // pre allocated work vectors used by Line2 functions
const wVA1 = new Vec2(), wVA2 = new Vec2(), wVA3 = new Vec2(); // pre allocated work vectors
const wVL1 = new Vec2(), wVL2 = new Vec2(), wVL3 = new Vec2(); // pre allocated work vectors used by Line2Array functions
const wL1 = new Line2(), wL2 = new Line2(), wL3 = new Line2(); // pre allocated work lines
function drawLable(text, from, to, ctx, col = ctx.strokeStyle, lw = ctx.lineWidth) {
ctx.fillStyle = ctx.strokeStyle = col;
ctx.lineWidth = lw;
ctx.beginPath();
ctx.lineTo(from.x, from.y);
ctx.lineTo(to.x, to.y);
ctx.stroke();
const w = ctx.measureText(text).width;
var offset = 8;
if (from.x < to.x) { ctx.fillText(text, to.x + offset + w / 2, to.y) }
else { ctx.fillText(text, to.x - offset - w / 2, to.y) }
}
function drawAngle(pos, lineA, lineB, radius, lablePos, ctx, col = ctx.strokeStyle, lw = ctx.lineWidth) {
ctx.strokeStyle = col;
ctx.lineWidth = lw;
const from = lineA.direction;
const angle = lineA.angleTo(lineB);
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius, from, from + angle, angle < 0);
ctx.stroke();
drawLable(
Math.rad2Deg(angle).toFixed(2) + Math.symbols.degrees,
Vec2.Vec(
pos.x + Math.cos(from + angle / 2) * radius,
pos.y + Math.sin(from + angle / 2) * radius
),
lablePos,
ctx,
col,
lw / 2,
);
}
canvas {
position : absolute; top : 0px; left : 0px;
background: #4D8;
}
<canvas id="canvas"></canvas>
I am using FabricJS version : 3.6.3
I want to make new FabricJS class called : Button
So that I have extend one class called Textbox from fabric js, which will Draw a Rectangle behind Text and it looking like a button.
But Problem is that, I can't set height to that Button because height is not allow in Texbox object.
I want to set Height and Width to Button object. Width is working Properly due to Textbox. it will also warp Text if width keep smaller then text width, and can be editable by double clicking on it. But only problem is that can't set Height to an object
it should be Text vertically center when Height is increase.
In short I want to make this kind of functionality in fabric js using object customization.
Expected Output :
but Actual Output :
Here Is my Code That Create button :
// fabric js custom button class
(function (fabric) {
"use strict";
// var fabric = global.fabric || (global.fabric = {});
fabric.Button = fabric.util.createClass(fabric.Textbox, {
type: "button",
stateProperties: fabric.Object.prototype.stateProperties.concat(
"buttonRx",
"buttonRy",
"buttonFill",
"buttonPadding",
"buttonStrokeColor",
"buttonStrokeWidth"
),
buttonRx: 0,
buttonRy: 0,
buttonFill: "#ffffff00",
buttonPadding: 0,
buttonHeight: 0,
buttonWidth: 0,
textAlign: "center",
buttonStrokeColor: "#000000",
buttonStrokeWidth: 0,
_dimensionAffectingProps: fabric.Text.prototype._dimensionAffectingProps.concat(
"width",
"fontSize"
),
cacheProperties: fabric.Object.prototype.cacheProperties.concat(
"buttonRx",
"buttonRy",
"buttonFill",
"buttonPadding",
"buttonStrokeColor",
"buttonStrokeWidth"
),
initialize: function (text, options) {
this.text = text;
this.callSuper("initialize", text, options);
/* this.on("scaling", function () {
console.log('scaling', this.getScaledHeight());
this.set({
height: this.getScaledHeight(),
scaleY: 1,
});
}); */
this._initRxRy();
},
_initRxRy: function () {
if (this.buttonRx && !this.buttonRy) {
this.buttonRy = this.buttonRx;
} else if (this.buttonRy && !this.buttonRx) {
this.buttonRx = this.buttonRy;
}
},
/* _setCenter(){
}, */
_render: function (ctx) {
// 1x1 case (used in spray brush) optimization was removed because
// with caching and higher zoom level this makes more damage than help
// this.width = this.width * this.scaleX;
// this.height = this.height * this.scaleY;
// (this.scaleX = 1), (this.scaleY = 1);
var rx = this.buttonRx ? Math.min(this.buttonRx, this.width / 2) : 0,
ry = this.buttonRy ? Math.min(this.buttonRy, this.height / 2) : 0,
w = this.width + this.buttonPadding,
h = this.height + this.buttonPadding,
x = -this.width / 2 - this.buttonPadding / 2,
y = -this.height / 2 - this.buttonPadding / 2,
isRounded = rx !== 0 || ry !== 0,
/* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */
k = 1 - 0.5522847498;
ctx.beginPath();
ctx.moveTo(x + rx, y);
ctx.lineTo(x + w - rx, y);
isRounded &&
ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry);
ctx.lineTo(x + w, y + h - ry);
isRounded &&
ctx.bezierCurveTo(
x + w,
y + h - k * ry,
x + w - k * rx,
y + h,
x + w - rx,
y + h
);
ctx.lineTo(x + rx, y + h);
isRounded &&
ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry);
ctx.lineTo(x, y + ry);
isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y);
ctx.closePath();
ctx.save();
if (this.buttonFill) {
ctx.fillStyle = this.buttonFill;
if (this.fillRule === "evenodd") {
ctx.fill("evenodd");
} else {
ctx.fill();
}
}
if (this.buttonStrokeWidth > 0) {
if (this.strokeUniform) {
ctx.scale(1 / this.scaleX, 1 / this.scaleY);
}
if (this.shadow && !this.shadow.affectStroke) {
this._removeShadow(ctx);
}
if (this.buttonStrokeColor) {
ctx.lineWidth = this.buttonStrokeWidth;
ctx.strokeStyle = this.buttonStrokeColor;
ctx.stroke();
} else {
ctx.lineWidth = this.buttonStrokeWidth;
ctx.stroke();
}
}
ctx.restore();
this.clearContextTop();
this._clearCache();
this.height = this.calcTextHeight();
this.saveState({ propertySet: "_dimensionAffectingProps" });
// this._renderPaintInOrder(ctx);
this._setTextStyles(ctx);
this._renderTextLinesBackground(ctx);
this._renderTextDecoration(ctx, "underline");
this._renderText(ctx);
this._renderTextDecoration(ctx, "overline");
this._renderTextDecoration(ctx, "linethrough");
this.initDimensions();
// this.callSuper('render', ctx);
},
toObject: function (propertiesToInclude) {
return this.callSuper(
"toObject",
[
"buttonRx",
"buttonRy",
"buttonFill",
"buttonPadding",
"buttonStrokeColor",
"buttonStrokeWidth",
"objectCaching",
].concat(propertiesToInclude)
);
},
});
fabric.Button.fromObject = function (object, callback) {
return fabric.Object._fromObject("Button", object, callback, "text");
};
})(fabric);
// fabric js class finish here
var canvas = [];
var cotainer = document.getElementById("canvas-container");
for (let i = 0; i < 1; i++) {
var width = 500,
height = 500;
var canvasEl = document.createElement("canvas");
canvasEl.id = "canvas-" + i;
cotainer.append(canvasEl);
var fabCanvas = new fabric.Canvas(canvasEl, {});
fabCanvas.setHeight(height);
fabCanvas.setWidth(width);
canvas.push(fabCanvas);
}
canvas.forEach((c) => {
var button = new fabric.Button("Click Me", {
text: "Click Me",
buttonStrokeColor: "#f00",
buttonStrokeWidth: 2,
width: 110,
fill: "#f00",
fontSize: 50,
width: 400,
buttonFill: "#42A5F5",
buttonRx: 15,
buttonRy: 15,
objectCaching: false,
fontFamily: "verdana",
});
c.add(button);
c.renderAll();
});
canvas{
border: 1px solid black
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.js"></script>
<div id="canvas-container">
</div>
The solution will be to set the scaleX and scaleY of the button text to 1 when you scale the Button Object and also set the font size of the text equal to its scale.
var tbox = new fabric.Button(v.textDisp, {
left: v.posX,
top: v.posY,
boxHeight: v.length // new create
});
fabric.Button = fabric.util.createClass(fabric.Textbox, {
type: "button",
stateProperties: fabric.Object.prototype.stateProperties.concat(
"buttonRx",
"buttonRy",
"buttonFill",
"buttonPadding",
"buttonStrokeColor",
"buttonStrokeWidth",
"boxHeight"
),
buttonRx: 0,
buttonRy: 0,
buttonPadding: 0,
buttonHeight: 0,
buttonWidth: 0,
buttonStrokeColor: "#000000",
buttonStrokeWidth: 0,
_dimensionAffectingProps: fabric.Text.prototype._dimensionAffectingProps.concat(
"width",
"fontSize"
),
cacheProperties: fabric.Object.prototype.cacheProperties.concat(
"buttonRx",
"buttonRy",
"buttonFill",
"buttonPadding",
"buttonStrokeColor",
"buttonStrokeWidth",
"boxHeight"
),
initialize: function (text, options) {
this.text = text;
this.callSuper("initialize", text, options);
/* this.on("scaling", function () {
console.log('scaling', this.getScaledHeight());
this.set({
height: this.getScaledHeight(),
scaleY: 1,
});
}); */
this._initRxRy();
},
_initRxRy: function () {
if (this.buttonRx && !this.buttonRy) {
this.buttonRy = this.buttonRx;
} else if (this.buttonRy && !this.buttonRx) {
this.buttonRx = this.buttonRy;
}
},
/* _setCenter(){
}, */
_render: function (ctx) {
// 1x1 case (used in spray brush) optimization was removed because
// with caching and higher zoom level this makes more damage than help
// this.width = this.width * this.scaleX;
// this.height = this.height * this.scaleY;
// (this.scaleX = 1), (this.scaleY = 1);
var rx = this.buttonRx ? Math.min(this.buttonRx, this.width / 2) : 0,
ry = this.buttonRy ? Math.min(this.buttonRy, this.height / 2) : 0,
w = this.width + this.buttonPadding,
h = this.height + this.buttonPadding,
x = -this.width / 2 - this.buttonPadding / 2,
y = -this.height / 2 - this.buttonPadding / 2,
hh = this.boxHeight * this.scaleY,
isRounded = rx !== 0 || ry !== 0,
k = 1 - 0.5522847498;
ctx.beginPath();
ctx.moveTo(x + rx, y);
ctx.lineTo(x + w - rx, y);
isRounded &&
ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry);
ctx.lineTo(x + w, y + hh - ry);
isRounded &&
ctx.bezierCurveTo(
x + w,
y + hh - k * ry,
x + w - k * rx,
y + hh,
x + w - rx,
y + hh
);
ctx.lineTo(x + rx, y + hh);
isRounded &&
ctx.bezierCurveTo(x + k * rx, y + hh, x, y + hh - k * ry, x, y + hh - ry);
ctx.lineTo(x, y + ry);
isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y);
ctx.closePath();
ctx.save();
if (this.buttonFill) {
ctx.fillStyle = this.buttonFill;
if (this.fillRule === "evenodd") {
ctx.fill("evenodd");
} else {
ctx.fill();
}
}
if (this.buttonStrokeWidth > 0) {
if (this.strokeUniform) {
ctx.scale(1 / this.scaleX, 1 / this.scaleY);
}
if (this.shadow && !this.shadow.affectStroke) {
this._removeShadow(ctx);
}
if (this.buttonStrokeColor) {
ctx.lineWidth = this.buttonStrokeWidth;
ctx.strokeStyle = this.buttonStrokeColor;
ctx.stroke();
} else {
ctx.lineWidth = this.buttonStrokeWidth;
ctx.stroke();
}
}
ctx.restore();
this.clearContextTop();
this._clearCache();
this.height = this.calcTextHeight();
this.saveState({ propertySet: "_dimensionAffectingProps" });
// this._renderPaintInOrder(ctx);
this._setTextStyles(ctx);
this._renderTextLinesBackground(ctx);
this._renderTextDecoration(ctx, "underline");
this._renderText(ctx);
this._renderTextDecoration(ctx, "overline");
this._renderTextDecoration(ctx, "linethrough");
this.initDimensions();
// this.callSuper('render', ctx);
},
toObject: function (propertiesToInclude) {
return this.callSuper(
"toObject",
[
"buttonRx",
"buttonRy",
"buttonFill",
"buttonPadding",
"buttonStrokeColor",
"buttonStrokeWidth",
"objectCaching",
"boxHeight"
].concat(propertiesToInclude)
);
},
});
After adding boxHeight, declare a variable as 'hh' instead of'h' in "_render" When drawing, change to'hh' instead of'h'
Say I have a set of four or more points that are on the perimeter of a rectangle, and that the rectangle is rotated by some unknown amount. I know that at least one point is on each side of the rectangle. One arbitrary side point is designated (0, 0), and the other points are the distance from this starting point. How can I get the non-rotated corner points of this rectangle?
assuming you're not trying to find a unique solution:
rotate your points around 0,0 until the top-most, bottom-most,
left-most, and right-most points are all different points
draw horizontal lines through the top-most and bottom-most, and vertical lines through the left-most and right-most
you're done
var points = [];
var bs = document.body.style;
var ds = document.documentElement.style;
bs.height = bs.width = ds.height = ds.width = "100%";
bs.border = bs.margin = bs.padding = 0;
var c = document.createElement("canvas");
c.style.display = "block";
c.addEventListener("mousedown", addPoint, false);
document.body.appendChild(c);
var ctx = c.getContext("2d");
var interval;
function addPoint(e) {
if (points.length >= 4) points = [];
points.push({
x: e.x - c.offsetLeft,
y: e.y - c.offsetTop
});
while (points.length > 4) points.shift();
redraw();
}
function rotateAround(a, b, r) {
d = {x:a.x - b.x, y:a.y - b.y};
return {
x: b.x + Math.cos(r) * d.x - Math.sin(r) * d.y,
y: b.y + Math.cos(r) * d.y + Math.sin(r) * d.x
}
}
function drawPoint(p) {
ctx.strokeStyle = "rgb(0,0,0)";
ctx.beginPath();
ctx.arc(p.x, p.y, 10, 0, 2 * Math.PI, true);
ctx.closePath();
ctx.stroke();
}
var last_few = [];
function redraw() {
if (interval) clearInterval(interval);
last_few = [];
c.width = window.innerWidth;
c.height = window.innerHeight;
ctx.clearRect(0, 0, c.width, c.height);
ctx.fillStyle = "rgb(200, 200, 200)";
ctx.font = "40px serif";
if (points.length < 4) {
ctx.fillText("click " + (4 - points.length) + " times", 20, 40);
points.forEach(drawPoint);
} else {
var average = {x:0, y:0};
points.forEach(function (p) {
average.x += p.x / 4;
average.y += p.y / 4;
});
var step = 0;
interval = setInterval(function () {
ctx.clearRect(0, 0, c.width, c.height);
ctx.fillText("click anywhere to start over", 20, 40);
last_few.forEach(function(r) {
ctx.strokeStyle = "rgb(200,255,200)";
ctx.save();
ctx.translate(average.x, average.y);
ctx.rotate((step -r.step) * Math.PI / 180);
ctx.strokeRect(r.lm - average.x, r.tm - average.y, (r.rm - r.lm), (r.bm - r.tm));
ctx.restore();
});
var tm = Infinity;
var bm = -Infinity;
var lm = Infinity;
var rm = -Infinity;
points.forEach(function (p) {
p = rotateAround(p, average, step * Math.PI / 180);
drawPoint(p);
tm = Math.min(p.y, tm);
bm = Math.max(p.y, bm);
lm = Math.min(p.x, lm);
rm = Math.max(p.x, rm);
});
if (points.every(function (p) {
p = rotateAround(p, average, step * Math.PI / 180);
return (p.x == lm) || (p.x == rm) || (p.y == tm) || (p.y == bm);
})) {
ctx.strokeStyle = "rgb(0,255,0)";
ctx.strokeRect(lm, tm, (rm - lm), (bm - tm));
last_few.push({tm:tm, bm:bm, lm:lm, rm:rm, step:step});
while(last_few.length > 30) last_few.shift();
} else {
ctx.strokeStyle = "rgb(255,0,0)";
ctx.strokeRect(lm, tm, (rm - lm), (bm - tm));
}
step++;
}, 30);
}
}
window.onresize = redraw;
redraw();
The calculations are done in the following code:
var MIN = -10.0,
MAX = 10.0,
RANGE = MAX - MIN;
getColor(max, min, val) {
var MIN_L = 40,
MAX_L = 100;
var color = new Color();
var h = 0 / 240;
var s = 80 / 240;
var l = (((MAX_L - MIN_L) / (max - min)) * val) / 240;
color.setHSL(h, s, l);
return color;
}
initGraph() {
var x = MIN,
y = MIN,
z = 0.0;
initData() {
var data = [];
for (var i = MIN; i < MAX; i++) {
var row = [];
for (var j = MIN; j < MAX; j++) {
double z = 2*( x * x + y * y);
print('$z');
row.add({
x: x,
y: y,
z: z
});
y++;
}
data.add(row);
x++;
}
return data;
}
var data = initData();
var geometry = new Geometry();
var colors = [];
var RANGE = data.length,
height = data[0].length;
data.forEach((col) {
col.forEach((val) {
geometry.vertices.add(new Vector3(x.toDouble(), y.toDouble(), z.toDouble()));
colors.add(getColor(2.5, 0, z.toDouble()));
});
});
offset(x, y) {
return x * RANGE + y;
}
for (var x = 0; x < RANGE - 1; x++) {
for (var y = 0; y < height - 1; y++) {
Vector3 vec0;
Vector3 vec1;
Vector3 n_vec;
// one of two triangle polygons in one rectangle
vec0 = (geometry.vertices[offset(x, y)] - geometry.vertices[offset(x + 1, y)]);
vec1 = (geometry.vertices[offset(x, y)] - geometry.vertices[offset(x, y + 1)]);
n_vec.crossInto(vec0, vec1).normalize();
geometry.faces.add(new Face3(offset(x, y), offset(x + 1, y), offset(x, y + 1), n_vec, [colors[offset(x, y)], colors[offset(x + 1, y)], colors[offset(x, y + 1)]]));
geometry.faces.add(new Face3(offset(x, y), offset(x, y + 1), offset(x + 1, y), n_vec.negate(), [colors[offset(x, y)], colors[offset(x, y + 1)], colors[offset(x + 1, y)]]));
// the other one
vec0 = (geometry.vertices[offset(x + 1, y)] - geometry.vertices[offset(x + 1, y + 1)]);
vec1 = (geometry.vertices[offset(x, y + 1)] - geometry.vertices[offset(x + 1, y + 1)]);
n_vec.crossInto(vec0, vec1).normalize();
geometry.faces.add(new Face3(offset(x + 1, y), offset(x + 1, y + 1), offset(x, y + 1), n_vec, [colors[offset(x + 1, y)], colors[offset(x + 1, y + 1)], colors[offset(x, y + 1)]]));
geometry.faces.add(new Face3(offset(x + 1, y), offset(x, y + 1), offset(x + 1, y + 1), n_vec.negate(), [colors[offset(x + 1, y)], colors[offset(x, y + 1)], colors[offset(x + 1, y + 1)]]));
}
}
var material = new MeshLambertMaterial(vertexColors: VertexColors);
var mesh = new Mesh(geometry, material);
scene.add(mesh);
}
The error seems to be on the occurrence of this line:
n_vec.crossInto(vec0, vec1).normalize();
What is the null object here and how do I solve this? Could the variable 'z' be causing the issue? It first showed null, and caused a similar error (that '*' cannot be applied) and I declared it as double and that got solved. I also have a suspicion in the below lines:
data.forEach((col) {
col.forEach((val) {
geometry.vertices.add(new Vector3(x.toDouble(), y.toDouble(), z.toDouble()));
n_vec is never initialized with an instance of Vector3. crossInto requires to be called on an instance, Either you create an instance first:
Vector3 n_vec = new Vector3.zero();
...
n_vec.crossInto(vec0, vec1).normalize();
Or you use the cross method, but it creates a new instance of Vector3 (you might want to avoid new instances, than I would move the variables out of the loop):
n_vec = vec0.cross(vec1).normalize();