Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

2d grid distorted when viewed in 3d?

in Questions Posts: 386

I created an 2d image of a square grid and then to view it in 3d i used it as the floor in the 3d lab project. Unexpectedly (for me) the grid does not look like what i would expect (the lines getting closer together as they go further away) instead the grid is distorted (as if there is a kink along the diagonal of the grid). Am i doing something wrong?

I would add an image to the post but i don't remember how to do that!?

Tagged:

Comments

  • IgnatzIgnatz Mod
    Posts: 5,396

    Post the code, please

  • dave1707dave1707 Mod
    Posts: 7,612

    @piinthesky Here's something I have, but I don't know if it will help or not. Slide your finger around the screen to look in different directions. It's a 2d grid in 3D space.

    displayMode(FULLSCREEN)   
    
    function setup()
        angH,angV=180,0 -- horizontal and verticle angle
    end
    
    function draw()
        background(0)
        x=math.cos(math.rad(angV))*math.sin(math.rad(angH))
        y=math.sin(math.rad(angV))
        z=math.cos(math.rad(angV))*math.cos(math.rad(angH)) 
        lookX=1000*x
        lookY=1000*y
        lookZ=1000*z
        perspective()
        camera(0,0,400,lookX,lookY,lookZ,0,1,0)
        stroke(255)
        strokeWidth(2)  
        for z=-1000,1000,20 do  
            line(z,-1000,z,1000)
            line(-1000,z,1000,z)
        end
    end
    
    function touched(t)
        if t.state==MOVING then
            angH=angH+t.deltaX/5
            angV=angV-t.deltaY/5
        end
    end
    
  • IgnatzIgnatz Mod
    Posts: 5,396

    Don't draw the floor the way it is done in the 3D demo. You should use meshes to draw in 3D, not sprites. (If you don't know meshes, then you have no business drawing in 3D. Learn meshes first!).

    function setup()
        local img=MakeGrid(100,100,10) --create grid once only
        floor=MakeFloor(0,-300,800,5000,img) --make a floor mesh
    end
    
    function draw()
        background(120)
        perspective()
        camera(0,100,200,0,40,-100)
        -- see how simple it is if you set it up correctly
        --no need to translate or rotate, just draw it
        floor:draw() 
    end
    
    --if you know meshes, this is very simple
    function MakeFloor(x,z,w,d,img) --x,z are centre of floor, w=width,d=depth
        local m=mesh()
        local x1,x2,z1,z2=x-w/2,x+w/2,z+d/2,z-d/2
        m.vertices={vec3(x1,0,z1),vec3(x2,0,z1),vec3(x2,0,z2),vec3(x2,0,z2),vec3(x1,0,z2),vec3(x1,0,z1)}
        m.texCoords={vec2(0,0),vec2(1,0),vec2(1,1),vec2(1,1),vec2(0,1),vec2(0,0)}
        m.texture=img
        m:setColors(color(255))
        return m
    end
    
    --create an image with a grid in it
    function MakeGrid(w,h,s) --width, height of image, s=size of each square
        local img=image(w,h)
        setContext(img)
        stroke(0)
        strokeWidth(1)
        for i=0,w,s do line(i,0,i,h) end
        for i=0,h,s do line(0,i,w,i) end
        setContext()
        return img
    end
    
    
  • Posts: 2,020

    Without seeing code its hard to say what the distortion you're describing is, but you could experiment with the field of view parameter (the first parameter in the perspective command).

    If you have any kind of photography background then a lot of this will be familiar. A relatively wide-angle lens (eg perspective(60)) will have to, by definition, bend straight lines to fit all of the view in, this is true with real-life lenses as it is with virtual ones in the digital world. It's particularly noticeable towards the corners of the frame, where spherical objects will start to stretch into egg shapes. Calling perspective() without any parameters will default to a 45 degree FoV, regarded as a "natural" FoV (ie close to the human eye, and therefore what we would call in our anthropocentric way, "undistorted". If bumble-bees had created OpenGL, they might have something to say about that). A more zoom lens (say, perspective(30)) will have a lot less distortion, but also have less depth separation (objects won't recede as much as they get further away), and of course, less wide of a field of view. Generally, first-person perspective games tend to go for the wide end, around 60 degrees. Although this introduces distortions which can appear unnatural, the wider view is a way to simulate human peripheral vision.

    You can have fun recreating Hitchcock's Vertigo effect, by tracking the camera in at the same time as zooming out.

  • edited May 2016 Posts: 386

    Thanks for your advice.

    I am making an 3D event display for the Antares deep sea neutrino telescope using codea and the @loopspace shape library. It allows to visualise high energy muon particles passing through the telescope emitting cherenkov light which is detected by the photosensors (if interested see antares.in2p3.fr for more details).

    The first video below shows the display using a sprite for the reference system grid. You can see the kink in the grid lines i was referring to.

    In the next video i create the reference system grid by creating a thin 3D block and add the grid texture to it. The grid lines now behave better but obviously i still don't have it right. Interestingly, the grid lines on the top and underneath of the block are orthogonal! I need to figure out how to wrap the texture correctly around the block.

    I also tried @Ignatz code, but for some reason at the moment it yields a grey rectangle when inserted in my code (works fine as standalone code).

    edit: fixed my stupid bug in the Ignatz method now it works...

  • IgnatzIgnatz Mod
    Posts: 5,396

    Very nice! Let us know if you have any more problems. If it runs slow, you can fake the bubble shapes in 2D.

  • Posts: 386

    If you have a trick to make spheres in 2D that correctly handle the shading as they are rotated i would be very interested. Our next telescope has ten times more strings, so the fps will become a big issue.

  • IgnatzIgnatz Mod
    edited May 2016 Posts: 5,396

    The fps is affected by the total number of vertices, half of which are never seen (but still require processing), and most of which are not needed anyway as many of the spheres are very small and don't need high res meshes. So using actual 3D for such small spheres is overkill.

    The simplest may be to fake the 3D spheres using a single circular b/w 2D image that looks like a shaded sphere, and colour it using a transparent blend, maybe with a shader for speed.

    If that sounds ok, we can mock something up. If you need something more complex, please specify it clearly so we don't waste each other's time.

  • IgnatzIgnatz Mod
    edited May 2016 Posts: 5,396

    @piinthesky - this code creates and displays a fake 3D ball in any colour, viewed from any angle. You simply translate to the position you want, set the scale to the size you want, and draw it

    function setup()
        ball=MakeBall() --create just one ball mesh
        --you can play with colours
        parameter.integer("Red",0,255,255)
        parameter.integer("Green",0,255,255)
        parameter.integer("Blue",0,255,255)
    end
    
    function draw()
        background(120)
        perspective()
        camera(0,0,200,0,0,-1) 
        --draw the first ball at 0,0,0
        ball:setColors(color(Red,Green,Blue))
        ball:draw()
        --draw a second smaller ball In a different colour
        pushMatrix()
        translate(0,50,0)
        scale(0.8,0.8)
        ball:setColors(color(255-Red,255-Green,255-Blue))
        ball:draw()
        popMatrix()
    end
    
    --only runs once
    function MakeBall()
        --build the ball image for this demo
        --you will probably want to import a good image instead
        local r=100
        local base=150
        local spread=0.7
        local img=image(r*2,r*2)
        setContext(img)
        fill(base)
        ellipse(r,r,r)
        local p=vec2(1,1)*(r+r*0.1)
        for i=r*spread,1,-1 do
            c=(255-base)*(1-i/r/spread)^1.2+base
            fill(c)
            --fill(255)
            ellipse(p.x,p.y,i)
        end
        setContext()
    
        --create the mesh
        local m=mesh()
        m:addRect(0,0,r,r)
        m.texture=img
        return m
    end
    
  • Posts: 386

    Thanks @Ignatz. To test i introduced your 2D sphere into the 3D lab example. I rotate the plane of the image to always face the camera so that it gives the illusion of being a sphere. This works nicely, but if there is another image behind the sphere a square frame around the sphere is noticable which deletes the images behind.

    Interestingly, when i tried to make a movie of the effect using the codea movie tool i am not able to do so, as activating the filming changes the images on the screen and partially fixes the problem!

    I was able to film the equivalent effect with the standard 3d lab objects.

    Note, that for your code above noStroke() should be applied otherwise the gradient coloring does not work correctly.

  • IgnatzIgnatz Mod
    edited May 2016 Posts: 5,396

    @piinthesky - ah yes, I should have remembered.

    You need to draw the circles from furthest to nearest, which means sorting them before drawing them. This will do it. Assuming you have the circles in a table like this

    --each element of the table is a subtable holding the
    --circle position plus the size of the circle
    circles={
        {vec3(1,1,0),0.6},
        {vec3(20,35,0),0.4}
    }
    
    --then if the camera is at position C (a vec3), you can sort it with
    table.sort(circles,function(a,b) return a[1]:dist(C)>b[1]:dist(C) end)
    

    The reason you need to sort is that when OpenGL draws your images, and two images overlap, it only shows the front image. However, if the front image has transparent pixels, OpenGL treats them as opaque, and so if the front image is drawn first, and then the back image, OpenGL won't draw any pixels for the back image if they are behind a transparent pixel from the front image.

    The only solution is to draw these images from furthest to nearest, as above.

  • dave1707dave1707 Mod
    edited May 2016 Posts: 7,612

    @piinthesky Your post looked interesting, so I thought I'd see what I could come up with. I tried to keep it simple and still use 3D spheres. This runs at about 16 FPS on my iPad Air. This could probable be speeded up more, but I'm not going to try now. Just thought I'd post it just in case you see anything you could use.

    EDIT: Added code to randomly change the color of some of the spheres.

    EDIT: Modified the code. It now runs at approx 56 FPS on iPad Air with 320 spheres.

    EDIT: Added the white verticle posts, FPS now about 52.

    supportedOrientations(LANDSCAPE_ANY)
    
    function setup()
        FPS=60
        parameter.integer("ex",-100,1000,150)
        parameter.integer("ey",-100,1000,30)
        parameter.integer("ez",-100,1000,160)  
        -- creare x,y,z 3d sphere coordinates
        tab={}
        lim=13
        for n=0,lim do
            tab[n]={}
            for m=0,lim do
                x=math.sin(math.pi*m/lim)*math.cos(2*math.pi*n/lim)
                y=math.sin(math.pi*m/lim)*math.sin(2*math.pi*n/lim)
                z=math.cos(math.pi*m/lim)
                tab[n][m]=vec3(x,y,z)
            end
        end    
        -- create sphere triangles using x,y,x coord.
        sph={}
        for n=0,lim-1 do
            for m=0,lim-1 do
                table.insert(sph,tab[n][m])
                table.insert(sph,tab[n][m+1])
                table.insert(sph,tab[n+1][m+1])  
                table.insert(sph,tab[n][m])
                table.insert(sph,tab[n+1][m])
                table.insert(sph,tab[n+1][m+1])
            end
        end    
        sphere=mesh()
        sphere.vertices=sph
        sphere:setColors(255,0,0,255)
    
        sphere1=mesh()
        sphere1.vertices=sph
        sphere1:setColors(255,255,0)
        cnt=10
    end
    
    function rnd()
        cnt=cnt+1
        if cnt<5 then
            return
        end
        cnt=0   
        col={} 
        for x=1,4 do
            col[x]={}
            for y=1,30 do
                col[x][y]={}
                for z=1,4 do
                    col[x][y][z]={}
                    col[x][y][z]=0
                    if math.random(100)>80 then
                        col[x][y][z]=1
                    end               
                end
            end
        end
    end
    
    function draw()  
        rnd()
        background(40, 40, 50)    
        FPS=0.9*FPS+0.1/DeltaTime
        text(FPS//1,WIDTH/2,HEIGHT-10)
        perspective()
        camera(ex,ey,ez,0,0,0)  
    
        -- draw base
        fill(86, 161, 231, 255)
        stroke(255)
        strokeWidth(1)
        pushMatrix()
        rotate(90,1,0,0)
        rect(20,20,110,110)
        popMatrix()  
    
        -- draw lines on base
        pushMatrix()
        rotate(90,1,0,0)
        stroke(0)
        translate(0,0,-1)
        for x=30,120,30 do
            for y=30,120,30 do
                line(x,30,x,120)
                line(30,y,120,y)
            end
        end
        popMatrix()   
    
        -- draw verticle posts
        stroke(255)
        for x=1,4 do
            for y=1,4 do
                pushMatrix()
                translate(x*30,0,y*30)
                line(0,0,0,80)
                popMatrix()
            end
        end
    
        -- draw multiple spheres
        stroke(255)
        for x=1,4 do
            for y=1,20 do
                for z=1,4 do
                    pushMatrix()
                    translate(x*30,y*4,z*30)
                    if col[x][y][z]==1 then
                        sphere1:draw()
                    else
                        sphere:draw()
                    end
                    popMatrix()
                end
            end
        end
    end
    
  • Posts: 386

    Thanks @dave1707 that is very nice. You confirm that the real spheres really hurt the fps. I am pretty sure that i will be obliged to adopt the 2D sphere trick suggested by @Ignatz in which the plane of the 2D circle is rotated to face the camera.

    @Ignatz,@dave1707, rotating the plane of the circle was easy when the camera moved in the horizontal plane around the 2D sphere. Once the camera moves out of the plane it gets trickier to rotate the 2D sphere to face the camera. If the camera location, the sphere centre and the normal vector of the 2D sphere are known, how do i figure out the euler angles which need to be applied?

  • IgnatzIgnatz Mod
    edited May 2016 Posts: 5,396

    @piinthesky - you are starting to need quite a few 3D techniques, that you will find in my "3D in Codea" ebook here

    https://www.dropbox.com/sh/mr2yzp07vffskxt/AACqVnmzpAKOkNDWENPmN4psa

    Look for Billboards on p.24 (they are 2D objects rotated to face the camera)

    I suggest you skim the whole book, it contains everything I learned while programming 3D in Codea

  • IgnatzIgnatz Mod
    edited May 2016 Posts: 5,396

    @piinthesky - this code should handle the rotation for you. Rotating toward a point in three dimensions is tricky, but I've provided a very efficient little function to do it for you.

    EDITED to include more balls and test FPS. I can get over 200 balls before FPS drops below 60, on my Air 2. Code below has 216 balls.

    function setup()
        ball=MakeBall()
        camPos=vec3(0,0,200)
        --circles table stores position and size and colour of each ball
        circles={}
        local n=6 --total number of balls is the cube of this figure
        local u=0
        for i=0,n do
            for j=0,n do
                for k=1,n do
                    u=u+1
                    local c=color(math.random(0,255),math.random(0,255),math.random0,255)
                    circles[u]={vec3(i*10-25,j*10,-k*10),0.03+math.random()*0.03,c}
                end
            end
        end
        --camera movement
        camPos=vec3(0,10,100)
        parameter.integer("cx",-200,200,0) --manual camera controls
        parameter.integer("cy",-50,50,10)
        parameter.integer("cz",-200,200,100)  
        FPS=60
    end
    
    function draw()
        background(120)
        FPS=0.9*FPS+0.1/DeltaTime
        perspective()
        camPos=vec3(cx,cy,cz)
        camera(cx,cy,cz,0,50,-50)
        table.sort(circles,function(a,b) return a[1]:dist(camPos)>b[1]:dist(camPos) end)
        for i=1,#circles do
            pushMatrix()
            local c=circles[i]
            --translate(c[1]:unpack())
            translate(c[1].x,c[1].y,c[1].z)
            modelMatrix(LookAtMatrix(c[1],camPos))
            scale(c[2],c[2])
            ball:setColors(c[3])
            ball:draw()
            popMatrix()
        end
        --revert to 2D to write on screen
        ortho()
        viewMatrix(matrix())
        fill(255)
        text("FPS = "..math.floor(FPS),50,HEIGHT-50)
    end
    
    function LookAtMatrix(source,target,up)
    local Z=(source-target):normalize()
    up=up or vec3(0,1,0)
    local X=(up:cross(Z)):normalize()
    local Y=(Z:cross(X)):normalize()
    return matrix(X.x,X.y,X.z,0,Y.x,Y.y,Y.z,0,Z.x,Z.y,Z.z,0,source.x,source.y,source.z,1)
    end
    
    function MakeBall()
        --build the ball image, you will probably want to import a good image instead
        local r=100
        local base=150
        local spread=0.7
        local img=image(r*2,r*2)
        setContext(img)
        fill(base)
        ellipse(r,r,r)
        local p=vec2(1,1)*(r+r*0.1)
        for i=r*spread,1,-1 do
            c=(255-base)*(1-i/r/spread)^1.2+base
            fill(c)
            --fill(255)
            ellipse(p.x,p.y,i)
        end
        setContext()
    
        --create the mesh
        local m=mesh()
        m:addRect(0,0,r,r)
        m.texture=img
        return m
    end
    
    
    
  • IgnatzIgnatz Mod
    Posts: 5,396

    @piinthesky - edited above code to test speed

  • Posts: 386

    @ignatz wonderful, you are a scholar and a gentleman!

  • IgnatzIgnatz Mod
    Posts: 5,396

    3D is tough!

  • IgnatzIgnatz Mod
    Posts: 5,396

    @piinthesky - If you need more speed, you can optimise a bit. For example, you only need to sort the circles when they get out of order. Unless you are moving the camera very quickly, you probably only need to do this once or twice a second, so you can use a counter like so

    --in setup
    timer=0
    
    --in draw
    if math.fmod(timer,30)==0 then --twice a second
        --sort
    end
    timer=timer+1
    
  • dave1707dave1707 Mod
    edited May 2016 Posts: 7,612

    @piinthesky I modified my code above. It runs at about 56 FPS with 320 spheres. The spheres don't look as good as @Ignatz , but I increased my speed.

    EDIT: Changed my code again to add the white verticle posts back. FPS now about 52.

  • IgnatzIgnatz Mod
    Posts: 5,396

    Mine does nearly 500 before slowing down, but I do have a slightly faster iPad, and I didn't draw posts

  • Just an update on my efforts to make a 3D neutrino event display with Codea. This time for the KM3NeT telescope with 116 detection strings....

    To keep the fps high, in the end i used full 3d spheres (not billboarding), but only display the spheres close to camera. Very happy with the performance.

Sign In or Register to comment.