Currently, I'm attempting to make multiple beziers have equidistant points. I'm currently using cubic interpolation to find the points, but because the way beziers work some areas are more dense than others and proving gross for texture mapping because of the variable distance. Is there a way to find points on a bezier by distance rather than by percentage? Furthermore, is it possible to extend this to multiple connected curves?
This is called "arc length" parameterization. I wrote a paper about this several years ago:
http://www.saccade.com/writing/graphics/RE-PARAM.PDF
The idea is to pre-compute a "parameterization" curve, and evaluate the curve through that.
distance between P_0 and P_3 (in cubic form), yes, but I think you knew that, is straight forward.
Distance on a curve is just arc length:
fig 1 http://www.codecogs.com/eq.latex?%5Cint_%7Bt_0%7D%5E%7Bt_1%7D%20%7B%20|P'(t)|%20dt
where:
fig 2 http://www.codecogs.com/eq.latex?P%27(t)%20=%20[%7Bx%27,y%27,z%27%7D]%20=%20[%7B%5Cfrac%7Bdx(t)%7D%7Bdt%7D,%5Cfrac%7Bdy(t)%7D%7Bdt%7D,%5Cfrac%7Bdz(t)%7D%7Bdt%7D%7D]
(see the rest)
Probably, you'd have t_0 = 0, t_1 = 1.0, and dz(t) = 0 (2d plane).
I know this is an old question but I recently ran into this problem and created a UIBezierPath extention to solve for an X coordinate given a Y coordinate and vise versa. Written in swift.
https://github.com/rkotzy/RKBezierMath
extension UIBezierPath {
func solveBezerAtY(start: CGPoint, point1: CGPoint, point2: CGPoint, end: CGPoint, y: CGFloat) -> [CGPoint] {
// bezier control points
let C0 = start.y - y
let C1 = point1.y - y
let C2 = point2.y - y
let C3 = end.y - y
// The cubic polynomial coefficients such that Bez(t) = A*t^3 + B*t^2 + C*t + D
let A = C3 - 3.0*C2 + 3.0*C1 - C0
let B = 3.0*C2 - 6.0*C1 + 3.0*C0
let C = 3.0*C1 - 3.0*C0
let D = C0
let roots = solveCubic(A, b: B, c: C, d: D)
var result = [CGPoint]()
for root in roots {
if (root >= 0 && root <= 1) {
result.append(bezierOutputAtT(start, point1: point1, point2: point2, end: end, t: root))
}
}
return result
}
func solveBezerAtX(start: CGPoint, point1: CGPoint, point2: CGPoint, end: CGPoint, x: CGFloat) -> [CGPoint] {
// bezier control points
let C0 = start.x - x
let C1 = point1.x - x
let C2 = point2.x - x
let C3 = end.x - x
// The cubic polynomial coefficients such that Bez(t) = A*t^3 + B*t^2 + C*t + D
let A = C3 - 3.0*C2 + 3.0*C1 - C0
let B = 3.0*C2 - 6.0*C1 + 3.0*C0
let C = 3.0*C1 - 3.0*C0
let D = C0
let roots = solveCubic(A, b: B, c: C, d: D)
var result = [CGPoint]()
for root in roots {
if (root >= 0 && root <= 1) {
result.append(bezierOutputAtT(start, point1: point1, point2: point2, end: end, t: root))
}
}
return result
}
func solveCubic(a: CGFloat?, var b: CGFloat, var c: CGFloat, var d: CGFloat) -> [CGFloat] {
if (a == nil) {
return solveQuadratic(b, b: c, c: d)
}
b /= a!
c /= a!
d /= a!
let p = (3 * c - b * b) / 3
let q = (2 * b * b * b - 9 * b * c + 27 * d) / 27
if (p == 0) {
return [pow(-q, 1 / 3)]
} else if (q == 0) {
return [sqrt(-p), -sqrt(-p)]
} else {
let discriminant = pow(q / 2, 2) + pow(p / 3, 3)
if (discriminant == 0) {
return [pow(q / 2, 1 / 3) - b / 3]
} else if (discriminant > 0) {
let x = crt(-(q / 2) + sqrt(discriminant))
let z = crt((q / 2) + sqrt(discriminant))
return [x - z - b / 3]
} else {
let r = sqrt(pow(-(p/3), 3))
let phi = acos(-(q / (2 * sqrt(pow(-(p / 3), 3)))))
let s = 2 * pow(r, 1/3)
return [
s * cos(phi / 3) - b / 3,
s * cos((phi + CGFloat(2) * CGFloat(M_PI)) / 3) - b / 3,
s * cos((phi + CGFloat(4) * CGFloat(M_PI)) / 3) - b / 3
]
}
}
}
func solveQuadratic(a: CGFloat, b: CGFloat, c: CGFloat) -> [CGFloat] {
let discriminant = b * b - 4 * a * c;
if (discriminant < 0) {
return []
} else {
return [
(-b + sqrt(discriminant)) / (2 * a),
(-b - sqrt(discriminant)) / (2 * a)
]
}
}
private func crt(v: CGFloat) -> CGFloat {
if (v<0) {
return -pow(-v, 1/3)
}
return pow(v, 1/3)
}
private func bezierOutputAtT(start: CGPoint, point1: CGPoint, point2: CGPoint, end: CGPoint, t: CGFloat) -> CGPoint {
// bezier control points
let C0 = start
let C1 = point1
let C2 = point2
let C3 = end
// The cubic polynomial coefficients such that Bez(t) = A*t^3 + B*t^2 + C*t + D
let A = CGPointMake(C3.x - 3.0*C2.x + 3.0*C1.x - C0.x, C3.y - 3.0*C2.y + 3.0*C1.y - C0.y)
let B = CGPointMake(3.0*C2.x - 6.0*C1.x + 3.0*C0.x, 3.0*C2.y - 6.0*C1.y + 3.0*C0.y)
let C = CGPointMake(3.0*C1.x - 3.0*C0.x, 3.0*C1.y - 3.0*C0.y)
let D = C0
return CGPointMake(((A.x*t+B.x)*t+C.x)*t+D.x, ((A.y*t+B.y)*t+C.y)*t+D.y)
}
// TODO: - future implementation
private func tangentAngleAtT(start: CGPoint, point1: CGPoint, point2: CGPoint, end: CGPoint, t: CGFloat) -> CGFloat {
// bezier control points
let C0 = start
let C1 = point1
let C2 = point2
let C3 = end
// The cubic polynomial coefficients such that Bez(t) = A*t^3 + B*t^2 + C*t + D
let A = CGPointMake(C3.x - 3.0*C2.x + 3.0*C1.x - C0.x, C3.y - 3.0*C2.y + 3.0*C1.y - C0.y)
let B = CGPointMake(3.0*C2.x - 6.0*C1.x + 3.0*C0.x, 3.0*C2.y - 6.0*C1.y + 3.0*C0.y)
let C = CGPointMake(3.0*C1.x - 3.0*C0.x, 3.0*C1.y - 3.0*C0.y)
return atan2(3.0*A.y*t*t + 2.0*B.y*t + C.y, 3.0*A.x*t*t + 2.0*B.x*t + C.x)
}
}
Related
I am trying to place evenly-spaced markers/dots on a quadratic curve drawn with HTML Canvas API. Found a nice article explaining how the paths are calculated in the first place, at determining coordinates on canvas curve.
There is a formula, at the end, to calculate the angle:
function getQuadraticAngle(t, sx, sy, cp1x, cp1y, ex, ey) {
var dx = 2*(1-t)*(cp1x-sx) + 2*t*(ex-cp1x);
var dy = 2*(1-t)*(cp1y-sy) + 2*t*(ey-cp1y);
return Math.PI / 2 - Math.atan2(dx, dy);
}
The x/y pairs that we pass, are the current position, the control point and the end position of the curve - exactly what is needed to pass to the canvas context, and t is a value from 0 to 1. Unless I somehow misunderstood the referenced article.
I want to do something very similar - place my markers over the distance specified s, rather than use t. This means, unless I am mistaken, that I need to calculate the length of the "curved path" and from there, I could probably use the above formula.
I found a solution for the length in JavaScript at length of quadratic curve. The formula is similar to:
.
And added the below function:
function quadraticBezierLength(x1, y1, x2, y2, x3, y3) {
let a, b, c, d, e, u, a1, e1, c1, d1, u1, v1x, v1y;
v1x = x2 * 2;
v1y = y2 * 2;
d = x1 - v1x + x3;
d1 = y1 - v1y + y3;
e = v1x - 2 * x1;
e1 = v1y - 2 * y1;
c1 = a = 4 * (d * d + d1 * d1);
c1 += b = 4 * (d * e + d1 * e1);
c1 += c = e * e + e1 * e1;
c1 = 2 * Math.sqrt(c1);
a1 = 2 * a * (u = Math.sqrt(a));
u1 = b / u;
a = 4 * c * a - b * b;
c = 2 * Math.sqrt(c);
return (
(a1 * c1 + u * b * (c1 - c) + a * Math.log((2 * u + u1 + c1) / (u1 + c))) /
(4 * a1)
);
}
Now, I am trying to space markers evenly. I thought that making "entropy" smooth - dividing the total length by the step length would result in the n markers, so going using the 1/nth step over t would do the trick. However, this does not work. The correlation between t and distance on the curve is not linear.
How do I solve the equation "backwards" - knowing the control point, the start, and the length of the curved path, calculate the end-point?
Not sure I fully understand what you mean by "space markers evenly" but I do have some code that I did with curves and markers that maybe can help you ...
Run the code below, it should output a canvas like this:
function drawBezierCurve(p0, p1, p2, p3) {
distance = 0
last = null
for (let t = 0; t <= 1; t += 0.0001) {
const x = Math.pow(1 - t, 3) * p0[0] + 3 * Math.pow(1 - t, 2) * t * p1[0] + 3 * (1 - t) * Math.pow(t, 2) * p2[0] + Math.pow(t, 3) * p3[0];
const y = Math.pow(1 - t, 3) * p0[1] + 3 * Math.pow(1 - t, 2) * t * p1[1] + 3 * (1 - t) * Math.pow(t, 2) * p2[1] + Math.pow(t, 3) * p3[1];
ctx.lineTo(x, y);
if (last) {
distance += Math.sqrt((x - last[0]) ** 2 + (y - last[1]) ** 2)
if (distance >= 30) {
ctx.rect(x - 1, y - 1, 2, 2);
distance = 0
}
}
last = [x, y]
}
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
drawBezierCurve([0, 0], [40, 300], [200, -90], [300, 150]);
ctx.stroke();
<canvas id="canvas" width=300 height=150></canvas>
I created the drawBezierCurve function, there I'm using a parametric equation of a bezier curve and then I use lineTo to draw between points, and also we get a distance between points, the points are very close so my thinking is OK to use the Pythagorean theorem to calculate the distance, and the markers are just little rectangles.
I'm building an isometric map tile image in Node and I'm stuck at the second layer rendering, I can't figure out how to adjust items in the y axis
Here's my code so far:
let i = 0;
for (let layer of map) {
const xTiles = layer.length;
const yTiles = layer[0].length;
for (let Xi = xTiles - 1; Xi >= 0; Xi--) {
for (let Yi = 0; Yi < yTiles; Yi++) {
const imgIndex = layer[Xi][Yi];
if (imgIndex == null || imgIndex == -1) {
continue;
}
const tile = tiles[imgIndex];
const offX = (Xi * tileColOffset / 2 + Yi * tileColOffset / 2 + originX) + (i * ((tileColOffset - tile.width) / 2));
const offY = (Yi * tileRowOffset / 2 - Xi * tileRowOffset / 2 + originY) - (i * ((tileRowOffset / 2)));
ctx.drawImage(tile, offX, offY);
}
}
i++;
}
I can center the sprite on the x axis but not on the y one, probably because sprites have different heights. The code above reproduces this
As you can see the taller sprites are quite centered, but the small ones are not. Any suggestion?
Thanks!
Just found out that I have to calculate the difference between the half height of the ground tile and the height of the sprite:
let i = 0;
for (let layer of map) {
const xTiles = layer.length;
const yTiles = layer[0].length;
for (let Xi = xTiles - 1; Xi >= 0; Xi--) {
for (let Yi = 0; Yi < yTiles; Yi++) {
const imgIndex = layer[Xi][Yi];
if (imgIndex == null || imgIndex == -1) {
continue;
}
const tile = tiles[imgIndex];
let offX, offY
if (i === 0) {
offX = (Xi * tileColOffset / 2 + Yi * tileColOffset / 2 + originX);
offY = (Yi * tileRowOffset / 2 - Xi * tileRowOffset / 2 + originY);
} else {
offX = (Xi * tileColOffset / 2 + Yi * tileColOffset / 2 + originX) + (i * ((tileColOffset - tile.width) / 2));
offY = (Yi * tileRowOffset / 2 - Xi * tileRowOffset / 2 + originY) + ((i * (tileRowOffset / 2 - tile.height)));
}
ctx.drawImage(tile, offX, offY);
}
}
i++;
}
Well the question says it all. I am looking for an algorithm to convert HSI (not HSL and not HSV) to RGB, assuming that all H, S, and I are > 0 and < 1.
I was hoping I could produce something like: http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV but for HSI w/o using cosine function. I am not quite sure if it is possible. Is there a way to compute C and m (as referred to in Wikipedia article for HSV and HSL) for HSI?
Thanks.
FYI, I am using this function for RGB to HSI (so I am trying to make the inverse):
public static void toHSI(byte R, byte G, byte B, out double H, out double S, out double I)
{
byte MAX, MIN;
if (R > G) { if (R > B) { MAX = R; MIN = (G < B ? G : B); } else { MAX = B; MIN = (G < R ? G : R); } }
else { if (G > B) { MAX = G; MIN = (R < B ? R : B); } else { MAX = B; MIN = (G < R ? G : R); } }
I = (double)(R + G + B) / 765;
if (I == 0) { H = S = 0; } // achromatic
else
{
double DIF = (double)(MAX - MIN);
S = 1 - (double)MIN / (255 * I);
if (MAX == R) { H = (double)(G - B) / DIF + (G < B ? 6 : 0); }
else if (MAX == G) { H = (double)(B - R) / DIF + 2; }
else { H = (double)(R - G) / DIF + 4; }
H /= 6;
}
}
You could try this I am not sure if it is helpful but you need to treat your values in the functions as floats. Hue is a number between 0-360. Saturation is 0.00 -1.00 and anything in between. Intensity aka Value is also used and is also a value of 0.00 -1.00 and anything in between. The result of the function are three values of an array which can then be used for rgb colorspace of values 0-255.
///I use this
// the function result will be the values of the array rgb[3] and will be the rgb values 0-255
///float H is values 0-360 because there are 360 degrees of color in hsi colorspace
///float S is 0.00 - 1.00 and aything in between
///float I is 0.00 - 1.00 and aything in between
///The input to our function is going to be hsi_to_rgb (Hue, Saturation, Intensity(brightness))
int rgb[3]; ///number of channels rgb = 3
void hsi_to_rgb(float H, float S, float I) {
int r, g, b;
if (H > 360) {
H = H - 360;
}
H = fmod(H, 360); // cycle H around to 0-360 degrees
H = 3.14159 * H / (float)180; // Convert to radians.
S = S > 0 ? (S < 1 ? S : 1) : 0; // clamp S and I to interval [0,1]
I = I > 0 ? (I < 1 ? I : 1) : 0;
if (H < 2.09439) {
r = 255 * I / 3 * (1 + S * cos(H) / cos(1.047196667 - H));
g = 255 * I / 3 * (1 + S * (1 - cos(H) / cos(1.047196667 - H)));
b = 255 * I / 3 * (1 - S);
} else if (H < 4.188787) {
H = H - 2.09439;
g = 255 * I / 3 * (1 + S * cos(H) / cos(1.047196667 - H));
b = 255 * I / 3 * (1 + S * (1 - cos(H) / cos(1.047196667 - H)));
r = 255 * I / 3 * (1 - S);
} else {
H = H - 4.188787;
b = 255 * I / 3 * (1 + S * cos(H) / cos(1.047196667 - H));
r = 255 * I / 3 * (1 + S * (1 - cos(H) / cos(1.047196667 - H)));
g = 255 * I / 3 * (1 - S);
}
//set the output to the array
rgb[0] = r;
rgb[1] = g;
rgb[2] = b;
}
I am writing a simple ray shader and I am trying to prodcue a dice with a cube and a number of spheres representing the dots. The spheres are correct, but the sides of the cube are on the x, y and z axes. The cube is centred around 0, 0, 0.
I have checked that the coordinate of the vertices are correct. I am assuming that my ray calculation is correct as the spheres are in the correct positions.
Here is the code for the ray calculation
Ray Image::RayThruPixel(float i, float j)
{
float alpha = m_tanFOVx * ((j - m_halfWidth) / m_halfWidth);
float beta = m_tanFOVy * ((m_halfHeight - i) / m_halfHeight);
vec3 *coordFrame = m_camera.CoordFrame();
vec3 p1 = (coordFrame[U_VEC] * alpha) + (coordFrame[V_VEC] * beta) - coordFrame[W_VEC];
return Ray(m_camera.Eye(), p1);
}
where m_tanFOVx is tan(FOVx / 2) and m_tanFOVy is tan(FOVy / 2) FOVx and FOVy are in radians.
To find the intersection of the ray and triangle my code is as follows:
bool Triangle::Intersection(Ray ray, float &fDistance)
{
static float epsilon = 0.000001;
bool bHit = false;
float fMinDist(10000000);
float divisor = glm::dot(ray.p1, normal);
// if divisor == 0 then the ray is parallel with the triangle
if(divisor > -epsilon && divisor < epsilon)
{
bHit = false;
}
else
{
float t = (glm::dot(v0, normal) - glm::dot(ray.p0, normal)) / divisor;
if(t > 0)
{
vec3 P = ray.p0 + (ray.p1 * t);
vec3 v2 = P - m_vertexA;
v0 = m_vertexB - m_vertexA;
v1 = m_vertexC - m_vertexA;
normal = glm::normalize(glm::cross(v0, v1));
d00 = glm::dot(v0, v0);
d01 = glm::dot(v0, v1);
d11 = glm::dot(v1, v1);
denom = d00 * d11 - d01 * d01;
float d20 = glm::dot(v2, v0);
float d21 = glm::dot(v2, v1);
float alpha = (d11 * d20 - d01 * d21) / denom;
float beta = (d00 * d21 - d01 * d20) / denom;
float gamma = 1.0 - alpha - beta;
vec3 testP = alpha * m_vertexA + beta * m_vertexB + gamma * m_vertexC;
if((alpha >= 0 ) &&
(beta >= 0) &&
(alpha + beta <= 1))
{
bHit = true;
fDistance = t;
}
}
}
return bHit;
}
Basically I want two mix two colours color1 and color2.
Since simple calculation's bring up stuff like blue+yellow = grey ((color1.r + color2.r)/2 etc) i did some research and found that apparently mixing colors in order for the mixed color to look like we expect it too (e.g. blue+yellow = green) isn't that straight forward.
What another stackoverflow post taught me was that in order two achieve the mixture correctly i'd have to use the Lab* space / CIELAB and linked to the wikipedia page about this topic.
I found it informative but i couldn't really understand how to convert RGB to (sRGB and than to) Lab* - how to mix the obtained colors and how to convert back
I hope somebody here can help me
Thanks,
Samuel
1) convert sRGB to RGB. From GEGL:
static inline double
linear_to_gamma_2_2 (double value)
{
if (value > 0.0030402477F)
return 1.055F * pow (value, (1.0F/2.4F)) - 0.055F;
return 12.92F * value;
}
static inline double
gamma_2_2_to_linear (double value)
{
if (value > 0.03928F)
return pow ((value + 0.055F) / 1.055F, 2.4F);
return value / 12.92F;
}
2) RGB to CIELAB. Look in OpenCV source [/src/cv/cvcolor.cpp]. There are functions for color space conversions [icvBGRx2Lab_32f_CnC3R]
3) mix color channels.
4) make all the color conversions back.
To interpolate between two RGB colours in the LAB colour space, you first need to convert each colour to LAB via XYZ (RGB -> XYZ -> LAB).
function RGBtoXYZ([R, G, B]) {
const [var_R, var_G, var_B] = [R, G, B]
.map(x => x / 255)
.map(x => x > 0.04045
? Math.pow(((x + 0.055) / 1.055), 2.4)
: x / 12.92)
.map(x => x * 100)
// Observer. = 2°, Illuminant = D65
X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805
Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722
Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505
return [X, Y, Z]
}
function XYZtoRGB([X, Y, Z]) {
//X, Y and Z input refer to a D65/2° standard illuminant.
//sR, sG and sB (standard RGB) output range = 0 ÷ 255
let var_X = X / 100
let var_Y = Y / 100
let var_Z = Z / 100
var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986
var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415
var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570
return [var_R, var_G, var_B]
.map(n => n > 0.0031308
? 1.055 * Math.pow(n, (1 / 2.4)) - 0.055
: 12.92 * n)
.map(n => n * 255)
}
You may use this function to interpolate between two LAB colours
const sum = (a, b) => a.map((_, i) => a[i] + b[i])
const interpolate = (a, b, p) => {
return sum(
a.map(x => x * p),
b.map(x => x * (1 - p)),
)
}
const colour1 = [...]
const colour2 = [...]
interpolate(colour1, colour2, 0.2) // take 20% of colour1 and 80% of colour2
Finally, convert the result back from LAB to RGB
function XYZtoLAB([x, y, z]) {
const [ var_X, var_Y, var_Z ] = [ x / ref_X, y / ref_Y, z / ref_Z ]
.map(a => a > 0.008856
? Math.pow(a, 1 / 3)
: (7.787 * a) + (16 / 116))
CIE_L = (116 * var_Y) - 16
CIE_a = 500 * (var_X - var_Y)
CIE_b = 200 * (var_Y - var_Z)
return [CIE_L, CIE_a, CIE_b]
}
function LABtoXYZ([l, a, b]) {
const var_Y = (l + 16) / 116
const var_X = a / 500 + var_Y
const var_Z = var_Y - b / 200
const [X, Y, Z] = [var_X, var_Y, var_Z]
.map(n => Math.pow(n, 3) > 0.008856
? Math.pow(n, 3)
: (n - 16 / 116) / 7.787)
return [X * ref_X, Y * ref_Y, Z * ref_Z]
}
Some other helpful functions
const parseRGB = s => s.substring(s.indexOf('(') + 1, s.length - 1).split(',')
.map(ss => parseInt(ss.trim()))
const RGBtoString = ([r, g, b]) => `rgb(${r}, ${g}, ${b})`
All together:
document.body.style.backgroundColor = RGBtoString(XYZtoRGB(LABtoXYZ(interpolate(
XYZtoLAB(RGBtoXYZ(parseRGB('rgb(255, 0, 0)'))),
XYZtoLAB(RGBtoXYZ(parseRGB('rgb(0, 255, 0)'))),
0.2,
))))
Reference for colour space conversions: http://www.easyrgb.com/en/math.php