Competition! Using a new 3D shape library

@LoopSpace is releasing a great code library of 3D shapes which you can use freely, to produce results like this

https://www.youtube.com/watch?v=DSdRPpzX8Hw

It includes some lighting effects, because 3D shapes need some texture or shading.

Here is the code, which produces the examples above when you run it. The code library has extra features you can discover by reading the descriptions of the functions. You don’t need to know much about 3D to enjoy playing with the shapes.

LoopSpace and I are co-sponsoring a competition to see who can create the best results with it. You could make a

  • “three in a row” jewel game,
  • pretty patterns, or
  • just create a 3D model using basic shapes

We’ll offer a modest prize of $20 in iTunes credit, but the real reward is the title of ShapeMaster of Codea. ^:)^

You don’t need to be a 3D expert to try it, because you can use the code provided, to play with the shapes. However, if you want to write your own code, you will need to know something about meshes and 3D.

Post any entries here, along with comments or queries.

@ignatz @loopspace fantastic job here! Thanks for sharing.
^:)^ ^:)^ ^:)^ ^:)^ ^:)^ ^:)^ ^:)^ ^:)^

Great job! This should be included as an example for the next Codea update. Along with Cider. Please… :slight_smile:

The code is all by LoopSpace

There is a “raw” button at top left of the code, this puts it on a page by itself, that may help.

i am having difficulties copying the code from the github, is there a recommended method?

This library is great.
One thing i’ll probably modify is to add the option to modify an existing shape rather than adding a new one.
Is this something you’ve already planned to do, or would consider doing?

@Jmv38 - actually, the library allows you to add a shape using an existing mesh and vertices, and specify which vertex to start from, so you can overwrite existing vertices with a new shape.

This is what you can do with just a few cylinders (the ends can be different sizes).

https://www.youtube.com/watch?v=70WANosAsvE

@ignatz great! Good thing i’ve asked!

This is very cool.

Here’s a quick little match game I made with it:

-- Match

displayMode(FULLSCREEN)
function setup()
    wScale, hScale = WIDTH / 1024, HEIGHT / 768
    
    local light = vec3(0,1,0)
    
    objects = {}

    objects[1]=addBlock{center=vec3(0,0,0),size=15,color=color(0, 255, 0, 255),light=light,ambience=.5}

    objects[2]=addSphere{centre=vec3(0,0,0),size=10,texture="Cargo Bot:Starry Background",light=light,ambience=.5} 
    
    objects[3]=addSphereSegment{centre=vec3(0,0,0),size=10,color=color(0, 192, 255, 255),
        startLongitude=30,deltaLongitude=100,light=light,ambience=.5} 

    objects[4]=addJewel{centre=vec3(0,0,0),size=15,sides=10,color=color(203, 78, 188, 255),light=light,ambience=.5}

    objects[5]=addCylinder{centre=vec3(0,0,0),height=20,radius=7.5,color=color(107, 59, 108, 255),
            light=light,ambience=.5,faceted=false,size=20}
    
    
    
    shapes = {}
    local gap = 50
    for i = 1, 16 do
        shapes[i] = {}
        
        shapes[i].id = math.random(1, #objects)
        
        local a, b = math.modf((i-1)/4)
        
        shapes[i].pos = vec3(a-1.5, b*4-1.5, 0) * gap
        shapes[i].rpos = vec2(WIDTH / 2 + (a-1.5)*185, HEIGHT / 2 + (b-0.375)*750)
        shapes[i].row, shapes[i].col = b * 4, a
        shapes[i].scale = 1
    end
    
    selected = {}
    
    rot = vec3(0,0,0)  
    deltaRot = vec3(.3,.3,.3)*4
    score = 0
    lost = false
end

function orientationChanged()
    wScale, hScale = WIDTH / 1024, HEIGHT / 768
    
    if shapes then
        for i = 1, 16 do
            local a, b = math.modf((i-1)/4)
            shapes[i].rpos = vec2(WIDTH / 2 + (a-1.5)*185, HEIGHT / 2 + (b-0.375)*750)
        end
    end
end

function resetBoard()
    for i, s in ipairs(shapes) do
        s.id = math.random(1, #objects)
        tween(0.5, s, { scale = 1 })
    end
    score = 0
    lost = false
    
    ANIMATING = true
    tween.delay(0.6, function() 
        ANIMATING = false
    end)
end

function draw()
    background(255, 255, 255, 255)

    textAlign(CENTER)
    if not lost then
        perspective()  --puts Codea in 3D mode
        camera(0,0,250 / wScale,0,0,0) --camera is 250 pixels back from 0, looking at (0,0,0)
        for _,s in pairs(shapes) do
            pushMatrix()
            translate(s.pos.x,s.pos.y,s.pos.z)
            rotate(rot.x,1,0,0) rotate(rot.y,0,1,0) rotate(rot.z,0,0,1)
            scale(s.scale, s.scale, s.scale)
            --use this next line with all your objects that use lighting
            
            if s.id ~= 0 then
                local ob = objects[s.id]
                if ob.shader then ob.shader.invModel = modelMatrix():inverse():transpose() end
                ob:draw()
            end
            
            popMatrix()  
        end
        
        viewMatrix( matrix() )
        ortho()
        
        for i, s in ipairs(selected) do
            fill(184, 68, 68, 255) stroke(184,68,68,200) strokeWidth(25)
            
            if i < #selected then
                line(s.pos.x, s.pos.y, selected[i+1].pos.x, selected[i+1].pos.y)
            end
            
            noStroke()
            ellipse(s.pos.x, s.pos.y, 35)
        end
        
        fill(0, 127) fontSize(WIDTH / 4)
        text(math.floor(score+0.5), WIDTH / 2, HEIGHT / 2)
    else
        fontSize(WIDTH / 8)
        text("game over\
tap to restart", WIDTH / 2, HEIGHT / 2)
    end

    
    rot=rot+deltaRot --change rotation
end

function checkMoves()
    local match = false
    for i, s in ipairs(shapes) do
        local up, right = shapes[i+1], shapes[i+4]
    
        if (s.row < 3 and up ~= nil and s.id == up.id) or (right ~= nil and s.id == right.id) then
            match = true
            break
        end
    end
    
    if not match then
        sound("Game Sounds One:Pac Death 2")
        ANIMATING = true
        for i, s in ipairs(shapes) do
            tween(0.25, s, { scale = 0 })
        end
        tween.delay(0.6, function() 
            ANIMATING = false
            lost = true
        end)
    end
end

function touched(t)
    if ANIMATING then return end
    if lost and t.state == ENDED then
        resetBoard()
    elseif lost then
        return
    end
    
    if t.state == BEGAN or t.state == MOVING then
        for i,s in ipairs(shapes) do
            if vec2(t.x, t.y):dist(s.rpos) <= 75 and not s.selected then
                local rdif,cdif = 2,2
                if #selected > 0 then
                    rdif, cdif = math.abs(s.row - selected[#selected].row), math.abs(s.col - selected[#selected].col)
                end
                if #selected == 0 or (s.id == selected[1].id and rdif + cdif <= 1) then
                    table.insert(selected, { pos = s.rpos, id = s.id, row = s.row, col = s.col })
                    s.selected = true
                    sound("Game Sounds One:Block 1")
                end
            end
        end
    elseif t.state == ENDED then
        if #selected > 1 then
            sound("Game Sounds One:Block 3")
            tween(0.5, _G, { score = score + ((2^(#selected)) / 2) })
        end
        
        for i, s in ipairs(shapes) do
            s.switch = false
            s.d_id = i
            if s.selected and #selected > 1 then
                s.id = 0
                s.switch = true
            end
            s.o_id = s.id
            s.selected = false
        end
            
        if #selected > 1 then
            for i = 1, #shapes, 1 do
                if shapes[i].row > 0 then
                    for j = 1, shapes[i].row do
                        if shapes[i-j].switch then
                            local ors = shapes[i-j].switch
                            shapes[i-j].switch = shapes[i-j+1].switch
                            shapes[i-j+1].switch = ors
                            
                            local op = shapes[i-j].d_id
                            shapes[i-j].d_id = shapes[i-j+1].d_id
                            shapes[i-j+1].d_id = op
                        end
                    end
                end
            end
            
            -- animate and replace empty spots
            ANIMATING = true
            for i, s in ipairs(shapes) do
                local dest = s.pos
                local og_id, og = s.d_id, shapes[s.d_id].pos
                tween(0.5, shapes[s.d_id], { pos = dest }, nil, function()
                    shapes[og_id].pos = og
                    s.id = shapes[s.d_id].o_id
                    
                    if s.id == 0 then
                        s.id = math.random(1, #objects)
                        s.scale = 1
                    end
                    
                end)
            end
            tween.delay(0.6, function()
                ANIMATING = false
                checkMoves()
            end)
        end
        
        selected = {}
    end
end

EDIT: added animated movement of gems after scoring, changed scoring to be exponential (2^number_of_gems_selected)

EDIT 2: sound effects, losing, no diagonal matches (is that the correct rule?)

EDIT 3: portrait mode support

Just a short comment to say that although the core code is mine, it wouldn’t have seen the light of day if not for Ignatz’ persuasion and suggestions for improvements so Ignatz deserves lots of credit too.

Also, the core code is already in an example project with Codea: the Roller Coaster. But it’s not as user-friendly so maybe I should rewrite the 'Coaster to use this version.

Now to download and try JakAttak’s game …

Hi @LoopSpace, @ignatz,

First a big thank you for the excellent 3D package that you provided for us. I am still digesting it, trying to figure out how it works etc.

Initially I tried to compress it - strip out the comments and move functions, libs etc to separate tabs - shader and comments no problem but moving (or copying) object functions e.g. function addJewel(t) onto a separate tab resulted in an error ‘attempt to call global __initmesh (a nil value)’.

Is this down to tab order?

My objective is to have the libraries in a separate ‘applette’ as a library which I can call via dependencies. Can you help here?

Other thing - you declare local __initmesh etc locally outside any function - are there any requirements positionally for these declarations and what influence does that have on moving functions to tabs?

Now - I’m going to try out JakAttack’s game too …

Thanks again,

Bri_G

:-?

HI @JakAttack,

Neat game - very cool. How does it score and what are the objectives?
Might have guessed I haven’t read your code yet - back to the game.

Thanks,

Bri_G

:smiley:

hey everyone, I got bored and decided to make a little something with this :slight_smile:

You get a cube, you can rotate it with swipes, press a single time on the screen, and the face which has the most ‘visible area’ on the 2D screen, will get ‘loaded’, some of the faces contain more shapes

There’s a text parameter which you need to type something in, and then press the ‘check’ button, if you have the right answer, the output will tell you

I didn’t have an aweful lot of time, so returning to the cube view while viewing a face, is also done with a parameter

It’s just a little thing, it’s my first 3D experience and I’d really like to thank @loopspace and @ignatz for making this something that’s fairly easy to understand, even if you haven’t done 3D before :slight_smile:

function setup()
    
    startShapes()
    
    CUBE = 0
    GREEN = 1
    YELLOW = 2
    CYAN = 3
    BLUE = 4
    RED = 5
    MAGENTA = 6
    
    state = CUBE
    
    timer = false
    RGB = ''
    pos = {x = 0, y = 0}
    statepos = {x = 0, y = 0}
    parameter.integer('state', 0, 6, 0)
    parameter.action('Back to cube', function() state = CUBE end)
    parameter.text('RGB', '')
    parameter.action('check answer', check)
    
    tt = false
    
    time = 0
end

function draw()
    
    pushMatrix()
    background(127, 127, 127, 255)
    perspective(45)
    camera(0,0,400,0,0,0, 0,1,0)
    if state == CUBE then
        rotate(pos.x, 1, 0, 0)  rotate(pos.y, 0, 1, 0)
        test:draw()
        test2:draw()
        test3:draw()
    else
        if state == GREEN then
            rotate(45, 1, 0, 0)
            test2:draw()
            test:draw()
            time = time + 1
            if time%60 == 0 and time <= 600 then
                tt = not(tt)
            end
            if time >= 1000 then
                time = 0
            end
            if tt then
                testShape2:draw()
            else
                testShape:draw()
            end
        end
        if state == YELLOW then
            rotate(270, 1, 0, 0)
            test2:draw()
        end
        if state == CYAN then
            test:draw()
            rotate(statepos.x, 1, 0, 0) rotate(statepos.y, 0, 1, 0)
        end
        if state == BLUE then
            rotate(270, 0, 1, 0) rotate(45, 0, 0, 1)
            test3:draw()
            test2:draw()
            jewel:draw()
            minus:draw()
            cyl:draw()
        end
        if state == RED then
            rotate(180, 0, 1, 0)
            test:draw()
            rotate(statepos.x, 1, 0, 0) rotate(statepos.y, 0, 1, 0)
            for i = 0, #jewels do
                jewels[i]:draw()
            end
        end
        if state == MAGENTA then
            rotate(90, 0, 1, 0)
            test3:draw()
            rotate(statepos.x, 1, 0, 0) rotate(statepos.y, 0, 1, 0)
        end
    end
    popMatrix()
    
end

function touched(t)
    if state == CUBE then
        if math.abs(t.deltaY) > 5 then pos.x = pos.x - t.deltaY/3 reset() end
        if math.abs(t.deltaX) > 5 then pos.y = pos.y + t.deltaX/3 reset() end
        if pos.x >= 360 then pos.x = pos.x - 360 end
        if pos.x < 0 then pos.x = pos.x + 360 end
        if pos.y >= 360 then pos.y = pos.y - 360 end
        if pos.y < 0 then pos.y = pos.y + 360 end
        
        if t.state == BEGAN then
            timer = true
        end
        
        if t.state == ENDED and timer then
            reset()
            start()
        end
    else
        if math.abs(t.deltaY) > 5 then statepos.x = statepos.x - t.deltaY/3 reset() end
        if math.abs(t.deltaX) > 5 then statepos.y = statepos.y + t.deltaX/3 reset() end
    end
end

function reset()
    timer = false
end

function start()
    if pos.x > 45 and pos.x < 135 then
        state = GREEN
    elseif pos.x > 225 and pos.x < 315 then
        state = YELLOW
    else
        if pos.y > 45 and pos.y < 135 then
            if pos.x > 135 and pos.x < 225 then
                state = BLUE
            else
                state = MAGENTA
            end
        elseif pos.y > 315  or pos.y < 45 then
            if pos.x > 135 and pos.x < 225 then
                state = RED
            else
                state = CYAN
            end
        elseif pos.y < 315 and pos.y > 225 then
            if pos.x > 135 and pos.x < 225 then
                state = MAGENTA
            else
                state = BLUE
            end
        else
            if pos.x > 135 and pos.x < 225 then
                state = CYAN
            else
                state = RED
            end
        end
    end
end
function startShapes()
    
    -- yellow, jewels
    jewels = {}
    jewels[0] = addJewel{size = 20, color = color(255, 0, 0, 255), origin = vec3(100,0,0), light = light, ambience = 0.75}
    jewels[1] = addJewel{size = 20, color = color(255, 255, 0, 255), origin = vec3(-100,0,0), light = light, ambience = 0.75}
    jewels[2] = addJewel{size = 20, color = color(255, 0, 255, 255), origin = vec3(0,100,0), light = light, ambience = 0.75}
    jewels[3] = addJewel{size = 20, color = color(0, 255, 0, 255), origin = vec3(0,-100,0), light = light, ambience = 0.75}
    jewels[4] = addJewel{size = 20, color = color(0, 255, 255, 255), origin = vec3(0,0,100), light = light, ambience = 0.75}
    jewels[5] = addJewel{size = 20, color = color(0, 0, 255, 255), origin = vec3(0,0,-100), light = light, ambience = 0.75}
    
    
    --green, tests
    testShape = addCylinder{center = vec3(0,50,0), height = 20, radius = 30, color = color(255, 113, 0, 255), light = light, ambient = 0,8, faceted = false, size = 20}
    testShape2 = addCylinder{center = vec3(0,45,0), height = 20, radius = 30, color = color(255, 113, 0, 255), light = light, ambient = 0,8, faceted = false, size = 20}
    
    -- blue, sum/...
    jewel = addJewel{size = 10, axis = vec3(1,0,0), color = color(255, 0, 0, 255), origin = vec3(60,0,30), light = light, ambience = 0.75}
    minus = addBlock{center = vec3(60,0,0), width = 5, height = 5, depth = 20, color = color(255, 255, 255, 255), light = light, ambience = 0.9}
    cyl = addCylinder{center = vec3(50,0,-30), height = 20, radius = 10, color = color(0, 255, 0, 255), light = light, ambient = 0,8, faceted = false, size = 20, axis = vec3(1,0,0)}
    
    
    
    --cube
    local colors = {
    color(255, 0, 0, 255),
    color(255, 0, 0, 255),
    color(255, 0, 0, 255),
    color(255, 0, 0, 255),
    color(0, 255, 255, 255),
    color(0, 255, 255, 255),
    color(0, 255, 255, 255),
    color(0, 255, 255, 255)
    }
    local BFaces = {
        {1,2,3,4},
        {5,7,6,8},
        {1,1,1,1},
        {1,1,1,1},
        {1,1,1,1},
        {1,1,1,1}
    }
    test = addBlock{center = vec3(0,0,0), size = 100, color = colors, faces = BFaces}
    
    local colors = {
    color(255, 255, 0, 255),
    color(255, 255, 0, 255),
    color(0, 255, 0, 255),
    color(0, 255, 0, 255),
    color(255, 255, 0, 255),
    color(255, 255, 0, 255),
    color(0, 255, 0, 255),
    color(0, 255, 0, 255)
    }
    local BFaces = {
        {1,1,1,1},
        {1,1,1,1},
        {1,5,2,6},
        {3,4,7,8},
        {1,1,1,1},
        {1,1,1,1}
    }
    test2 = addBlock{center = vec3(0,0,0), size = 100, color = colors, faces = BFaces}
    
    local colors = {
    color(255, 0, 255, 255),
    color(0, 0, 255, 255),
    color(255, 0, 255, 255),
    color(0, 0, 255, 255),
    color(255, 0, 255, 255),
    color(0, 0, 255, 255),
    color(255, 0, 255, 255),
    color(0, 0, 255, 255)
    }
    local BFaces = {
        {1,1,1,1},
        {1,1,1,1},
        {1,1,1,1},
        {1,1,1,1},
        {2,6,4,8},
        {1,3,5,7}
    }
    test3 = addBlock{center = vec3(0,0,0), size = 100, color = colors, faces = BFaces}
end

function check()
    if RGB == '651' then print("VICTORY!!!") else print("NOPE") end
end

@Bri_G, thanks. Currently there is no objective but I may expand it. Scoring is exponential so two in a row is worth 2 points and then every addition block doubles the value

@Bri_G I’m not sure what you are trying to accomplish by moving code. The logic of the naming of the functions is as follows: any function which starts __ is an internal function and not exposed to global use. All other functions are in the global namespace.

You can’t really split the code up, though, because every global function uses several internal functions so they need access to those internal functions and so need to be in the same chunk. There are one or two exceptions, such as the lighting function (though note that __initmesh uses it), but the point of the internal functions was to avoid repeating stuff common to several shapes and so it needs to be available to all of those shapes.

Taking out comments is, of course, always going to be fine.

What are you trying to split up? If you put that library in its own tab (without the draw and setup functions) then it ought to work just fine. If you want to include it as a dependency, put the draw and setup functions in Main and everything else in a single separate tab.

HI @LoopSpace,

Thanks for the feedback - told me what I needed to know. My main interest is in how you’ve approached this and if I can make it modular so that I can reduce the overall code size. Need to digest your code a bit longer.

Thanks again for putting this together - smart piece of work.

Bri_G

:smiley:

@Bri_G I’ve never quite understood this desire to have minimal size code. Ignatz keeps going on at me to reduce the code as well.

You could remove shapes that you don’t need, I suppose. But if you put it in a library and include it as a dependency, then what does the size matter?

If you did want to remove a few shapes, I could do a map of the dependencies between the functions if you like. Then you could easily see which ones are needed by which shapes.

@LoopSpace Very cool, well done.