1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/scripts/utils3d.js Thu Mar 31 23:53:48 2011 +0200
1.3 @@ -0,0 +1,530 @@
1.4 +//
1.5 +// initWebGL
1.6 +//
1.7 +// Initialize the Canvas element with the passed name as a WebGL object and return the
1.8 +// WebGLRenderingContext.
1.9 +//
1.10 +// Load shaders with the passed names and create a program with them. Return this program
1.11 +// in the 'program' property of the returned context.
1.12 +//
1.13 +// For each string in the passed attribs array, bind an attrib with that name at that index.
1.14 +// Once the attribs are bound, link the program and then use it.
1.15 +//
1.16 +// Set the clear color to the passed array (4 values) and set the clear depth to the passed value.
1.17 +// Enable depth testing and blending with a blend func of (SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
1.18 +//
1.19 +function initWebGL(canvasName, vshader, fshader, attribs, clearColor, clearDepth)
1.20 +{
1.21 + var canvas = document.getElementById(canvasName);
1.22 + var gl = canvas.getContext("webkit-3d");
1.23 +
1.24 + // create our shaders
1.25 + var vertexShader = loadShader(gl, vshader);
1.26 + var fragmentShader = loadShader(gl, fshader);
1.27 +
1.28 + if (!vertexShader || !fragmentShader)
1.29 + return null;
1.30 +
1.31 + // Create the program object
1.32 + gl.program = gl.createProgram();
1.33 +
1.34 + if (!gl.program)
1.35 + return null;
1.36 +
1.37 + // Attach our two shaders to the program
1.38 + gl.attachShader (gl.program, vertexShader);
1.39 + gl.attachShader (gl.program, fragmentShader);
1.40 +
1.41 + // Bind attributes
1.42 + for (var i in attribs)
1.43 + gl.bindAttribLocation (gl.program, i, attribs[i]);
1.44 +
1.45 + // Link the program
1.46 + gl.linkProgram(gl.program);
1.47 +
1.48 + // Check the link status
1.49 + var linked = gl.getProgrami(gl.program, gl.LINK_STATUS);
1.50 + if (!linked) {
1.51 + // something went wrong with the link
1.52 + var error = gl.getProgramInfoLog (gl.program);
1.53 + console.log("Error in program linking:"+error);
1.54 +
1.55 + gl.deleteProgram(gl.program);
1.56 + gl.deleteProgram(fragmentShader);
1.57 + gl.deleteProgram(vertexShader);
1.58 +
1.59 + return null;
1.60 + }
1.61 +
1.62 + gl.useProgram(gl.program);
1.63 +
1.64 + gl.clearColor (clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
1.65 + gl.clearDepth (clearDepth);
1.66 +
1.67 + gl.enable(gl.DEPTH_TEST);
1.68 + gl.enable(gl.BLEND);
1.69 + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
1.70 +
1.71 + return gl;
1.72 +}
1.73 +
1.74 +//
1.75 +// loadShader
1.76 +//
1.77 +// 'shaderId' is the id of a <script> element containing the shader source string.
1.78 +// Load this shader and return the WebGLShader object corresponding to it.
1.79 +//
1.80 +function loadShader(ctx, shaderId)
1.81 +{
1.82 + var shaderScript = document.getElementById(shaderId);
1.83 + if (!shaderScript) {
1.84 + console.log("*** Error: shader script '"+shaderId+"' not found");
1.85 + return null;
1.86 + }
1.87 +
1.88 + if (shaderScript.type == "x-shader/x-vertex")
1.89 + var shaderType = ctx.VERTEX_SHADER;
1.90 + else if (shaderScript.type == "x-shader/x-fragment")
1.91 + var shaderType = ctx.FRAGMENT_SHADER;
1.92 + else {
1.93 + console.log("*** Error: shader script '"+shaderId+"' of undefined type '"+shaderScript.type+"'");
1.94 + return null;
1.95 + }
1.96 +
1.97 + // Create the shader object
1.98 + var shader = ctx.createShader(shaderType);
1.99 + if (shader == null) {
1.100 + console.log("*** Error: unable to create shader '"+shaderId+"'");
1.101 + return null;
1.102 + }
1.103 +
1.104 + // Load the shader source
1.105 + ctx.shaderSource(shader, shaderScript.text);
1.106 +
1.107 + // Compile the shader
1.108 + ctx.compileShader(shader);
1.109 +
1.110 + // Check the compile status
1.111 + var compiled = ctx.getShaderi(shader, ctx.COMPILE_STATUS);
1.112 + if (!compiled) {
1.113 + // Something went wrong during compilation; get the error
1.114 + var error = ctx.getShaderInfoLog(shader);
1.115 + console.log("*** Error compiling shader '"+shaderId+"':"+error);
1.116 + ctx.deleteShader(shader);
1.117 + return null;
1.118 + }
1.119 +
1.120 + return shader;
1.121 +}
1.122 +
1.123 +//
1.124 +// makeBox
1.125 +//
1.126 +// Create a box with vertices, normals and texCoords. Create VBOs for each as well as the index array.
1.127 +// Return an object with the following properties:
1.128 +//
1.129 +// normalObject WebGLBuffer object for normals
1.130 +// texCoordObject WebGLBuffer object for texCoords
1.131 +// vertexObject WebGLBuffer object for vertices
1.132 +// indexObject WebGLBuffer object for indices
1.133 +// numIndices The number of indices in the indexObject
1.134 +//
1.135 +function makeBox(ctx)
1.136 +{
1.137 + // box
1.138 + // v6----- v5
1.139 + // /| /|
1.140 + // v1------v0|
1.141 + // | | | |
1.142 + // | |v7---|-|v4
1.143 + // |/ |/
1.144 + // v2------v3
1.145 + //
1.146 + // vertex coords array
1.147 + var vertices = new WebGLFloatArray(
1.148 + [ 1, 1, 1, -1, 1, 1, -1,-1, 1, 1,-1, 1, // v0-v1-v2-v3 front
1.149 + 1, 1, 1, 1,-1, 1, 1,-1,-1, 1, 1,-1, // v0-v3-v4-v5 right
1.150 + 1, 1, 1, 1, 1,-1, -1, 1,-1, -1, 1, 1, // v0-v5-v6-v1 top
1.151 + -1, 1, 1, -1, 1,-1, -1,-1,-1, -1,-1, 1, // v1-v6-v7-v2 left
1.152 + -1,-1,-1, 1,-1,-1, 1,-1, 1, -1,-1, 1, // v7-v4-v3-v2 bottom
1.153 + 1,-1,-1, -1,-1,-1, -1, 1,-1, 1, 1,-1 ] // v4-v7-v6-v5 back
1.154 + );
1.155 +
1.156 + // normal array
1.157 + var normals = new WebGLFloatArray(
1.158 + [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // v0-v1-v2-v3 front
1.159 + 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right
1.160 + 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // v0-v5-v6-v1 top
1.161 + -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // v1-v6-v7-v2 left
1.162 + 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, // v7-v4-v3-v2 bottom
1.163 + 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1 ] // v4-v7-v6-v5 back
1.164 + );
1.165 +
1.166 +
1.167 + // texCoord array
1.168 + var texCoords = new WebGLFloatArray(
1.169 + [ 1, 1, 0, 1, 0, 0, 1, 0, // v0-v1-v2-v3 front
1.170 + 0, 1, 0, 0, 1, 0, 1, 1, // v0-v3-v4-v5 right
1.171 + 1, 0, 1, 1, 0, 1, 0, 0, // v0-v5-v6-v1 top
1.172 + 1, 1, 0, 1, 0, 0, 1, 0, // v1-v6-v7-v2 left
1.173 + 0, 0, 1, 0, 1, 1, 0, 1, // v7-v4-v3-v2 bottom
1.174 + 0, 0, 1, 0, 1, 1, 0, 1 ] // v4-v7-v6-v5 back
1.175 + );
1.176 +
1.177 + // index array
1.178 + var indices = new WebGLUnsignedByteArray(
1.179 + [ 0, 1, 2, 0, 2, 3, // front
1.180 + 4, 5, 6, 4, 6, 7, // right
1.181 + 8, 9,10, 8,10,11, // top
1.182 + 12,13,14, 12,14,15, // left
1.183 + 16,17,18, 16,18,19, // bottom
1.184 + 20,21,22, 20,22,23 ] // back
1.185 + );
1.186 +
1.187 + var retval = { };
1.188 +
1.189 + retval.normalObject = ctx.createBuffer();
1.190 + ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject);
1.191 + ctx.bufferData(ctx.ARRAY_BUFFER, normals, ctx.STATIC_DRAW);
1.192 +
1.193 + retval.texCoordObject = ctx.createBuffer();
1.194 + ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.texCoordObject);
1.195 + ctx.bufferData(ctx.ARRAY_BUFFER, texCoords, ctx.STATIC_DRAW);
1.196 +
1.197 + retval.vertexObject = ctx.createBuffer();
1.198 + ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject);
1.199 + ctx.bufferData(ctx.ARRAY_BUFFER, vertices, ctx.STATIC_DRAW);
1.200 +
1.201 + ctx.bindBuffer(ctx.ARRAY_BUFFER, 0);
1.202 +
1.203 + retval.indexObject = ctx.createBuffer();
1.204 + ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, retval.indexObject);
1.205 + ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, indices, ctx.STATIC_DRAW);
1.206 + ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, 0);
1.207 +
1.208 + retval.numIndices = indices.length;
1.209 +
1.210 + return retval;
1.211 +}
1.212 +
1.213 +//
1.214 +// makeSphere
1.215 +//
1.216 +// Create a sphere with the passed number of latitude and longitude bands and the passed radius.
1.217 +// Sphere has vertices, normals and texCoords. Create VBOs for each as well as the index array.
1.218 +// Return an object with the following properties:
1.219 +//
1.220 +// normalObject WebGLBuffer object for normals
1.221 +// texCoordObject WebGLBuffer object for texCoords
1.222 +// vertexObject WebGLBuffer object for vertices
1.223 +// indexObject WebGLBuffer object for indices
1.224 +// numIndices The number of indices in the indexObject
1.225 +//
1.226 +function makeSphere(ctx, radius, lats, longs)
1.227 +{
1.228 + var geometryData = [ ];
1.229 + var normalData = [ ];
1.230 + var texCoordData = [ ];
1.231 + var indexData = [ ];
1.232 +
1.233 + for (var latNumber = 0; latNumber <= lats; ++latNumber) {
1.234 + for (var longNumber = 0; longNumber <= longs; ++longNumber) {
1.235 + var theta = latNumber * Math.PI / lats;
1.236 + var phi = longNumber * 2 * Math.PI / longs;
1.237 + var sinTheta = Math.sin(theta);
1.238 + var sinPhi = Math.sin(phi);
1.239 + var cosTheta = Math.cos(theta);
1.240 + var cosPhi = Math.cos(phi);
1.241 +
1.242 + var x = cosPhi * sinTheta;
1.243 + var y = cosTheta;
1.244 + var z = sinPhi * sinTheta;
1.245 + var u = 1-(longNumber/longs);
1.246 + var v = latNumber/lats;
1.247 +
1.248 + normalData.push(x);
1.249 + normalData.push(y);
1.250 + normalData.push(z);
1.251 + texCoordData.push(u);
1.252 + texCoordData.push(v);
1.253 + geometryData.push(radius * x);
1.254 + geometryData.push(radius * y);
1.255 + geometryData.push(radius * z);
1.256 + }
1.257 + }
1.258 +
1.259 + longs += 1;
1.260 + for (var latNumber = 0; latNumber < lats; ++latNumber) {
1.261 + for (var longNumber = 0; longNumber < longs; ++longNumber) {
1.262 + var first = (latNumber * longs) + (longNumber % longs);
1.263 + var second = first + longs;
1.264 + indexData.push(first);
1.265 + indexData.push(second);
1.266 + indexData.push(first+1);
1.267 +
1.268 + indexData.push(second);
1.269 + indexData.push(second+1);
1.270 + indexData.push(first+1);
1.271 + }
1.272 + }
1.273 +
1.274 + var retval = { };
1.275 +
1.276 + retval.normalObject = ctx.createBuffer();
1.277 + ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject);
1.278 + ctx.bufferData(ctx.ARRAY_BUFFER, new WebGLFloatArray(normalData), ctx.STATIC_DRAW);
1.279 +
1.280 + retval.texCoordObject = ctx.createBuffer();
1.281 + ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.texCoordObject);
1.282 + ctx.bufferData(ctx.ARRAY_BUFFER, new WebGLFloatArray(texCoordData), ctx.STATIC_DRAW);
1.283 +
1.284 + retval.vertexObject = ctx.createBuffer();
1.285 + ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject);
1.286 + ctx.bufferData(ctx.ARRAY_BUFFER, new WebGLFloatArray(geometryData), ctx.STATIC_DRAW);
1.287 +
1.288 + retval.numIndices = indexData.length;
1.289 + retval.indexObject = ctx.createBuffer();
1.290 + ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, retval.indexObject);
1.291 + ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(indexData), ctx.STREAM_DRAW);
1.292 +
1.293 + return retval;
1.294 +}
1.295 +
1.296 +//
1.297 +// loadObj
1.298 +//
1.299 +// Load a .obj file from the passed URL. Return an object with a 'loaded' property set to false.
1.300 +// When the object load is complete, the 'loaded' property becomes true and the following
1.301 +// properties are set:
1.302 +//
1.303 +// normalObject WebGLBuffer object for normals
1.304 +// texCoordObject WebGLBuffer object for texCoords
1.305 +// vertexObject WebGLBuffer object for vertices
1.306 +// indexObject WebGLBuffer object for indices
1.307 +// numIndices The number of indices in the indexObject
1.308 +//
1.309 +function loadObj(ctx, url)
1.310 +{
1.311 + var obj = { loaded : false };
1.312 + obj.ctx = ctx;
1.313 + var req = new XMLHttpRequest();
1.314 + req.obj = obj;
1.315 + req.onreadystatechange = function () { processLoadObj(req) };
1.316 + req.open("GET", url, true);
1.317 + req.send(null);
1.318 + return obj;
1.319 +}
1.320 +
1.321 +function processLoadObj(req)
1.322 +{
1.323 + console.log("req="+req)
1.324 + // only if req shows "complete"
1.325 + if (req.readyState == 4) {
1.326 + doLoadObj(req.obj, req.responseText);
1.327 + }
1.328 +}
1.329 +
1.330 +function doLoadObj(obj, text)
1.331 +{
1.332 + vertexArray = [ ];
1.333 + normalArray = [ ];
1.334 + textureArray = [ ];
1.335 + indexArray = [ ];
1.336 +
1.337 + var vertex = [ ];
1.338 + var normal = [ ];
1.339 + var texture = [ ];
1.340 + var facemap = { };
1.341 + var index = 0;
1.342 +
1.343 + var lines = text.split("\n");
1.344 + for (var lineIndex in lines) {
1.345 + var line = lines[lineIndex].replace(/[ \t]+/g, " ").replace(/\s\s*$/, "");
1.346 +
1.347 + // ignore comments
1.348 + if (line[0] == "#")
1.349 + continue;
1.350 +
1.351 + var array = line.split(" ");
1.352 + if (array[0] == "v") {
1.353 + // vertex
1.354 + vertex.push(parseFloat(array[1]));
1.355 + vertex.push(parseFloat(array[2]));
1.356 + vertex.push(parseFloat(array[3]));
1.357 + }
1.358 + else if (array[0] == "vt") {
1.359 + // normal
1.360 + texture.push(parseFloat(array[1]));
1.361 + texture.push(parseFloat(array[2]));
1.362 + }
1.363 + else if (array[0] == "vn") {
1.364 + // normal
1.365 + normal.push(parseFloat(array[1]));
1.366 + normal.push(parseFloat(array[2]));
1.367 + normal.push(parseFloat(array[3]));
1.368 + }
1.369 + else if (array[0] == "f") {
1.370 + // face
1.371 + if (array.length != 4) {
1.372 + console.log("*** Error: face '"+line+"' not handled");
1.373 + continue;
1.374 + }
1.375 +
1.376 + for (var i = 1; i < 4; ++i) {
1.377 + if (!(array[i] in facemap)) {
1.378 + // add a new entry to the map and arrays
1.379 + var f = array[i].split("/");
1.380 + var vtx, nor, tex;
1.381 +
1.382 + if (f.length == 1) {
1.383 + vtx = parseInt(f[0]) - 1;
1.384 + nor = vtx;
1.385 + tex = vtx;
1.386 + }
1.387 + else if (f.length = 3) {
1.388 + vtx = parseInt(f[0]) - 1;
1.389 + tex = parseInt(f[1]) - 1;
1.390 + nor = parseInt(f[2]) - 1;
1.391 + }
1.392 + else {
1.393 + console.log("*** Error: did not understand face '"+array[i]+"'");
1.394 + return null;
1.395 + }
1.396 +
1.397 + // do the vertices
1.398 + var x = 0;
1.399 + var y = 0;
1.400 + var z = 0;
1.401 + if (vtx * 3 + 2 < vertex.length) {
1.402 + x = vertex[vtx*3];
1.403 + y = vertex[vtx*3+1];
1.404 + z = vertex[vtx*3+2];
1.405 + }
1.406 + vertexArray.push(x);
1.407 + vertexArray.push(y);
1.408 + vertexArray.push(z);
1.409 +
1.410 + // do the textures
1.411 + x = 0;
1.412 + y = 0;
1.413 + if (tex * 2 + 1 < texture.length) {
1.414 + x = texture[tex*2];
1.415 + y = texture[tex*2+1];
1.416 + }
1.417 + textureArray.push(x);
1.418 + textureArray.push(y);
1.419 +
1.420 + // do the normals
1.421 + x = 0;
1.422 + y = 0;
1.423 + z = 1;
1.424 + if (nor * 3 + 2 < normal.length) {
1.425 + x = normal[nor*3];
1.426 + y = normal[nor*3+1];
1.427 + z = normal[nor*3+2];
1.428 + }
1.429 + normalArray.push(x);
1.430 + normalArray.push(y);
1.431 + normalArray.push(z);
1.432 +
1.433 + facemap[array[i]] = index++;
1.434 + }
1.435 +
1.436 + indexArray.push(facemap[array[i]]);
1.437 + }
1.438 + }
1.439 + }
1.440 +
1.441 + // set the VBOs
1.442 + obj.normalObject = obj.ctx.createBuffer();
1.443 + obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.normalObject);
1.444 + obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new WebGLFloatArray(normalArray), obj.ctx.STATIC_DRAW);
1.445 +
1.446 + obj.texCoordObject = obj.ctx.createBuffer();
1.447 + obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.texCoordObject);
1.448 + obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new WebGLFloatArray(textureArray), obj.ctx.STATIC_DRAW);
1.449 +
1.450 + obj.vertexObject = obj.ctx.createBuffer();
1.451 + obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.vertexObject);
1.452 + obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new WebGLFloatArray(vertexArray), obj.ctx.STATIC_DRAW);
1.453 +
1.454 + obj.numIndices = indexArray.length;
1.455 + obj.indexObject = obj.ctx.createBuffer();
1.456 + obj.ctx.bindBuffer(obj.ctx.ELEMENT_ARRAY_BUFFER, obj.indexObject);
1.457 + obj.ctx.bufferData(obj.ctx.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(indexArray), obj.ctx.STREAM_DRAW);
1.458 +
1.459 + obj.loaded = true;
1.460 +}
1.461 +
1.462 +//
1.463 +// loadImageTexture
1.464 +//
1.465 +// Load the image at the passed url, place it in a new WebGLTexture object and return the WebGLTexture.
1.466 +//
1.467 +function loadImageTexture(ctx, url)
1.468 +{
1.469 + var texture = ctx.createTexture();
1.470 + texture.image = new Image();
1.471 + texture.image.onload = function() { doLoadImageTexture(ctx, texture.image, texture) }
1.472 + texture.image.src = url;
1.473 + return texture;
1.474 +}
1.475 +
1.476 +function doLoadImageTexture(ctx, image, texture)
1.477 +{
1.478 + ctx.enable(ctx.TEXTURE_2D);
1.479 + ctx.bindTexture(ctx.TEXTURE_2D, texture);
1.480 + ctx.texImage2D(ctx.TEXTURE_2D, 0, image);
1.481 + ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MAG_FILTER, ctx.LINEAR);
1.482 + ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MIN_FILTER, ctx.LINEAR_MIPMAP_LINEAR);
1.483 + ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_S, ctx.CLAMP_TO_EDGE);
1.484 + ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_T, ctx.CLAMP_TO_EDGE);
1.485 + ctx.generateMipmap(ctx.TEXTURE_2D)
1.486 + ctx.bindTexture(ctx.TEXTURE_2D, 0);
1.487 +}
1.488 +
1.489 +//
1.490 +// Framerate object
1.491 +//
1.492 +// This object keeps track of framerate and displays it as the innerHTML text of the
1.493 +// HTML element with the passed id. Once created you call snapshot at the end
1.494 +// of every rendering cycle. Every 500ms the framerate is updated in the HTML element.
1.495 +//
1.496 +Framerate = function(id)
1.497 +{
1.498 + this.numFramerates = 10;
1.499 + this.framerateUpdateInterval = 500;
1.500 + this.id = id;
1.501 +
1.502 + this.renderTime = -1;
1.503 + this.framerates = [ ];
1.504 + self = this;
1.505 + var fr = function() { self.updateFramerate() }
1.506 + setInterval(fr, this.framerateUpdateInterval);
1.507 +}
1.508 +
1.509 +Framerate.prototype.updateFramerate = function()
1.510 +{
1.511 + var tot = 0;
1.512 + for (var i = 0; i < this.framerates.length; ++i)
1.513 + tot += this.framerates[i];
1.514 +
1.515 + var framerate = tot / this.framerates.length;
1.516 + framerate = Math.round(framerate);
1.517 + document.getElementById(this.id).innerHTML = "Framerate:"+framerate+"fps";
1.518 +}
1.519 +
1.520 +Framerate.prototype.snapshot = function()
1.521 +{
1.522 + if (this.renderTime < 0)
1.523 + this.renderTime = new Date().getTime();
1.524 + else {
1.525 + var newTime = new Date().getTime();
1.526 + var t = newTime - this.renderTime;
1.527 + var framerate = 1000/t;
1.528 + this.framerates.push(framerate);
1.529 + while (this.framerates.length > this.numFramerates)
1.530 + this.framerates.shift();
1.531 + this.renderTime = newTime;
1.532 + }
1.533 +}
1.534 \ No newline at end of file