3D dress-up game?

@Ignatz it’s your rotation code rotating your mesh and using your texture-drawing code, and it works great, so I’d say you got some things right!

Update: still working on this, making incremental improvements. Thanks to @Ignatz, I am displaying multiple meshes that I can draw on with my finger. Frame rate is really low, but I’m working on it.

I’m surprised your frame rate is low, unless it’s been low all along. Is it related to the number of meshes, or anything you can identify?

This is why I was asking about the number of vertices. You can check it quite easily, just find the part of the code where the vertices table is assigned m.vertices = verts or whatever and add print("vertices:", #verts).

If you have too many, you can lower the poly count in Blender with a decimate modifier, or by removing parts of the mesh that you can’t see (eg sometimes you see character rigs with inner mouths, teeth etc, for doing facial animation, which you probably don’t need)

Or m.size

@Ignatz, @Yojimbo: there has to be something more fundamental I’m doing wrong, because I’m seeing frame rates around 15fps while simply displaying two textured squares. Which is, what, eight vertices total?

I’m doing a bit of ground-up re-inspection of my code to try and find the problem.

I will say this though: math.floor() seems to realllly slow stuff down.

I doubt it’s math.floor. You could try integer divide //1

We need more detail. Can you post some code, or pseudo code, to give us an idea of the work Codea is doing?

PS a square is 6 vertices (2 triangles), so two of them is 12 vertices. You should have no problem drawing thousands at 60 FPS, so something else is causing it (and I doubt it’s math.floor).

The code is pretty much the same as the code I already posted above, but I do want to do a little more digging of my own before I post the current state of it. I think, after all, @Yojimbo2000 may have hit the nail on the head in warning me about mixing up 2-D and 3-D rendering orders

Yes, you should do your 2D drawing before or after 3D

@Ignatz I may be measuring FPS wrong. I grabbed the code I’m using from your tilt-racer project. I may be deploying it wrong.

--globals.FPS defined as 60 in setup

function draw()  
    backingMode(STANDARD)
    background(69, 119, 104, 255)
    globals.FPS=globals.FPS*0.9+.1/DeltaTime
    text("FPS: "..math.floor(globals.FPS),25,HEIGHT-25)
end

With nothing else going on at all, it shows FPS 15 or 16 most of the time. I must be doing it wrong.

I added this profiler to the drawing to texture code above:

profiler={}

function profiler.init(silent)    
    profiler.del=0
    profiler.c=0
    profiler.fps=0
    profiler.mem=0
    if not silent then
        parameter.watch("profiler.fps")
        parameter.watch("profiler.mem")
    end
end

function profiler.draw()
    profiler.del = profiler.del +  DeltaTime
    profiler.c = profiler.c + 1
    if profiler.c==10 then
        profiler.fps=profiler.c/profiler.del
        profiler.del=0
        profiler.c=0
        profiler.mem=collectgarbage("count", 2)
    end
end

I got 45 fps when not touching, dropping to around 22-25 fps when touching. I’m on an iPad Air. That does seem way, way lower than it should be. You really want this running at 60 fps.

Also, in the code above, it seems as if you have separated the 2d and 3d drawing?

I haven’t fully pulled this apart, but based on what I’ve done previously with drawing to offscreen buffers (eg the Gaussian blur shader I ported), I would make the offscreen buffer image as small as you can make it. Given that the touch event only has an accuracy of 0-255, overlayImage does not need to be 2000x1400 pixels, does it? (remember that Codea automatically converts all pixels to retina coordinates). It should just be 255 by 255. So when you draw ellipses to it, divide the coords of the touch by 4. (Or, given the retina-ization, 128x128, and divide by 8)?

Also, why is the buffer image WIDTH x HEIGHT if you only draw to a 10x10 section of it? It could just be 10x10 pixels.

Is there any need to clip? Just make the image the size you need and translate.

With the Gaussian blur shader, quartering the size of the offscreen buffer made a big difference to the speed

Here’s a version that quarters the size of the two buffers. When not touching, it’s still 45 fps, but it doesn’t drop quite so much when you do touch. Not as big a saving as I’d hoped for. The overlay image can probably go down to /8, although the overlay sprite would look a bit weird.

-- MakePlane

function setup()
    textMode(CORNER)
  --  noStroke()
    setUpTests()
    makePlane = MakePlane()
    texture = makePlane.plane.mesh.texture
    colorCoordinates = {}
    overlayImage = image(WIDTH/4, HEIGHT/4)
    setUpBufferDisplay()
    profiler.init()
end

function setUpBufferDisplay()
    bufferImage = image(WIDTH/4, HEIGHT/4)
    local bufferFract = 1 / 6
    cameoSize = vec2(WIDTH * bufferFract, HEIGHT * bufferFract)
    camoPos = vec2((cameoSize.x/2)+20, (cameoSize.y/2)+20)
    setContext(bufferImage) --fill image with yellow for visibility
    strokeWidth(0)
    fill(220, 210, 122, 255)
    rect(0,0,WIDTH,HEIGHT)
    setContext()
end

function setUpTests()
    local parameterTable = {} --series of buttons to confirm visuals
    local assignableAction = function() end
    local nextParameter = function ()
        parameter.clear()
        if #parameterTable == 0 then
            return
        end
        local testDefinition = parameterTable[1]
        local passAction = function()
            print(testDefinition..": PASS")
            assignableAction()
        end
        local failAction = function()
            print(testDefinition..": FAIL")
            assignableAction()
        end
        parameter.action("PASS if "..testDefinition, passAction)
        parameter.action("FAIL", failAction)
        table.remove(parameterTable, 1)
    end
    assignableAction = nextParameter
    parameterTable = {"plane is showing", "touching draws red dots", "dragging leaves trail", "touch activates shader", "buffer image in corner", "clipping drawn in buffer", "mesh clips in buffer", "buffer perspective matches", "buffer coordinates read", "touch draws to texture", "draws in right place", "all touches draw"}
    nextParameter()
end

function draw()
    backingMode(STANDARD)
    background(69, 119, 104, 255)
    drawPlane()
    draw2D()
    profiler.draw()
end

function drawPlane()
    pushMatrix()
    makePlane:draw()
    popMatrix() 
end

function draw2D()
    ortho()
    viewMatrix(matrix())
 --   sprite(overlayImage, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    sprite(bufferImage, camoPos.x, camoPos.y, cameoSize.x, cameoSize.y)
    if #colorCoordinates > 0 then
        drawTouchesToTexture()
    end
end

function drawTouchesToTexture()
    local textureX, textureY, baseX, baseY, marker, intCoord, fX, fY
    textureX, textureY = 0, 0
    for i in ipairs(colorCoordinates) do
        coord = colorCoordinates[i] * 0.25
        intCoord = vec2(math.floor(coord.x), math.floor(coord.y))
        baseX, baseY, marker = bufferImage:get(intCoord.x, intCoord.y)
        if marker ==  1 then
            fX, fY = baseX / 255, baseY / 255
            textureX = texture.width * fX
            textureY = texture.height * fY
            setContext(texture)
            fill(0, 43, 255, 255)
            ellipse(textureX, textureY, 25)
            setContext()
        end
    end
    text("x: "..textureX..", y: "..math.floor(textureY, 10, 10))
    colorCoordinates = {}
end

function touched(touch)
    setContext(overlayImage)
    fill(255, 4, 0, 255)
    ellipse(touch.x/4, touch.y/4, 3)
    setContext()
    if touch.state == MOVING then
        toggleShader(true)
        drawClipToBufferPosition(touch)
        table.insert(colorCoordinates, vec2(touch.x, touch.y))
    else
        toggleShader(false)
    end
end

function drawClipToBufferPosition(touch)
        setContext(bufferImage)
        pushMatrix()
        clip((touch.x*0.25) - 10, (touch.y*0.25) - 10, 20, 20)
        makePlane:draw()
        popMatrix()
        setContext()
end

function toggleShader(onOrOff)
    if onOrOff == true then
        local planeShader = shader(PlaneShader.v, PlaneShader.f)
        makePlane.plane.mesh.shader = planeShader
    else 
        makePlane.plane.mesh.shader = nil
    end
end

profiler={}

function profiler.init(silent)    
    profiler.del=0
    profiler.c=0
    profiler.fps=0
    profiler.mem=0
    if not silent then
        parameter.watch("profiler.fps")
        parameter.watch("profiler.mem")
    end
end

function profiler.draw()
    profiler.del = profiler.del +  DeltaTime
    profiler.c = profiler.c + 1
    if profiler.c==10 then
        profiler.fps=profiler.c/profiler.del
        profiler.del=0
        profiler.c=0
        profiler.mem=collectgarbage("count", 2)
    end
end

Ok, I see what you mean about not separating 2d and 3d draws. So when you touch, you actually set up the camera twice, draw the plane twice etc, hence the frame rate dropping. I guess it’s tricky because you need continuous touch events as your finger moves

Bingo, I got it. For some reason, it is backingMode(STANDARD) in the draw loop. Comment that out, and for some reason, fps jumps up to 60. Here’s a version that tries to do all the 3d drawing in one go. I don’t think it makes much difference though, as you need to re-establish the camera when you setContext to a different image anyway. It also gets rid of the colorCoordinates table loop (as it only ever contains one value? Were you planning on implementing multi-touch later?), to try to speed things up a bit. The fps stays at 60 fps though, regardless of whether you’re touching. So, quarter-size buffers (smaller is possible) and not setting the backing mode each frame triple the running speed, and only allowing for a single touch seems to help too.

And the overlay image is just there for testing isn’t it? So you could get rid of that in the final version.

Edit (tidied up the line drawing). Sorry I left the code in such a messy state. But feel that speed!


--# Main
-- MakePlane

function setup()
    textMode(CORNER)
    planeShader = shader(PlaneShader.v, PlaneShader.f)
  --  noStroke()
    setUpTests()
    makePlane = MakePlane()
    texture = makePlane.plane.mesh.texture
    colorCoordinates = {}
    overlayImage = image(WIDTH/4, HEIGHT/4)
    setUpBufferDisplay()
    profiler.init()
end

function setUpBufferDisplay()
    bufferImage = image(WIDTH/4, HEIGHT/4)
    local bufferFract = 1 / 6
    cameoSize = vec2(WIDTH * bufferFract, HEIGHT * bufferFract)
    camoPos = vec2((cameoSize.x/2)+20, (cameoSize.y/2)+20)
    setContext(bufferImage) --fill image with yellow for visibility
    strokeWidth(0)
    fill(220, 210, 122, 255)
    rect(0,0,WIDTH,HEIGHT)
    setContext()
end

function setUpTests()
    local parameterTable = {} --series of buttons to confirm visuals
    local assignableAction = function() end
    local nextParameter = function ()
        parameter.clear()
        if #parameterTable == 0 then
            return
        end
        local testDefinition = parameterTable[1]
        local passAction = function()
            print(testDefinition..": PASS")
            assignableAction()
        end
        local failAction = function()
            print(testDefinition..": FAIL")
            assignableAction()
        end
        parameter.action("PASS if "..testDefinition, passAction)
        parameter.action("FAIL", failAction)
        table.remove(parameterTable, 1)
    end
    assignableAction = nextParameter
    parameterTable = {"plane is showing", "touching draws red dots", "dragging leaves trail", "touch activates shader", "buffer image in corner", "clipping drawn in buffer", "mesh clips in buffer", "buffer perspective matches", "buffer coordinates read", "touch draws to texture", "draws in right place", "all touches draw"}
    nextParameter()
end

function draw()
   -- backingMode(STANDARD)
    background(69, 119, 104, 255)
    drawPlane()
    draw2D()
    profiler.draw()
end

function drawPlane()
    pushMatrix()
  --  makePlane:setupPerspective()
    if touching then
        toggleShader(true)
        drawClipToBufferPosition(touching)
        toggleShader(false)
      --  touching = nil
    end
  --  pushMatrix()
    makePlane:draw()
    
    popMatrix() 
end

function draw2D()
    ortho()
    viewMatrix(matrix())
    sprite(overlayImage, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    sprite(bufferImage, camoPos.x, camoPos.y, cameoSize.x, cameoSize.y)
   -- if #colorCoordinates > 0 then
    if touching then
        drawTouchesToTexture()
        touching = nil
    end
end

function drawTouchesToTexture()
    
    local textureX, textureY, baseX, baseY, marker, intCoord, fX, fY
    textureX, textureY = 0, 0
  --  for i in ipairs(colorCoordinates) do
       -- coord = colorCoordinates[i] * 0.25
        coord = touching * 0.25
        intCoord = vec2(math.floor(coord.x), math.floor(coord.y))
        baseX, baseY, marker = bufferImage:get(intCoord.x, intCoord.y)
        if marker ==  1 then
            fX, fY = baseX / 255, baseY / 255
            textureX = texture.width * fX
            textureY = texture.height * fY
            lastTexX = lastTexX or textureX
            lastTexY = lastTexY or textureY
            setContext(texture)
                stroke(0, 43, 255, 255)
                strokeWidth(25)
                line(textureX, textureY, lastTexX, lastTexY)
           -- ellipse(textureX, textureY, 25)
            setContext()
            lastTexX, lastTexY = textureX, textureY
        end
  --  end
  --  text("x: "..textureX..", y: "..math.floor(textureY, 10, 10))
   -- end
    
  --  colorCoordinates = {}
end

function touched(touch)
    
    setContext(overlayImage)
    fill(255, 4, 0, 255)
    ellipse(touch.x/4, touch.y/4, 3)
    setContext()
    
    touching = vec2(touch.x, touch.y)
    if touch.state == ENDED then
        touching, lastTexX, lastTexY = nil,nil, nil
    end
    --[[
    if touch.state == MOVING then
        touching = vec2(touch.x, touch.y)
       -- table.insert(colorCoordinates, vec2(touch.x, touch.y))
    else
        
        toggleShader(false)
    end
      ]]
end

function drawClipToBufferPosition(touch)
        setContext(bufferImage)
        pushMatrix()
     clip((touch.x*0.25) - 10, (touch.y*0.25) - 10, 20, 20)
        makePlane:draw()
     clip()
          popMatrix()
        setContext()
end

function toggleShader(onOrOff)
    if onOrOff == true then
        makePlane.plane.mesh.shader = planeShader
    else 
        makePlane.plane.mesh.shader = nil
    end
end

profiler={}

function profiler.init(silent)    
    profiler.del=0
    profiler.c=0
    profiler.fps=0
    profiler.mem=0
    if not silent then
        parameter.watch("profiler.fps")
        parameter.watch("profiler.mem")
    end
end

function profiler.draw()
    profiler.del = profiler.del +  DeltaTime
    profiler.c = profiler.c + 1
    if profiler.c==10 then
        profiler.fps=profiler.c/profiler.del
        profiler.del=0
        profiler.c=0
        profiler.mem=collectgarbage("count", 2)
    end
end


--# MakePlane
MakePlane = class()

function MakePlane:init()
    self:createSimpleMesh()
end

function MakePlane:draw()
    self:setupPerspective()
    self.plane.mesh:draw()
end

function MakePlane:createSimpleMesh()
    --settings for our rectangle (named "plane" below)
    self.plane={}
    self.plane.centre=vec3(0,0,0)
    self.plane.rotate=vec3(45,0,20)
    self.plane.size=vec2(500,500,0)
    strokeWidth(10)
    stroke(255, 0, 0, 255)
    --set up mesh, needed for shader
    self.plane.mesh=mesh()
    self.plane.mesh.texture = readImage("SpaceCute:Background")
    local s=self.plane.size
    local x1,y1,x2,y2,z=-s.x/2,-s.y/2,s.x/2,s.y/2,s.z
    self.plane.mesh.vertices={vec3(x1,y1,z),vec3(x2,y1,z),vec3(x2,y2,z),vec3(x2,y2,z),vec3(x1,y2,z),vec3(x1,y1,z)}
    self.plane.mesh.texCoords={vec2(0,0),vec2(1,0),vec2(1,1),vec2(1,1),vec2(0,1),vec2(0,0)}
    self.plane.mesh:setColors(color(255,255,0))
    self.img=image(WIDTH, HEIGHT)
    self.shaderImg=image(WIDTH, HEIGHT)
end

function MakePlane:setupPerspective()
    perspective()
    camera(0,0,900,0,0,-1)
    translate(self.plane.centre.x, self.plane.centre.y, self.plane.centre.z)
    rotate(self.plane.rotate.x,1,0,0)
    rotate(self.plane.rotate.y,0,1,0)
    rotate(self.plane.rotate.z,0,0,1)
end

PlaneShader = {

v = [[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
varying highp vec2 vTexCoord;

void main()
{
    vTexCoord = texCoord;
    gl_Position = modelViewProjection * position;
}
]],
f = [[
precision highp float;
varying highp vec2 vTexCoord;

void main()
{
    lowp float marker = 1.0 /255.0;
    gl_FragColor = vec4(vTexCoord.x,vTexCoord.y,marker,1.0);
}
]]}