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. A brief warning — because this tutorial loads up the details of the scene it’s showing from a separate file, it will not run in Chrome if you load it up as a file from your local machine. This is a Chrome security “feature”. So if you want to view a version on your own machine, you should either use Firefox beta or Minefield, or install a web server on your machine and view the page through that.

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.enable(gl.DEPTH_TEST);

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

    tick();
  }

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 projection and the model-view matrices:

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

    mat4.identity(mvMatrix);

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:

    mat4.rotate(mvMatrix, degToRad(-pitch), [1, 0, 0]);
    mat4.rotate(mvMatrix, degToRad(-yaw), [0, 1, 0]);
    mat4.translate(mvMatrix, [-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;
// 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. 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(degToRad(yaw)) * speed * elapsed;
        zPos -= Math.cos(degToRad(yaw)) * speed * elapsed;

        joggingAngle += elapsed * 0.6;  // 0.6 "fiddle factor" -- makes it feel more realistic :-)
        yPos = Math.sin(degToRad(joggingAngle)) / 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 degToRad function to adjust from degrees, which we use for readability, to radians, which the JavaScript trigonometry 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.

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

  1. Ankit says:

    I m new to Webgl , and i have used your Lesson 10 code to construct my own house 3D model but without lighting its not looking that real, i have Normal of all the surfaces and i tried your lesson 11 lighting code for it but didnt succeed ……plz can u give me some demo code to enable lighting on a flat surface
    Thanx

  2. Bender says:

    Hi mate, thaks very much for these lessons, helped quite a lot.

    But I have problem with this one. It just didnt work when copy the source to PSpad and start it locally. Other lessons works fine…

  3. Bender says:

    yes Im using local server and yes I tried chrome and mozilla. I belive the problem is in world.txt or in the source.

  4. giles says:

    @jgrc — thanks for the feedback, I’ll look at changing that.

    @Mark Nov 23rd — sorry, I don’t — the file is just the one from the original NeHe lessons I based these first ten tutorials on.

    @Mark Nov 25th, @phil — I don’t have any collision detection code to hand, sorry!

    @Rod — yup, it’s due to the model. The 6.0s for the UVs are for the floor and ceiling only — and it’s just because they’re 6 by 6 “tiles” — that is, repetitions of the texture — across. Basically if you want your image to appear just once on your rectangle, just use 1.0 for the maximum u and v. If you want it to appear twice horizontally and once vertically, use 2.0 for u and 1.0 for v. And so on.

    @tushar — can you see any WebGL demos at all? There’s nothing particularly special about this one…

  5. giles says:

    @Ankit — check out lesson 7

    @Bender — do you see the demo when you view it from the link under the YouTube video? If so, then the world.txt and the source definitely work. One thing that people often forget when copying the demos from this site is to also copy the glmatrix code, so you might want to double-check that.

  6. Maayan says:

    Hi,
    I have the same problem as Bender.
    And the demo is Working…

    i changed all the relative files paths to an absolute files paths in the site (for exsample, instand of “mud.gif”, i wrote “http://learningwebgl.com/lessons/lesson10/mud.gif”), and it’s not working! (I see black square…)

    what is wrong?

  7. giles says:

    That sounds like a cross-site problem; by default WebGL (and other JavaScript) isn’t allowed to read images for other websites (otherwise imagine a page that connected to your online banking site, which you were logged into in another browser tab, and downloaded private information). There is a way around it — the server that hosts the images can say “I don’t mind who sees these images” — but I don’t have that switched on on my site.

    What you need to do is set up a local web server to host your copy of the code, and then put the images there too. If you do that, everything comes from the same server, so when you use absolute paths like “http://myserver/images/mud.gif” then it will all work.

  8. Maayan says:

    I tried to put a link of some diffrante gif images from the internet, and it’s still not working :/

    do you have another idea?

  9. Bender says:

    Thanks, finally its working

    But one more thing, do you have any idea how to put a sky into it?

  10. Antonio says:

    I need a hint to simulate a jump on spacebar press!

  11. Confect says:

    Ran into a rather strange error that I can’t figure out.
    Seems to be centered around the line:

    var vals = lines[i].replace(/^\s+/, “”).split(/\s+/);

    When run from my html file, even if the source is identical to that hosted here, only the last vertex of the scene comes out with 5 elements in vals. All others have the 5 elements they should have plus an empty string (6, total).

    I’m not familiar with regular expressions, but I find it bizarre that the same code ran from [http://learningwebgl.com/lessons/lesson10/index.html] works fine, even when stepping through.

  12. Sime says:

    Giles, thansks for your lessons…

    For Confesct and others that example dont work..try changing

    this: var vals = lines[i].replace(/^\s+/, “”).split(/\s+/);

    to this: var vals = lines[i].replace(/^\s+/, “”).split(” “);

  13. Confect says:

    Afraid that expression made it worse Sime. I need to find some time to get a grip on regular expressions – I currently have no idea what the whole /^\s+/ business is about.

  14. Sime says:

    For me, it is working..after long hours of debuging and iterating throug loop

  15. Sime says:

    I use firefox

  16. gamemanj says:

    Bender,to draw a sky,add code before the camera translate and after the camera rotates(else the box won’t move with you correctly,and it won’t work) to make a box.
    Make it a very big box,then texture it.
    Now you have a skybox.
    Notes about the position of the code:
    If it is done before the camera rotates,the sky would look more like a giant helmet(in that it would rotate with your head)
    If it is done after the camera translate,you could touch the sky(because it wouldn’t move with you.

  17. Synoecium says:

    For which people that wanted to run the demo locally and haven’t done it i can offer a simple way to make the code work. Obviously the problem is in model file “world.txt”, more exactly with XMLHttpRequest class (i can be mistaken, but it works with my chrome browser).
    The solution is to get rid of loading coords from file and send plain string to the handleLoadedWorld() instead. For doing that just replace you function loadWorld with this piece of code (if you can offer a more elegant way to solve this problem, it would be great to learn it):

    function loadWorld() {
    /*var request = new XMLHttpRequest();
    //request.open(“GET”, “world.txt”);
    request.onreadystatechange = function () {
    if (request.readyState == 4) {
    handleLoadedWorld(request.responseText);
    }
    }
    request.send();*/
    handleLoadedWorld(“\nNUMPOLLIES 36\n\n// Floor 1\n-3.0 0.0 -3.0 0.0 6.0\n-3.0 0.0 3.0 0.0 0.0\n 3.0 0.0 3.0 6.0 0.0\n\n-3.0 0.0 -3.0 0.0 6.0\n 3.0 0.0 -3.0 6.0 6.0\n 3.0 0.0 3.0 6.0 0.0\n\n// Ceiling 1\n-3.0 1.0 -3.0 0.0 6.0\n-3.0 1.0 3.0 0.0 0.0\n 3.0 1.0 3.0 6.0 0.0\n-3.0 1.0 -3.0 0.0 6.0\n 3.0 1.0 -3.0 6.0 6.0\n 3.0 1.0 3.0 6.0 0.0\n\n// A1\n\n-2.0 1.0 -2.0 0.0 1.0\n-2.0 0.0 -2.0 0.0 0.0\n-0.5 0.0 -2.0 1.5 0.0\n-2.0 1.0 -2.0 0.0 1.0\n-0.5 1.0 -2.0 1.5 1.0\n-0.5 0.0 -2.0 1.5 0.0\n\n// A2\n\n 2.0 1.0 -2.0 2.0 1.0\n 2.0 0.0 -2.0 2.0 0.0\n 0.5 0.0 -2.0 0.5 0.0\n 2.0 1.0 -2.0 2.0 1.0\n 0.5 1.0 -2.0 0.5 1.0\n 0.5 0.0 -2.0 0.5 0.0\n\n// B1\n\n-2.0 1.0 2.0 2.0 1.0\n-2.0 0.0 2.0 2.0 0.0\n-0.5 0.0 2.0 0.5 0.0\n-2.0 1.0 2.0 2.0 1.0\n-0.5 1.0 2.0 0.5 1.0\n-0.5 0.0 2.0 0.5 0.0\n\n// B2\n\n 2.0 1.0 2.0 2.0 1.0\n 2.0 0.0 2.0 2.0 0.0\n 0.5 0.0 2.0 0.5 0.0\n 2.0 1.0 2.0 2.0 1.0\n 0.5 1.0 2.0 0.5 1.0\n 0.5 0.0 2.0 0.5 0.0\n\n// C1\n\n-2.0 1.0 -2.0 0.0 1.0\n-2.0 0.0 -2.0 0.0 0.0\n-2.0 0.0 -0.5 1.5 0.0\n-2.0 1.0 -2.0 0.0 1.0\n-2.0 1.0 -0.5 1.5 1.0\n-2.0 0.0 -0.5 1.5 0.0\n\n// C2\n\n-2.0 1.0 2.0 2.0 1.0\n-2.0 0.0 2.0 2.0 0.0\n-2.0 0.0 0.5 0.5 0.0\n-2.0 1.0 2.0 2.0 1.0\n-2.0 1.0 0.5 0.5 1.0\n-2.0 0.0 0.5 0.5 0.0\n\n// D1\n\n2.0 1.0 -2.0 0.0 1.0\n2.0 0.0 -2.0 0.0 0.0\n2.0 0.0 -0.5 1.5 0.0\n2.0 1.0 -2.0 0.0 1.0\n2.0 1.0 -0.5 1.5 1.0\n2.0 0.0 -0.5 1.5 0.0\n\n// D2\n\n2.0 1.0 2.0 2.0 1.0\n2.0 0.0 2.0 2.0 0.0\n2.0 0.0 0.5 0.5 0.0\n2.0 1.0 2.0 2.0 1.0\n2.0 1.0 0.5 0.5 1.0\n2.0 0.0 0.5 0.5 0.0\n\n// Upper hallway – L\n-0.5 1.0 -3.0 0.0 1.0\n-0.5 0.0 -3.0 0.0 0.0\n-0.5 0.0 -2.0 1.0 0.0\n-0.5 1.0 -3.0 0.0 1.0\n-0.5 1.0 -2.0 1.0 1.0\n-0.5 0.0 -2.0 1.0 0.0\n\n// Upper hallway – R\n0.5 1.0 -3.0 0.0 1.0\n0.5 0.0 -3.0 0.0 0.0\n0.5 0.0 -2.0 1.0 0.0\n0.5 1.0 -3.0 0.0 1.0\n0.5 1.0 -2.0 1.0 1.0\n0.5 0.0 -2.0 1.0 0.0\n\n// Lower hallway – L\n-0.5 1.0 3.0 0.0 1.0\n-0.5 0.0 3.0 0.0 0.0\n-0.5 0.0 2.0 1.0 0.0\n-0.5 1.0 3.0 0.0 1.0\n-0.5 1.0 2.0 1.0 1.0\n-0.5 0.0 2.0 1.0 0.0\n\n// Lower hallway – R\n0.5 1.0 3.0 0.0 1.0\n0.5 0.0 3.0 0.0 0.0\n0.5 0.0 2.0 1.0 0.0\n0.5 1.0 3.0 0.0 1.0\n0.5 1.0 2.0 1.0 1.0\n0.5 0.0 2.0 1.0 0.0\n\n\n// Left hallway – Lw\n\n-3.0 1.0 0.5 1.0 1.0\n-3.0 0.0 0.5 1.0 0.0\n-2.0 0.0 0.5 0.0 0.0\n-3.0 1.0 0.5 1.0 1.0\n-2.0 1.0 0.5 0.0 1.0\n-2.0 0.0 0.5 0.0 0.0\n\n// Left hallway – Hi\n\n-3.0 1.0 -0.5 1.0 1.0\n-3.0 0.0 -0.5 1.0 0.0\n-2.0 0.0 -0.5 0.0 0.0\n-3.0 1.0 -0.5 1.0 1.0\n-2.0 1.0 -0.5 0.0 1.0\n-2.0 0.0 -0.5 0.0 0.0\n\n// Right hallway – Lw\n\n3.0 1.0 0.5 1.0 1.0\n3.0 0.0 0.5 1.0 0.0\n2.0 0.0 0.5 0.0 0.0\n3.0 1.0 0.5 1.0 1.0\n2.0 1.0 0.5 0.0 1.0\n2.0 0.0 0.5 0.0 0.0\n\n// Right hallway – Hi\n\n3.0 1.0 -0.5 1.0 1.0\n3.0 0.0 -0.5 1.0 0.0\n2.0 0.0 -0.5 0.0 0.0\n3.0 1.0 -0.5 1.0 1.0\n2.0 1.0 -0.5 0.0 1.0\n2.0 0.0 -0.5 0.0 0.0\n”)
    }

  18. Synoecium says:

    http://pastebin.com/cy49qfRv

    Who wants this demo with jump possibility?))
    Jump on space bar, you can play with values in jump object, in order to change gravity (jump.accel) and start jump speed (jump.startSpeed);

  19. LastBeagle says:

    Just to help out anybody that may have had this same problem, and I realize this doesn’t make me seem to bright :) , but….

    When you’re grabbing files from the github repository to put on your local web server, be sure not just right click and save link as, if you do it will save github’s HTML representation of that file and will have a bunch of markup that will break the code. So I just copied and pasted the content into a new file, and saved it on my own. Small thing, but might help a few folks.

    Other than that, once I set it up locally (from the IIS on my machine), it worked fairly quickly.

    Giles, these are great tutorials, thanks so much!

  20. x13r4dx says:

    Is there a way to add collision to this demo? like, the walls are actually walls, instead of walk through barriers.

    i am looking to try making a single player game.

    Thanks

  21. Fog says:

    i’m having a problem with this particular lesson, as when i try to run it locally on a simple python server, nothing shows up. i’ve done it on both chrome and firefox and checked over to make sure i copy/paste/saved it in the right formats, but i still get a black canvas.

    if it helps, here’s a bit of info from the server’s command line:
    “GET /webgl-utils.js HTTP/1.1″ 200 -
    “GET /world.txt HTTP/1.1″ 200 -
    code 404, message File not found
    “GET /favicon.ico HTTP/1.1″ 404 -

  22. mohammad says:

    tnx Synoecium
    handleLoadedWorld functions works fine…
    very well…

  23. Grüse says:

    I have to admit that I’m having trouble understanding the part where the position is updated according to the current yaw and speed values:

    xPos -= Math.sin(degToRad(yaw)) * speed * elapsed;
    zPos -= Math.cos(degToRad(yaw)) * speed * elapsed;

    Could anyone explain this “simple bit of trigonometry” in a bit more detail for slow ones like me?

  24. Grüse says:

    Alright, I think I get it. Putting in actual values for yaw helped quite a bit. Still a bit confused as to why we’re subtracting the change in position here, but the general concept seems clear now.

  25. TranThoHoang says:

    hello,
    thank for the lessons,
    i have a model in 3ds max (or maya), how do i load it to the web by webgl?
    please help me, thank!!

  26. admin says:

    Hi Tony

    Unfortunately there is still no easy answer to this question! As in, there is no simple app where you can just upload a model and view it on your machine. There are a few online services where you can upload a model to a site and then view it in your browser. Typically you have to sign up. Two that I know and respect are http://verold.com/ and https://sketchfab.com/. They are different, both good, depending on what you are trying to do. Neither of them just let you preview the model for use in your own application… but there are other people working on tools like that, myself included. Watch this space. Good luck!

  27. TranThoHoang says:

    So, How to detect collision with the wall in this lesson

  28. helinjan says:

    Great tutorial once again!

    As some others, I had problems running this locally. My set-up is: Windows 7 (64-bit), and Chrome Canary started with ‘–allow-file-access-from-files’ command line option to allow loading of local files. Files like world.txt I saved to my hard drive directly from the web-site.

    The problem was that in ‘handleLoadedWorld()’ function the lines with vertices were split up incorrectly: They came up with six items, and thus were not recognized as vertex lines. My guess is that this has something to do with linefeed differences between windows and other operating systems (based on the fact that the unwanted “sixth item” was some kind of non-printing character).

    As a brute force solution I made following change. Instead of this line:

    var vals = lines[i].replace(/^\s+/, “”).split(/\s+/);

    I used these lines:

    lines[i] = lines[i].trimRight();
    lines[i] = lines[i].trimLeft();
    var vals = lines[i].split(/\s+/);

    It is not very elegant but works for me. Hopefully also helps others who have found no other solution.

Leave a Reply

Subscribe to RSS Feed Follow Learning WebGL on Twitter