In the last couple of episodes, we were introduced to OpenGL via wx in Elixir. Today we're going to expand on the existing work and introduce a basic tool for building '3D Bitmaps', where a bitmap is just a list of lists of bits, and each bit that's on is represented as a cube in the scene. Let's get started.
Project
We're starting out in the same project we were in
before. I've tagged it with
before_episode_238
if you want to follow along.
Let's have a look at Lesson12 to see what we'll be using as our starting point:
iex -S mix
gc = GameCore.start_link
GameCore.load(gc, Lesson12)
So here we can see a bunch of cubes that are spaced apart a bit and textured with an image. We don't care about the texturing, but the reused shape is useful. We'll look at that in a bit.
Let's go ahead and make a new module that we'll use for our bitmap renderer:
vim lib/cubes.ex
First, we'll read in Lesson12 just to get a good starting point:
:r lib/lesson_12.ex
# Remove the bits of state that we don't want to keep around
defmodule State do
defstruct [
:parent,
:config,
:canvas,
:timer,
:time,
:box,
:xrot,
:yrot,
]
end
# Remove Texture struct
def do_init(config) do
# ...
# Update our initial state to remove items we don't care for any longer
state = %State{
parent: parent,
config: config,
canvas: canvas,
xrot: 0.0,
yrot: 0.0
}
new_state = setup_gl(state)
timer = :timer.send_interval(20, self, :update)
{parent, %State{ new_state | timer: timer } }
end
def setup_gl(state) do
{w, h} = :wxWindow.getClientSize(state.parent)
resize_gl_scene(w, h)
new_state = setup_display_lists(state)
:gl.enable(:wx_const.gl_texture_2d)
:gl.shadeModel(:wx_const.gl_smooth)
:gl.clearColor(0.0, 0.0, 0.0, 0.0)
:gl.clearDepth(1.0)
:gl.enable(:wx_const.gl_depth_test)
:gl.depthFunc(:wx_const.gl_lequal)
:gl.enable(:wx_const.gl_light0)
:gl.enable(:wx_const.gl_lighting)
:gl.enable(:wx_const.gl_color_material)
:gl.hint(:wx_const.gl_perspective_correction_hint, :wx_const.gl_nicest)
new_state
end
def setup_display_lists(state) do
# Display Lists are groups of GL commands that have been stored for
# subsequent execution.
# :gl.genLists generates a contiguous set of empty display lists. Here
# we're only generating one. The return value is a positive integer that
# can be used to reference that display list in the future.
box = :gl.genLists(1)
# Next, we'll create a new list. The first argument is the list number, and
# the second is the mode we're using. We'll just compile the commands here.
# That means they won't be executed until the list is used later.
:gl.newList(box, :wx_const.gl_compile)
# Now we just use the existing GL interfaces that we've already become
# familiar with, and they'll be compiled into the display list rather than
# executed in place.
:gl.begin(:wx_const.gl_quads)
# Here we just create a cube that's 2 units on a side.
# Front Face
:gl.vertex3f(-1.0, -1.0, 1.0)
:gl.vertex3f( 1.0, -1.0, 1.0)
:gl.vertex3f( 1.0, 1.0, 1.0)
:gl.vertex3f(-1.0, 1.0, 1.0)
# Back Face
:gl.vertex3f(-1.0, -1.0, -1.0)
:gl.vertex3f(-1.0, 1.0, -1.0)
:gl.vertex3f( 1.0, 1.0, -1.0)
:gl.vertex3f( 1.0, -1.0, -1.0)
# Top Face
:gl.vertex3f(-1.0, 1.0, -1.0)
:gl.vertex3f(-1.0, 1.0, 1.0)
:gl.vertex3f( 1.0, 1.0, 1.0)
:gl.vertex3f( 1.0, 1.0, -1.0)
# Bottom Face
:gl.vertex3f(-1.0, -1.0, -1.0)
:gl.vertex3f( 1.0, -1.0, -1.0)
:gl.vertex3f( 1.0, -1.0, 1.0)
:gl.vertex3f(-1.0, -1.0, 1.0)
# Right Face
:gl.vertex3f( 1.0, -1.0, -1.0)
:gl.vertex3f( 1.0, 1.0, -1.0)
:gl.vertex3f( 1.0, 1.0, 1.0)
:gl.vertex3f( 1.0, -1.0, 1.0)
# Left Face
:gl.vertex3f(-1.0, -1.0, -1.0)
:gl.vertex3f(-1.0, -1.0, 1.0)
:gl.vertex3f(-1.0, 1.0, 1.0)
:gl.vertex3f(-1.0, 1.0, -1.0)
:gl.end
# And now we're done with that list.
:gl.endList
# Finally, we'll update our state so we can reference this displayList via
# its integer later.
%State{ state | box: box }
end
def draw(state) do
use Bitwise
:gl.clear(bor(:wx_const.gl_color_buffer_bit, :wx_const.gl_depth_buffer_bit))
# We aren't using the texture so remove this line
#:gl.bindTexture(:wx_const.gl_texture_2d, state.texture.id)
# Here we'll make up a bitmap that represents the same general data format
# that we used when creating our tetris board representation in the past.
bitmap =
[
[ 0, 0, 1, 0, 0 ],
[ 0, 0, 1, 0, 0 ],
[ 0, 1, 1, 0, 0 ]
]
# And we'll call a function, `draw_bitmap`, that will draw this bitmap as cubes.
draw_bitmap(bitmap, state)
state
end
# We get rid of everything after draw because we won't use it.
# We'll add a function for drawing a bitmap. This will just iterate over all
# of the rows and draw each of them.
def draw_bitmap(bitmap, state) do
for {row, row_num} <- Enum.with_index(bitmap) do
draw_row({row, row_num}, state)
end
end
# Drawing a row will iterate over all of the cells in that row and draw a box
# in each cell that has a 1 in it.
def draw_row({row, row_num}, state) do
for {cell, cell_num} <- Enum.with_index(row) do
draw_cell(row_num, {cell, cell_num}, state)
end
end
# When we draw a cell with a 0 in it, we don't do anything
def draw_cell(_, {0, _}, _), do: :ok
# Otherwise, we'll use the row and cell number to determine the appropriate
# offset
def draw_cell(row_num, {cell, cell_num}, state) do
# We'll set the cursor back to the origin
:gl.loadIdentity()
# We'll move the cursor appropriately for each cube, leaving a little
# padding betwen them. We'll also push the cursor well into the screen.
# These 'magic numbers' are just what I ended up with after some fiddling
# with an existing example.
:gl.translatef(1.4 + (cell_num * 2.8), ((6.0 - row_num) * 2.4) - 7.0, -40.0)
# We'll set the color to green
:gl.color3f(0.0, 1.0, 0.0)
# And we'll use `:gl.callList` to evaluate the pre-compiled display list
# commands, drawing a cube where the cursor is.
:gl.callList(state.box)
end
end
That's really it. This obviously should have felt an awful lot like the other tetris renderers. Let's see what it looks like:
iex -S mix
gc = GameCore.start_link
GameCore.load(gc, Cubes)
Summary
And there we have it. A basic bitmap renderer where each pixel is a cube with a little padding between pixels. I hope you enjoyed it!
Resources
- erlang
gl
documentation - knewter/nehe_opengl_elixir - branch name
before_episode_238
Comments for OpenGL: 3D Bitmaps