THREE.js Secondary camera rotated with head rotation in VR - three.js

I'm using THREE.js and Aframe ( in Exokit ) together and I have a component for a "selfie camera". I have a weird issue that when i enter VR the camera rotation is taken over by the head rotation. I understand how the camera rotation works has changed in recent versions of THREE.js ( ArrayCamera ) but I assumed that only affected the main camera and not all cameras in the scene.
Below is my hacky component that works fine in 2D mode but in VR it messes up. The worst thing about it is im fine with it being linked to the head, the camera itself is a child object of the main camera anyway so it appears in front of the users face when opened and is moved with the head rotation - but its off angle when in VR like its pointing down and to the left a bit.
Here are some screenshots that hopefully demonstrate the issue:
Edit: need 10 rep to post images so here are urls instead
2D Mode
VR Mode
Any help much appreciated!!
AFRAME.registerComponent('selfie-camera', {
schema:{
resolution:{type:'int',default:512},
fov:{type:'int',default:100},
aspect:{type:'number',default:1.5},
near:{type:'number',default:0.001},
far:{type:'number',default:1000}
},
init() {
this.el.addEventListener('loaded',()=>{
this.renderTarget = new THREE.WebGLRenderTarget(this.data.resolution*1.5, this.data.resolution,{ antialias: true });
this.el.getObject3D('mesh').material.map = this.renderTarget.texture;
this.cameraContainer = new THREE.Object3D();
this.el.object3D.add( this.cameraContainer );
this.el.takePicture = this.takePicture.bind(this);
this.el.setSide = this.setSide.bind(this);
this.wider = 1.5;
this.photoMultiplier = 2;
this.canvas = document.createElement('canvas');
});
this.testQuat = new THREE.Quaternion();
this.el.open = this.open.bind(this);
this.el.close = this.close.bind(this);
},
open(){
this.camera = new THREE.PerspectiveCamera( this.data.fov, this.data.aspect, this.data.near, this.data.far );
this.cameraContainer.add(this.camera);
new TWEEN.Tween(this.el.getAttribute('scale'))
.to(new THREE.Vector3(1,1,1), 650)
.easing(TWEEN.Easing.Exponential.Out).start();
},
close(){
new TWEEN.Tween(this.el.getAttribute('scale'))
.to(new THREE.Vector3(0.0000001,0.0000001,0.0000001), 200)
.onComplete(()=>{
this.cameraContainer.remove(this.camera);
delete this.camera;
})
.easing(TWEEN.Easing.Exponential.Out).start();
},
tick(){
if(this.camera){
this.camera.getWorldQuaternion(this.testQuat);
console.log(this.camera.quaternion);
}
this.el.getObject3D('mesh').material.visible = false;
if(this.isTakingPicture) {
this.renderTarget.setSize(this.data.resolution * this.wider * this.photoMultiplier, this.data.resolution * this.photoMultiplier);
}
this.el.sceneEl.renderer.render( this.el.sceneEl.object3D, this.camera, this.renderTarget );
if(this.isTakingPicture){
this.isTakingPicture = false;
this.pictureResolve(this.createImageFromTexture());
this.renderTarget.setSize(this.data.resolution * this.wider, this.data.resolution);
}
this.el.getObject3D('mesh').material.visible = true;
},
setSide(isFront){
let _this = this;
new TWEEN.Tween({y:this.cameraContainer.rotation.y})
.to({y:isFront?Math.PI:0}, 350)
.onUpdate(function(){
_this.cameraContainer.rotation.y = this.y;
})
.easing(TWEEN.Easing.Exponential.Out).start();
},
takePicture(){
return new Promise(resolve=>{
this.isTakingPicture = true;
this.pictureResolve = resolve;
})
},
createImageFromTexture() {
let width = this.data.resolution*this.wider*this.photoMultiplier,
height = this.data.resolution*this.photoMultiplier;
let pixels = new Uint8Array(4 * width * height);
this.el.sceneEl.renderer.readRenderTargetPixels(this.renderTarget, 0, 0, width, height, pixels);
pixels = this.flipPixelsVertically(pixels, width, height);
let imageData = new ImageData(new Uint8ClampedArray(pixels), width, height);
this.canvas.width = width;
this.canvas.height = height;
let context = this.canvas.getContext('2d');
context.putImageData(imageData, 0, 0);
return this.canvas.toDataURL('image/jpeg',100);
},
flipPixelsVertically: function (pixels, width, height) {
let flippedPixels = pixels.slice(0);
for (let x = 0; x < width; ++x) {
for (let y = 0; y < height; ++y) {
flippedPixels[x * 4 + y * width * 4] = pixels[x * 4 + (height - y) * width * 4];
flippedPixels[x * 4 + 1 + y * width * 4] = pixels[x * 4 + 1 + (height - y) * width * 4];
flippedPixels[x * 4 + 2 + y * width * 4] = pixels[x * 4 + 2 + (height - y) * width * 4];
flippedPixels[x * 4 + 3 + y * width * 4] = pixels[x * 4 + 3 + (height - y) * width * 4];
}
}
return flippedPixels;
}
});

You have to disable VR before rendering:
var renderer = this.el.sceneEl.renderer;
var vrEnabled = renderer.vr.enabled;
renderer.vr.enabled = false;
renderer.render(this.el.sceneEl.object3D, this.camera, this.renderTarget);
renderer.vr.enabled = vrEnabled;

Related

Randomly Generating Curved/Wavy Paths

I have a massive image of a map that is much larger than the viewport and centered in the viewport, which can be explored by the user by dragging the screen. In order to create a parallax effect, I used a massive image of clouds in the foreground. As the user explores the map via dragging, both the background and foreground move in a parallax fashion. So far, so good.
However, what I really want to do is give the image of clouds a "default" movement that would be randomly generated on each page load, so that the clouds would always be moving, even if the user is not dragging. I know this can be done by animating the foreground along a path, but I am not exactly sure how to go about this.
How can I randomly generate irregularly curved or wavy paths on each page load?
Does anybody know of any algorithms that can do this?
I also use a copy of the previous answers to realize a simplified version of what I hinted at in the comments.
Use a random walk on the unit circle, that is on the angle, to determine a velocity vector that slowly but randomly changes and move forward using cubic Bezier patches.
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 600;
var ch = c.height = 400;
var cx = cw / 4, cy = ch / 2;
var angVel = v.value;
var tension = t.value;
ctx.lineWidth = 4;
var npts = 60;
var dw = Array();
var xs = Array();
var ys = Array();
var vxs = Array();
var vys = Array();
function Randomize() {
for (var i = 0; i < npts; i++) {
dw[i] = (2*Math.random()-1);
}
}
function ComputePath() {
xs[0]=cx; ys[0]=cy;
var angle = 0;
for (var i = 0; i < npts; i++) {
vxs[i]=10*Math.cos(2*Math.PI*angle);
vys[i]=10*Math.sin(2*Math.PI*angle);
angle = angle + dw[i]*angVel;
}
for (var i = 1; i < npts; i++) {
xs[i] = xs[i-1]+3*(vxs[i-1]+vxs[i])/2;
ys[i] = ys[i-1]+3*(vys[i-1]+vys[i])/2;
}
}
function Draw() {
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
ctx.moveTo(xs[0],ys[0]);
for (var i = 1; i < npts; i++) {
var cp1x = xs[i-1]+tension*vxs[i-1];
var cp1y = ys[i-1]+tension*vys[i-1];
var cp2x = xs[i]-tension*vxs[i];
var cp2y = ys[i]-tension*vys[i]
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, xs[i], ys[i]);
}
ctx.stroke();
}
Randomize();
ComputePath();
Draw();
r.addEventListener("click",()=>{
Randomize();
ComputePath();
Draw();
})
v.addEventListener("input",()=>{
angVel = v.value;
vlabel.innerHTML = ""+angVel;
ComputePath();
Draw();
})
t.addEventListener("input",()=>{
tension = t.value;
tlabel.innerHTML = ""+tension;
Draw();
})
canvas{border:1px solid}
<canvas id = 'c'></canvas>
<table>
<tr><td>angular velocity:</td><td> <input type="range" id="v" min ="0" max = "0.5" step = "0.01" value="0.2" /></td><td id="vlabel"></td></tr>
<tr><td>tension</td><td> <input type="range" id="t" min ="0" max = "1" step = "0.1" value="0.8" /></td><td id="tlabel"></td></tr>
<tr><td>remix</td><td> <button id="r"> + </button></td><td></td></tr>
</table>
If your question is: How can I randomly generate curved or wavy paths? this is how I would do it: I'm using inputs type range to change the value for amplitude and frequency, but you can set those values randomly on load.
I hope it helps.
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 800;
var ch = c.height = 150;
var cx = cw / 2,
cy = ch / 2;
var amplitude = a.value;
var frequency = f.value;
ctx.lineWidth = 4;
function Draw() {
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
for (var x = 0; x < cw; x++) {
y = Math.sin(x * frequency) * amplitude;
ctx.lineTo(x, y+cy);
}
ctx.stroke();
}
Draw();
a.addEventListener("input",()=>{
amplitude = a.value;
Draw();
})
f.addEventListener("input",()=>{
frequency = f.value;
Draw();
})
canvas{border:1px solid}
<canvas id = 'c'></canvas>
<p>frequency: <input type="range" id="f" min ="0.01" max = "0.1" step = "0.001" value=".05" /></p>
<p>amplitude: <input type="range" id="a" min ="1" max = "100" value="50" /></p>
I was impressed by the functionality to be able to draw canvases in the SO answers, so I "stole" enxaneta code snippet and played a bit with it (hope that is ok).
The idea is to generate several random points (xs, ys) and for each x from the path to interpolate the y as y = sum{ys_i*w_i}/sum{w_i}, where w_i is some interpolation weight as a function of x. For example w_i(x) = (xs_i - x)^(-2). Hope this makes sense - if this is of any interested I'll try to provide more details.
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 600;
var ch = c.height = 150;
var cx = cw / 2,
cy = ch / 2;
var amplitude = a.value;
var frequency = f.value;
ctx.lineWidth = 4;
var npts = 20;
var xs = Array();
var ys = Array();
for (var i = 0; i < npts; i++) {
xs[i] = (cw/npts)*i;
ys[i] = 2.0*(Math.random()-0.5)*amplitude;
}
function Draw() {
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
for (var x = 0; x < cw; x++) {
y = 0.0;
wsum = 0.0;
for (var i = -5; i <= 5; i++) {
xx = x;
ii = Math.round(x/xs[1]) + i;
if (ii < 0) { xx += cw; ii += npts; }
if (ii >= npts) { xx -= cw; ii -= npts; }
w = Math.abs(xs[ii] - xx);
w = Math.pow(w, frequency);
y += w*ys[ii];
wsum += w;
}
y /= wsum;
//y = Math.sin(x * frequency) * amplitude;
ctx.lineTo(x, y+cy);
}
ctx.stroke();
}
Draw();
a.addEventListener("input",()=>{
amplitude = a.value;
for (var i = 0; i < npts; i++) {
xs[i] = (cw/npts)*i;
ys[i] = 2.0*(Math.random()-0.5)*amplitude;
}
Draw();
})
f.addEventListener("input",()=>{
frequency = f.value;
Draw();
})
canvas{border:1px solid}
<canvas id = 'c'></canvas>
<p>amplitude: <input type="range" id="a" min ="1" max = "100" value="50" /></p>
<p>frequency: <input type="range" id="f" min ="-10" max = "1" step = "0.1" value="-2" hidden/></p>
Deterministic random paths
Storing paths for random movements is not needed. Also random is another way of being very complex, and for humans it does not take much complexity to look randoms.
Thus with a little randomness to add to complexity you can make the appearance of the infinite non repeating sequence that and be rewound, stopped, slowed down speed up, and be fully deterministic and requiring only a single value to store.
Complex cycles.
To move a point in a circle around a center you can use sin and cos.
For example a point x,y and you want to move in a ball around that point at a distance of dist and a rate once a second. Example in snippet.
var px = 100; // point of rotation.
var py = 100;
const RPS = 1; // Rotations Per Second
const dist = 50; // distance from point
const radius = 25; // circle radius
function moveObj(time) { // Find rotated point and draw
time = (time / 1000) * PI2 * RPS; // convert the time to rotations per secon
const xx = Math.cos(time) * dist;
const yy = Math.sin(time) * dist;
drawCircle(xx, yy)
}
// Helpers
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function drawCircle(x,y,r = radius) {
ctx.setTransform(1,0,0,1,px,py);
ctx.fillStyle = "#fff";
ctx.beginPath();
ctx.arc(x,y,r,0,PI2);
ctx.fill();
}
function mainLoop(time) {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
moveObj(time);
requestAnimationFrame(mainLoop);
}
const PI = Math.PI;
const PI2 = PI * 2;
canvas {
background : #8AF;
border : 1px solid black;
}
<canvas id="canvas" width="200" height="200"></canvas>
Next let's move the point around which we rotate, using the method above.
Then for the ball we can change the phase of the rotation in x from the rotation in y. This means that the ball rotating around the now rotating point, and the balls rotating axis are out of phase.
The result is a more complex movements.
var px = 100; // point of rotation.
var py = 100;
const RPS_P = 0.1; // point Rotations Per Second 0.1 every 10 seconds
const RPS_X = 1; // Rotations Per Second in x axis of circle
const RPS_Y = 0.8; // Rotations Per Second in y axis of circle
const dist_P = 30; // distance from center point is
const dist = 50; // distance from point
const radius = 25; // circle radius
function moveObj(time) { // Find rotated point and draw
var phaseX = (time / 1000) * PI2 * RPS_X;
var phaseY = (time / 1000) * PI2 * RPS_Y;
const xx = Math.cos(phaseX) * dist;
const yy = Math.sin(phaseY) * dist;
drawCircle(xx, yy)
}
function movePoint(time) { // move point around center
time = (time / 1000) * PI2 * RPS_P;
px = 100 + Math.cos(time) * dist_P;
py = 100 + Math.sin(time) * dist_P;
}
// Helpers
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function drawCircle(x,y,r = radius) {
ctx.setTransform(1,0,0,1,px,py);
ctx.fillStyle = "#fff";
ctx.beginPath();
ctx.arc(x,y,r,0,PI2);
ctx.fill();
}
function mainLoop(time) {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
movePoint(time);
moveObj(time);
requestAnimationFrame(mainLoop);
}
const PI = Math.PI;
const PI2 = PI * 2;
canvas {
background : #8AF;
border : 1px solid black;
}
<canvas id="canvas" width="200" height="200"></canvas>
We can continue to add out of phase rotations. In the next example we now rotate the rotation point around the center, add out of phase rotation to that point and finally draw the ball with its out of phase rotation.
var px = 100; // point of rotation.
var py = 100;
const RPS_C_X = 0.43; // Rotation speed X of rotating rotation point
const RPS_C_Y = 0.47; // Rotation speed X of rotating rotation point
const RPS_P_X = 0.093; // point Rotations speed X
const RPS_P_Y = 0.097; // point Rotations speed Y
const RPS_X = 1; // Rotations Per Second in x axis of circle
const RPS_Y = 0.8; // Rotations Per Second in y axis of circle
const dist_C = 20; // distance from center point is
const dist_P = 30; // distance from center point is
const dist = 30; // distance from point
const radius = 25; // circle radius
function moveObj(time) { // Find rotated point and draw
var phaseX = (time / 1000) * PI2 * RPS_X;
var phaseY = (time / 1000) * PI2 * RPS_Y;
const xx = Math.cos(phaseX) * dist;
const yy = Math.sin(phaseY) * dist;
drawCircle(xx, yy)
}
function movePoints(time) { // Move the rotating pointe and rotate the rotation point
// around that point
var phaseX = (time / 1000) * PI2 * RPS_C_X;
var phaseY = (time / 1000) * PI2 * RPS_C_Y;
px = 100 + Math.cos(phaseX) * dist_C;
py = 100 + Math.sin(phaseY) * dist_C;
phaseX = (time / 1000) * PI2 * RPS_P_X;
phaseY = (time / 1000) * PI2 * RPS_P_Y;
px = px + Math.cos(phaseX) * dist_P;
py = py + Math.sin(phaseY) * dist_P;
}
// Helpers
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function drawCircle(x,y,r = radius) {
ctx.setTransform(1,0,0,1,px,py);
ctx.fillStyle = "#fff";
ctx.beginPath();
ctx.arc(x,y,r,0,PI2);
ctx.fill();
}
function mainLoop(time) {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
movePoints(time);
moveObj(time);
requestAnimationFrame(mainLoop);
}
const PI = Math.PI;
const PI2 = PI * 2;
canvas {
background : #8AF;
border : 1px solid black;
}
<canvas id="canvas" width="200" height="200"></canvas>
So now we have a very complex rotation. However as it is set to the time, you can repeat the movement by just setting the time back to the start. You don't need to store a long complex path.
Add a little random
You may see some repeating movement but if you make the phases of each axis a prime then the repeat time is the product of all the primes.
If you want many objects each with a different movement you can randomise the rotation rates and many more properties.
Javascript does not have a seeded random generator. However you can create one. With a seeded random generator you can us the seed to generate a random object. But if you use that seed again you get the same object. In the example below I us a seed from 0 to 10000000 to create a cloud. That means there are 10000000 unique clouds, but all repeatable.
Example of deterministic random clouds
Restart and it will repeat exactly the same. To change it to non deterministic random just add randSeed(Math.random() * 100000 | 0)
const seededRandom = (() => {
var seed = 1;
return { max : 2576436549074795, reseed (s) { seed = s }, random () { return seed = ((8765432352450986 * seed) + 8507698654323524) % this.max }}
})();
const randSeed = (seed) => seededRandom.reseed(seed|0);
const randSI = (min = 2, max = min + (min = 0)) => (seededRandom.random() % (max - min)) + min;
const randS = (min = 1, max = min + (min = 0)) => (seededRandom.random() / seededRandom.max) * (max - min) + min;
const randSPow = (min, max = min + (min = 0), p = 2) => (max + min) / 2 + (Math.pow(seededRandom.random() / seededRandom.max, p) * (max - min) * 0.5) * (randSI(2) < 1 ? 1 : -1);
const ctx = canvas.getContext("2d");
const W = ctx.canvas.width;
const H = ctx.canvas.height;
const DIAG = (W * W + H * H) ** 0.5;
const colors = {
dark : {
minRGB : [100 * 0.6,200 * 0.6,240 * 0.6],
maxRGB : [255 * 0.6,255 * 0.6,255 * 0.6],
},
light : {
minRGB : [100,200,240],
maxRGB : [255,255,255],
},
}
const getCol = (pos, range) => "rgba(" +
((range.maxRGB[0] - range.minRGB[0]) * pos + range.minRGB[0] | 0) + "," +
((range.maxRGB[1] - range.minRGB[1]) * pos + range.minRGB[1] | 0) + "," +
((range.maxRGB[2] - range.minRGB[2]) * pos + range.minRGB[2] | 0) + "," +(pos * 0.2 + 0.8) + ")";
const Cloud = {
x : 0,
y : 0,
dir : 0, // in radians
wobble : 0,
wobble1 : 0,
wSpeed : 0,
wSpeed1 : 0,
mx : 0, // Move offsets
my : 0,
seed : 0,
size : 2,
detail : null,
reset : true, // when true could resets
init() {
this.seed = randSI(10000000);
this.reset = false;
var x,y,r,dir,dist,f;
if (this.detail === null) { this.detail = [] }
else { this.detail.length = 0 }
randSeed(this.seed);
this.size = randSPow(2, 8); // The pow add bias to smaller values
var col = (this.size -2) / 6;
this.col1 = getCol(col,colors.dark)
this.col2 = getCol(col,colors.light)
var flufCount = randSI(5,15);
while (flufCount--) {
x = randSI(-this.size * 8, this.size * 8);
r = randS(this.size * 2, this.size * 8);
dir = randS(Math.PI * 2);
dist = randSPow(1) * r ;
this.detail.push(f = {x,r,y : 0,mx:0,my:0, move : randS(0.001,0.01), phase : randS(Math.PI * 2)});
f.x+= Math.cos(dir) * dist;
f.y+= Math.sin(dir) * dist;
}
this.xMax = this.size * 12 + this.size * 10 + this.size * 4;
this.yMax = this.size * 10 + this.size * 4;
this.wobble = randS(Math.PI * 2);
this.wSpeed = randS(0.01,0.02);
this.wSpeed1 = randS(0.01,0.02);
const aOff = randS(1) * Math.PI * 0.5 - Math.PI *0.25;
this.x = W / 2 - Math.cos(this.dir+aOff) * DIAG * 0.7;
this.y = H / 2 - Math.sin(this.dir+aOff) * DIAG * 0.7;
clouds.sortMe = true; // flag that coulds need resort
},
move() {
var dx,dy;
this.dir = gTime / 10000;
if(this.reset) { this.init() }
this.wobble += this.wSpeed;
this.wobble1 += this.wSpeed1;
this.mx = Math.cos(this.wobble) * this.size * 4;
this.my = Math.sin(this.wobble1) * this.size * 4;
this.x += dx = Math.cos(this.dir) * this.size / 5;
this.y += dy = Math.sin(this.dir) * this.size / 5;
if (dx > 0 && this.x > W + this.xMax ) { this.reset = true }
else if (dx < 0 && this.x < - this.xMax ) { this.reset = true }
if (dy > 0 && this.y > H + this.yMax) { this.reset = true }
else if (dy < 0 && this.y < - this.yMax) { this.reset = true }
},
draw(){
const s = this.size;
const s8 = this.size * 8;
ctx.fillStyle = this.col1;
ctx.setTransform(1,0,0,1,this.x+ this.mx,this.y +this.my);
ctx.beginPath();
for (const fluf of this.detail) {
fluf.phase += fluf.move + Math.sin(this.wobble * this.wSpeed1) * 0.02 * Math.cos(fluf.phase);
fluf.mx = Math.cos(fluf.phase) * fluf.r / 2;
fluf.my = Math.sin(fluf.phase) * fluf.r / 2;
const x = fluf.x + fluf.mx;
const y = fluf.y + fluf.my;
ctx.moveTo(x + fluf.r + s, y);
ctx.arc(x,y,fluf.r+ s,0,Math.PI * 2);
}
ctx.fill();
ctx.fillStyle = this.col2;
ctx.globalAlpha = 0.5;
ctx.beginPath();
for (const fluf of this.detail) {
const x = fluf.x + fluf.mx - s;
const y = fluf.y + fluf.my - s;
ctx.moveTo(x + fluf.r, y);
ctx.arc(x,y,fluf.r,0,Math.PI * 2);
}
ctx.fill();
ctx.globalAlpha = 0.6;
ctx.beginPath();
for (const fluf of this.detail) {
const x = fluf.x + fluf.mx - s * 1.4;
const y = fluf.y + fluf.my - s * 1.4;
ctx.moveTo(x + fluf.r * 0.8, y);
ctx.arc(x,y,fluf.r* 0.8,0,Math.PI * 2);
}
ctx.fill();
ctx.globalAlpha = 1;
}
}
function createCloud(size){ return {...Cloud} }
const clouds = Object.assign([],{
move() { for(const cloud of this){ cloud.move() } },
draw() { for(const cloud of this){ cloud.draw() } },
sortMe : true, // if true then needs to resort
resort() {
this.sortMe = false;
this.sort((a,b)=>a.size - b.size);
}
});
for(let i = 0; i < 15; i ++) { clouds.push(createCloud(40)) }
requestAnimationFrame(mainLoop)
var gTime = 0;
function mainLoop() {
gTime += 16;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
if(clouds.sortMe) { clouds.resort() }
clouds.move();
clouds.draw();
requestAnimationFrame(mainLoop);
}
body { padding : 0px; margin : 0px;}
canvas {
background : rgb(60,120,148);
border : 1px solid black;
}
<canvas id="canvas" width="600" height="200"></canvas>

img object width and height ignored when drawing on canvas

I'm changing width and height of an image object before drawing on canvas:
earth.onload = function () {
this.width = 50;
this.height = 50;
}
earth.src = 'images/earth-transparent.png';
// and later on
function drawPlanet(xCenter, yCenter, radius, speed, img) {
var ms = time.getSeconds() * 1000 + time.getMilliseconds();
var angle = ((2 * Math.PI) / (speed * 1000) * ms);
var xDelta = radius * Math.sin(angle);
var yDelta = radius * Math.cos(angle);
var x = xCenter + xDelta;
var y = yCenter + yDelta;
ctx.drawImage(img, x - img.width / 2, y - img.height / 2, img.width, img.height);
}
var time = new Date();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = 'rgba(0,153,255,0.4)';
ctx.save();
drawPlanet(300, 300, 200, 6, earth);
(drawPlanet is called in the callback of a setInterval, thus the load event of the image has fired) Unfortunately the size of the image being drawn is the original one. When I output width and height in the debugger their value is 50. Why is this?

Center rotated image in canvas

I need these images rendered in html5 canvas to be centered, rotated and resized depending on the size of the bounding box. I managed to rotate and resize the images but they are not centered anymore.
Can someone help me to fit these images in bounds and keep them in the center of the boxes? (fiddle updated)
http://jsfiddle.net/owtwv1a5/6/
var renderSprite = function(img, x, y, width,height, degree, scale){
var rads = degree * Math.PI/180;
var heightRatio = height/img.height;
var widthRatio = width/img.width;
var isRotated = (degree==90 || degree==270);
if (isRotated) {
var scale_ratio = height/img.width;
} else {
var scale_ratio = heightRatio;
}
var scaledImgHeight = img.height*scale_ratio;
var scaledImgWidth = img.width*scale_ratio;
var offsetX = width - scaledImgWidth;
if ((scaledImgHeight) < height) {
y += parseInt((height-scaledImgHeight)/2);
if (isRotated) {
x -= (scaledImgWidth - scaledImgHeight) / 2;
}
}
if ((scaledImgWidth) < width) {
x += parseInt((width-scaledImgWidth)/2);
if (isRotated) {
x -= (scaledImgWidth - scaledImgHeight) / 2;
}
}
ctx.save();
var centerX = x + scaledImgWidth * 0.5;
var centerY = y + scaledImgHeight * 0.5;
ctx.translate(centerX, centerY);
ctx.rotate(rads);
//ctx.scale(scale,scale);
ctx.translate(-centerX, -centerY);
ctx.drawImage(img, x,y, scaledImgWidth ,scaledImgHeight);
ctx.restore();
};

Animated circle using canvas with numbers counting up

The following jsFiddle http://jsfiddle.net/QsMVn/6/ has animated circles and the percentage showing how much of the circle has been filled. My aim is to have the percentages animated as well so that they move along with the line right next to the end of it. I can't figure out how to do that.
Code of jsFiddle:
// requestAnimationFrame Shim
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
})();
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
var endPercent = 85;
var curPerc = 0;
var counterClockwise = false;
var circ = Math.PI * 2;
var quart = Math.PI / 2;
context.lineWidth = 10;
context.strokeStyle = '#ad2323';
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.shadowBlur = 10;
context.shadowColor = '#656565';
function animate(current) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.arc(x, y, radius, -(quart), ((circ) * current) - quart, false);
context.stroke();
curPerc++;
if (curPerc < endPercent) {
requestAnimationFrame(function () {
animate(curPerc / 100);
});
}
}
animate();
You just use the angle you have (in radians) and calculate a distance based on that.
Prerequisites: Change a couple of lines above so you can reuse the radians:
var radians = (degrees - 90) * Math.PI / 180; // subtract 90 here
...
ctx.arc(W / 2, H / 2, W / 3, 0 - 90 * Math.PI / 180, radians, false);
Then use textAlign and textBaseline to center the text:
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
Calculate a position, demo shows text on the inside - for outside (or in the middle of arc) just adjust the dist value:
var dist = W / 3 - 40;
var tx = W * 0.5 + dist * Math.cos(radians);
var ty = H * 0.5 + dist * Math.sin(radians);
ctx.fillText(text, tx, ty);
Modified fiddle here
Hope this helps!

html5 canvas: overlapping paths not filling correctly

strange issue with HTML5 canvas: I am trying to draw one shape inside another. The outer shape is blue and the inner one red, but the end result is that both shapes end up red. If I step through the code, I can see the blue shape rendered correctly, but then the red shape renders over the blue one, even though it's smaller. Probably a problem with BeginPath/EndPath stuff, but I've seemingly tried every combination with no luck. I have lots more shapes to draw after this one, so I need to figure out how to correctly begin/end a shape before I resume work. Any help is appreciated.
<script type="text/javascript">
window.onload = function () {
var drawingCanvas = document.getElementById('canvas1');
// Is element in the DOM and does browser support canvas
if (drawingCanvas && drawingCanvas.getContext) {
// Init drawing context
var InfieldColor = "#BDB76B";
var OutfieldColor = "#F5F5F5";
var iGrassLen = Math.min(drawingCanvas.width, drawingCanvas.height) * 0.7;
var iRad = iGrassLen * 1.475;
var iAng = -60 * Math.PI / 180;
var iptInfBez0x = iRad * Math.cos(iAng);
var iptInfBez0y = -(iRad * Math.sin(iAng));
iAng = -30 * Math.PI / 180;
var iptInfBez1x = iRad * Math.cos(iAng);
var iptInfBez1y = -(iRad * Math.sin(iAng));
var iInfieldLen = (iGrassLen * (88 / 124));
var iBaseLen = iInfieldLen / 12;
//this is the relative offset between Dixon infield and outfield
var iOutfieldLen = iGrassLen * (282 / 124)
//bezier control points for outfield circle
iRad = iOutfieldLen * 1.31;
iAng = -60 * Math.PI / 180;
var iptOutBez0x = iRad * Math.cos(iAng);
var iptOutBez0y = -(iRad * Math.sin(iAng));
iAng = -30 * Math.PI / 180;
var iptOutBez1x = iRad * Math.cos(iAng);
var iptOutBez1y = -(iRad * Math.sin(iAng));
var iHRLen0 = (340 * iInfieldLen / 90) * 1.025; //iInfieldLen = 90 feet. (plus a fudge factor)
var iHRLen1 = (370 * iInfieldLen / 90) * 1.025;
var iHRLen2 = (400 * iInfieldLen / 90) * 1.025;
var iMoundWid = iInfieldLen / 10;
var context = drawingCanvas.getContext('2d');
context.fillStyle = "#FFFF00";
context.fillRect(0, 0, drawingCanvas.width, drawingCanvas.height);
context.beginPath;
context.moveTo(0, 0);
context.lineTo(iGrassLen, 0);
context.bezierCurveTo(iptInfBez1x, iptInfBez1y, iptInfBez0x, iptInfBez0y, 0, iGrassLen); // bezier curve
context.lineTo(0, 0);
context.closePath();
context.fillStyle = "blue";
context.fill();
context.lineWidth = 1;
context.strokeStyle = "black";
context.stroke();
//infield rectangle
context.beginPath;
context.rect(0, 0, iInfieldLen - (iBaseLen / 4), iInfieldLen - (iBaseLen / 4));
context.closePath;
context.fillStyle = "red";
context.fill();
context.lineWidth = 1;
context.strokeStyle = "black";
context.stroke();
}
}
</script>
context.beginPath;
...
context.closePath;
You forgot (). Without that, these are just discarded references to a function, not calls to it.

Resources