Optimizing Complex Mobius Transformations on a Fragment Shader - glsl

I'm developing my own graphics engine to render all sorts of fractals (like my video here for example), and I'm currently working on optimizing my code for Julia Set matings (see this question and my project for more details). In the fragment shader, I use this function:
vec3 JuliaMatingLoop(dvec2 z)
{
...
for (int k = some_n; k >= 0; --k)
{
// z = z^2
z = dcproj(c_2(z));
// Mobius Transformation: (ma[k] * z + mb[k]) / (mc[k] * z + md[k])
z = dcproj(dc_div(cd_mult(ma[k], z) + mb[k], dc_mult(mc[k], z) + md[k]));
}
...
}
And after reading this, I realized that I'm doing Mobius transformations in this code, and (mathematically speaking) I can use matrices to accomplish the same operation. However, the a, b, c, and d constants are all complex numbers (represented as ma[k], mb[k], mc[k], and md[k] in my code), whereas the elements in GLSL matrices contain only real numbers (rather than vec2). And so to my question: is there a way to optimize these Mobius transformations using matrices in GLSL? Or any other way of optimizing this part of my code?
Helper functions (I need to use doubles for this part, so I can't optimize by switching to using floats):
// Squaring
dvec2 c_2(dvec2 c)
{
return dvec2(c.x*c.x - c.y*c.y, 2*c.x*c.y);
}
// Multiplying
dvec2 dc_mult(dvec2 a, dvec2 b)
{
return dvec2(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x);
}
// Dividing
dvec2 dc_div(dvec2 a, dvec2 b)
{
double x = b.x * b.x + b.y * b.y;
return vec2((a.x * b.x + a.y * b.y) / x, (b.x * a.y - a.x * b.y) / x);
}
// Riemann Projecting
dvec2 dcproj(dvec2 c)
{
if (!isinf(c.x) && !isinf(c.y) && !isnan(c.x) && !isnan(c.y))
return c;
return dvec2(infinity, 0);
}

I'm not sure if this will help, but yes you can do complex arithmetic by matrices.
If you regard a complex number z as a real two-vector with components Re(z), Im(z)
Then
A*z + B ~ (Re(A) -Im(A) ) * (Re(z)) + (Re(B))
(Im(A) Re(A) ) (Im(z)) (Im(B))
Of course you actually want
(A*z + B) / (C*z + D)
If you compute
A*z+b as (x)
(y)
C*z+d as (x')
(y')
Then the answer you seek is
inv( x' -y') * ( x)
( y' x' ) ( y)
i.e
(1/(x'*x'+y'*y')) * (x' y') * (x)
(-y' x') (y)
One thing to note, though, is that in these formulae, as in your code, division is not implemented as robustly as it could be. The trouble lies in evaluating b.x * b.x + b.y * b.y. This could overflow to infinity, or underflow to 0, even though the result of division could be quite reasonable. A commonly used way round this is Smith's method eg here and if you search for 'robust complex division' you'll find more recent work. Often this sort of thing matters little, but if you are iterating off to infinity it could make a difference.

Related

Anyway to avoid floating point operation for Max Points on Line problem

I'm trying to solve "Max Points on Line" problem on Leet code. I inevitably need to do floating point operation to calculation Y-Intercept and slope of each line. Due to my past bad experience, I'm trying to avoid floating point operations as much as I can. Do you have any suggestion how I can do that here?
I am using LeetCode framework for development and pretty much just have access to standard C++ library.
Tried using double or long double but one of the test cases already pushes the numbers to the limits of the accuracy of these data types.
//P1[0] is X coordinate for point P1 and P1[1] is Y coordinate
long double slopeCalc( vector<int> &p1, vector<int> &p2 )
{
if( p1[0] == p2[0] && p1[1] == p2[1] )
{
return DBL_MIN;
}
if( p1[0] == p2[0] && p1[1] != p2[1] )
{
return DBL_MAX;
}
return ( (long double)p2[1] - (long double)p1[1] ) / ((long double)p2[0] - (long double)p1[0]);
}
long double yIntersectionCalc( vector<int> &p1, vector<int> &p2 )
{
if( p1[0] == p2[0] && p1[1] == p2[1] )
{
return DBL_MIN;
}
if( p1[0] == p2[0] && p1[1] != p2[1] )
{
return DBL_MAX;
}
return ((long double)p1[1]*(long double)p2[0] - (long double)p2[1]*(long double)p1[0]) / (long double)(p2[0] - p1[0]);
}
If the two points are (0, 0) and (94911150, 94911151) the slope is calculated as 1 which is inaccurate. I'm trying to avoid the floating point division if possible.
NOTE: Max Points on a Line problem is to be given points in 2D space (in this case integer coordinates) and find the maximum number of points that are on one line. E.g if the points are (0,0), (2,2), (4,3), (1,1) the answer is 3 which are points (0,0), (1,1), and (2,2)
In integer coordinates, the alignment test of three points can be written as the expression
(Xb - Xa) (Yc - Ya) - (Yb - Ya) (Xc - Xa) = 0
Assuming that the range of the coordinates requires N bits, the computation of the deltas takes N+1 bits, and exact evaluation of the expression takes 2N+2 bits at worse. There is little that you can do against that.
In your case, 64 bits integers should be enough.
A piece of advice: avoid working with the slope/intercept representation.
If you want to avoid using floating point, what you can do to determine if a point z is collinear with two other points x and y is to compute the determinant of the matrix
{{1,z1,z2},{1,x1,x2},{1,y1,y2}}
If the determinant is 0, then they are collinear. Since computing the determinant using the permutation definition involves only multiplication and addition/subtraction, all your computations will remain as integers. The reason it will be 0 is that the determinant is twice the area of the triangle with x,y,z as vertices,which is zero if and only if the triangle is degenerate.
Another approach would be to use Fraction objects, in particular the slope and the intercept of the lines defined by two integers are identified as Fractions ("rational numbers"), and a reduced fraction is identified by its numerator and denominator, so you can use the pair of fractions (slope,intercept) as identifiers and since you never use floating point arithmetic you won't need to deal with roundoff error. See https://martin-thoma.com/fractions-in-cpp/ for a sample implementation of Fractions, the important part is that you can use the arithmetic operators, and normalization.
EDIT: boost has a rational number library, if you want to use it https://www.boost.org/doc/libs/1_68_0/libs/rational/
Given points a,b,c, look at the slopes b,c make to a common point, a:
ba.x = b.x - a.x
ba.y = b.y - a.y
ba.s = ba.y / ba.x
ca.x = c.x - a.x
ca.y = c.y - a.y
ca.s = ca.y / ca.x
The points a,b,c are co-linear iff the lines AB and BC have a common slope, i.e.:
ba.s == ca.s
Substituting and rearranging to remove the divides:
ba.y / ba.x == ca.y / ca.x
ba.y * ca.x / ba.x == ca.y
ba.y * ca.x == ca.y * ba.x
Substitute in the original formulas for those, then a,b,c are co-linear iff:
(b.y - a.y) * (c.x - a.x) == (c.y - a.y) * (b.x - a.x)
Note that the determinant answer could also be rearranged into this form which proves this approach. But this form has just 2 multiplications rather than 12 for a naive determinant implementation.

Point translation ortghogonally to the line

I am currently working on a project of drawing thick polylines and I am using interpolation in OpenGL. I managed to calculate all the necessary points but I need to draw two more points. I need to translate one point orthogonally to the line connecting two points. The scatch below shows what are the points. Point L is to be translated for the distance between L and nJ orthogonally to the line AB (B is the central point). Similar thing is with translation to the nK.
I have written the code:
float alpha = atan2(B.y - A.y,B.x - A.x) - deg90;
float alpha2 = atan2(C.y - B.y, C.x - B.x) - deg90;
nJ.x = L.x + w*cos(alpha); // w is distance between A1 and A2
nJ.y = L.y + w*sin(alpha);
nK.x = L.x + w*cos(alpha2);
nK.y = L.y + w*sin(alpha2);
The code works only for some points, not all. I need to fix + sing in above calculations of nJ and nK, but I do not know how. Anyone having suggestion?
First you need the left-hand-side function:
lhs(v) = [-v.y, v.x]
This turns a vector 90 degrees counter-clockwise.
Now you need the turn function:
turn(u, v, w) = sign(lhs(v - u), w - v)
If you have a polyline from u to v to w, turn(u,v,w) tells you whether it's a left turn (counter-clockwise turn) (positive), right turn (clockwise turn) (negative), or colinear (0).
There are four infinite lines in your picture that run parallel to ab and bc, with a distance of w between each pair.
The lines on the lower part are:
f(s) = (a + 0.5 * w * normalize(lhs(b - a))) + (b - a) * s
g(t) = (b + 0.5 * w * normalize(lhs(c - b))) + (c - b) * t
You want to find the intersection of the two lines; i.e., you want to solve for s and t in f(s) = g(t). This is just a system of two linear equations with two unknowns.
The solution is your point L = f(s) = g(t) in the picture.
To compute I you can use the exact same idea:
f(s) = (a - 0.5 * w * normalize(lhs(b - a))) + (b - a) * s
g(t) = (b - 0.5 * w * normalize(lhs(c - b))) + (c - b) * t
Solve for I = f(s) = g(t).
Update
Once you have L you can compute Kn and Jn as follows.
Kn = L - w * normalize(lhs(b - a))
Jn = L - w * normalize(lhs(c - b))
In computational geometry code, trigonometry is usually a code smell - it's not always wrong, but it usually is wrong. Try to stick to linear algebra.

How to do ray plane intersection?

How do I calculate the intersection between a ray and a plane?
Code
This produces the wrong results.
float denom = normal.dot(ray.direction);
if (denom > 0)
{
float t = -((center - ray.origin).dot(normal)) / denom;
if (t >= 0)
{
rec.tHit = t;
rec.anyHit = true;
computeSurfaceHitFields(ray, rec);
return true;
}
}
Parameters
ray represents the ray object.
ray.direction is the direction vector.
ray.origin is the origin vector.
rec represents the result object.
rec.tHit is the value of the hit.
rec.anyHit is a boolean.
My function has access to the plane:
center and normal defines the plane
As wonce commented, you want to also allow the denominator to be negative, otherwise you will miss intersections with the front face of your plane. However, you still want a test to avoid a division by zero, which would indicate the ray being parallel to the plane. You also have a superfluous negation in your computation of t. Overall, it should look like this:
float denom = normal.dot(ray.direction);
if (abs(denom) > 0.0001f) // your favorite epsilon
{
float t = (center - ray.origin).dot(normal) / denom;
if (t >= 0) return true; // you might want to allow an epsilon here too
}
return false;
First consider the math of the ray-plane intersection:
In general one intersects the parametric form of the ray, with the implicit form of the geometry.
So given a ray of the form x = a * t + a0, y = b * t + b0, z = c * t + c0;
and a plane of the form: A x * B y * C z + D = 0;
now substitute the x, y and z ray equations into the plane equation and you will get a polynomial in t. you then solve that polynomial for the real values of t. With those values of t you can back substitute into the ray equation to get the real values of x, y and z.
Here it is in Maxima:
Note that the answer looks like the quotient of two dot products!
The normal to a plane is the first three coefficients of the plane equation A, B, and C.
You still need D to uniquely determine the plane.
Then you code that up in the language of your choice like so:
Point3D intersectRayPlane(Ray ray, Plane plane)
{
Point3D point3D;
// Do the dot products and find t > epsilon that provides intersection.
return (point3D);
}
Math
Define:
Let the ray be given parametrically by q = p + t*v for initial point p and direction vector v for t >= 0.
Let the plane be the set of points r satisfying the equation dot(n, r) + d = 0 for normal vector n = (a, b, c) and constant d. Fully expanded, the plane equation may also be written in the familiar form ax + by + cz + d = 0.
The ray-plane intersection occurs when q satisfies the plane equation. Substituting, we have:
d = -dot(n, q)
= -dot(n, p + t * v)
= -dot(n, p) + t * dot(n, v)
Rearranging:
t = -(dot(n, p) + d) / dot(n, v)
This value of t can be used to determine the intersection by plugging it back into p + t*v.
Example implementation
std::optional<vec3> intersectRayWithPlane(
vec3 p, vec3 v, // ray
vec3 n, float d // plane
) {
float denom = dot(n, v);
// Prevent divide by zero:
if (abs(denom) <= 1e-4f)
return std::nullopt;
// If you want to ensure the ray reflects off only
// the "top" half of the plane, use this instead:
//
// if (-denom <= 1e-4f)
// return std::nullopt;
float t = -(dot(n, p) + d) / dot(n, v);
// Use pointy end of the ray.
// It is technically correct to compare t < 0,
// but that may be undesirable in a raytracer.
if (t <= 1e-4)
return std::nullopt;
return p + t * v;
}
implementation of vwvan's answer
Vector3 Intersect(Vector3 planeP, Vector3 planeN, Vector3 rayP, Vector3 rayD)
{
var d = Vector3.Dot(planeP, -planeN);
var t = -(d + Vector3.Dot(rayP, planeN)) / Vector3.Dot(rayD, planeN);
return rayP + t * rayD;
}

Given two points and two vectors, find point of intersection [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How do you detect where two line segments intersect?
Given two points a and b plus two vectors v and u I want to find a third point c, which is the point of intersection in the following manner:
vector2 intersection(vector2 a, vector2 v, vector2 b, vector2 u)
{
float r, s;
a + r * v = b + s * u;
r * v - s * u = b - a
r * v.x - s * u.x = b.x - a.x
r * v.y - s * u.y = b.y - a.y
}
Is there any other way than using gaussian elimination to solve this system? Or is this the best (or at least an acceptable) way to handle this?
EDIT:
Definition of vector2
typedef union vector2
{
float v[2];
struct { float x, y; };
} vector2;
a and b are also of type vector2, because the only difference between a point and a vector is in the the way it is transformed by an affine transformation.
Looks like an assignment problem to me. Here is the logic that will help you write the code.
Let us call the first Ray as R0.
Locus of a point on R0 is defined as P:
P = P0 + alpha x V0
For the second ray R1:
P = P1 + beta x V1
Since they should intersect:
P0 + alpha x V0 = P1 + beta x V1
alpha and beta are unknowns and we have two equations in x any y.
Solve for the unknowns and get back the point of intersection.
i.e.,
P0.x + alpha * V0.x = P1.x + beta * V1.x
P0.y + alpha * V0.y = P1.y + beta * V1.y
solve for alpha and beta.
If there is a real positive solution for both alpha and beta, rays intersect.
If there is a real but at least one negative solution for both alpha and beta, extended rays intersect.
It's simple math.
But, first, check that you have intersection. If both vector are parallel you will fail to solve that:
// Edit this line to protect from division by 0
if (Vy == 0 && Uy == 0) || ((Vy != 0 && Uy != 0 && (Vx/Vy == Ux/Uy)) // => Fail.
Then (I won't show the calculation because they are long but the result is):
R = (AxUy - AyUx + ByUx - BxUy) / (VyUx - VxUy)
S = (Ax - Bx + RVx) / Ux
Hope that helped you.

Using Perlin noise to create lightning?

Actually I am having several questions related to the subject given in the topic title.
I am already using Perlin functions to create lightning in my application, but I am not totally happy about my implementation.
The following questions are based on the initial and the improved Perlin noise implementations.
To simplify the issue, let's assume I am creating a simple 2D lightning by modulating the height of a horizontal line consisting of N nodes at these nodes using a 1D Perlin function.
As far as I have understood, two subsequent values passed to the Perlin function must differ by at least one, or the resulting two values will be identical. That is because with the simple Perlin implementation, the Random function works with an int argument, and in the improved implementation values are mapped to [0..255] and are then used as index into an array containing the values [0..255] in a random distribution. Is that right?
How do I achieve that the first and the last offset value (i.e. for nodes 0 and N-1) returned by the Perlin function is always 0 (zero)? Right now I am modulation a sine function (0 .. Pi) with my Perlin function to achieve that, but that's not really what I want. Just setting them to zero is not what I want, since I want a nice lightning path w/o jaggies at its ends.
How do I vary the Perlin function (so that I would get two different paths I could use as animation start and end frames for the lightning)? I could of course add a fixed random offset per path calculation to each node value, or use a differently setup permutation table for improved Perlin noise, but are there better options?
That depends on how you implement it and sample from it. Using multiple octaves helps counter integers quite a bit.
The octaves and additional interpolation/sampling done for each provides much of the noise in perlin noise. In theory, you should not need to use different integer positions; you should be able to sample at any point and it will be similar (but not always identical) to nearby values.
I would suggest using the perlin as a multiplier instead of simply additive, and use a curve over the course of the lightning. For example, having perlin in the range [-1.5, 1.5] and a normal curve over the lightning (0 at both ends, 1 in the center), lightning + (perlin * curve) will keep your ends points still. Depending on how you've implemented your perlin noise generator, you may need something like:
lightning.x += ((perlin(lightning.y, octaves) * 2.0) - 0.5) * curve(lightning.y);
if perlin returns [0,1] or
lightning.x += (perlin(lightning.y, octaves) / 128.0) * curve(lightning.y);
if it returns [0, 255]. Assuming lightning.x started with a given value, perhaps 0, that would give a somewhat jagged line that still met the original start and end points.
Add a dimension to the noise for every dimension you add to the lightning. If you're modifying the lightning in one dimension (horizontal jagged), you need 1D perlin noise. If you want to animate it, you need 2D. If you wanted lightning that was jagged on two axis and animated, you'd need 3D noise, and so on.
After reading peachykeen's answer and doing some (more) own research in the internet, I have found the following solution to work for me.
With my implementation of Perlin noise, using a value range of [0.0 .. 1.0] for the lightning path nodes work best, passing the value (double) M / (double) N for node M to the Perlin noise function.
To have a noise function F' return the same value for node 0 and node N-1, the following formula can be applied: F'(M) = ((M - N) * F(N) + N * F (N - M)) / M. In order to have the lightning path offsets begin and end with 0, you simply need to subtract F'(0) from all lightning path offsets after having computed the path.
To randomize the lightning path, before computing the offsets for each path node, a random offset R can be computed and added to the values passed to the noise function, so that a node's offset O = F'(N+R). To animate a lightning, two lightning paths need to be computed (start and end frame), and then each path vertex has to be lerped between its start and end position. Once the end frame has been reached, the end frame becomes the start frame and a new end frame is computed. For a 3D path, for each path node N two offset vectors can be computed that are perpendicular to the path at node N and each other, and can be scaled with two 1D Perlin noise values to lerp the node position from start to end frame position. That may be cheaper than doing 3D Perlin noise and works quite well in my application.
Here is my implementation of standard 1D Perlin noise as a reference (some stuff is virtual because I am using this as base for improved Perlin noise, allowing to use standard or improved Perlin noise in a strategy pattern application. The code has been simplified somewhat as well to make it more concise for publishing it here):
Header file:
#ifndef __PERLIN_H
#define __PERLIN_H
class CPerlin {
private:
int m_randomize;
protected:
double m_amplitude;
double m_persistence;
int m_octaves;
public:
virtual void Setup (double amplitude, double persistence, int octaves, int randomize = -1);
double ComputeNoise (double x);
protected:
double LinearInterpolate (double a, double b, double x);
double CosineInterpolate (double a, double b, double x);
double CubicInterpolate (double v0, double v1, double v2, double v3, double x);
double Noise (int v);
double SmoothedNoise (int x);
virtual double InterpolatedNoise (double x);
};
#endif //__PERLIN_H
Implementation:
#include <math.h>
#include <stdlib.h>
#include "perlin.h"
#define INTERPOLATION_METHOD 1
#ifndef Pi
# define Pi 3.141592653589793240
#endif
inline double CPerlin::Noise (int n) {
n = (n << 13) ^ n;
return 1.0 - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0;
}
double CPerlin::LinearInterpolate (double a, double b, double x) {
return a * (1.0 - x) + b * x;
}
double CPerlin::CosineInterpolate (double a, double b, double x) {
double f = (1.0 - cos (x * Pi)) * 0.5;
return a * (1.0 - f) + b * f;
}
double CPerlin::CubicInterpolate (double v0, double v1, double v2, double v3, double x) {
double p = (v3 - v2) - (v0 - v1);
double x2 = x * x;
return v1 + (v2 - v0) * x + (v0 - v1 - p) * x2 + p * x2 * x;
}
double CPerlin::SmoothedNoise (int v) {
return Noise (v) / 2 + Noise (v-1) / 4 + Noise (v+1) / 4;
}
int FastFloor (double v) { return (int) ((v < 0) ? v - 1 : v; }
double CPerlin::InterpolatedNoise (double v) {
int i = FastFloor (v);
double v1 = SmoothedNoise (i);
double v2 = SmoothedNoise (i + 1);
#if INTERPOLATION_METHOD == 2
double v0 = SmoothedNoise (i - 1);
double v3 = SmoothedNoise (i + 2);
return CubicInterpolate (v0, v1, v2, v3, v - i);
#elif INTERPOLATION_METHOD == 1
return CosineInterpolate (v1, v2, v - i);
#else
return LinearInterpolate (v1, v2, v - i);
#endif
}
double CPerlin::ComputeNoise (double v) {
double total = 0, amplitude = m_amplitude, frequency = 1.0;
v += m_randomize;
for (int i = 0; i < m_octaves; i++) {
total += InterpolatedNoise (v * frequency) * amplitude;
frequency *= 2.0;
amplitude *= m_persistence;
}
return total;
}
void CPerlin::Setup (double amplitude, double persistence, int octaves, int randomize) {
m_amplitude = (amplitude > 0.0) ? amplitude : 1.0;
m_persistence = (persistence > 0.0) ? persistence : 2.0 / 3.0;
m_octaves = (octaves > 0) ? octaves : 6;
m_randomize = (randomize < 0) ? (rand () * rand ()) & 0xFFFF : randomize;
}