Howdy, Stranger!

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

beta 1.3

12357

Comments

  • Posts: 447

    fyi drawMesh(nil) freezes the app

  • BortelsBortels Mod
    Posts: 1,557

    @Simeon - Ah, meshtest adds more light.

    You can only have one texture - but each triangle (or rect) can have an offset into that texture. So - you could do a sprite sheet, and do rects that are subsets of the whole sprite sheet, and each rect would then have, in effect, it's own texture (or at least a texture that is chosen from the sprites available in the spritesheet). I did that here http://home.bortels.us/public/lots4.html in javascript some time ago - nice to see some of the techniques will transfer over. (underscores need for us to be able to import graphics...)

    Is there a limit to the texture size imposed by OpenGL? (I mean - I know there is, I'm just wondering what).

  • JohnJohn Admin Mod
    Posts: 643

    As far as I know, 2048x2048 is the limit for iPad. Could be wrong though, since i haven't tried making a bigger one.

  • SimeonSimeon Admin Mod
    Posts: 5,708

    2048x2048 is the texture limit. Not sure if you can reach that in Codea – I haven't tried.

  • JohnJohn Admin Mod
    Posts: 643

    Beat you

  • BortelsBortels Mod
    Posts: 1,557

    Heh - now I'm back to GLSL. I want a fast way to operate on vertices. :-)

    Here's the line of thought - I've been playing with scenegraphs, consisting of nodes that do:

    pushMatrix()
    transform()
    rotate()
    scale()
    possibly sprite()
    draw child nodes
    popMatrix()
    

    Each child is just another scenegraph node. So - I can do things like a map with vehicles with turrets, or planets with ship orbiting, and so on. Each node is at least transform/scale/rotate, a sprite draw, and bookkeeping.

    It seems with a mesh, I can fold the sprite operations to a single call (for some restrictions on sprites) - but I'm still going thru the same transform/rotate/scale activity (that's what I'm waiting on matrices to speed up). I suspect rotate(), which I assume translates to a single matrix operation beneath, is going to be faster than me modifying three vertices in a mesh table by using trig (scale and transform being faster in my case because no trig functions are involved - I assume underneath them all for you are just matrix math).

    Just trying to figure out if the savings in composite time offsets the extra math work. Am I missing a sweet shortcut of some sort because this is so new?

  • BortelsBortels Mod
    Posts: 1,557

    Mind you - what I'm doing so far with the above, just with sprite draws, is actually surprisingly fast. But if I can up the number of objects drawn from tens to hundreds or thousands, things like particle effects (smoke, and exhaust, and vapor trails) become possible.

  • SimeonSimeon Admin Mod
    Posts: 5,708

    We've just been discussing something @ruilov touched on - transparent batching of sprites by Codea. It actually seems pretty feasible for us to batch repeated calls to sprite() into a single internal "mesh". I think this will be a goal for us in 1.3.1.

  • BortelsBortels Mod
    Posts: 1,557

    Is the restriction of a single sprite for a given mesh() a mesh() restriction, or is it of the underlying OpenGL layer? I'll be blunt - My OpenGL experience, where I was actually writing code, is heinously out-of-date (OpenGL 1.2, years ago). So I'm finding a ton of what I "know" turns out to be wrong, usually in a delightful way, but there you go.

  • SimeonSimeon Admin Mod
    edited January 2012 Posts: 5,708

    It's an OpenGL limitation. OpenGL is basically a big state machine, so when you submit data to be drawn by OpenGL it must use the current state. Changing the currently "bound" texture requires a state change, so in order to draw with different textures you must change the state and submit a new draw call to OpenGL.

    One way many game developers get around this is by "atlasing" all your textures into a single, large, image. Then you can submit many polygons to OpenGL in one go and use the texture coordinates of the vertices to specify which section of the "atlas" to use.

    I'm actually considering having Codea use an atlas-packing algorithm internally on the built-in sprite packs so that all sprites from the same pack are drawn in one go (unless you do something in-between that causes a state change, such as rendering an ellipse which changes the currently "bound" shader program).

    Edit: you can actually create your own atlases now with setContext() and mesh. Especially if you have a "scene" with a lot of sprites that you want to render over and over again.

  • BortelsBortels Mod
    Posts: 1,557

    Yeah - the atlas was what I was refering to by "sprite sheet" above.

  • Posts: 447

    I must say I haven't really got meshes. I mean I figured out the API you provided, and that is fast, but I don't understand why.

    What is it good for? Drawing lots of sprites that are the same? Drawing lots of images that are the same? If I draw a draw a mesh into an image (w/ setContext) and then draw that image is it still fast? Faster then if I hand constructed the image pixel by pixel and then drew the image?

  • SimeonSimeon Admin Mod
    edited January 2012 Posts: 5,708

    I'm sure @John will answer before me but you can do the following with meshes:

    • Draw lots of instances of objects that use the same texture, or portion of the same texture in a "batch". This is much faster because the overhead of submitting geometry to the GPU only happens once.
    • Draw any polygonal structure you wish, as long as you can compose it out of triangles
    • Assign "tint" or "fill" colours not just to the entire object, but to individual vertices and have those tint and fill colours linearly interpolate across vertices
  • JohnJohn Admin Mod
    Posts: 643

    Ok, so a mesh is just a list of triangles, thats it. The addRect and setRect methods are just convenience functions for instancing a large number of sprites. The underlying data structures are just more efficient and more in line with what the GPU is actually doing. The intent of the mesh api is to allow people to draw triangles as quickly as possible, and that allows lots of interesting things to be created.

    It doesn't matter how you draw an image, that image will always take roughly the same amount of time to render. A mesh is just always drawing triangles, which may or may not be faster, depending on the number of triangles being drawn and the number of pixels being filled

    You might think that having a single texture is too limiting for it to be useful, but if you use an atlas you can easily have multiple sprites in a single texture, which you can display on individual rects by using setRectTex with the appropriate texture coordinates.

    The sprite command is much nicer to work with because it just works without any setup required, but its also very inefficient in terms of GPU utilisation. We are looking into ways of making it faster internally, which would alleviate a lot of problems for people but for now the mesh api allows for flexible and fast drawing of arbitrary geometry, for those who want it.

  • BortelsBortels Mod
    Posts: 1,557

    They're a hard concept to wrap your head around.

    In a nutshell - you can give the GPU a list of triangles (or rectangles, which are really 2 triangles next to each other), and you can tell it the colors of the corners (in which case it'll shade evenly), or a texture (which can be part of a larger texture, so you can have different sprites on them), and then you can with one operation say "draw it" - so hundreds of sprite() calls can turn into one very fast drawing operation.

    The difference between that and just compositing to an image is that you can then modify the positions of the sprites (or colors), and say "draw it", and boom done - whereas with an image() you composited to with setContext(), you'd have to redraw them all again.

    They're best suited, right now, for large collections of identical objects - a particle fountain being a really good example. So - fireworks, smoke, vapor trails, that sort of thing. They can also work well with a drawing many nearly identical things - say, for example, a super mario brothers level, where there are only a few tens of types of sprites.

    But I say as I type this: @simeon and @john, I notice the drawmesh command can't take an x or y offset. I guess I can just transform() first, right? I'll try it out. The idea with a platformer level is you could draw it all out, then just modify your transform() to scroll the level around.

    Later on, as we get more opengl access, we should be able to do even more to meshes - transform the vertices on the GPU itself with GLSL, alter shading, and so on. This is just a taste.

  • BortelsBortels Mod
    Posts: 1,557

    @Simeon also points out something really handy - the mesh() is our much-asked for "how do we draw arbitrary filled polygons?". use a mesh. Set a fill, give the points, bam. And really fast, too.

  • JohnJohn Admin Mod
    edited January 2012 Posts: 643

    That reminds me, I was thinking of adding this:

    -- when set to true, any rect added with addRect or set with setRect
    -- is transformed by the current transform on the stack 
    myMesh.useTransform = true
    myMesh:addRect(width, height)
    
  • SimeonSimeon Admin Mod
    Posts: 5,708

    @Bortels at the moment meshes do not respect the current transform matrix. This makes sense as you are just adding vertexes to a data structure. The X,Y you specify should be the X,Y that gets put in. So the property @John mentions above would allow added vertices to be automatically multiplied by the current transform matrix.

  • BortelsBortels Mod
    Posts: 1,557

    Hmmm - so I can't transform() to scroll a larger-than-the-screen mesh? Boo! We need that - Anything we can do to avoid having to traverse the table in lua will be a good thing.

    Wait - I need to clarify. I'm not talking about on adding - I'm talking on draw. Do the following bits of code draw the mesh at the same place on-screen?

    mesh(m)
    

    and

    transform(WIDTH/2, HEIGHT/2)
    mesh(m)
    

    ???

    I guess I could not be lazy and try, eh? :-)

  • JohnJohn Admin Mod
    Posts: 643

    You can transform the mesh itself like it were any other thing you can draw in Codea. What simeon mentioned is that when you add something to it via addRect, the rectangle itself isn't transformed by the current transform matrix.

  • SimeonSimeon Admin Mod
    Posts: 5,708

    Yeah, sorry @Bortels - I meant that addRect and other convenience functions do not respect the transform stack. drawMesh() does respect it.

  • BortelsBortels Mod
    Posts: 1,557

    Heh - closer inspection of the mesh example showed that.

    Hmm. I don't see a big need to transform a rect on adding - it might be convenient, but it's not a giant necessity. What would really be handy are ways to apply a transform to arbitrary triangles (or rects) already in the mesh.

    Abstracting out, and pretending like addRect does that it's not a list of triangles but of rectangles, some way to transform/scale/rotate rect N in the mesh would be handy. Again - we can do it ourselves, but I think this sort of thing will be many orders of magnitude more common than adding a rect.

    Having said that - for the purposes in mind now, just being able to transform the mesh as a whole is sufficient.

  • Posts: 2,161

    @Simeon also points out something really handy - the mesh() is our much-asked for "how do we draw arbitrary filled polygons?". use a mesh. Set a fill, give the points, bam. And really fast, too.

    Once someone implements a triangulation algorithm ...

    I'm playing around with this, and perhaps was a bit ambitious on my first go as I seem to have crashed Codea (too many print statements, nothing serious)! Now that I've reduced the numbers a little, I still can't get anything to draw on the screen. I have some set-up routine that defines a whole slew of coordinates. Then I take those coordinates and from them generate a load of triangles. I add these triangles to the mesh.vertices table via table.insert(mesh.vertices,vertex), then call setColor to set them all to the same colour, and finally drawMesh in the draw routine to draw them. Result: ingen ting. What am I doing wrong there?

    ...

    Hmm, further experimenting reveals that one can't treat the vertices as a table for writing (though it seems to work with reading). Remedy seems to be to set up a temporary table, add to that, then set the vertices accordingly.

    Since mesh can take vec3 objects, might I suggest that we now be able to do basic arithmetic on vec3 objects? It would be far more convenient to be able to write x = o + y and similar.

  • BortelsBortels Mod
    Posts: 1,557

    "Once someone implements a triangulation algorithm" - uh...

    Implementing a triangulation algorithm is left as an exercise to the reader. OH YEAH.

    I might take a whack at it. It's good to have something to keep me humble.

    There's a wikipedia page for it, how hard could it be, right? ha...

  • Posts: 2,161

    Torus drawn with Codea

    Oh yes! This is ab-so-!@)(#&$-lutely amazing.

  • Posts: 2,161

    (If I take out a few unnecessary bits of stuff, that comes in under 100 lines of code.)

  • BortelsBortels Mod
    Posts: 1,557

    Neat! Clean it up and share the code, please. Or don't clean it up. I was pretty sure we'd be able to squeeze 3D (or pseudo-3D) out of this...

  • Posts: 2,161



    -- Use this function to perform your initial setup function setup()     print("Hello World!")     torus = {}     innerR = 70     outerR = 200     step = 10     local origin = vec3(WIDTH/2,HEIGHT/2)     local tilt = math.pi/6     local k     local zmax,zmin = 0,0     for i = 0,359,step do         for j = 0,359,step do             local x,y,z             local w = {}             for l = 1,4 do                 k = l - 1                                  x =                  (outerR                   + innerR * math.cos(math.rad(j + math.floor(k/2) * step)))                 * math.cos(math.rad(i + (k%2) * step))                                  y =                  innerR * math.sin(math.rad(j + math.floor(k/2) * step))                 z =                  (outerR                   + innerR * math.cos(math.rad(j + math.floor(k/2) * step)))                 * math.sin(math.rad(i + (k%2) * step))                 w[l] = vec3(origin.x + x,                     origin.y + math.cos(tilt)*y - math.sin(tilt) *z,                     origin.z + math.cos(tilt)*z + math.sin(tilt) *y                     )                 zmax = math.max(zmax,w[l].z)                 zmin = math.min(zmin,w[l].z)             end             wz = (w[1].z + w[2].z + w[3].z + w[4].z)/4             table.insert(torus,{w,wz})                      end     end     table.sort(torus,function(a,b) return a[2] < b[2] end)     torusmesh = mesh()     local vertices = {}     local colours = {}     local n = 0     local a     local m = 255/(zmax - zmin)     local c = -m*zmin     for k,v in ipairs(torus) do         table.insert(vertices,v[1][1])         a = m*v[1][1].z + c         table.insert(colours,color(a,a,a,255))         table.insert(vertices,v[1][2])         a = m*v[1][2].z + c         table.insert(colours,color(a,a,a,255))         table.insert(vertices,v[1][3])         a = m*v[1][3].z + c         table.insert(colours,color(a,a,a,255))         table.insert(vertices,v[1][2])         a = m*v[1][2].z + c         table.insert(colours,color(a,a,a,255))         table.insert(vertices,v[1][3])         a = m*v[1][3].z + c         table.insert(colours,color(a,a,a,255))         table.insert(vertices,v[1][4])         a = m*v[1][4].z + c         table.insert(colours,color(a,a,a,255))         n = n + 1     end     --[[     vertices = {vec2(100,100),vec2(200,100),vec2(100,200),                 vec2(200,100),vec2(200,200),vec2(100,200)}                 --]]     torusmesh.vertices = vertices     torusmesh.colors = colours     print(n)     --torusmesh.vertices = {vec2(100,100),vec2(200,100),vec2(100,200)}     n = 0     for k,v in ipairs(torusmesh.vertices) do         n = n + 1     end     print(n)     --torusmesh:setColors(255,255,255,255)     if torusmesh.valid then     print("hello")     end end -- This function gets called once every frame function draw()     -- This sets a dark background color      background(40, 40, 50)     -- This sets the line thickness     strokeWidth(5)     drawMesh(torusmesh)     -- Do your drawing here      end
  • Posts: 2,161

    Not all that pretty, but get's the job done. You can see a few bits of experiments left lying around in the comments. Also, I tried testing torusmesh.valid but it wasn't. As it drew anyway, I wasn't all that concerned.

    Part of this was to test what order the triangles were rendered in. I didn't do a full test as I suspected "first to last" and that worked, so I guess that's the case. Knowing this is quite important where the triangles can overlap.

  • SimeonSimeon Admin Mod
    Posts: 5,708

    Wow, @Andrew, that's really impressive! By the way I noticed you had two crashes (in the TestFlight crash reports). Do you remember where they occurred?

    By the way: we are thinking to change drawMesh(m) to mesh:draw(). Using drawMesh() initially was my idea but I'm finding that it looks kind of ugly and out of place with all the other drawing functions.

  • BortelsBortels Mod
    Posts: 1,557

    Making it consistent makes sense.

    Just noticed that the table-of-contents in the help shows "mesh.clear" (for example) but the actual call uses a colon. The detailed examples are correct.

    Thank you for posting the code, Andrew - it took me a bit, but I followed what you did.

  • Posts: 2,161

    Ah, I didn't know that you got automatic reports. I guess that makes sense! I'll take more notice of when it crashes and let you know the circumstances - what information would be useful to have?

    Were the reported crashes close together and of the order of an hour ago? If so, they were caused by putting a print statement in the routine that generated the vertices in the above code. As I was getting no output whatsoever I stuck a print in to see what was getting called. However, the loop was generating something of the order of 1000 points so Codea "froze" (or just took so long to do anything that I gave up waiting). I exited Codea and then relaunched, whereupon it crashed again (probably because I simply exited by hitting the home button so didn't really exit at all). After that, it was fine.

    If either of the crashes is older, it probably relates to trying to launch a program where there are files missing from the Info.plist. I've done that a couple of times and Codea doesn't like it.

  • Posts: 2,161

    I prefer mesh:draw() as well.

  • SimeonSimeon Admin Mod
    Posts: 5,708

    @Andrew ah that makes sense. It was complaining about an index being out of bounds in an array of size 11. So that points to something small, like file loading.

  • Posts: 2,161


    displayMode(FULLSCREEN) function setup()     torus = Torus({         innerRadius = 100,         outerRadius = 200,         step = 15     }) end function draw()     background(40, 40, 50)     strokeWidth(5)     tilt = math.atan2(Gravity.z,Gravity.y)     torus:update({tilt = tilt})     drawMesh(torusmesh) end
  • Posts: 2,161

    Torus = class() function Torus:init(t)     t = t or {}     self.step = t.step or 20     self.innerR = t.innerRadius or 50     self.outerR = t.outerRadius or 100     self:initialise() end function Torus:draw()     drawMesh(self.mesh) end function Torus:initialise()     local torus = {}     local innerR = self.innerR     local outerR = self.outerR     local step = self.step     local k     for i = 0,359,step do         for j = 0,359,step do             local x,y,z             local w = {}             for l = 1,4 do                 k = l - 1                                  x =                  (outerR                   + innerR * math.cos(math.rad(j + math.floor(k/2) * step)))                 * math.cos(math.rad(i + (k%2) * step))                                  y =                  innerR * math.sin(math.rad(j + math.floor(k/2) * step))                 z =                  (outerR                   + innerR * math.cos(math.rad(j + math.floor(k/2) * step)))                 * math.sin(math.rad(i + (k%2) * step))                 w[l] = vec3(x,y,z)             end             table.insert(torus,w)         end     end     self.vertices = torus     self.mesh = mesh() end function Torus:update(t)     t = t or {}     local origin = t.origin or vec3(WIDTH/2,HEIGHT/2,0)     local tilt = t.tilt or math.pi/6     local torus = {}     local c = math.cos(tilt)     local s = math.sin(tilt)     local zmax = 0     local zmin = 0     for k,v in ipairs(self.vertices) do         local x = {}         local z = 0         for l,u in ipairs(v) do             x[l] = vec3(origin.x + u.x,                         origin.y + u.y * c - u.z * s,                         origin.z + u.y * s + u.z * c)             z = z + x[l].z         end         z = z/4         zmax = math.max(zmax,z)         zmin = math.min(zmin,z)         table.insert(torus,{x,z})     end     table.sort(torus,function(a,b) return a[2] < b[2] end)     torusmesh = mesh()     local vertices = {}     local colours = {}     local a     local m = 255/(zmax - zmin)     local c = -m*zmin     for k,v in ipairs(torus) do         table.insert(vertices,v[1][1])         a = m*v[1][1].z + c         table.insert(colours,color(a,a,a,255))         table.insert(vertices,v[1][2])         a = m*v[1][2].z + c         table.insert(colours,color(a,a,a,255))         table.insert(vertices,v[1][3])         a = m*v[1][3].z + c         table.insert(colours,color(a,a,a,255))         table.insert(vertices,v[1][2])         a = m*v[1][2].z + c         table.insert(colours,color(a,a,a,255))         table.insert(vertices,v[1][3])         a = m*v[1][3].z + c         table.insert(colours,color(a,a,a,255))         table.insert(vertices,v[1][4])         a = m*v[1][4].z + c         table.insert(colours,color(a,a,a,255))     end     torusmesh.vertices = vertices     torusmesh.colors = colours     self.mesh = torusmesh end
  • edited January 2012 Posts: 2,161

    That was an itch I just had to scratch. With step size of 10 then the delay gets appreciable (that's 36*36*2 = 2592 triangles), but not too bad. Step size of 15 is fine, I think, both on speed and how it looks. Almost certainly I should be able to simplify the code and condense the loops down a bit - just haven't tried yet.

    In FULLSCREEN (ie with the buttons) then the buttons are a bit difficult to see when against a dark background. Maybe they should be outlined in something a bit lighter?

  • JohnJohn Admin Mod
    Posts: 643

    Reminds me of those Magic Eye solution pictures.

  • Posts: 2,161

    Hmm, 3D effects via Magic Eye ...

  • Posts: 447

    Thanks for the explanations everyone. Andrew that's impressive!

    An atlas sounds like a cool tricky, indeed make one for each sprite pack and boom.

  • SimeonSimeon Admin Mod
    Posts: 5,708

    Build 13 is going up - note that it will break existing mesh code due to the removal of drawMesh(). Use mesh:draw() from now on.

    @Andrew it includes the orientation notifications you mentioned.

    Here's an example by @John of using the new triangulate() command to generate a list of triangles given a list of points: http://twolivesleft.com/Codea/Projects/Triangulate.codea

  • BortelsBortels Mod
    Posts: 1,557

    Andrew's code above is an example of what I was trying to articulate - he's built a mesh already, what we need is something like

    Mesh:transform(rotate, 0, 15, 0) -- rotate y 15 degrees
    

    He's essentially rebuilding the mesh from scratch every iteration, even though the actual geometry isn't changing.

    So - mesh is awesome, but we will need tools to manipulate the mesh as a whole without having to iterate thru it point by point. GLSL is one way, but even hard coding some effects like rotate would be super.

  • BortelsBortels Mod
    Posts: 1,557

    Triangulate code is fun, and easily can crash codea. Not complaining, just reporting.

  • Posts: 2,161

    What's the actual triangulation algorithm? It seems to assume that the polygon is simple (no self-intersections) and, more seriously, that the vertices are ordered clockwise. Not only is that the wrong way around ...

  • SimeonSimeon Admin Mod
    Posts: 5,708

    I believe it's the Box2D triangulation algorithm.

    By simple, you mean convex? Or no holes? I think it expects the latter.

  • JohnJohn Admin Mod
    Posts: 643

    The triangulation uses a basic unoptimised Ear Clipping algorithm that comes with Box2D. I think its meant to be counter-clockwise but because it assumes coordinates are from top to bottom. If you know of a good library that is easy to use, I can look at using that instead.

  • BortelsBortels Mod
    Posts: 1,557

    The only other example code I could find (from the Love2D collision library) seems to have the same limitations:

    https://github.com/vrld/HardonCollider/blob/master/polygon.lua

    The real issue is me, the user - I'm not tapping points in any specific order in the demo :-)

  • Posts: 447

    physics crash: if you do something like physics.body(POLYGON,0,0,10,10) it crashes codea

  • SimeonSimeon Admin Mod
    Posts: 5,708

    It definitely needs extra checking, thanks for pointing these out. Any time you can crash Codea with malformed code, please let us know so we can fix the offending functions.

  • edited January 2012 Posts: 447

    yeah better on the beta..

    another one: physics.gravity() settings seems to persist across runs where I press the reset button on the lower left corner. If I exit back to the code editor, then it goes back to the default value.

    to clarify: if I do physics.gravity(physics.gravity()*2) then if I keep pressing reset, the gravity strength keeps doubling.

Sign In or Register to comment.