For one of my courses I need to program a 3D application in html5 canvas. For this I chose to use Three.js. For now, all I'm trying to do is render a simple plane. I also want to be able to move the camera along the x-axis by holding down the mouse and dragging. I based my code on the code found here. It's basically a plane which is spinning around (or the camera is circling around the plane, not sure which). I threw away all the code for the animation and added code for mousedown, mousemove, etc events. When I load the page it renders the plane (inanimate) just fine, but when I try to use my mouse to move it around it remains completely unresponsive. In my code you may have noticed I changed some things I maybe shouldn't have (made some variables global and changed some function names) in order to get things working, but the result remained the same.
This is the first time I'm working with javascript and I realize I can't just change existing code and expect it to work. I looked into javascript verification and I found JSLint. I downloaded the plugin for notepad++ and tried to verify my code that way. It doesn't like the html tags and it especially hates the Three.js library I'm trying to import. It doesn't pick up on it when I import it as url and it doesn't import it when I download it and import the file either. When I just copied the code of the entire library into the file it just gave me a lot of errors about the library itself and stopped verifying somehwere before my code even started.
In conclusion: I would like some help with the project itself but also with setting up a pleasant development environment (with proper verification that doesn't ignore imports).
Finally, my code so far:
<!DOCTYPE HTML>
<html>
<head>
<title>Canvas demo</title>
</head>
<body>
<script type="text/javascript" src="three.js">
</script>
<script type="text/javascript">
var camera;
var scene;
var plane;
var renderer;
var clickX;
var clickY;
var mousedown;
window.onload = function () {
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//camera
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.y = -450;
camera.position.z = 400;
camera.rotation.x = 45;
//scene
scene = new THREE.Scene();
//plane
plane = new THREE.Mesh(new THREE.PlaneGeometry(300, 300), new THREE.MeshBasicMaterial({color: 0x0000ff}));
plane.overdraw = true;
scene.add(plane);
rerender();
};
window.requestAnimFrame = (function (callback){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
function rerender(){
//render
renderer.render(scene, camera);
//request new frame
requestAnimFrame(function(){
rerender();
});
}
$('#canvas').mousedown(function(e){
clickX = e.pageX - this.offsetLeft;
mousedown = true;
});
$('#canvas').mousemove(function(e){
if(mousedown) {
var xDiff = e.pageX - this.offsetLeft - clickX;
camera.position.x = xDiff;
rerender();
}
});
$('#canvas').mouseup(function(e){
mousedown = false;
});
$('#canvas').mouseleave(function(e){
mousedown = false;
});
</script>
</body>
</html>
You have various issues here that are preventing your code from running properly. I've gone through and fixed them, and you can now move the camera along the x-axis:
http://jsfiddle.net/TVWYn/6/
Your issues were:
You're using jQuery and not including the jQuery library. The dollar
sign function is not part of JavaScript, it's (in this case) part of
a third-party library called jQuery that makes working with the DOM
easier. Without including the jQuery library, your mouse handlers
couldn't run.
Even with jQuery included, the order in which your setup functions
executed was incorrect. Originally you had your Three.js setup
process such that it would execute once the DOM was initialized.
That's good. However, you were trying to register mouse event
handlers immediately after the JS was acquired by the browser. This
is not good. You're asking JS to attach mouse event handlers to DOM elements that don't exist at that point in time. To fix this,
you need to ensure that the DOM has been initialized (ex. running your setup inside a window.onload) and your
required elements have already been inserted (in this case,
#canvas). Or, attach your event handlers to the DOM objects directly (ex. renderer.domElement).
You were referencing #canvas without ever setting the id of your
canvas element.
With regard to setting up a proper development environment, I would strongly suggest that you start using Chrome and its developer tools (which are launched via ctrl+shift+j).
Related
I have a project using HTML5 Canvas (createjs) and I've had issues with spikes on text strokes, meaning I have to set the miterlimit of the 2d context. However, the parent (which I have no control over) scales the canvas when the window is resized, which obviously resets the canvas context.
Now, I want to avoid putting an onresize event inside the client - my first thought is just to use the createjs Ticker thus:
createjs.Ticker.addEventListener("tick", handleTick);
function handleTick(event) {
var ctx = canvas.getContext('2d');
ctx.miterLimit = 2;
}
Though this seems a little wasteful, is there a more efficient way of doing this, without using DOM events?
Your approach might work, but its definitely a hack, since you can't expect that context properties will be maintained, or that they won't be applied in the wrong place.
If you do want to patch the display list to update the context, you can use the "drawstart" event, which fires before the display list is drawn:
stage.on("drawstart", function(e) {
var ctx = stage.canvas.getContext("2d");
ctx.miterLimit = 2;
});
However if you want a better approach that is instance-specific, you can extend the Text class to append any context properties you want. Here is a quick example where miterLimit is stored and applied any time the text is drawn. In this example, you can create multiple instances and set different miter limits on them. Note that you might want to also support other properties such as lineJoin.
http://jsfiddle.net/cr4hmgqp/2/
(function() {
"use strict"
function MiterText(text, font, color, miterLimit) {
this.Text_constructor(text,font,color);
this.miterLimit = miterLimit;
};
var p = createjs.extend(MiterText, createjs.Text);
p.draw = function(ctx, ignoreCache) {
ctx.miterLimit = this.miterLimit;
if (this.Text_draw(ctx, ignoreCache)) { return true; }
return true;
};
p.clone = function() {
return this._cloneProps(new MiterText(this.text, this.font, this.color, this.miterLimit));
};
createjs.MiterText = createjs.promote(MiterText, "Text");
}());
Note that this issue should hopefully be fixed in the next version of EaselJS. Here is the tracked issue: https://github.com/CreateJS/EaselJS/issues/781
Cheers.
I have the following code:
var posX1 = 0, posY1 = 100;
var speedX1 = random(1,3), speedY1 = random(1,3);
var posX2 = 0, posY2 = 200;
var speedX2 = 2, speedY2 = 0;
function setup()
{
createCanvas(640, 480);
}
function draw()
{
// redraw the background so you blank the screen
background(255);
if(posX1 > width)
{
speedX1 = -speedX1;
}
// update the position based on the speed
posX1 += speedX1;
posY1 += speedY1;
// draw a ball
strokeWeight(20);
point(posX1, posY1);
//
posX2 += speedX2;
posY2 += speedY2;
//draw a ball
strokeWeight(20);
point(posX2, posY2);
}
Its in P5. I basically want the two circles to race each other at random speeds between 1 and 3 but instead they dont even appear on the screen. Can anyone point out where I'm going wrong?
You can't use P5.js functions before setup() is called.
If you run this code and look at the JavaScript console (which should always be your first step) you'll see that you're getting an error saying that random() is not defined. If you then Google that error, you'll get a ton of results explaining what's going on.
From the P5.js FAQ:
Why can't I assign variables using p5 functions and variables before setup()?
The explanation for this is a little complicated, but it has to do
with the way the library is setup in order to support both global and
instance mode. To understand what's happening, let's first look at the
order things happen when a page with p5 is loaded (in global mode).
Scripts in <head> are loaded.
<body> of HTML page loads (when this is complete, the onload event fires, which then triggers step 3).
p5 is started, all functions are added to the global namespace.
So the issue is that the scripts are loaded and evaluated before p5 is
started, when it's not yet aware of the p5 variables. If we try to
call them here, they will cause an error. However, when we use p5
function calls inside setup() and draw() this is ok, because the
browser doesn't look inside functions when the scripts are first
loaded. This is because the setup() and draw() functions are not
called in the user code, they are only defined, so the stuff inside of
them isn't run or evaluated yet.
It's not until p5 is started up that the setup() function is actually
run (p5 calls it for you), and at this point, the p5 functions exist
in the global namespace.
In other words, you have to define your variables at the top of your sketch, but only assign them inside the setup() function:
var speedX1;
var speedY1;
function setup()
{
createCanvas(640, 480);
speedX1 = random(1,3);
speedY1 = random(1,3);
}
I have a larger model that freeze my scene.
As I don't need this model from the beginning it would be cool to load this model in the background. Are webworkers a solution for this?
Can anyone guide me how to accomplish it , or is it possible at all ?
Thanks.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var loader = new THREE.JSONLoader();
loader.load("models.js", function (smooth) {
smooth.mergeVertices();
smooth.computeFaceNormals();
smooth.computeVertexNormals();
var modifier = new THREE.SubdivisionModifier(1);
modifier.modify(smooth);
var mesh = new THREE.Mesh(smoothnew THREE.MeshBasicMaterial({
color: 0x00ff00
}));
scene.add(mesh);
});
var render = function () {
requestAnimationFrame(render);
renderer.render(scene, camera);
};
render();
JavaScript is single threaded and uses "cooperative" scheduling for its events, which means that if you have a function with a long loop that doesn't give up its execution nothing else will be able to be executed in the meantime.
Your method in the loader is essentially that, a single method that'll run its computations to completion before allowing other JavaScript code to run. So rather than using a worker you might be able to split it up a bit by making it more event driven and improve the experience that way, something like this:
loader.load("models.js", function (smooth) {
smooth.mergeVertices();
smooth.computeFaceNormals();
smooth.computeVertexNormals();
window.setTimeout( function() {
var modifier = new THREE.SubdivisionModifier(1);
modifier.modify(smooth);
window.setTimeout( function() {
var mesh = new THREE.Mesh(smoothnew THREE.MeshBasicMaterial({
color: 0x00ff00
}));
scene.add(mesh);
}, 100 );
}, 100 );
});
(The points where I added setTimeout() are entirely arbitrary since I have no way of knowing where the biggest delays are without a jsfiddle to run your code + data).
This will only really work if none of these method calls themselves take up the majority of time. If e.g. mergeVertices() itself takes up most of the CPU time the only way to solve the freezing is by offloading that computation to a worker. The you'll need to pass the data around, compute it on the worker, and have the main code add it to the scene (the worker doesn't have access to the WebGL context). The complexity of that solution might not make the effort worth it however.
Workers are not suitable for a solution that requires DOM manipulation
I am working with ThreeJS on a basic 3d scene that has OrbitControls. Everything works great, except it causes my entire site to lag, as it is looping itself even when the user is not looking at it. I want a function that I can call to start and stop the rendering when certain conditions are met (in this case, the user isn't viewing the canvas). I have a start function that works just fine, but the stop function does not seem to be working, as my site goes unbearably slow after ThreeJS has initialized.
I have looked and looked for a solution to this problem, and have found a couple 'solutions', but for whatever reason they do not work with my application. My assumption is that these solutions are from old versions of ThreeJS.
Here is my code in my main.js file:
var scene,
camera,
controls,
render,
requestId = undefined;
function init() {
scene = new THREE.Scene();
var threeJSCanvas = document.getElementById("threeJS");
camera = new THREE.PerspectiveCamera( 75, threeJSCanvas.width / threeJSCanvas.height, 0.1, 1000 );
controls = new THREE.OrbitControls( camera );
// Controls and Camera settings
// Create Geometry.
}
function render() {
requestId = requestAnimationFrame(render);
renderer.render(scene, camera);
}
function start() {
render();
}
function stop() {
window.cancelAnimationFrame(requestId);
requestId = undefined;
}
In my other javascript file, there is a conditional inside of my pageChange function (this is a multipage app), that looks like the following:
if (page == 5) { // The page with the canvas on it
if (!locationsRendered) {
init();
locationsRendered = true;
}
} else { // If the page is not the page with the canvas on it
if (locationsRendered) {
stop();
}
}
locationsRendered is initialized earlier in this second javascript file in the local scope.
Any help would be much appreciated, as I can not let this simple 3D scene lag my entire app after it has been loaded. It's just not realistic.
If your scene is static, there is no reason for an animation loop. You only need to re-render when the camera moves due to a mouse or touch event.
Just use this pattern:
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.addEventListener( 'change', render );
function render() {
renderer.render( scene, camera );
}
three.js r.67
I was using trackball controls in my scene and therefore couldn't use the solution above (as the trackball controls continue updating after mouse events finish triggering).
To solve this problem, I used:
function animate() {
renderer.render(scene, camera);
controls.update();
}
renderer.setAnimationLoop(animate);
That runs the animation loop indefinitely. To pause the animation, one can then specify null as the animation loop:
renderer.setAnimationLoop(null); // pause the animation
And to resume the animation, just pass the animation loop again:
renderer.setAnimationLoop(animate); // resume the animation
An alternative solution to completely stopping the render loop is to reduce the frames per second rate and thereby reducing resource consumption.
This approach is particularly useful if you need responsive update on your scene while not necessarily animating, but also need to snap back normal speeds when you need to.
a simple setTimout() achieves this nicely.
var fps 10;
function render() {
//reduce framerate
setTimeout(()=>{
requestAnimationFrame(render);
//must be called to enable rotating
controls.update();
renderer.render(scene, camera);
}, 1000/fps)
};
JsFiddle (note: It doesn't show anything, it's merely a way for me to show my code in a neater format) http://jsfiddle.net/h6tVR/
I am new to HTML5 canvas and have decided to play about and see what I can do with it. So far I've been able to draw a locally hosted image onto the canvas and even do a bit of basic tiling:
window.onload = function(){
var GameClosure = function() {
var canv = document.getElementById("canv");
var canvContext = canv.getContext("2d");
var sprite = new Image();
sprite.src = "sprite.png"
var tile = new Image();
tile.src = "tile.png"
function loadSprite(){
sprite.onload = function(){
canvContext.drawImage(sprite, 50, 50);
};
}
function loadTiles(){
tile.onload = function(){
for(var i = 0; i < 800; i += 16){
for(var r = 0; r < 608; r += 16){
canvContext.drawImage(tile, i, r);
}
}
};
}
return{
loadTiles: loadTiles,
loadSprite: loadSprite
};
}();
GameClosure.loadTiles();
GameClosure.loadSprite();
}
I am getting an odd problem with this. When I load it up, the majority of the time, only the tiles will load up. I've tried a couple of things so far, I've switched the GameClosure.loadTiles() and GameClosure.loadSprite(); calls to see if the load order made any difference. It doesn't. I even tried creating a second context and assigning the tiles to one and the sprite to another, but this made no difference. Commenting out the tile call produces the sprite correctly.
It gets even odder. I was refreshing the page rapidly and I noticed that occasionally (with no pattern to it, sometime it could happen 3 times in a row, other time once in 20) the tiles would load AND the sprite would load on top as I would expect it to.
Can this be fixed? My only guess is that my code is running somewhat asyncronously and the for loops creating the tiles are completing after the sprite has been loaded, but looking at my code I don't see where this could be happening.
Separate the concerns. Wait for all resources to be loaded (and the document), then launch your game. Always be sure to hook event handler before assigning src to avoid 'random' (cache-related, in fact) behaviors.
When you set an onload handler, your javascript will continue while the resource loads in the background. The handler will be executed when the resource has loaded. You have no way to tell when that will happen and in what order.
When you have multiple resources and want to call your draw-function the moment the last one has loaded, you could have a global preloader-object. Each onload-handler should call a function on the preloader to inform it that the resource has loaded. That function should check if all resources have reported in, and when that's the case execute the draw-function.
Also, when you set an onload-handler and the resource is already loaded. When you set .src and the resource is in the browsers cache, it will get loaded instantly. So you always need to first set .onload and then set .src.