WebGL Lesson 10 – loading a world, and the most basic kind of camera

<< Lesson 9Lesson 11 >>

Welcome to my number ten in my series of WebGL tutorials, based on number 10 in the NeHe OpenGL tutorials. In it, we’ll load up a 3D scene from a file (meaning that we could easily switch the file out and extend the demo), and write some simple code so that we can move around within it — a kind of nano-Doom, with its own WAD file format :-)

Here’s what the lesson looks like when run on a browser that supports WebGL:

Click here and you’ll see the live WebGL version, if you’ve got a browser that supports it; here’s how to get one if you don’t. You should find yourself in a room with walls made up of photos of Lionel Brits, who wrote the original OpenGL tutorial on which this is based :-) You can run around the room, and outside it, using the cursor keys or WASD, and you can look up or down using Page Up and Page Down. One thing to look out for is that your viewpoint goes up and down in a “jogging” kind of manner as you run, just for a bit more realism.

More on how it all works below…

The usual warning: these lessons are targeted at people with a reasonable amount of programming knowledge, but no real experience in 3D graphics; the aim is to get you up and running, with a good understanding of what’s going on in the code, so that you can start producing your own 3D Web pages as quickly as possible. If you haven’t read the previous tutorials already, you should probably do so before reading this one — here I will only explain the new stuff.

There may be bugs and misconceptions in this tutorial. If you spot anything wrong, let me know in the comments and I’ll correct it ASAP.

There are two ways you can get the code for this example; just “View Source” while you’re looking at the live version, or if you use GitHub, you can clone it (and the other lessons) from the repository there.

Just as with the last few lessons, the easiest way to explain what’s going on in this page is to start from the bottom and work our way up. Let’s start off with the HTML code inside the body tags at the bottom, which (for the first time since lesson 1!) has something interesting in it:

<body onload="webGLStart();">
<a href="http://learningwebgl.com/blog/?p=1067">&lt;&lt; Back to Lesson 10</a><br />

  <canvas id="lesson10-canvas" style="border: none;" width="500" height="500"></canvas>

  <div id="loadingtext">Loading world...</div>

  <br/>
Use the cursor keys or WASD to run around, and <code>Page Up</code>/<code>Page Down</code> to
look up and down.

<br/>
<br/>
<a href="http://learningwebgl.com/blog/?p=1067">&lt;&lt; Back to Lesson 10</a>

</body>

So, we have a HTML DIV that holds some placeholder text to display while the world that we’re going to display is loading; if the connection between my server and your machine was slow when you loaded the demo above, you might have seen it. However, the message appeared on top of the canvas, not underneath it as you would expect from the HTML. This movement was managed by a bit of CSS code further up, just at the end of the HTML head:

<style type="text/css">
    #loadingtext {
        position:absolute;
        top:250px;
        left:150px;
        font-size:2em;
        color: white;
    }
</style>

So, that’s the HTML. Now let’s take a look at the JavaScript.

The first thing to see is a simple change to our standard webGLStart function; as well as the usual setup stuff, it calls a new function to load the world from the server:

  function webGLStart() {
    var canvas = document.getElementById("lesson10-canvas");
    initGL(canvas);
    initShaders();
    initTexture();
    loadWorld();

    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    gl.clearDepth(1.0);

    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);

    document.onkeydown = handleKeyDown;
    document.onkeyup = handleKeyUp;

    setInterval(tick, 15);
  }

Let’s jump up to that code now; the loadWorld function is just above drawScene, about three quarters of way through the file. It looks like this:

  function loadWorld() {
    var request = new XMLHttpRequest();
    request.open("GET", "world.txt");
    request.onreadystatechange = function() {
      if (request.readyState == 4) {
        handleLoadedWorld(request.responseText);
      }
    }
    request.send();
  }

This style of code may well look familiar; it’s very similar to the kind of thing we used to load the textures. We create an XMLHttpRequest object, which will handle all of the loading, and tell it to use an HTTP GET request to get the file called world.txt from the same directory on the same server as the current page. We specify a callback function to be updated at different stages of the download, and this in turn calls a handleLoadedWorld when the XMLHttpRequest reports that its readyState is 4, which happens when the file has been completely loaded. Once all this has been set up, we tell the XMLHttpRequest to start the process of getting the file by calling its send method.

So, let’s move on to handleLoadedWorld, which is just above loadWorld.

  var worldVertexPositionBuffer = null;
  var worldVertexTextureCoordBuffer = null;
  function handleLoadedWorld(data) {

The function’s job is to parse the contents of the loaded file and use them to create two buffers of the kind we’ve seen so much of in previous lessons. The contents of the loaded file are passed in as a string parameter called data, and the first bit of the code simply parses it. The format of the file we’re using for this example is very simple; it contains a list of triangles, each specified by three vertices. Each vertex is on a line to itself, containing five values: its X, Y and Z coordinates, and its S and T texture coordinates. The file also contains comments (lines starting with //), and blank lines, both of which are ignored, and there is a line at the top that specifies the total number of triangles (though we don’t actually use this).

Now, is this a terribly good file format? Well, actually, no — it’s pretty terrible! It omits a lot of information that we’d like to put into a real scene; for example, normals, or different textures for different objects. In a real-world example, you would use a different format, or even JSON. I’ve stuck with this format, however, because (a) it’s the one used by the original OpenGL lesson and (b) it’s nice and simple to parse. However, having said all that, I’ll not explain the parsing code in detail. Here it is:

    var lines = data.split("\n");
    var vertexCount = 0;
    var vertexPositions = [];
    var vertexTextureCoords = [];
    for (var i in lines) {
      var vals = lines[i].replace(/^\s+/, "").split(/\s+/);
      if (vals.length == 5 && vals[0] != "//") {
        // It is a line describing a vertex; get X, Y and Z first
        vertexPositions.push(parseFloat(vals[0]));
        vertexPositions.push(parseFloat(vals[1]));
        vertexPositions.push(parseFloat(vals[2]));

        // And then the texture coords
        vertexTextureCoords.push(parseFloat(vals[3]));
        vertexTextureCoords.push(parseFloat(vals[4]));

        vertexCount += 1;
      }
    }

At the end of the day, all this really does is take all of the lines with five space-separated values and assume that they contain vertices, and builds up vertexPositions and vertexTextureCoords arrays using them. It also keeps a count of vertices in vertexCount.

The next bit of code should be very familiar-looking by now:

    worldVertexPositionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, worldVertexPositionBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexPositions), gl.STATIC_DRAW);
    worldVertexPositionBuffer.itemSize = 3;
    worldVertexPositionBuffer.numItems = vertexCount;

    worldVertexTextureCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, worldVertexTextureCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexTextureCoords), gl.STATIC_DRAW);
    worldVertexTextureCoordBuffer.itemSize = 2;
    worldVertexTextureCoordBuffer.numItems = vertexCount;

So, we create two buffers containing the vertex details we’ve loaded. Finally, once all that’s done, we clear out the DIV in the HTML that was showing the words “Loading World…”:

    document.getElementById("loadingtext").textContent = "";
}

That’s all the code required to load the world from a file. Before we go on to look at the code that actually uses it, let’s stop briefly and look at something interesting in the world.txt file. The first three vertices, describing the first triangle in the scene, look like this:

// Floor 1
-3.0  0.0 -3.0 0.0 6.0
-3.0  0.0  3.0 0.0 0.0
 3.0  0.0  3.0 6.0 0.0

Remember, thats X, Y, Z, S, T, where S and T are the texture coordinates. You can see that the texture coordinates are between 0 and 6. But I previously said that texture coordinates range from 0 to 1. What’s going on? The answer is that when you ask for a point in a texture, the S and T coordinates are automatically taken modulo one, so 5.5 is taken from the same point in the texture image as 0.5. This means that the texture is, in effect, automatically tiled so that it repeats as many times as required to fill the triangle. That’s obviously very useful when you have a small texture that you want to use on a large object — say, a brickwork texture to cover a wall.

Right, let’s move on to the next interesting bit of code: drawScene. The first thing it does is check whether or not the buffers that are created when we have finished loading the world have been loaded; if they have not, it bails out:

  function drawScene() {
    gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    if (worldVertexTextureCoordBuffer == null || worldVertexPositionBuffer == null) {
      return;
    }

If we do have the buffers set up, the next step is to do our usual setup for the perspective and the model-view matrices:

    perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);

    loadIdentity();

The next step is to handle our camera — that is, to allow for the fact that we want our viewpoint to move through the scene. The first thing to remember is that, like so many things, WebGL doesn’t support cameras directly, but simulating one is easy enough. If we had a camera, for this simple example we would want to be able to say that it was positioned at a particular X, Y, Z coordinate, and had a certain tilt around the X axis from looking upwards or downwards (called pitch) and a certain angle around the Y axis from having turned left or right (called yaw). Because we can’t change the position of the camera — which is always effectively at (0, 0, 0) looking straight down the Z axis, what we want to do is somehow tell WebGL to adjust the scene that we have to draw, which is specified using X, Y, Z coordinates in what we call world space, into a new frame of reference based on the position and rotation of the camera, which we call eye space.

A simple example might help here. Let’s imagine that we have a really simple scene, in which there is a cube with its centre at (1, 2, 3) in world space. Nothing else. We want to simulate a camera that is positioned at (0, 0, 7), facing down the Z axis, with no pitch or yaw. To do that, we transform the world space coordinates, and we wind up with eye space coordinates for the centre of the cube of (1, 2, -4). Rotations complicate things a little, but not much.

It’s probably pretty clear that this is yet another case for using matrices; and we could in fact keep a “camera matrix” which represents the camera’s position and rotation. But for this example we can keep it even simpler. We can just use our existing model-view matrix.

It turns out (and may well be intuitively obvious by extrapolating from the example above) that we can simulate a camera by “backing out” of the scene in the opposite direction to the way we would move if we were going to the position and rotation of the camera, and then drawing the scene using our usual relative coordinate system. If we imagine ourselves as the camera, we would position ourselves by moving to the appropriate position, then rotating appropriately. So, to “back out”, we undo the rotation, and then undo the move.

Putting it more mathemetically, we can simulate a camera that is at the position (x, y, z) and rotated by a yaw of ψ and a pitch of θ by first rotating by around the X axis, then by around the Y axis, and then by moving to (-x, -y, -z). Once that’s done, we’ve put the model-view matrix in a state such that everything drawn from then on can use world coordinates, and it will get automatically transformed to eye coordinates by the magic of our matrix multiplication in the vertex shader.

There are other ways of positioning cameras, and we’ll go over them in later lessons. For now, here’s the code for this one:

    mvRotate(-pitch, [1, 0, 0]);
    mvRotate(-yaw, [0, 1, 0]);
    mvTranslate([-xPos, -yPos, -zPos]);

Right, once that’s done, all we need to do is draw the scene as described in the buffers that were loaded up earlier. Here’s the code, it should be pretty familiar from previous lessons:

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, mudTexture);
    gl.uniform1i(shaderProgram.samplerUniform, 0);

    gl.bindBuffer(gl.ARRAY_BUFFER, worldVertexTextureCoordBuffer);
    gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, worldVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);

    gl.bindBuffer(gl.ARRAY_BUFFER, worldVertexPositionBuffer);
    gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, worldVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

    setMatrixUniforms();
    gl.drawArrays(gl.TRIANGLES, 0, worldVertexPositionBuffer.numItems);
  }

So, now we’ve covered the bulk of the new code in this lesson. The final bit is the code we use to control our movement, including the “jogging” movement as you run. As in the previous lessons, keyboard actions in this page are designed to give everyone the same rate of movement, however fast or slow their machine. Owners of faster machines get the benefit of a better frame rate, not faster movement!

The way this works is that in our handleKeys function, we use the set of keys currently being pressed by the user to work out a speed — that is, a rate of change of position — a rate of change of the pitch, and a rate of change of the yaw. These will all be zero if no keys are being pressed, or they will be set to fixed values, in units per millisecond, if appropriate keys are being pressed. Here’s the code, which you’ll find about two thirds of the way through the file:

  var pitch = 0;
  var pitchRate = 0;

  var yaw = 0;
  var yawRate = 0;

  var xPos = 0;
  var yPos = 0.4;
  var zPos = 0;

  var speed = 0;

  function handleKeys() {
    if (currentlyPressedKeys[33]) {
      // Page Up
      pitchRate = 0.1;
    } else if (currentlyPressedKeys[34]) {
      // Page Down
      pitchRate = -0.1;
    } else {
      pitchRate = 0;
    }

    if (currentlyPressedKeys[37] || currentlyPressedKeys[65]) {
      // Left cursor key or A
      yawRate = 0.1;
    } else if (currentlyPressedKeys[39] || currentlyPressedKeys[68]) {
      // Right cursor key or D
      yawRate = -0.1;
    } else {
      yawRate = 0;
    }

    if (currentlyPressedKeys[38] || currentlyPressedKeys[87]) {
      // Up cursor key or W
      speed = 0.003;
    } else if (currentlyPressedKeys[40] || currentlyPressedKeys[83]) {
      // Down cursor key
      speed = -0.003;
    } else {
      speed = 0;
    }

  }

So, taking an example from the above, if the left cursor key is pressed then our yawRate is set to 0.1°/ms, which is 100°/s — or, in other words, we start spinning to the left at a rate of one revolution every 3.6 seconds.

These rate-of-change values are, as you’d expect from the previous lessons, used in the animate function to set the values of xPos and zPos, as well as the yaw and the pitch. yPos is also set in animate, but by slightly different logic. Let’s take a look at all of that; you can see the code in the file just below drawScene, near the bottom. Here are the first few lines:

  var lastTime = 0;
  var piOver180 = Math.PI / 180;
  // Used to make us "jog" up and down as we move forward.
  var joggingAngle = 0;
  function animate() {
    var timeNow = new Date().getTime();
    if (lastTime != 0) {
      var elapsed = timeNow - lastTime;

Most of that is our normal code to work out the number of milliseconds elapsed from the last time animate was called. The constant piOver180 is just something useful for the body of the function. joggingAngle is more interesting. The way we get our jogging effect as we move around is by making our Y position follow a sine wave about a central value at “head height” whenever we are not at rest. joggingAngle is the angle we’re pushing into the sine function in order to get our current position.

Let’s look at the code that does that, and also adjusts x and z to allow for our movement:

      if (speed != 0) {
        xPos -= Math.sin(yaw * piOver180) * speed * elapsed;
        zPos -= Math.cos(yaw * piOver180) * speed * elapsed;

        joggingAngle += elapsed * 0.6;  // 0.6 "fiddle factor" -- makes it feel more realistic :-)
        yPos = Math.sin(joggingAngle * piOver180) / 20 + 0.4
      }

Obviously changing position and the jogging effect should only take place when we’re actually moving — so if the speed is not zero, xPos and zPos are adjusted by the current speed by using a simple bit of trigonometry (using the piOver180 scale to adjust from degrees, which we and WebGL use, to radians, which the JavaScript trig functions use). Next, joggingAngle is moved on and used to work out our current yPos. All of the numbers we’re using are multiplied by the number of milliseconds since the last call, which makes perfect sense in the case of speed, it being already in terms of units per millisecond. The 0.6 by which we’re adjusting the elapsed number of milliseconds for the joggingAngle is just something determined by trial and error to have a nice, realistic effect.

Once that’s done, we need to adjust yaw and pitch by their respective rates of change, which can be done even when we’re not moving:

      yaw += yawRate * elapsed;
      pitch += pitchRate * elapsed;

Once that’s done, we just need to record the current time so that we can work out the elapsed number of milliseconds next time animate is called, and we’re done:

    }
    lastTime = timeNow;
  }

And that’s it! Now you know all there is to learn from this lesson: a simple way to load up a scene graph from a text file, and one way to implement a camera.

The next lesson will show how to display a sphere, and rotate it using mouse events, and will also explain how you can use rotation matrices to avoid an irritating problem called gimbal-lock.

<< Lesson 9Lesson 11 >>

Acknowledgments: The original version of this tutorial was written by Lionel Brits aka βetelgeuse, and was published on NeHe’s site. This Stack Overflow question showed me how to load a text file up from JavaScript, and this page helped me debug the resulting code.

You can leave a response, or trackback from your own site.

24 Responses to “WebGL Lesson 10 – loading a world, and the most basic kind of camera”

  1. murphy says:

    For the splitting of input lines, you could also use .split(/ +/) to avoid getting empty tokes.

  2. murphy says:

    You wrote “mvRotate(pitch, [1, 0, 0])” in you article for the camera magic code, which lacks a minus. The code is correct though.

  3. Александр Витальевич says:

    Моя тематика . 90 руб.за 1000 знаков.

  4. giles says:

    @murphy — Thanks! I didn’t realise you could use regexes there. I’ve made the change you suggest, though I also had to use a replace() to get rid of leading spaces. Thanks also re: the mvRotate typo; I’ve fixed it.

    @Aleksandr — the person who’s asking for translation is not me, you should post your prices on the free-lance.ru site.

  5. Niavlys says:

    I love this idea, but I hope things will be faster at the end. I’m using Firefox 3.7a1pre (Minefield) right now and these examples are horribly slow, except for the first ones… Maybe this is because of software rendering.

  6. giles says:

    Thanks! The slowness could well be software rendering — on my laptop, which has an Intel graphics card for which I can’t find drivers to support OpenGL 2.0 (which is needed for WebGL to run with hardware acceleration), I see everything slow right down as soon as the page uses textures, which sounds similar to what you see. On my desktop machine, which does support hardware rendering, all of my own demos are fine (though some of the really hard-core GPU-intensive stuff I’ve seen be people like IQ can get a bit slow…)

  7. Niavlys says:

    Now testing with Firefox 3.7a1pre with Ubuntu, under which OpenGl is present, and almost everything works perfectly. I’m pretty much impressed this time :)

  8. giles says:

    Excellent! Glad you like them :-)

  9. Shy says:

    seem to have some HTML mixup at the top there.

  10. giles says:

    Sorry, can’t see that — where’s the problem?

  11. Milos says:

    I found fuction makeLookAt inglUtil.js which should be work same as gluLookAt in openGL.
    I can’t figure out how to change position of viewer and his direction by using this function. Ok, maybe this funciton isn’t key for my problem. I want to put viewer on (0,10,0) and viewer should looking in direction of y axe. How to fix this?

  12. giles says:

    OK, remember that the trick is to start off by moving to the opposite of your camera’s position, and then to draw everything from then on using relative positions. In order to position a camera at (0, 10, 0), you’d want to move up by 10 units, and then rotate around the X axis by +90 degrees. So, you start off your drawScene function by doing that in reverse:

        mvRotate(-90, [1, 0, 0]);
        mvTranslate([0, -10, 0]);
    

    Next, you want to draw your scene. So let’s say you wanted to draw one object at (1, 2, 3) and another at (4, 5, 6). You would do this:

        mvPushMatrix();
        mvTranslate([1, 2, 3]);
        // Draw first object
        mvPopMatrix();
    
        mvPushMatrix();
        mvTranslate([4, 5, 6]);
        // Draw first object
        mvPopMatrix();
    

    Does that help?

  13. meriadeg says:

    Hi, I want to make the rotation with the mouse instead of doing it with the arrows of the keyboard.

    Do you know where I can find a source code withe that, or how I can do that ?

    I know that is possible, beacause Quake 2 and 3 show it. But the source code isn”t very lisible, and the rotation effect is too fast.

  14. meriadeg says:

    And I try to load the world.txt with the source code but without success. The chromium java console tell me it can’t send the xml request.

  15. Adam West says:

    Hi giles,

    first: thanks for your effort to share your knowledge. Meriadeg (2nd above) got a interesting question btw.

    1) For me it would be interesting to know how you would implement the camera into a scene which loads a .json-file. I guess its pretty much the same but a tutorial or a hint would be nice as I am no proger (yet)

    2) Are there any WebGL-Tutorials written for guys who are not familiar to JavaScript and Stuff – not the other way around?

    Thanks for advice

    AW

  16. giles says:

    @meriadeg — check out the mousemove JavaScript event, that should do the trick. Re: loading the world.txt from a file — that’s a security restriction in Chromium, it won’t do XmlHttpRequests from files. The only real solutions are to use Minefield or to set up a local web server for development.

    @Adam West — in lesson 14 I show how to load a JSON file, that might help. I try not to assume any JavaScript knowledge in each of my tutorials beyond what I’ve explained in the previous ones… but I might be rushing ahead a bit. Unfortunately I don’t know of any tutorials that assume less knowledge.

  17. ila says:

    Hi giles:

    I find a easy way to solve the XHR problem in Chromium

    Change:
    function loadWorld() {
    var request = new XMLHttpRequest();
    request.open(”GET”, “world.txt”);
    request.onreadystatechange = function() {
    if (request.readyState == 4) {
    handleLoadedWorld(request.responseText);
    }
    }
    request.send();
    }

    To:
    function loadWorld() {
    var str=”";
    str += “\n”;
    str += “NUMPOLLIES 36\n”;
    str += “\n”;
    str += “// Floor 1\n”;
    str += “-3.0 0.0 -3.0 0.0 6.0\n”;
    str += “-3.0 0.0 3.0 0.0 0.0\n”;
    str += ” 3.0 0.0 3.0 6.0 0.0\n”;
    str += “\n”;
    str += “-3.0 0.0 -3.0 0.0 6.0\n”;
    str += ” 3.0 0.0 -3.0 6.0 6.0\n”;
    str += ” 3.0 0.0 3.0 6.0 0.0\n”;
    str += “\n”;
    str += “// Ceiling 1\n”;
    str += “-3.0 1.0 -3.0 0.0 6.0\n”;
    str += “-3.0 1.0 3.0 0.0 0.0\n”;
    str += ” 3.0 1.0 3.0 6.0 0.0\n”;
    str += “-3.0 1.0 -3.0 0.0 6.0\n”;
    str += ” 3.0 1.0 -3.0 6.0 6.0\n”;
    str += ” 3.0 1.0 3.0 6.0 0.0\n”;
    str += “\n”;
    str += “// A1\n”;
    str += “\n”;
    str += “-2.0 1.0 -2.0 0.0 1.0\n”;
    str += “-2.0 0.0 -2.0 0.0 0.0\n”;
    str += “-0.5 0.0 -2.0 1.5 0.0\n”;
    str += “-2.0 1.0 -2.0 0.0 1.0\n”;
    str += “-0.5 1.0 -2.0 1.5 1.0\n”;
    str += “-0.5 0.0 -2.0 1.5 0.0\n”;
    str += “\n”;
    str += “// A2\n”;
    str += “\n”;
    str += ” 2.0 1.0 -2.0 2.0 1.0\n”;
    str += ” 2.0 0.0 -2.0 2.0 0.0\n”;
    str += ” 0.5 0.0 -2.0 0.5 0.0\n”;
    str += ” 2.0 1.0 -2.0 2.0 1.0\n”;
    str += ” 0.5 1.0 -2.0 0.5 1.0\n”;
    str += ” 0.5 0.0 -2.0 0.5 0.0\n”;
    str += “\n”;
    str += “// B1\n”;
    str += “\n”;
    str += “-2.0 1.0 2.0 2.0 1.0\n”;
    str += “-2.0 0.0 2.0 2.0 0.0\n”;
    str += “-0.5 0.0 2.0 0.5 0.0\n”;
    str += “-2.0 1.0 2.0 2.0 1.0\n”;
    str += “-0.5 1.0 2.0 0.5 1.0\n”;
    str += “-0.5 0.0 2.0 0.5 0.0\n”;
    str += “\n”;
    str += “// B2\n”;
    str += “\n”;
    str += ” 2.0 1.0 2.0 2.0 1.0\n”;
    str += ” 2.0 0.0 2.0 2.0 0.0\n”;
    str += ” 0.5 0.0 2.0 0.5 0.0\n”;
    str += ” 2.0 1.0 2.0 2.0 1.0\n”;
    str += ” 0.5 1.0 2.0 0.5 1.0\n”;
    str += ” 0.5 0.0 2.0 0.5 0.0\n”;
    str += “\n”;
    str += “// C1\n”;
    str += “\n”;
    str += “-2.0 1.0 -2.0 0.0 1.0\n”;
    str += “-2.0 0.0 -2.0 0.0 0.0\n”;
    str += “-2.0 0.0 -0.5 1.5 0.0\n”;
    str += “-2.0 1.0 -2.0 0.0 1.0\n”;
    str += “-2.0 1.0 -0.5 1.5 1.0\n”;
    str += “-2.0 0.0 -0.5 1.5 0.0\n”;
    str += “\n”;
    str += “// C2\n”;
    str += “\n”;
    str += “-2.0 1.0 2.0 2.0 1.0\n”;
    str += “-2.0 0.0 2.0 2.0 0.0\n”;
    str += “-2.0 0.0 0.5 0.5 0.0\n”;
    str += “-2.0 1.0 2.0 2.0 1.0\n”;
    str += “-2.0 1.0 0.5 0.5 1.0\n”;
    str += “-2.0 0.0 0.5 0.5 0.0\n”;
    str += “\n”;
    str += “// D1\n”;
    str += “\n”;
    str += “2.0 1.0 -2.0 0.0 1.0\n”;
    str += “2.0 0.0 -2.0 0.0 0.0\n”;
    str += “2.0 0.0 -0.5 1.5 0.0\n”;
    str += “2.0 1.0 -2.0 0.0 1.0\n”;
    str += “2.0 1.0 -0.5 1.5 1.0\n”;
    str += “2.0 0.0 -0.5 1.5 0.0\n”;
    str += “\n”;
    str += “// D2\n”;
    str += “\n”;
    str += “2.0 1.0 2.0 2.0 1.0\n”;
    str += “2.0 0.0 2.0 2.0 0.0\n”;
    str += “2.0 0.0 0.5 0.5 0.0\n”;
    str += “2.0 1.0 2.0 2.0 1.0\n”;
    str += “2.0 1.0 0.5 0.5 1.0\n”;
    str += “2.0 0.0 0.5 0.5 0.0\n”;
    str += “\n”;
    str += “// Upper hallway – L\n”;
    str += “-0.5 1.0 -3.0 0.0 1.0\n”;
    str += “-0.5 0.0 -3.0 0.0 0.0\n”;
    str += “-0.5 0.0 -2.0 1.0 0.0\n”;
    str += “-0.5 1.0 -3.0 0.0 1.0\n”;
    str += “-0.5 1.0 -2.0 1.0 1.0\n”;
    str += “-0.5 0.0 -2.0 1.0 0.0\n”;
    str += “\n”;
    str += “// Upper hallway – R\n”;
    str += “0.5 1.0 -3.0 0.0 1.0\n”;
    str += “0.5 0.0 -3.0 0.0 0.0\n”;
    str += “0.5 0.0 -2.0 1.0 0.0\n”;
    str += “0.5 1.0 -3.0 0.0 1.0\n”;
    str += “0.5 1.0 -2.0 1.0 1.0\n”;
    str += “0.5 0.0 -2.0 1.0 0.0\n”;
    str += “\n”;
    str += “// Lower hallway – L\n”;
    str += “-0.5 1.0 3.0 0.0 1.0\n”;
    str += “-0.5 0.0 3.0 0.0 0.0\n”;
    str += “-0.5 0.0 2.0 1.0 0.0\n”;
    str += “-0.5 1.0 3.0 0.0 1.0\n”;
    str += “-0.5 1.0 2.0 1.0 1.0\n”;
    str += “-0.5 0.0 2.0 1.0 0.0\n”;
    str += “\n”;
    str += “// Lower hallway – R\n”;
    str += “0.5 1.0 3.0 0.0 1.0\n”;
    str += “0.5 0.0 3.0 0.0 0.0\n”;
    str += “0.5 0.0 2.0 1.0 0.0\n”;
    str += “0.5 1.0 3.0 0.0 1.0\n”;
    str += “0.5 1.0 2.0 1.0 1.0\n”;
    str += “0.5 0.0 2.0 1.0 0.0\n”;
    str += “\n”;
    str += “\n”;
    str += “// Left hallway – Lw\n”;
    str += “\n”;
    str += “-3.0 1.0 0.5 1.0 1.0\n”;
    str += “-3.0 0.0 0.5 1.0 0.0\n”;
    str += “-2.0 0.0 0.5 0.0 0.0\n”;
    str += “-3.0 1.0 0.5 1.0 1.0\n”;
    str += “-2.0 1.0 0.5 0.0 1.0\n”;
    str += “-2.0 0.0 0.5 0.0 0.0\n”;
    str += “\n”;
    str += “// Left hallway – Hi\n”;
    str += “\n”;
    str += “-3.0 1.0 -0.5 1.0 1.0\n”;
    str += “-3.0 0.0 -0.5 1.0 0.0\n”;
    str += “-2.0 0.0 -0.5 0.0 0.0\n”;
    str += “-3.0 1.0 -0.5 1.0 1.0\n”;
    str += “-2.0 1.0 -0.5 0.0 1.0\n”;
    str += “-2.0 0.0 -0.5 0.0 0.0\n”;
    str += “\n”;
    str += “// Right hallway – Lw\n”;
    str += “\n”;
    str += “3.0 1.0 0.5 1.0 1.0\n”;
    str += “3.0 0.0 0.5 1.0 0.0\n”;
    str += “2.0 0.0 0.5 0.0 0.0\n”;
    str += “3.0 1.0 0.5 1.0 1.0\n”;
    str += “2.0 1.0 0.5 0.0 1.0\n”;
    str += “2.0 0.0 0.5 0.0 0.0\n”;
    str += “\n”;
    str += “// Right hallway – Hi\n”;
    str += “\n”;
    str += “3.0 1.0 -0.5 1.0 1.0\n”;
    str += “3.0 0.0 -0.5 1.0 0.0\n”;
    str += “2.0 0.0 -0.5 0.0 0.0\n”;
    str += “3.0 1.0 -0.5 1.0 1.0\n”;
    str += “2.0 1.0 -0.5 0.0 1.0\n”;
    str += “2.0 0.0 -0.5 0.0 0.0\n”;
    handleLoadedWorld(str);

    }

    Thanks for your amazing WebGL Tutorials.

  18. Meriadeg says:

    Thanks Giles,

    It’s a good link :)
    Now i’m sure à i have to work hard javascript fort webGl.

    In fact I should wanted a key number like 54 or another number to get the ID of mouse boutton or event. Lol, like there is with any button of the Keyboard.

  19. giles says:

    @ila — right, that would work! Not the best solution if you’re trying to demonstrate how to load stuff from external files, though :-)

    @Meriadeg — glad to help!

  20. najib says:

    Thanks for your work.

  21. Ramon says:

    I’ve a bunch of old codes and some stuff from gametutorials
    about moving in a 3D world using gluLookat

    I’m also a newbie in Javascript I’ve found makeLookAt() at glUtils.
    but how does it work?
    Does anyone have a working version of “makeLookAt” or “LookAt”?

  22. [...] quickly discovered that Lesson 10 involved downloading a small scene and navigating it Doom-style, so I skipped ahead and using it as [...]

  23. Paul says:

    Please, I need help.
    With Chrome, I could run all the lessons. But when I try to download all the page to my computer, I couldn’t run this lesson at local ( though I could run lesson 1->9 at local)

  24. Ramon says:

    Well
    I finally found out how to makeLookAt work.
    http://www.opengl.org/wiki/GluLookAt_code

    thanks man!

Leave a Reply

Subscribe to RSS Feed Follow me on Twitter!