Canvex is written entirely in JavaScript, with the browser's
canvas
implementation doing the processing-intensive work of
shuffling pixels around. The canvas is limited to 2D
graphics, so all the 3D effects are built by Canvex using the 2D
operations.
The engine is based on raycasting in a similar fashion to the Build engine. The world is split into sectors (convex polygons of constant floor/ceiling colour and lighting). The edges of the polygons are either solid textured walls, or portal walls leading into an attached sector. To compute a single column of pixels, a ray is cast out from the camera into the surrounding sector, and tracked through portal walls until it hits a solid wall.
The solid wall is drawn first. Tracing backwards along the path of the ray, floors and ceilings are drawn, and the textured steps above/below portal walls (when between sectors of differing floor/ceiling heights) are drawn too.
Drawing one-pixel-wide columns is slow, because it requires a lot of
calls to the canvas's drawImage
function. The main
optimisation is to draw an entire wall at once when it is first hit by a
ray: one ray is cast to find the wall at the left column of the viewport,
and then the wall is drawn up to its right end (or to the end of the
viewport, whichever is closer). drawImage
can only handle
rectangles, so the wall (which has perspective and gets smaller in the
distance) is split into a number of vertical strips, typically no less
than eight pixels wide.
Since the <canvas>
tag is not part of the XHTML
1.0 specification which Canvex uses, gaining acceptance from validators
is a little tricky. But XHTML claims to be Extensible, so it makes sense
to extend it by adding
elements to the DTD. The following DOCTYPE declaration keeps
browsers and the validator happy,
and is hopefully not doing anything too wrong:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" [ <!ELEMENT canvas (#PCDATA)> <!ATTLIST canvas id ID #IMPLIED style CDATA #IMPLIED height CDATA #IMPLIED width CDATA #IMPLIED > <!ENTITY % misc.inline "ins | del | script | canvas"> ]>
Various small things (bugs and features and differences) were discovered during development, particularly when comparing Firefox and Opera, and may be of interest to anyone else working in this area. Speed comparisons were done in a fairly simplistic way, so you should not trust them to give correct advice.
opera-2dgame
canvas context, allowing you
to manually control when the canvas is redrawn to the screen, which
sounds useful (particularly to avoid the occasional black flashes that
occur when the browser has to guess when to copy the canvas contents to
the screen). But updateCanvas
doesn't seem to work at all
– it's never redrawn (unless you drag another window over it to
force painting). Canvex instead avoids the flashing in Opera by doing
double-buffering.
drawImage
can use either an image
or a
canvas
as the source. The image
version is
several times faster in Firefox; the canvas
version is
several times faster in Opera.
obj.a
is faster than obj[0]
which is faster than obj['a']
. In Firefox, there is no
difference. Thus, it seems faster (as well as more readable) to use
e.g. { x: 1.2, y: 3.4 }
instead of [ 1.2, 3.4
]
for storing most data.