In response to some question on the codea.io forum, Dave1707 showed me a neat trick with text. It taught me something, and raised some questions.

Codea includes a rather active forum, at #codea.io, and Dave1707 is one of the most knowledgeable and helpfiul people there. One of many, but he definitely stands out.

He took the trouble to look at these articles and noticed my hack for floating text. He provided this sample program, which we’ll talk about today.

-- Text Example
-- from Dave1707
-- RJ 20200211

displayMode(STANDARD)

function setup()  
   assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")        
   scene = craft.scene()
   skyMaterial=scene.sky.material
   skyMaterial.sky=color(148, 148, 223, 255)
   skyMaterial.horizon=color(183, 223, 159, 255)
   v=scene.camera:add(OrbitViewer,vec3(0,0,0), 100, 0, 1000)
   --v.rx,v.ry=45,60
    v.rx,v.ry = 90,0

   img=image(500,150)
   setContext(img)
   background(0,0,0,0)
   fill(0)
   stroke(241, 44, 44, 255)
   fontSize(100)
   rectMode(CORNER)
   strokeWidth(5)
   rect(0,0,500,150)
   fill(255)
   textAlign(CENTER)
   text("Hey Ron",250,75)
   setContext()

   createText()
end

function draw()
   update(DeltaTime)
   scene:draw()
end

function update(dt)
   scene:update(dt)
end

function createText()
   t=scene:entity()
   t.position=vec3(0,0,0)
   t.model = craft.model.plane(vec2(40,20))
   t.material = craft.material("Materials:Specular")
   t.material.map = img
   t.material.blendMode = NORMAL
   t.model.indices={1,2,3, 1,3,4, 4,3,1, 3,2,1}
    -- corners:
    -- 1 2
    -- 4 3
    -- triangles go counter-clockwise? for normal outward?
end

I changed the text to say “Hey Ron”. Here’s what the program does now. I”ll move the camera around for you a bit, then we’ll discuss what I’ve learned from the program.

The img thing

The first learning was very interesting. I learned what this setContext() thing does:

    img=image(500,150)
    setContext(img)
    background(0,0,0,0)
    fill(0)
    stroke(241, 44, 44, 255)
    fontSize(100)
    rectMode(CORNER)
    strokeWidth(5)
    rect(0,0,500,150)
    fill(255)
    textAlign(CENTER)
    text("Hey Ron",250,75)
    setContext()

I thought of Codea as always drawing on the screen. Silly me: it can draw on any image (a colored raster image; a bitmap). So the code above basically draws a 500x150 rectangle in red (reddish: 241,44,44). Then draws text in color black (255). The setContext() at the end points us back to the screen.

At this point, the global img contains our picture in 75,000 pixels. (500x150). Then the text is made to be part of a 3D object with this code:

function createText()
    t=scene:entity()
    t.position=vec3(0,0,0)
    t.model = craft.model.plane(vec2(40,20))
    t.material = craft.material("Materials:Specular")
    t.material.map = img
    t.material.blendMode = NORMAL
    t.model.indices={1,2,3, 1,3,4, 4,3,1, 3,2,1}
    -- corners:
    -- 1 2
    -- 4 3
    -- triangles go counter-clockwise? for normal outward?
    -- t.model.indices={1,2,3, 3,2,1}
end

This is: create an entity at 0,0,0, make its display model a plane (size 40x20?), set its materials, its map (the way the surface looks) to our image, set the mysterious blendMode, and define the triangles to be drawn (all four available on the two sides of the plane.

The comments above are mine, notes to myself about what appears to be going on.

If our plane is only 40x20, why is the picture so big on the screen? I think that has to do with the camera positioning, but I want to know more. I think I’ll add a couple of entities. Hold my chai, I’m goin in.

I added this:

    createText()
    createMarker(-20,0,0)
    createMarker(20,0,0)


function createMarker(x,y,z)
    local t = scene:entity()
    t.position = vec3(x,y,z)
    t.model = craft.model.icosphere(5)
    t.material = craft.material("Materials:Specular")
end

Lo! and behold! This is what I got. First try, I swear it:

“Clearly”, if I resize that plane, it’ll distort the red rectangle and text accordingly. Here’s what I get with the plane set to 40x80:

You can see what Materials:Specular does: it makes the sign reflective if the sun shines on it. We’ll talk about the sun another time, but it’s out there somewhere. Basically it has no visible image, it just casts light from a given direction.

Triangles

As my little corners comment shows, the corners of the plane are 1,2,3,4, clockwise from top left. (Remember that Lua indexes start at zero. As in most mesh systems, Codea’s 3D divides everything into triangles. My best guess based on experimenting is that triangles follow a left-hand rule: Clockwise around results in the triangle facing outward in the direction of your left thumb. So …

   t.model.indices={1,2,3, 1,3,4, 4,3,1, 3,2,1}

… defines the top right forward facing triangle, then the bottom left outward, then the bottom left (as we face the rectangle) facing backward, then the top right facing backward. Hmm, that’s hard to describe, isn’t it?

Anyway, given our plane, those four corner lists make it display both front and back. The back text is backward, I’m pretty sure, because we’re looking at the back of the plane, and there’s no way to make it go forward on both sides of a single pane. Think “painted on glass”.

I experimented with removing one or more of the triples, and reordering them, which is how I figured out that triangles are enumerated counter-clockwise, and normals follow the left-hand rule. (Unless I’ve confused myself, which is always possible.)

When I put in those two icospheres, I was worried that I might have to list all their triangles. That would be tedious. Fortunately, I didn’t need to do that. So I commented out the setting of indices for the plane, and the result is that the front of the plane displays and the back does not. So a model knows its triangles, but you can override the list if you have reason to do so.

There are other clever things you can do with models. For example, I believe you can color the vertices and that if you do, you’ll get a gradient. I’m not sure what tells me that, something I’ve looked at over the time I’ve had Codea. And I’m sure there’s more.

For now, we’ve learned a lot. Let me sum up.

Summing Up

Complete source

I didn’t mention it, but Dave1707 suggested that I put the complete source of the program in the articles. I used to do that in other series, but had forgotten. So I plan to go back and use Working Copy to fetch the old versions and paste them into at least some of the articles. I apologize to anyone (of the two of you) who was inconvenienced.

Relating to that, the next article will tell you about what happened with my code manager, Working Copy. Short form: lost everything, my fault, not Working Copy’s.

Image context

We learned that you can create a raster image and draw on it, and then apply that image as the map of an object. We used it here for text, but it may be useful for other purposes. I’m sure we can also import a picture, though I’ve not yet looked at how to do it.

craft.model

We learned a fair amount about using craft model to create shapes. It may not be clear from this, but I’ve also seen examples where a complete mesh is created by enumerating all its vertices and all the triangles connecting them. That could get to be a pain, but maybe we need to explore it later. Without that feature, the only built-in models seem to be icospheres, planes, and cubes. Not as interesting as they might be, but remember Minecraft is made out of cubes.

There are some cube-oriented examples in Codea. Perhaps we’ll explore those in due time.

Why am I here?

I’m mindful that my mission, to the extent I had one, was to figure out how to explain Codea, and especially Codea Craft, in terms that seem to me to lead to “good” design. So far we’ve just scratched the surface of that. I’m still learning, filling my brain a couple of hours at a time.

Hang on. And if you’re reading and getting value from these articles, please drop me an email (ronjeffries at acm dot org) or a tweet (@RonJeffries). Thanks!

Here’s the current version:

-- Text Eample
-- from Dave1707
-- RJ 20200211

displayMode(STANDARD)

function setup()
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")
    scene = craft.scene()
    skyMaterial=scene.sky.material
    skyMaterial.sky=color(148, 148, 223, 255)
    skyMaterial.horizon=color(183, 223, 159, 255)
    v=scene.camera:add(OrbitViewer,vec3(0,0,0), 100, 0, 1000)
    --v.rx,v.ry=45,60
    v.rx,v.ry = 90,0
    
    img=image(500,150)
    setContext(img)
    background(0,0,0,0)
    fill(0)
    stroke(241, 44, 44, 255)
    fontSize(100)
    rectMode(CORNER)
    strokeWidth(5)
    rect(0,0,500,150)
    fill(255)
    textAlign(CENTER)
    text("Hey Ron",250,75)
    setContext()
    
    createText()
    createMarker(-20,0,0)
    createMarker(20,0,0)
end

function draw()
    update(DeltaTime)
    scene:draw()
end

function update(dt)
    scene:update(dt)
end

function createText()
    t=scene:entity()
    t.position=vec3(0,0,0)
    t.model = craft.model.plane(vec2(40,20))
    t.material = craft.material("Materials:Specular")
    t.material.map = img
    t.material.blendMode = NORMAL
    -- t.model.indices={1,2,3, 1,3,4, 4,3,1, 3,2,1}
    -- corners:
    -- 1 2
    -- 4 3
    -- triangles go counter-clockwise? for normal outward?
    -- t.model.indices={1,2,3, 3,2,1}
end

function createMarker(x,y,z)
    local t = scene:entity()
    t.position = vec3(x,y,z)
    t.model = craft.model.icosphere(5)
    t.material = craft.material("Materials:Specular")
end