Three js keyboard rotation - javascript

can anyone help me in making a 3D object rotate on a y axis? with a press of keyboard button? (B)
I've been sitting on this issue for many days now, my code breaks every time i try to do something,
please please please
this is the 3D object that needs turning
https://www.dropbox.com/s/8yefhx3yc11zbik/b.obj?dl=0
and this is the code
var lesson6 = {
scene: null,
camera: null,
renderer: null,
container: null,
controls: null,
clock: null,
stats: null,
init: function() { // Initialization
this.scene = new THREE.Scene();
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight;
// prepare camera
var VIEW_ANGLE = 15, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 1, FAR = 0;
this.camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR);
this.scene.add(this.camera);
this.camera.position.set(10, 200, 0);
this.camera.lookAt(new THREE.Vector3(0,30,0));
// prepare renderer
this.renderer = new THREE.WebGLRenderer({ antialias:true });
this.renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
this.renderer.shadowMapEnabled = true;
this.renderer.shadowMapSoft = true;
// prepare container
this.container = document.createElement('div');
document.body.appendChild(this.container);
this.container.appendChild(this.renderer.domElement);
// events
THREEx.WindowResize(this.renderer, this.camera);
// prepare controls (OrbitControls)
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.controls.target = new THREE.Vector3(0, 0, 0);
this.controls.maxDistance = 2000;
//keyboard control
// L I G H T S
//bottom
var light = new THREE.DirectionalLight(0xffffff, 1);
light.castShadow = true;
light.shadowCameraVisible = true;
light.shadowCameraNear = 100;
light.shadowCameraFar = 200;
light.shadowCameraLeft = -20; // CHANGED
light.shadowCameraRight = 20; // CHANGED
light.shadowCameraTop = 20; // CHANGED
light.shadowCameraBottom = -20; // CHANGED
light.position.set(180 ,20, 0); // CHANGED
this.scene.add(light);
this.scene.add( new THREE.DirectionalLightHelper(light, 0.2) );
//left
var dlight = new THREE.DirectionalLight(0xffffff, 1);
dlight.castShadow = true;
dlight.shadowCameraVisible = true;
dlight.shadowCameraNear = 100;
dlight.shadowCameraFar = 200;
dlight.shadowCameraLeft = -20; // CHANGED
dlight.shadowCameraRight = 20; // CHANGED
dlight.shadowCameraTop = 20; // CHANGED
dlight.shadowCameraBottom = -20; // CHANGED
dlight.position.set(0, 20, 100); // CHANGED
this.scene.add(dlight);
this.scene.add( new THREE.DirectionalLightHelper(dlight, 0.2) );
// add simple ground
// load a model
this.loadModel();
},
loadModel: function() {
// prepare loader and load the model
var oLoader = new THREE.OBJLoader();
oLoader.load('b.obj', function( geometry ) {
var material = new THREE.MeshLambertMaterial({ color: "red" });
var mesh = new THREE.Mesh (geometry, material);
geometry.traverse( function(child) {
if (child instanceof THREE.Mesh) {
// apply custom material
child.material = material;
child.castShadow = true;
child.receiveShadow = true;
}
});
geometry.rotation.y = -Math.PI / -1.7;
geometry.position.x = 0;
geometry.position.y = 0;
geometry.position.z = 0;
geometry.scale.set(1, 1, 1);
lesson6.scene.add(geometry);
});
}
};
// Animate the scene
function animate() {
requestAnimationFrame(animate);
render();
}
// Render the scene
function render() {
if (lesson6.renderer) {
lesson6.renderer.render(lesson6.scene, lesson6.camera);
}
}
// Initialize lesson on page load
function initializeLesson() {
lesson6.init();
animate();
}
if (window.addEventListener)
window.addEventListener('load', initializeLesson, false);
else if (window.attachEvent)
window.attachEvent('onload', initializeLesson);
else window.onload = initializeLesson;

When dealing with that sort of input, you want the button press to NOT modify any object. You want your keyup/keydown/other input events to do as little as possible: toggle a true/false variable or change a number.
In this example:
function handleKeyDown(event) {
if (event.keyCode === 66) { //66 is "b"
window.isBDown = true;
}
}
function handleKeyUp(event) {
if (event.keyCode === 66) {
window.isBDown = false;
}
}
window.addEventListener('keydown', handleKeyDown, false);
window.addEventListener('keyup', handleKeyUp, false);
From there, you can modify your animate() function to be:
function animate() {
requestAnimationFrame(animate);
if (window.isBDown) {
//Increment your object's Y rotation value a little bit.
//Ideally, you would listen to animate()'s first argument,
//which is a high resolution timestamp that says the current time
//in milliseconds since the tab was opened.
}
render();
}
Note how all the work happens in animate(). Choose the correct speed, and the object will slowly rotate until you release the key. (Once comfortable with this concept, you can also add acceleration and momentum toanimate().)
Gamepad joysticks work the same way, except that instead of true or false, you are given a decimal number. You can multiply this into whatever value you use for "speed". Usually 0 means the joystick is centered, 1 means the joystick is maxed out at that axis, and some number between means input is being delivered, but not all the way (or is being pulled diagonally).

Related

Render-on-Demand Three.js multiple canvases (with class implementation)

My case is like this: I have an asset inventory with multiple assets and I want whenever the user hovers on top of them to start the rendering with an OrbitController (I would prefer Trackball but I know that it is not possible due to a bug). The point is that the "this" variable of the class instance can not pass into the render method of the class :
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r122/build/three.module.js';
import {OrbitControls} from 'https://threejsfundamentals.org/threejs/resources/threejs/r122/examples/jsm/controls/OrbitControls.js';
class Main {
constructor(canvasId)
{
this.canvas = document.querySelector(canvasId);
this.renderer = new THREE.WebGLRenderer({canvas: this.canvas});
this.fov = 75;
this.aspect = 2; // the canvas default
this.near = 0.1;
this.far = 5;
this.camera = new THREE.PerspectiveCamera(this.fov, this.aspect, this.near, this.far);
this.camera.position.z = 2;
this.controls = new OrbitControls(this.camera, this.canvas);
this.controls.target.set(0, 0, 0);
this.controls.update();
this.scene = new THREE.Scene();
this.color = 0xFFFFFF;
this.intensity = 1;
this.light = new THREE.DirectionalLight(this.color, this.intensity);
this.light.position.set(-1, 2, 4);
this.scene.add(this.light);
this.boxWidth = 1;
this.boxHeight = 1;
this.boxDepth = 1;
this.geometry = new THREE.BoxGeometry(this.boxWidth, this.boxHeight, this.boxDepth);
}
makeInstance(geometry, color, x){
let material = new THREE.MeshPhongMaterial(color);
let cube = new THREE.Mesh(geometry, material);
this.scene.add(cube);
cube.position.x = x;
return cube;
}
resizeRendererToDisplaySize(renderer) {
this.canvas = this.renderer.domElement;
let width = this.canvas.clientWidth;
let height = this.canvas.clientHeight;
let needResize = this.canvas.width !== width || this.canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
render() {
// this -> control object or window whereas it should be the class instance
if (this.resizeRendererToDisplaySize(this.renderer)) {
this.canvas = this.renderer.domElement;
this.camera.aspect = this.canvas.clientWidth / this.canvas.clientHeight;
this.camera.updateProjectionMatrix();
}
this.renderer.render(this.scene, this.camera);
}
starter() {
this.makeInstance(this.geometry, 0x44aa88, 0);
this.makeInstance(this.geometry, 0x8844aa, -2);
this.makeInstance(this.geometry, 0xaa8844, 2);
this.render();
this.controls.addEventListener('change', this.render);
window.addEventListener('resize', this.render);
// note: this is a workaround for an OrbitControls issue
// in an iframe. Will remove once the issue is fixed in
// three.js
window.addEventListener('mousedown', (e) => {
e.preventDefault();
window.focus();
});
window.addEventListener('keydown', (e) => {
e.preventDefault();
});
}
}
let main = new Main('#a');
main.starter();
The only thing that I could do to work (but not correctly) is to replace "this" inside the render method with the "main" instance
render() {
if (main.resizeRendererToDisplaySize(this.renderer)) {
main.canvas = main.renderer.domElement;
main.camera.aspect = main.canvas.clientWidth / main.canvas.clientHeight;
main.camera.updateProjectionMatrix();
}
main.renderer.render(main.scene, main.camera);
}
However in this manner I kill the class, and I am not able to generate other instances e.g. main2
I have generate a codepen: https://codepen.io/jimver04/pen/XWjLPLo
You are providing this.render to the event handlers. this.render is technically a pointer to the prototype function (Main.prototype.render).
When the handler is fired, the context will be dependent on how the origin executes the handler. In most cases, the context given to the handler will be that of the origin (or possibly even null). For example, window.addEventListener( 'resize', handler ); will fire resize events by calling the equivalent of handler.call( window, event );
There are a couple ways to get around this.
Arrow Functions implicit this
Arrow functions use the this of the context in which they are defined. So if your arrow function is defined in a member function of your class, and that member function is being executed within a context of an instance of that class, then this within the body of that arrow function will reference the instance.
class Test{
constructor(){
this.name = "Test"
// Arrow function defined within the context of an instance:
window.addEventListener( 'resize', () => {
console.log( this.name ) // prints: "Test"
} )
// Normal function will execute with "window" as its "this"
window.addEventListener( 'resize', function() {
console.log( this.name ) // results may vary based on your browser, but "this" is not an instance of Test
} )
}
}
Binding a function to a context
Using bind can attach a function to a specified context. The bind function returns a new function permanently bound to the provided context.
class Test{
constructor(){
this.name = "Test"
this.boundRender = this.render.bind( this ) // returns "render" bound permanently to "this"
window.addEventListener( 'resize', this.boundRender )
}
render(){
console.log( this.name )
}
}
The example above will still print "Test" to the console. Even though the resize event wants to execute the handler with the window context, the bind has overridden that and locked the handler to executing within the context of the instance to which it was bound.
Thanks TheJim01 ! Below is the final solution with bind methodology that works (the solution with arrows does not work). Also can be found analytically in https://codepen.io/jimver04/pen/XWjLPLo
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r122/build/three.module.js';
import {OrbitControls} from 'https://threejsfundamentals.org/threejs/resources/threejs/r122/examples/jsm/controls/OrbitControls.js';
class Main {
constructor(canvasId)
{
this.canvas = document.querySelector(canvasId);
this.renderer = new THREE.WebGLRenderer({canvas: this.canvas});
this.fov = 75;
this.aspect = 2; // the canvas default
this.near = 0.1;
this.far = 5;
this.camera = new THREE.PerspectiveCamera(this.fov, this.aspect, this.near, this.far);
this.camera.position.z = 2;
this.controls = new OrbitControls(this.camera, this.canvas);
this.controls.target.set(0, 0, 0);
this.controls.update();
this.scene = new THREE.Scene();
this.color = 0xFFFFFF;
this.intensity = 1;
this.light = new THREE.DirectionalLight(this.color, this.intensity);
this.light.position.set(-1, 2, 4);
this.scene.add(this.light);
this.boxWidth = 1;
this.boxHeight = 1;
this.boxDepth = 1;
this.geometry = new THREE.BoxGeometry(this.boxWidth, this.boxHeight, this.boxDepth);
this.boundRender = this.render.bind( this );
}
makeInstance(geometry, color, x){
let material = new THREE.MeshPhongMaterial(color);
let cube = new THREE.Mesh(geometry, material);
this.scene.add(cube);
cube.position.x = x;
return cube;
}
resizeRendererToDisplaySize(renderer) {
this.canvas = this.renderer.domElement;
let width = this.canvas.clientWidth;
let height = this.canvas.clientHeight;
let needResize = this.canvas.width !== width || this.canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
render() {
if (this.resizeRendererToDisplaySize(this.renderer)) {
this.canvas = this.renderer.domElement;
this.camera.aspect = this.canvas.clientWidth / this.canvas.clientHeight;
this.camera.updateProjectionMatrix();
}
this.renderer.render(this.scene, this.camera);
}
starter() {
this.makeInstance(this.geometry, 0x44aa88, 0);
this.makeInstance(this.geometry, 0x8844aa, -2);
this.makeInstance(this.geometry, 0xaa8844, 2);
this.render();
this.controls.addEventListener('change', this.boundRender );
window.addEventListener('resize', this.boundRender);
// note: this is a workaround for an OrbitControls issue
// in an iframe. Will remove once the issue is fixed in
// three.js
window.addEventListener('mousedown', (e) => {
e.preventDefault();
window.focus();
});
window.addEventListener('keydown', (e) => {
e.preventDefault();
});
}
}
let main = new Main('#a');
main.starter();
let main2 = new Main('#b');
main2.starter();

Canvas doesn't repaint when tab inactive/ backgrounded for recording ( WebGL)

I am building online web application which renders a video on a canvas and then records the canvas using canvas.captureStream() and mediaRecorder. The problem is that when the user switches tab or minimizes the window the canvas freezes. My animation keeps running as I have used webWorkerSetInterval(Hacktimer.js). As per chrome they have not yet provided a solution https://bugs.chromium.org/p/chromium/issues/detail?id=639105.
Can anyone suggest a work-around? I tried new window which doesn't allow to minimize but was unsuccessful. The recording doesn't stop when switching window.(stops only when tab is switched or minimized)
NB:
Now that this question has been specifically edited to treat webgl contexts, it may not be completely a duplicate of this previous answer, which indeed doesn't work with webgl contexts ; but because of an chrome bug...
So this answer will show you how to workaround this bug, while waiting for a fix from chrome.
The linked answer made use of the WebAudio API's timing method to create an Timed loop, not tied to the screen refresh-rate nor the window / tab's visibility.
But as said in the header, this currently doesn't work with webgl contexts on chrome.
The easy workaround, is to use an offscreen 2d context as the stream source, and to draw our webgl canvas onto this 2d context :
function startRecording(webgl_renderer, render_func) {
// create a clone of the webgl canvas
var canvas = webgl_renderer.domElement.cloneNode();
// init an 2D context
var ctx = canvas.getContext('2d');
function anim(){
// render the webgl Animation
render_func();
// draw the wegbl canvas on our 2D one
ctx.clearRect(0,0,canvas.width, canvas.height);
ctx.drawImage(webgl_renderer.domElement, 0,0);
}
var fps = 60;
// start our loop #60fps
var stopAnim = audioTimerLoop(anim, 1000 / fps);
// maximum stream rate set as 60 fps
var cStream = canvas.captureStream(fps);
let chunks = [];
var recorder = new MediaRecorder(cStream);
recorder.ondataavailable = e => chunks.push(e.data);
recorder.onstop = e => {
// we can stop our loop
stopAnim();
var url = URL.createObjectURL(new Blob(chunks));
var v = document.createElement('video');
v.src = url;
v.controls = true;
document.body.appendChild(v);
}
recorder.start();
// stops the recorder in 20s, try to change tab during this time
setTimeout(function() {
recorder.stop();
}, 20000);
btn.parentNode.removeChild(btn);
}
/*
An alternative timing loop, based on AudioContext's clock
#arg callback : a callback function
with the audioContext's currentTime passed as unique argument
#arg frequency : float in ms;
#returns : a stop function
*/
function audioTimerLoop(callback, frequency) {
var freq = frequency / 1000; // AudioContext time parameters are in seconds
var aCtx = new AudioContext();
// Chrome needs our oscillator node to be attached to the destination
// So we create a silent Gain Node
var silence = aCtx.createGain();
silence.gain.value = 0;
silence.connect(aCtx.destination);
onOSCend();
var stopped = false; // A flag to know when we'll stop the loop
function onOSCend() {
var osc = aCtx.createOscillator();
osc.onended = onOSCend; // so we can loop
osc.connect(silence);
osc.start(0); // start it now
osc.stop(aCtx.currentTime + freq); // stop it next frame
callback(aCtx.currentTime); // one frame is done
if (stopped) { // user broke the loop
osc.onended = function() {
aCtx.close(); // clear the audioContext
return;
};
}
};
// return a function to stop our loop
return function() {
stopped = true;
};
}
/* global THREE */
/* Note that all rAF loop have been removed
since they're now handled by our 'audioTimerLoop' */
(function() {
'use strict';
var WIDTH = 500, HEIGHT = 500;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, WIDTH / HEIGHT, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(WIDTH , HEIGHT);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.CubeGeometry(5, 5, 5);
var material = new THREE.MeshLambertMaterial({
color: 0x00fff0
});
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 12;
var pointLight = new THREE.PointLight(0xFFFFFF);
pointLight.position.x = 10;
pointLight.position.y = 50;
pointLight.position.z = 130;
scene.add(pointLight);
var render = function() {
var delta = Math.random() * (0.06 - 0.02) + 0.02;
cube.rotation.x += delta;
cube.rotation.y += delta;
cube.rotation.z -= delta;
renderer.render(scene, camera);
};
render();
console.clear();
btn.onclick = function(){startRecording(renderer, render);};
}());
body {
margin: 0;
background: #000;
}
button{
position: absolute;
top: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/85/three.min.js"></script>
<!-- Mobile devices need an user interaction to start the WebAudio API -->
<button id="btn">Start</button>

three.js webcam as background

I am trying to get the camera video of background of a threejs scene. Below is the code. Currently the background mesh is coming as blank. Not sure why this is happening.
I was following the below link to replace the image with the camera video.
http://jeremy.assenat.free.fr/Lab/threejsbg/
(function () {
var video, videoImage, videoImageContext, videoTexture;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
window.URL = window.URL || window.webkitURL;
var camvideo = document.getElementById('monitor');
if (!navigator.getUserMedia)
{
document.getElementById('errorMessage').innerHTML =
'Sorry. <code>navigator.getUserMedia()</code> is not available.';
} else {
navigator.getUserMedia({video: true}, gotStream, noStream);
}
function gotStream(stream)
{
if (window.URL)
{
camvideo.src = window.URL.createObjectURL(stream);
} else // Opera
{
camvideo.src = stream;
}
camvideo.onerror = function (e)
{
stream.stop();
};
stream.onended = noStream;
}
function noStream(e)
{
var msg = 'No camera available.';
if (e.code == 1)
{
msg = 'User denied access to use camera.';
}
document.getElementById('errorMessage').textContent = msg;
}
var color = 0x000000;
// Create your main scene
var scene = new THREE.Scene();
// Create your main camera
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// Create lights
var light = new THREE.PointLight(0xEEEEEE);
light.position.set(20, 0, 20);
scene.add(light);
var lightAmb = new THREE.AmbientLight(0x777777);
scene.add(lightAmb);
// Create your renderer
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.domElement.className = "mainCanvas"
// Create a cube
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshLambertMaterial({
color: 0xff00ff,
ambient: 0x121212,
emissive: 0x121212
});
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// Set up the main camera
camera.position.z = 5;
///////////
// VIDEO //
///////////
video = document.getElementById('monitor');
videoImage = document.getElementById('videoImage');
videoImageContext = videoImage.getContext('2d');
videoImageContext.translate(320, 0);
videoImageContext.scale(-1, 1);
// background color if no video present
videoImageContext.fillStyle = '#000000';
videoImageContext.fillRect(0, 0, videoImage.width, videoImage.height);
videoTexture = new THREE.Texture(videoImage);
videoTexture.minFilter = THREE.LinearFilter;
videoTexture.magFilter = THREE.LinearFilter;
var movieMaterial = new THREE.MeshBasicMaterial({map: videoTexture, overdraw: true, side: THREE.DoubleSide});
// the geometry on which the movie will be displayed;
// movie image will be scaled to fit these dimensions.
var movieGeometry = new THREE.PlaneGeometry(100, 100, 1, 1);
var movieScreen = new THREE.Mesh(movieGeometry, movieMaterial);
movieScreen.position.set(0, 50, 0);
/* scene.add(movieScreen);
camera.position.set(0, 150, 300);
camera.lookAt(movieScreen.position);
*/
// Load the background texture
/* var texture = THREE.ImageUtils.loadTexture('images/checkerboard.jpg');
var backgroundMesh = new THREE.Mesh(
new THREE.PlaneGeometry(2, 2, 0, 0),
new THREE.MeshBasicMaterial({
map: videoTexture
})
);*/
var backgroundMesh = movieScreen;
backgroundMesh.material.depthTest = false;
backgroundMesh.material.depthWrite = false;
// Create your background scene
var backgroundScene = new THREE.Scene();
var backgroundCamera = new THREE.Camera();
backgroundScene.add(backgroundCamera);
backgroundScene.add(backgroundMesh);
// Rendering function
var render = function () {
requestAnimationFrame(render);
// Update the color to set
if (color < 0xdddddd)
color += 0x0000ff;
// Update the cube color
cube.material.color.setHex(color);
// Update the cube rotations
cube.rotation.x += 0.05;
cube.rotation.y += 0.02;
renderer.autoClear = false;
renderer.clear();
renderer.render(backgroundScene, backgroundCamera);
renderer.render(scene, camera);
};
render();
})();
If you want the video to be static in background, I wouldn't transfer the video into the 3D scene as texture. Instead I would leave it in a div placed behind the canvas. The canvas can be set transparent, so only the meshes will be rendered, but background stays clear.
var renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setClearAlpha(0.0);
.parent {
position: relative;
width: 1024px;
height: 768px;
}
#monitor, #mainCanvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#monitor {
transform: scaleX(-1);
}
<div class="parent">
<div id="monitor"></div>
<div id="mainCanvas"></div>
</div>
There doesn't seem to be any code that actually draws to the texture as the video plays. You will probably need to add something like
videoImageContext.drawImage(video, 0, 0);
videoTexture.needsUpdate = true;
to your renderloop.

Mouse position is only read on the first frame

i have been having trouble with reading a mouse position on a canvas. The code is working (semi) correctly as it reads the position when clicking he canvas in IE but only on one frame, in chrome it is just displaying the value as 0.
Here is the full code:
<script>
var blip = new Audio("blip.mp3");
blip.load();
var levelUp = new Audio("levelUp.mp3");
levelUp.load();
var canvas = document.getElementById('game');
var context = canvas.getContext('2d');
context.font = '18pt Calibri';
context.fillStyle = 'white';
//load and draw background image
var bgReady = false;
var background = new Image();
background.src = 'images/background.jpg';
background.onload = function(){
bgReady = true;
}
var startMessage = 'Click the canvas to start';
//load plane image
var planeReady = false;
var planeImage = new Image();
planeImage.src = 'images/plane.png';
planeImage.onload = function() {
planeReady = true;
}
//load missile image
var missileReady = false;
var missileImage = new Image();
missileImage.src = 'images/missile-flipped.gif';
missileImage.onload = function() {
missileReady = true;
}
//initialise lives and score
var score = 0;
var lives = 3;
var missilesLaunched = 0;
var missileSpeed = 5;
var level = 1;
var missileX = 960;
var missileY = Math.random() * 500;
if (missileY > 480) {
missileY = 480;
}
function getMousePos(canvas, event) {
return {
x: input.x - rect.left,
y: input.y - rect.top
};
}
function update_images(event) {
var pos = getMousePos(canvas.getBoundingClientRect(), mouseInput);
planeImage.y = pos.y;
missileX = missileX - missileSpeed;
if (missileX < - 70) {
missilesLaunched++;
missileX = 960;
missileY = Math.random() * 500;
if (missileY > 480) {
missileY = 480;
}
blip.play();
score = missilesLaunched;
if (score % 5 == 0) {
missileSpeed = missileSpeed + 2;
level++;
levelUp.play();
}
}
}
function reload_images() {
if (bgReady = true) {
context.drawImage(background, 0, 0);
}
if (planeReady = true) {
context.drawImage(planeImage, 10, planeImage.y);
}
if (missileReady = true) {
context.drawImage(missileImage, missileX, missileY);
}
context.fillText('Lives: ' + lives, 200, 30);
context.fillText('Score: ' + score, 650, 30);
context.fillText('Level: ' + missileSpeed, 420, 30);
context.fillText('Position: ' + missileImage.y, 420, 70);
}
function main(event) {
var mouseInput = { x: 0, y: 0 };
document.addEventListener("mousemove", function (event) {
mouseInput.x = event.clientX;
mouseInput.y = event.clientY;
});
update_images(event);
reload_images();
if (lives > 0) {
window.requestAnimationFrame(main);
}
else {
}
}
function start() {
context.drawImage(background, 0, 0);
context.fillText('Click the canvas to start', 350, 250);
function startMain(event) {
game.removeEventListener("click", startMain);
main(event);
}
canvas.addEventListener("mousedown", startMain);
}
start();
</script>
Joe, you should actually be capturing the mouse position every time you click...
...but you're actually also starting a new game (without stopping the old one), every time you click, too.
First problem: starting game engine several times to draw on the same instance of the canvas
Solution:
In your start function, you need to remove the mousedown event listener, after you've triggered it.
function start () {
// ... other setup
function startMain (event) {
canvas.removeEventListener("click", startMain);
main(event);
}
canvas.addEventListener("click", startMain);
}
Now it will only listen for the first click, before starting, and will only start once.
Second Problem: mouse doesn't update as expected
Solution: two issues here...
...first, you are passing event into main on first call...
...after that, you're passing main into requestAnimationFrame.
requestAnimationFrame won't call it with an event, it will call it with the number of microseconds (or ms or some other unit as a fractional precision of ms) since the page was loaded.
So the first time you got main({ type: "mousedown", ... });.
The next time you get main(4378.002358007);
So lets refactor the startMain we had above, so that main never ever collects an event, just a time.
function startMain ( ) {
canvas.removeEventListener("click", startMain);
requestAnimationFrame(main);
}
The next problem is that even if you were getting just events, you're only ever capturing a click event (which as we mentioned earlier, fires a new copy of the game logic).
Your solution is to separate the code which catches mouse events from the code which reads mouse position.
var mouseInput = { x: 0, y: 0 };
document.addEventListener("mousemove", function (event) {
mouseInput.x = event.clientX;
mouseInput.y = event.clientY;
});
function getMousePos (rect, input) {
return {
x : input.x - rect.left,
y : input.y - rect.top
};
}
// currently in updateImages (should not be there, but... a different story)
var pos = getMousePos(canvas.getBoundingClientRect(), mouseInput);
You've got other problems, too...
You're calling getMousePos and passing in game at the moment. I don't see where game is defined in your JS, so either you're making game somewhere else (begging for bugs), or it's undefined, and your app blows up right there.
You should really be building this with your console / dev-tools open, in a hands-on fashion, and cleaning bugs in each section, as you go.

Recursive Call in Javascript Class (requestanimationframe)

I have a simple class that I am using to manage scenes in three.js. I am having issues with the requestAnimationFrame loop finding the function reference. I know I am missing something fundamental here, trapped in some this nightmare. Do I need to use bind or call to pass the this reference to requestAnimationFrame?
var THREE = THREE || {};
var SceneBuddy = SceneBuddy || {};
SceneBuddy = function(scene, camera) {
this.scene = scene;
this.camera = camera;
this.sceneClock = new THREE.Clock();
this.renderer = {};
this.resolution = {};
this.controls = {};
};
//Start Animate
SceneBuddy.prototype.startAnimate = function() {
requestAnimationFrame(this.startAnimate); //this does not work, type error
this.render.call(this);
};
//Render Function
SceneBuddy.prototype.render = function() {
var delta = this.sceneClock.getDelta();
this.controls.update(delta);
this.renderer.render(this.scene,this.camera);
};
//Setup Renderer
SceneBuddy.prototype.initRenderer = function(resolution) {
if (!Detector.webgl) {
Detector.addGetWebGLMessage();
return;
}
else {
var renderer = new THREE.WebGLRenderer({
antialias: true,
preserveDrawingBuffer: true
});
renderer.setSize(resolution.x, resolution.y);
renderer.shadowMapEnabled = true;
this.resolution = resolution;
this.renderer = renderer;
}
};
I am using currently using SceneBuddy like this :
var camera = new THREE.PerspectiveCamera(75, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 100000);
var scene = new THREE.Scene();
var sceneBuddy = new SceneBuddy(scene, camera);
sceneBuddy.initRenderer({x: 940, y: 400});
sceneBuddy.attachRenderer(container); //attaches renderer to dom element
sceneBuddy.initControls();
sceneBuddy.startAnimate(); // broken.
Use bind when passing a function to specify what this will be when the function is called:
SceneBuddy.prototype.startAnimate = function() {
requestAnimationFrame(this.startAnimate.bind(this));
this.render();
};

Categories

Resources