python-opengl icon indicating copy to clipboard operation
python-opengl copied to clipboard

code error in chapter 9 linestrip

Open nyfpos opened this issue 3 years ago • 5 comments

It seems to draw nothing if points at [[10., 4.], [20., 4.], [15., 4.]]

The code is in below.

uniform vec2 resolution;
uniform float antialias, thickness, linelength;
attribute vec4 prev, curr, next;
varying vec2 v_uv;

void main() {
    float w = thickness/2.0 + antialias;
    vec2 p;
    vec2 t0 = normalize(curr.xy - prev.xy);
    vec2 t1 = normalize(next.xy - curr.xy);
    vec2 n0 = vec2(-t0.y, t0.x);
    vec2 n1 = vec2(-t1.y, t1.x);

    // Cap at start
    if (prev.xy == curr.xy) {
        v_uv = vec2(-w, curr.z*w);
        p = curr.xy - w*t1 + curr.z*w*n1;
    // Cap at end
    } else if (curr.xy == next.xy) {
        v_uv = vec2(linelength+w, curr.z*w);
        p = curr.xy + w*t0 + curr.z*w*n0;
    // Body
    } else {
        vec2 miter = normalize(n0 + n1);
        float dy = w / dot(miter, n1);
        v_uv = vec2(curr.w, curr.z*w);
        p = curr.xy + dy*curr.z*miter;
    }
    gl_Position = vec4(2.0*p/resolution-1.0, 0.0, 1.0);
}

I guess it happened when points previous, current, next are collinear and the next point's x or y less than current point.

Another case: The drawing of points [[453, 138], [647, 137], [453, 139]] is weird. And points [[453, 138], [647, 137], [453, 138]] draw nothing.

nyfpos avatar Jan 13 '22 10:01 nyfpos

If you change the three 4. into 4.1 4.2 and 4.3 it works?

rougier avatar Jan 17 '22 16:01 rougier

I figure out what happened. In the fragment shader, we are using v_uv.x to decide which points are cap.

It would be wrong if the end of point is 'before' it's previous point or 'before' the first point. But I still no idea how to fix it.

nyfpos avatar Jan 23 '22 06:01 nyfpos

where is this test exactly?

rougier avatar Jan 31 '22 17:01 rougier

The whole code is bellow:

import sys
import ctypes
import numpy as np
from glumpy import app, gloo, gl

vertex = """
uniform vec2 resolution;
uniform float antialias;
uniform float thickness;
uniform float linelength;
attribute vec4 prev, curr, next;
varying vec2 v_uv;
void main() {
    float w = thickness/2.0 + antialias;
    vec2 p;
    if (prev.xy == curr.xy) {
        vec2 t1 = normalize(next.xy - curr.xy);
        vec2 n1 = vec2(-t1.y, t1.x);
        v_uv = vec2(-w, curr.z*w);
        p = curr.xy - w*t1 + curr.z*w*n1;
    } else if (curr.xy == next.xy) {
        vec2 t0 = normalize(curr.xy - prev.xy);
        vec2 n0 = vec2(-t0.y, t0.x);
        v_uv = vec2(linelength+w, curr.z*w);
        p = curr.xy + w*t0 + curr.z*w*n0;
    } else {
        vec2 t0 = normalize(curr.xy - prev.xy);
        vec2 t1 = normalize(next.xy - curr.xy);
        vec2 n0 = vec2(-t0.y, t0.x);
        vec2 n1 = vec2(-t1.y, t1.x);
        vec2 miter = normalize(n0 + n1);
        float dy = w / dot(miter, n1);
        v_uv = vec2(curr.w, curr.z*w);
        p = curr.xy + dy*curr.z*miter;
    }
    gl_Position = vec4(2.0*p/resolution-1.0, 0.0, 1.0);
} """

fragment = """
uniform float antialias;
uniform float thickness;
uniform float linelength;
varying vec2 v_uv;

void main() {
    float d = 0;
    float w = thickness/2.0 - antialias;

    // Cap at start
    if (v_uv.x < 0)
        d = length(v_uv) - w;

    // Cap at end
    else if (v_uv.x >= linelength)
        d = length(v_uv - vec2(linelength,0)) - w;

    // Body
    else
        d = abs(v_uv.y) - w;

    if( d < 0) {
        gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        d /= antialias;
        gl_FragColor = vec4(0.0, 0.0, 0.0, exp(-d*d));
    }
} """

window = app.Window(2*512, 512, color=(1,1,1,1))

@window.event
def on_resize(width, height):
    #spiral["resolution"] = width, height
    shape["resolution"] = width, height

@window.event
def on_draw(dt):
    window.clear()    
    shape.draw(gl.GL_TRIANGLE_STRIP)

def bake(P, closed=False):
    epsilon = 1e-10
    n = len(P)
    if closed and ((P[0]-P[-1])**2).sum() > epsilon:
        P = np.append(P, P[0])
        P = P.reshape(n+1,2)
        n = n+1
    #print("n=", n)
    V = np.zeros(((1+n+1),2,4), dtype=np.float32)
    V_prev, V_curr, V_next = V[:-2], V[1:-1], V[2:]
    V_curr[...,0] = P[:,np.newaxis,0]
    V_curr[...,1] = P[:,np.newaxis,1]
    V_curr[...,2] = 1,-1

    L = np.cumsum(np.sqrt(((P[1:]-P[:-1])**2).sum(axis=-1))).reshape(n-1,1)
    V_curr[1:,:,3] = L
    if closed:
        V[0], V[-1] = V[-3], V[2]
    else:
        V[0], V[-1] = V[1], V[-2]
    print(V_curr)
    return V_prev, V_curr, V_next, L[-1]

P = np.array([[4, 1], [4, 12], [4, 24]]).astype(np.float32)
V_prev, V_curr, V_next, length = bake(P, False)
shape = gloo.Program(vertex, fragment)
shape["prev"], shape["curr"], shape["next"]  = V_prev, V_curr, V_next
shape["thickness"] = 1.0
shape["antialias"] = 1.5
shape["linelength"] = length

app.run()

It works fine when the P is [[4, 1], [4, 12], [4, 24]], but shows nothing when P is [[4, 1], [4, 12], [4, 6]]

nyfpos avatar Feb 09 '22 08:02 nyfpos

v_uv.x is supposed to be the curvilinear coordinate along the line. For the actual line, it goes from 0 to linelength. Negativr means starting cap end > linemenght means ending cap. If the calculation of this curvilnear coordnate is wrong, then it might be problelatic. If all the point are aligned, there might be a division by 0 somewhere but the shader will not raise any kind of exception and will simply silently fails. This might be the explanation.

rougier avatar Feb 15 '22 08:02 rougier