• Create Account

# Sirisian

Member Since 05 Oct 2005
Offline Last Active Apr 29 2016 05:21 PM

### Calculate time t along a 2D cubic bezier equal to a given tangent vector

17 January 2016 - 01:59 AM

I asked this over on stackoverflow, but I'm not sure it'll be answered there.

I have a cubic bezier defined by four points. I need to find the time t along the cubic bezier where the tangent is equal to a given vector. This problem is not as straightforward as it may seem on first glance. I'll explain the basic math first for how I approached it so you can find flaws and possibly a better solution.

A 2D cubic bezier and its tangent can be defined by these equations. Specifically the tangent:

```    T(t) = -3(1-t)^2 * P0 + 3(1-t)^2 * P1 - 6t(1-t) * P1 - 3t^2 * P2 + 6t(1-t) * P2 + 3t^2 * P3
```

And expanded for a 2D vector:

```    T_x(t) = -3(1-t)^2 * x0 + 3(1-t)^2 * x1 - 6t(1-t) * x1 - 3t^2 * x2 + 6t(1-t) * x2 + 3t^2 * x3
T_y(t) = -3(1-t)^2 * y0 + 3(1-t)^2 * y1 - 6t(1-t) * y1 - 3t^2 * y2 + 6t(1-t) * y2 + 3t^2 * y3
```

Then we also have a vector (x, y) representing the tangent we want to find the time t for.

These are simple quadratic equations so we just need an equation to solve. We can take the cross product (vx0 * vy1 - vy0 * vx1) between the two and solve for 0. This would find when the tangent of the cubic bezier is equal to our given tangent vector and we'd solve for t. (I don't care if the vector is opposite the tangent so if our vector is (1, 0) then it would also look for (-1, 0)). In Mathematica solving for t with this cross product approach would look like this:

```    Solve[(-3(1-t)^2*x0+3(1-t)^2*x1-6t(1-t)*x1-3t^2*x2+6t(1-t)*x2+3t^2*x3)*y-(-3(1-t)^2*y0+3(1-t)^2*y1-6t(1-t)*y1-3t^2*y2+6t(1-t)*y2+3t^2*y3)*x==0,t,Reals]
```

Mathematica would then output:

```    {{t->ConditionalExpression[(x0 y-2 x1 y+x2 y-x y0+2 x y1-x y2)/(x0 y-3 x1 y+3 x2 y-x3 y-x y0+3 x y1-3 x y2+x y3)-\[Sqrt]((x1^2 y^2-x0 x2 y^2-x1 x2 y^2+x2^2 y^2+x0 x3 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x0 y y2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2-x x0 y y3+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x0 y-3 x1 y+3 x2 y-x3 y-x y0+3 x y1-3 x y2+x y3)^2),(x>(x2 y-x3 y)/(y2-y3)&&x0>(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y>0&&y2>y3)||(x<(x2 y-x3 y)/(y2-y3)&&x0<(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y>0&&y2>y3)||(x<(x2 y-x3 y)/(y2-y3)&&x0<(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y<0&&y2<y3)||(x<(x2 y-x3 y)/(y2-y3)&&y<0&&x0>(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y2>y3)||(x<(x2 y-x3 y)/(y2-y3)&&y2<y3&&x0>(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y>0)||(x0<(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y<0&&x>(x2 y-x3 y)/(y2-y3)&&y2>y3)||(x0<(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y2<y3&&x>(x2 y-x3 y)/(y2-y3)&&y>0)||(y<0&&y2<y3&&x>(x2 y-x3 y)/(y2-y3)&&x0>(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3))]},

{t->ConditionalExpression[(x0 y-2 x1 y+x2 y-x y0+2 x y1-x y2)/(x0 y-3 x1 y+3 x2 y-x3 y-x y0+3 x y1-3 x y2+x y3)+\[Sqrt]((x1^2 y^2-x0 x2 y^2-x1 x2 y^2+x2^2 y^2+x0 x3 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x0 y y2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2-x x0 y y3+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x0 y-3 x1 y+3 x2 y-x3 y-x y0+3 x y1-3 x y2+x y3)^2),(x>(x2 y-x3 y)/(y2-y3)&&x0>(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y>0&&y2>y3)||(x<(x2 y-x3 y)/(y2-y3)&&x0<(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y>0&&y2>y3)||(x<(x2 y-x3 y)/(y2-y3)&&x0<(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y<0&&y2<y3)||(x<(x2 y-x3 y)/(y2-y3)&&y<0&&x0>(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y2>y3)||(x<(x2 y-x3 y)/(y2-y3)&&y2<y3&&x0>(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y>0)||(x0<(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y<0&&x>(x2 y-x3 y)/(y2-y3)&&y2>y3)||(x0<(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3)&&y2<y3&&x>(x2 y-x3 y)/(y2-y3)&&y>0)||(y<0&&y2<y3&&x>(x2 y-x3 y)/(y2-y3)&&x0>(x1^2 y^2-x1 x2 y^2+x2^2 y^2-x1 x3 y^2+x x2 y y0-x x3 y y0-2 x x1 y y1+x x2 y y1+x x3 y y1+x^2 y1^2+x x1 y y2-2 x x2 y y2-x^2 y0 y2-x^2 y1 y2+x^2 y2^2+x x1 y y3+x^2 y0 y3-x^2 y1 y3)/(x2 y^2-x3 y^2-x y y2+x y y3))]}}
```

Here's an image that's easier to see. That said most of those cases have duplicate variables so it's much simpler than it looks. (Both condition cases are identical and the solutions are a positive or negative case in the equation since it solved a quadratic equation). In code form this is easy to see:

```    var temp1 = (tx2 - tx3) / (y2 - y3);
var temp2 = (tx1 * tx1 + tx2 * tx2 + tx2 * (ty0 + ty1 - 2 * ty2) + tx1 * (-tx2 - tx3 - 2 * ty1 + ty2 + ty3) + tx3 * (ty1 - ty0) + ty1 * ty1 - ty0 * ty2 + ty2 * ty2 + ty0 * ty3 - ty1 * (ty2 + ty3)) / (tangent.y * (tx2 - tx3 - ty2 + ty3));
console.log ('Temp1: ', temp1, ' Temp2: ', temp2);
if
(
tangent.x < temp1 &&
(
tangent.y < 0 &&
(
x0 < temp2 && y2 < y3 ||
x0 > temp2 && y2 > y3
) ||
tangent.y > 0 &&
(
x0 < temp2 && y2 > y3 ||
x0 > temp2 && y2 < y3
)
) ||
tangent.x > temp1 &&
(
tangent.y < 0 &&
(
x0 < temp2 && y2 > y3 ||
x0 > temp2 && y2 < y3
) ||
tangent.y > 0 &&
(
x0 < temp2 && y2 < y3 ||
x0 > temp2 && y2 > y3
)
)
)
{
var tx0ty0 = tx0 - ty0;
var ty1tx1 = ty1 - tx1;
var tx2ty2 = tx2 - ty2;

var temp6 = 2 * (tx0ty0 + tx2ty2) + 4 * ty1tx1;
var temp5 = tx0ty0 + 3 * (tx2ty2 + ty1tx1) + ty3 - tx3;
var temp7 = temp6 * temp6 - 4 * (tx0ty0 + ty1tx1) * temp5;
var temp3 = Math.sqrt(temp7);
var temp4 = 2 * temp5;
var t1 = (temp6 - temp3) / temp4;
var t2 = (temp6 + temp3) / temp4;
}```

So what we have is two possible times as we'd expect since the problem is quadratic. Here's an interactive example in JS. That example uses a hardcoded tangent vector of (0.707, 0.707). (So a vector pointing down and to the right in that coordinate system).

There are problems though with the above code. Even correcting for floating point errors in the inequalities and square root calculations there are cases that aren't well defined. Like when y2 - y3 is 0 resulting in a division by zero case. There are subtleties to this also like in certain cases temp4 will have valid results that are very close to zero either producing the correct result or due to floating point issues generating a value for t1 and t2 much larger than expected. I've noticed this specifically in the cases where t1 or t2 are 0.5. Was thinking that flipping it across the the diagonal and solving again might solve some edge cases, but I'm just not confident on that approach.

What I'd like is a tried and tested approach, possibly with a code example, or another way to tackle this without weird edge cases.

### FBO with differing texture sizes? (glViewportArray?)

11 March 2012 - 05:46 PM

// edit hmm apparently when using different texture sizes it uses the minimum sized texture for all of them. So what I was trying was not possible.

I've been learning FBOs and wanted to try dealing with frame buffer textures that were different sizes. So in my test I created a texture the resolution of the screen to store parts of my model that didn't glow. Then I created a texture half the width and height of the screen for the part for the part of the model that did glow. My test was that I'd have a shader that would output to both textures. This works when both textures are the same width and height, but if I change the second texture (the glow one) to be half the width and height suddenly my viewport is cut into half when rendering just the first non-glow texture.

So the working code is as follow:
```// Model Texture
glGenTextures(1, &modelTexture);
glBindTexture(GL_TEXTURE_2D, modelTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
// Blur Texture
glGenTextures(1, &modelBlurTexture);
glBindTexture(GL_TEXTURE_2D, modelBlurTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
// Depth buffer
glGenRenderbuffers(1, &modelDepthRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, modelDepthRenderBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
// Model FBO
glGenFramebuffers(1, &modelFBO);
glBindFramebuffer(GL_FRAMEBUFFER, modelFBO);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, modelDepthRenderBuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, modelTexture, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, modelBlurTexture, 0);
GLenum modelTextureFBOStatus;
if ((modelTextureFBOStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER)) != GL_FRAMEBUFFER_COMPLETE)
{
std::cerr << "glCheckFramebufferStatus: error " << modelTextureFBOStatus << std::endl;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
```

And the render code is just:
```// Draw the model to the FBO
GLfloat viewports[] = { 0, 0, width, height, 0, 0, width, height };
glViewportArrayv(0, 2, viewports);
glBindFramebuffer(GL_FRAMEBUFFER, modelFBO);
GLenum modelBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
glDrawBuffers(2, modelBuffers);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindVertexArray(model.GPU.VAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, noiseTexture);
glDrawElements(GL_TRIANGLES, model.CPU.IBO.size(), GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));
glBindVertexArray(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
```
Notice I have glViewportArray set so the first viewport is (0, 0, width, height) mapped to (left, bottom, width, height). Am I using that correct? According the documentation: http://www.opengl.or...ewportArray.xml it says I am. Meaning the normalized device coordinates (-1, -1) would map to the window coordinates of (0, 0) and (1, 1) would map to (width, height) which seems right.

The above produces this:

But if I change the blurTexture to half the width and height to:
```glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width / 2, height / 2, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
```
suddenly the modelTexture's results change when I run the program:

I can't figure out why that is happening. I'd appreciate it if someone could explain what I'm doing wrong. I imagine I've just misunderstood how FBOs and viewports arrays work together to allow different texture sizes. (If it isn't clear I'm using OpenGL 4.2).

### Single timer event dispatcher.

12 February 2012 - 05:04 PM

I have a need to asynchronously execute many timed events (Could go over 1000 events). So the basic idea is that I have a priority queue of events and the timer should execute the first one and then wait for the next item and execute it when the time comes. Events can be inserted concurrently and must have a way to be removed. I'm not sure what the best design looks like in C#.

I'll describe my current implementation:
[source lang="csharp"]public class TimeEvent : IComparable<TimeEvent>{ public DateTime Time { get; set; } public bool Enabled { get; set; } public TimeEvent() { Enabled = true; } public int CompareTo(TimeEvent other) { return Time.CompareTo(other.Time); }}[/source]

Then I have a derived TimeEvent class. As an example (not that important):
[source lang="csharp"]internal class TimeEvent1 : TimeEvent{ public Action<Packet> Callback { get; set; } public SourceConnectionRequestTimeout(Action<Packet> callback, TimeSpan time) { Callback = callback; Time = DateTime.Now + time; }}[/source]

Ignoring the odd use of inheritance, the basic dispatcher uses a Timer instance. "timeEvents" is a basic heap implementation for a priority queue.
[source lang="csharp"]TimeEventDispatcher = new Timer((o) =>{ lock (TimeEventDispatcher) { var timeEvent = timeEvents.Pop(); if (timeEvent.Enabled) { if (timeEvent is TimeEvent1) { (timeEvent as TimeEvent1).Callback(); } } // Stop the timer if there are no other events in the priority queue. If there are events then wait for the next one. if (!timeEvents.Empty) { timeEvent = timeEvents.Peek(); var delay = Math.Max(0, (int)(timeEvent.Time - DateTime.Now).TotalMilliseconds); TimeEventDispatcher.Change(delay, Timeout.Infinite); } }}, null, Timeout.Infinite, Timeout.Infinite);[/source]
Now to add a TimeEvent instance I'm locking the timer so the code in the callback can't execute at the same time. Then I insert the new time event instance. If it happens to be the first item in the priority queue this means the timer needs to be changed to update at the new earlier time. (Basically if the item in the priority queue was set to expire 100 ms from now and we insert an item to execute 50 ms from now then we need to change the timer to 50 ms). This also conveniently starts the timer if it isn't already started.
[source lang="csharp"]public void AddTimeEvent(TimeEvent timeEvent){ lock (TimeEventDispatcher) { timeEvents.Push(timeEvent); if (timeEvents.Peek() == timeEvent) { var delay = Math.Max(0, (int)(timeEvent.Time - DateTime.Now).TotalMilliseconds); TimeEventDispatcher.Change(delay, Timeout.Infinite); } }}[/source]
Removing an event is done by locking the timer and setting the Enabled property on the time event instance to false. So when it's processed it's removed without executing the item. Pretty simple solution.

Okay so the above solution seems like it would work. There's a race condition in the system where if a callback is execute and doesn't get to the lock statement by the time another time event is inserted before the most recent one the it'll execute the wrong one. But that can only happen if you insert an item that must be executed right away so it has no ill-effect on the algorithm. Also all the time events are very small so when they execute they don't block the system so they don't need to be executed asynchronously themselves (via tasks or somethings).

Is there a better algorithm in C# for handling this situation? (Mostly caring about performance). Anything my algorithm is missing assuming it should be design to handle possibly thousands of time events.

(Also if anyone is curious about the actual problem, this is a networking library timer like the one found in TCP that manages resend events and other timer events).

### Whitehouse: We the People petition system

14 October 2011 - 11:18 PM

We The People launched a few weeks ago. I was skeptical at first, but it seems to be doing fairly well. The commenting system still seems to be in development.

Sorting by popularity has a lot of interesting topics. I won't bias the thread by listing my favorites. Fun system though if you live in the US. (I believe it's international though so anyone can participate).

I voted a few times already. Waiting for the comment system. I already submitted all of my feedback to make things better. (I imagine they know about the minor usability problems).