I on a project making projection of maps onto a point cloud possible in Potree.
We are loading the maps from OpenStreetMap's tile API and are loading more tiles as the user zooms on point cloud in order to give the user a more detailed experience. We are projecting the data from the tiles to the point cloud using Three.js Texture which we are extracting from a canvas element where we place the tiles.
We have made all we code to get the tiles from OSM, cut them in the right size and place them in the canvas. And the solution is working quite well for smaller point clouds, but as the area grows and more tiles/images are needed and the canvas element gets bigger (we are scaling the canvas to follow the zoom level in order to give a better resolution) we get into trouble with an huge memory use (around 10 gb in the task manager, but then I investigate the memory use in the chrome dev tools the memory use is a lot lower). The big memory use will cause the browser to become unresponsive and eventually die.
My question is there for is there some kind of memory leak in this code or something you have to be very careful about when you use images and canvasses?
The code:
Potree.MapTextureManager = class MapTextureManager {
constructor(projection, bbMin, bbMax) {
this.projection = projection;
this.bbMin = bbMin;
this.bbMax = bbMax;
this._mapCanvas = document.getElementById("texture");
let ratio = (bbMax[0] - bbMin[0]) / (bbMax[1] - bbMin[1]);
let minHeight = 256;
this._mapCanvas.width = minHeight * ratio;
this._mapCanvas.height = minHeight;
this._minWeb = proj4(swiss, WGS84, [this.bbMin[0], this.bbMin[1]]);
this._maxWeb = proj4(swiss, WGS84, [this.bbMax[0], this.bbMax[1]]);
this.updateTexture(this._minWeb, this._maxWeb);
this._cachedTexture = null;
this._drawnImages = [];
this.geometryNodeIds = new Set();
this._cachedTileImages = [];
this._currentMaxZoom = this.getTiles(this._minWeb, this._maxWeb)[0].zoom;
}
updateTextureFor(visibleNodes, matrixWorld) {
visibleNodes.forEach(visibleNode => {
if (!this.geometryNodeIds.has(visibleNode.geometryNode.id)) {
this.geometryNodeIds.add(visibleNode.geometryNode.id);
var swiss = proj4.defs("test");
var WGS84 = proj4.defs("WGS84");
let nodeBox = Potree.utils.computeTransformedBoundingBox(visibleNode.geometryNode.boundingBox, matrixWorld);
let minWeb = proj4(swiss, WGS84, [nodeBox.min.x, nodeBox.min.y]);
let maxWeb = proj4(swiss, WGS84, [nodeBox.max.x, nodeBox.max.y]);
this.updateTexture(minWeb, maxWeb);
}
});
}
updateTexture(minWeb, maxWeb) {
let canvasEl = this._mapCanvas;
let tiles = this.getTiles(minWeb, maxWeb);
let tilePromises = this.tilePromisesFor(tiles);
tilePromises.forEach(tilePromise => {
tilePromise.then(tileImage => {
if (tileImage.tile.zoom > this._currentMaxZoom) {
this.resizeCanvasTo(tileImage.tile.zoom);
}
this._cachedTileImages.push(tileImage);
this._cachedTileImages.sort((tileImage1, tileImage2) => {
if (tileImage1.tile.zoom >= tileImage2.tile.zoom) {
return 1;
} else {
return -1;
}
});
let myArray = this._cachedTileImages.filter((el) => !this._drawnImages.includes(el));
myArray.forEach(tileImage => {
// if (this._drawnImages.indexOf(tileImage) === -1) {
this.drawTileOnCanvas(canvasEl, tileImage.image, tileImage.tile);
this._drawnImages.push(tileImage);
// }
});
if (this._cachedTexture) {
this._cachedTexture.dispose();
this._cachedTexture = null;
}
});
});
}
get mapTexture() {
if (this._cachedTexture) {
return this._cachedTexture;
}
let texture = new THREE.CanvasTexture(this._mapCanvas);
texture.minFilter = THREE.LinearFilter;
texture.needsUpdate = true;
this._cachedTexture = texture;
return texture;
}
getTiles(minCoord, maxCoord, zoom = 1) {
let maxZoom = 18;
let minNumberOfTiles = 4;
let minX = this.long2tile(minCoord[0], zoom);
let minY = this.lat2tile(minCoord[1], zoom);
let maxX = this.long2tile(maxCoord[0], zoom);
let maxY = this.lat2tile(maxCoord[1], zoom);
let arrayX = [minX, maxX].sort();
let arrayY = [minY, maxY].sort();
let tiles = [];
for (var x = arrayX[0]; x <= arrayX[1]; x++) {
for (var y = arrayY[0]; y <= arrayY[1]; y++) {
tiles.push({ X: x, Y: y, zoom: zoom });
}
}
// We want at least minNumberOfTiles tiles per pointcloud node
if (tiles.length >= minNumberOfTiles || zoom === maxZoom) {
return tiles;
} else {
return this.getTiles(minCoord, maxCoord, zoom + 1);
}
}
tilePromisesFor(Tiles) {
return Tiles.map(function (tile, i) {
return new Promise((resolve, reject) => {
let image = new Image(256, 256);
image.crossOrigin = "Anonymous";
image.onload = function () {
let data = { tile: tile, image: image };
resolve(data);
}
image.src = "https://tile.openstreetmap.org" + "/" + tile.zoom + "/" + tile.X + "/" + tile.Y + ".png";
})
});
}
drawTileOnCanvas(canvas, image, tile) {
let ctx = canvas.getContext("2d");
ctx.drawImage(image, sX, sY, imageWidthToBeDrawn, imageHeightToBeDrawn, dX, dY, drawingWidth, drawingHeight);
image.src = "";
image = null;
}
resizeCanvasTo(zoom) {
let canvas = this._mapCanvas;
let multiplier = Math.pow(2, zoom - this._currentMaxZoom);
let ctx = canvas.getContext("2d");
// create a temporary canvas obj to cache the pixel data //
var temp_cnvs = document.createElement('canvas');
var temp_cntx = temp_cnvs.getContext('2d');
// set it to the new width & height and draw the current canvas data into it //
temp_cnvs.width = canvas.width * multiplier;;
temp_cnvs.height = canvas.height * multiplier;;
temp_cntx.drawImage(canvas, 0, 0);
// resize & clear the original canvas and copy back in the cached pixel data //
canvas.width = canvas.width * multiplier;
canvas.height = canvas.height * multiplier;
ctx.scale(multiplier, multiplier);
ctx.drawImage(temp_cnvs, 0, 0);
this._currentMaxZoom = zoom;
temp_cnvs = null;
temp_cntx = null;
}
};
I have tried to remove some of the unnecessary code. So please say if you are missing something.
Related
Goal
To get this mini-game working in next.js powered environment 'the right way'.
Background
The linked game uses Three.js which in turn requires the window object to update certain variables according to the game logic.
And next.js does not have a window object defined on the server side, as explained here.
So, to tackle this problem, I moved the window object inside of useEffect hook provided by react as mentioned in this article.
Problem
Moving window inside of useEffect worked but other helper variables depends on its value, so to make it all work without errors, I had to move the entire javascript code with some minor adjustments inside of useEffect of a component page that I created.
Extra
I started this project by trying to recreate this game using react-three-fiber as it abstracts well with Next.js but I had to change my ways because the documentation was not clear and lacks example code.
To even change the default camera setting from perspective to orthographic inside of Canvas tag was a little unruly.
But as explained by Bruno Simon, Paul Henschel and others, three becomes quite hard to manage/scale/change/reuse when the code becomes complex.
And I wish to maintain the component property of my code.
How shall I go about it?
Thankyou for any leads, references to courses, articles.
This stackoverflow thread poses a similar question to mine but I have my what ifs.
Code
import styles from '../styles/Home.module.css'
import { React, useState, useLayoutEffect, useRef, useEffect } from 'react'
import * as THREE from 'three'
import * as CANNON from 'cannon'
export default function Home() {
useEffect(() => {
window.focus(); // Capture keys right away (by default focus is on editor)
let camera, scene, renderer; // ThreeJS globals
let world; // CannonJs world
let lastTime; // Last timestamp of animation
let stack; // Parts that stay solid on top of each other
let overhangs; // Overhanging parts that fall down
const boxHeight = 1; // Height of each layer
const originalBoxSize = 3; // Original width and height of a box
let autopilot;
let gameEnded;
let robotPrecision; // Determines how precise the game is on autopilot
const scoreElement = document.getElementById("score");
const instructionsElement = document.getElementById("instructions");
const resultsElement = document.getElementById("results");
init();
// Determines how precise the game is on autopilot
function setRobotPrecision() {
robotPrecision = Math.random() * 1 - 0.5;
}
function init() {
autopilot = true;
gameEnded = false;
lastTime = 0;
stack = [];
overhangs = [];
setRobotPrecision();
// Initialize CannonJS
world = new CANNON.World();
world.gravity.set(0, -10, 0); // Gravity pulls things down
world.broadphase = new CANNON.NaiveBroadphase();
world.solver.iterations = 40;
// Initialize ThreeJs
const aspect = window.innerWidth / window.innerHeight;
const width = 10;
const height = width / aspect;
camera = new THREE.OrthographicCamera(
width / -2, // left
width / 2, // right
height / 2, // top
height / -2, // bottom
0, // near plane
100 // far plane
);
/*
// If you want to use perspective camera instead, uncomment these lines
camera = new THREE.PerspectiveCamera(
45, // field of view
aspect, // aspect ratio
1, // near plane
100 // far plane
);
*/
camera.position.set(4, 4, 4);
camera.lookAt(0, 0, 0);
scene = new THREE.Scene();
// Foundation
addLayer(0, 0, originalBoxSize, originalBoxSize);
// First layer
addLayer(-10, 0, originalBoxSize, originalBoxSize, "x");
// Set up lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.6);
dirLight.position.set(10, 20, 0);
scene.add(dirLight);
// Set up renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animation);
document.body.appendChild(renderer.domElement);
}
function startGame() {
autopilot = false;
gameEnded = false;
lastTime = 0;
stack = [];
overhangs = [];
if (instructionsElement) instructionsElement.style.display = "none";
if (resultsElement) resultsElement.style.display = "none";
if (scoreElement) scoreElement.innerText = 0;
if (world) {
// Remove every object from world
while (world.bodies.length > 0) {
world.remove(world.bodies[0]);
}
}
if (scene) {
// Remove every Mesh from the scene
while (scene.children.find((c) => c.type == "Mesh")) {
const mesh = scene.children.find((c) => c.type == "Mesh");
scene.remove(mesh);
}
// Foundation
addLayer(0, 0, originalBoxSize, originalBoxSize);
// First layer
addLayer(-10, 0, originalBoxSize, originalBoxSize, "x");
}
if (camera) {
// Reset camera positions
camera.position.set(4, 4, 4);
camera.lookAt(0, 0, 0);
}
}
function addLayer(x, z, width, depth, direction) {
const y = boxHeight * stack.length; // Add the new box one layer higher
const layer = generateBox(x, y, z, width, depth, false);
layer.direction = direction;
stack.push(layer);
}
function addOverhang(x, z, width, depth) {
const y = boxHeight * (stack.length - 1); // Add the new box one the same layer
const overhang = generateBox(x, y, z, width, depth, true);
overhangs.push(overhang);
}
function generateBox(x, y, z, width, depth, falls) {
// ThreeJS
const geometry = new THREE.BoxGeometry(width, boxHeight, depth);
const color = new THREE.Color(`hsl(${30 + stack.length * 4}, 100%, 50%)`);
const material = new THREE.MeshLambertMaterial({ color });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, y, z);
scene.add(mesh);
// CannonJS
const shape = new CANNON.Box(
new CANNON.Vec3(width / 2, boxHeight / 2, depth / 2)
);
let mass = falls ? 5 : 0; // If it shouldn't fall then setting the mass to zero will keep it stationary
mass *= width / originalBoxSize; // Reduce mass proportionately by size
mass *= depth / originalBoxSize; // Reduce mass proportionately by size
const body = new CANNON.Body({ mass, shape });
body.position.set(x, y, z);
world.addBody(body);
return {
threejs: mesh,
cannonjs: body,
width,
depth
};
}
function cutBox(topLayer, overlap, size, delta) {
const direction = topLayer.direction;
const newWidth = direction == "x" ? overlap : topLayer.width;
const newDepth = direction == "z" ? overlap : topLayer.depth;
// Update metadata
topLayer.width = newWidth;
topLayer.depth = newDepth;
// Update ThreeJS model
topLayer.threejs.scale[direction] = overlap / size;
topLayer.threejs.position[direction] -= delta / 2;
// Update CannonJS model
topLayer.cannonjs.position[direction] -= delta / 2;
// Replace shape to a smaller one (in CannonJS you can't simply just scale a shape)
const shape = new CANNON.Box(
new CANNON.Vec3(newWidth / 2, boxHeight / 2, newDepth / 2)
);
topLayer.cannonjs.shapes = [];
topLayer.cannonjs.addShape(shape);
}
window.addEventListener("mousedown", eventHandler);
window.addEventListener("touchstart", eventHandler);
window.addEventListener("keydown", function (event) {
if (event.key == " ") {
event.preventDefault();
eventHandler();
return;
}
if (event.key == "R" || event.key == "r") {
event.preventDefault();
startGame();
return;
}
});
function eventHandler() {
if (autopilot) startGame();
else splitBlockAndAddNextOneIfOverlaps();
}
function splitBlockAndAddNextOneIfOverlaps() {
if (gameEnded) return;
const topLayer = stack[stack.length - 1];
const previousLayer = stack[stack.length - 2];
const direction = topLayer.direction;
const size = direction == "x" ? topLayer.width : topLayer.depth;
const delta =
topLayer.threejs.position[direction] -
previousLayer.threejs.position[direction];
const overhangSize = Math.abs(delta);
const overlap = size - overhangSize;
if (overlap > 0) {
cutBox(topLayer, overlap, size, delta);
// Overhang
const overhangShift = (overlap / 2 + overhangSize / 2) * Math.sign(delta);
const overhangX =
direction == "x"
? topLayer.threejs.position.x + overhangShift
: topLayer.threejs.position.x;
const overhangZ =
direction == "z"
? topLayer.threejs.position.z + overhangShift
: topLayer.threejs.position.z;
const overhangWidth = direction == "x" ? overhangSize : topLayer.width;
const overhangDepth = direction == "z" ? overhangSize : topLayer.depth;
addOverhang(overhangX, overhangZ, overhangWidth, overhangDepth);
// Next layer
const nextX = direction == "x" ? topLayer.threejs.position.x : -10;
const nextZ = direction == "z" ? topLayer.threejs.position.z : -10;
const newWidth = topLayer.width; // New layer has the same size as the cut top layer
const newDepth = topLayer.depth; // New layer has the same size as the cut top layer
const nextDirection = direction == "x" ? "z" : "x";
if (scoreElement) scoreElement.innerText = stack.length - 1;
addLayer(nextX, nextZ, newWidth, newDepth, nextDirection);
} else {
missedTheSpot();
}
}
function missedTheSpot() {
const topLayer = stack[stack.length - 1];
// Turn to top layer into an overhang and let it fall down
addOverhang(
topLayer.threejs.position.x,
topLayer.threejs.position.z,
topLayer.width,
topLayer.depth
);
world.remove(topLayer.cannonjs);
scene.remove(topLayer.threejs);
gameEnded = true;
if (resultsElement && !autopilot) resultsElement.style.display = "flex";
}
function animation(time) {
if (lastTime) {
const timePassed = time - lastTime;
const speed = 0.008;
const topLayer = stack[stack.length - 1];
const previousLayer = stack[stack.length - 2];
// The top level box should move if the game has not ended AND
// it's either NOT in autopilot or it is in autopilot and the box did not yet reach the robot position
const boxShouldMove =
!gameEnded &&
(!autopilot ||
(autopilot &&
topLayer.threejs.position[topLayer.direction] <
previousLayer.threejs.position[topLayer.direction] +
robotPrecision));
if (boxShouldMove) {
// Keep the position visible on UI and the position in the model in sync
topLayer.threejs.position[topLayer.direction] += speed * timePassed;
topLayer.cannonjs.position[topLayer.direction] += speed * timePassed;
// If the box went beyond the stack then show up the fail screen
if (topLayer.threejs.position[topLayer.direction] > 10) {
missedTheSpot();
}
} else {
// If it shouldn't move then is it because the autopilot reached the correct position?
// Because if so then next level is coming
if (autopilot) {
splitBlockAndAddNextOneIfOverlaps();
setRobotPrecision();
}
}
// 4 is the initial camera height
if (camera.position.y < boxHeight * (stack.length - 2) + 4) {
camera.position.y += speed * timePassed;
}
updatePhysics(timePassed);
renderer.render(scene, camera);
}
lastTime = time;
}
function updatePhysics(timePassed) {
world.step(timePassed / 1000); // Step the physics world
// Copy coordinates from Cannon.js to Three.js
overhangs.forEach((element) => {
element.threejs.position.copy(element.cannonjs.position);
element.threejs.quaternion.copy(element.cannonjs.quaternion);
});
}
window.addEventListener("resize", () => {
// Adjust camera
console.log("resize", window.innerWidth, window.innerHeight);
const aspect = window.innerWidth / window.innerHeight;
const width = 10;
const height = width / aspect;
camera.top = height / 2;
camera.bottom = height / -2;
// Reset renderer
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.render(scene, camera);
});
})
return (
<div className={styles.container}>
<div className={styles.nav}>
<h2>Ritik Jangir</h2>
</div>
</div>
)
}
I'm still a beginner coder and I'm making a project where the eye positions (networked by socket) of two clients when landing on the same positions on the canvas would play the music note it lands on. I'm still in the beginning stages of this project and currently, I'm trying to draw the client's eye position on the canvas while the grid of music notes on a p5 renderer object. I had coded the grid to add an ellipse to where the mouse has clicked. The grid is successfully drawn but I no longer can interact with the grid i.e add or remove ellipses on click.
So now I'm a bit lost on how to solve this issue. Before when the eye was being drawn on p5.renderer I also tried the clear() function to get rid of the trails but it didn't work.
So I have two problems 1) trying to get rid of eye position trails and 2) using create graphics with mouse pressed function not working in code below.
// NETWORKED + EYE VARIABLES
let socket = io();
let ctracker;
let clients = {};
let data = {};
let w = 1200;
let h = 600;
let leftEyeX, leftEyeY, rightEyeX, rightEyeY;
let cnvPosX;
let cnvPosY;
/// SOUND VARIABLES //
let bell, bell1, bell2, bell3; // contains sound source
let bPat, b1Pat, b2Pat; // an array of no.s that we can use to make beats // 1 = on, 0= off
let bPhrase, b1Phrase, b2Phrase; // defines how the beat pattern is interpreted
let part; // attach all the instruments to the to make it into a machine i.e on/off
let bpmCTRL;
let beatLength; // how big is sequence before it starts looping
let cellSize;
let cnv;
let overlayCnv;
let bg, largeText,smallText, MleftEye, MRightEye;
let row1 =[];
function preload() {
// background
bg = loadImage("https://cdn.glitch.com/e024d4d5-2c9e-473c-acd3-c2e01a1ef9cd%2Fdaw-bg.png?v=1589319965142");
//main game instruction
largeText = loadImage("https://cdn.glitch.com/e024d4d5-2c9e-473c-acd3-c2e01a1ef9cd%2FAsset%2015.png?v=1589381930278");
// small game description
smallText = loadImage("https://cdn.glitch.com/e024d4d5-2c9e-473c-acd3-c2e01a1ef9cd%2FAsset%203.png?v=1589381926354");
//sound files
bell = loadSound (
"https://cdn.glitch.com/e024d4d5-2c9e-473c-acd3-c2e01a1ef9cd%2F203490__tesabob2001__g-5.mp3?v=1589326854551",
() => {}
);
bell1 = loadSound (
"https://cdn.glitch.com/e024d4d5-2c9e-473c-acd3-c2e01a1ef9cd%2F203485__tesabob2001__c5.mp3?v=1589326924689",
() => {}
);
bell2 = loadSound (
"https://cdn.glitch.com/e024d4d5-2c9e-473c-acd3-c2e01a1ef9cd%2F203489__tesabob2001__f5.mp3?v=1589326917439",
() => {}
);
bell3 = loadSound (
"https://cdn.glitch.com/e024d4d5-2c9e-473c-acd3-c2e01a1ef9cd%2F203491__tesabob2001__g-4.mp3?v=1589326867294", () => {}
);
};
function setup() {
cnvPosX = 120;
cnvPosY = 50;
// setup camera capture
var videoInput = createCapture(VIDEO);
videoInput.size(w,h);
videoInput.position(cnvPosX, cnvPosY);
videoInput.hide();
// setup canvas
ctracker = new clm.tracker();
ctracker.init(pModel);
ctracker.start(videoInput.elt);
noStroke();
socket.on('getUpdate', function(data){
clients[data.name] = data;
});
cnv = createCanvas(w, h);
cnv.position(cnvPosX,cnvPosY)
overlayCnv = createGraphics(w,h);
// overlayCnv.position(cnvPosX,cnvPosY);
overlayCnv.mousePressed(canvasPressed);
beatLength = 6;
cellSize = 200;
numOfRows = 3;
// canvas for eyes
// basic structure of a DAW
// time is a sceduled delay in note play time
bPat = [0, 0, 0, 0, 0, 0];
b1Pat = [0, 0, 0, 0, 0, 0];
b2Pat = [0, 0, 0, 0, 0, 0];
function selectSong() {
row1 = [bell3, bell];
selected = row1[floor(random(2))];
console.log(selected);
selected.play();
}
// name, callback, array
bPhrase = new p5.Phrase(
"bell",
time => {
selectSong()
},
bPat
);
b1Phrase = new p5.Phrase(
"bell1",
time => {
// make a variable to go there insiead of bell -> use random function to give a value to the variable
bell1.play(time); },
b1Pat
);
b2Phrase = new p5.Phrase(
"bell2",
time => {
bell2.play(time);
},
b2Pat
);
part = new p5.Part();
part.addPhrase(bPhrase);
part.addPhrase(b1Phrase);
part.addPhrase(b2Phrase);
bpmCTRL = createSlider(30, 200, 60, 1); // smallest val, highest val, starting val, incremental val
bpmCTRL.position(10, 10); // x, y
bpmCTRL.input(() => {
part.setBPM(bpmCTRL.value());
});
part.setBPM("60");
drawMatrix();
///// user interact
function canvasPressed() {
console.log("mousepressed")
let rowClicked = floor(numOfRows * (mouseY / height));
let columnClicked = floor(beatLength * (mouseX / width));
if (rowClicked === 0) {
console.log("first row");
bPat[columnClicked] = +!bPat[columnClicked];
} else if (rowClicked === 1) {
console.log("second row");
b1Pat[columnClicked] = +!b1Pat[columnClicked];
} else if (rowClicked === 2) {
console.log("third row");
b2Pat[columnClicked] = +!b2Pat[columnClicked];
}
drawMatrix();
}
/// drawing the grid
function drawMatrix() {
overlayCnv.background(bg);
//line
overlayCnv.stroke(25,40,100);
overlayCnv.strokeWeight(2);
for (let i = 0; i < beatLength + 1; i++) {
overlayCnv.line(i * cellSize, 0, i * cellSize, height);
}
for (let i = 0; i < numOfRows + 1; i++) {
overlayCnv.line(0, (i * height) / numOfRows, width, (i * height) / numOfRows);
}
//circle
overlayCnv.noStroke();
overlayCnv.fill(25,40,100);
for (let i = 0; i < beatLength; i++) {
if (bPat[i] === 1) {
overlayCnv.ellipse(i * cellSize + 0.5 * cellSize, 100, 50);
}
if (b1Pat[i] === 1) {
overlayCnv.ellipse(i * cellSize + 0.5 * cellSize, 300, 40);
}
if (b2Pat[i] === 1) {
overlayCnv.ellipse(i * cellSize + 0.5 * cellSize, 500, 30);
}
}
}
image(overlayCnv, 0, 0);
}
function draw() {
let positions = ctracker.getCurrentPosition();
for (let i = 0; i < positions.length; i++) {
// draw ellipse at each position point
leftEyeX = positions[27][0];
leftEyeY = positions[27][1];
rightEyeX = positions[32][0];
rightEyeY = positions[32][1];
// ellipse(positions[i][0], positions[i][1], 8, 8);
fill(255);
ellipse(rightEyeX, rightEyeY, 18, 18);
fill(255);
ellipse(leftEyeX, leftEyeY, 18, 18);
// formatting each point into a dict entry to send over socket io
data[(27).toString() + "." + '0'] = leftEyeX;
data[(27).toString() + "." + '1'] = leftEyeY;
data[(32).toString() + "." + '0'] = rightEyeX;
data[(32).toString() + "." + '1'] = rightEyeY;
// image(eyeCnv,0,0); // x, y relative to canvas x & y
}
}
/////// conditional to check if all files are loaded
function keyPressed() {
if (key === " ") {
if (bell.isLoaded() && bell1.isLoaded() && bell2.isLoaded()) {
if (!part.isPlaying) {
part.loop();
} else {
part.stop();
}
} else {
console.log("relax");
}
}
}
Nevermind, the error was just that background kept drawing in draw()
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I have been doing a little experimenting with canvas, creating drawings using lines shapes text etc, and inserting .png files. The inserting .png files is the bit that I cannot get to work.
Edit:
Undesired behaviour of this code: I load shapes to the graphics context, then load an image file to the graphics context, however when the graphics context is drawn, the image is at behind the shapes, despite being drawn last.
I wanted the image file to be at the top, in front of the shapes.
Desired behaviour: To bring image file to the front of the canvas, so it is not hidden by shapes drawn in the graphics context.
function loadImage(name) {
images[name] = new Image();
images[name].src = "DogWalking/" + name + ".png";
images[name].onload = function() {
graphics.drawImage(this, 0, 300);
canvas.bringToFront(this);
};
}
the function for drawing is called here:
function draw() {
graphics.save(); // to make sure changes don't carry over from one call to the next
graphics.fillStyle = "transparent"; // background color
graphics.fillRect(0,0,wWidth, wHeight);
graphics.fillStyle = "black";
applyLimits(graphics,xleft,xright,ytop,ybottom,true);
graphics.lineWidth = pixelSize;
world.draw(graphics);
graphics.drawImage(images["dog-walking11"],200,200);
graphics.restore();
}
code for the whole page is
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<head>
<title>Hierarchical Modeling 2D</title>
<style>
#messagediv {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 0;
background-color: indigo;
}
#canvasdiv {
position: absolute;
left: 0;
top: 0;
z-index: 10;
background-color: transparent;
}
</style>
<script type="text/javascript" src="rgbcolor.js"></script>
<script type="text/javascript">
"use strict";
var totalResources = 17;
var numResourcesLoaded = 0;
var images = {};
function loadImage(name) {
images[name] = new Image();
images[name].src = "DogWalking/" + name + ".png";
images[name].onload = function() {
//
graphics.drawImage(this, 0, 300);
canvas.bringToFront(this);
}
}
var canvas; // DOM object corresponding to the canvas
var graphics; // 2D graphics context for drawing on the canvas
var ctx; // 2D graphics context for drawing on the canvas
var myNumber = 0, myNumber2 = 0, myInterval, myInterval2, myelement, thisdiv, printx;
var mycoords = new Array();
var pcoords = new Array(); //coordinates of the portal.
//var pcoords = [[0,0], [50,300], [250,150]]; //coordinates of the portal.
var nocoords = 2;
var frameNumber = 0; // Current frame number.
var frameNumber2 = 0;
var sun;
var sun2;
var ground;
var world;
var pixelSize;
var wWidth;
var wHeight;
var portals = new Array("calendar1","alternativsearch","art1", "directory1");
var portalsval = new Array();
var portalsobj;
var leftj = new Array(3,1,4,2);
var forwards = "http://www.alternativworld.com";
// ---------------- Set Page Layout ----------------
// function to set size of canvas and location of portals
function pageLayout() {
var w = window, d = document, e = d.documentElement, g = d.getElementsByTagName('body')[0];
wWidth = w.innerWidth || e.clientWidth || g.clientWidth;
wHeight = w.innerHeight|| e.clientHeight|| g.clientHeight;
// Adjust wWidth and wHeight if ratio does not match scenary 7 by 5.
if (wWidth/wHeight != 7/5)
if (wWidth/wHeight > 7/5) {
var widthPortion = 5 * wWidth/wHeight;
wWidth = wWidth * 7 / widthPortion;
} else {
var heightPortion = 7 * wHeight/wWidth;
wHeight = wHeight * 5 / heightPortion;
}
var widthheight, localerror = false;
widthheight = Math.min(wWidth, wHeight);
if(widthheight < 400){
var localerror = true;
}
if (localerror == true)
alert("Warning, the page size of your browser or your screen resolution may be too small to correctly view this web page.");
var theCanvas = d.getElementById("theCanvas");
theCanvas.height = wHeight;
theCanvas.width = wWidth;
}
//Function to listen to the mouse events and see if a link is selected.
function doMouseDown(evt) {
var r = canvas.getBoundingClientRect();
var x = Math.round(evt.clientX - r.left);
var y = Math.round(evt.clientY - r.top);
alert(evt.clientX+ " " + evt.clientY);
for (var i = portals.length+1; i >= 0; i--) {
var p = pcoords[i];
if (Math.abs(p[0] - x) <= 50 && Math.abs(p[1] - y) <= 50) {
document.location.href = forwards;
return;
} else if (Math.abs(0 - x) <= 50 && Math.abs(0 - y) <= 50){
document.location.href = "http://www.alternativeuk.co.uk";
return;
}
}
}
// ---------------- The object-oriented scene graph API ------------------
/**
* The base class for all nodes in the scene graph data structure.
*/
function SceneGraphNode() {
this.fillColor = null; // If non-null, the default fillStyle for this node.
this.strokeColor = null; // If non-null, the default strokeStyle for this node.
}
SceneGraphNode.prototype.doDraw = function(g) {
// This method is meant to be abstract and must be
// OVERRIDDEN in any actual object in the scene graph.
// It is not meant to be called; it is called by draw().
throw "doDraw not implemented in SceneGraphNode"
}
SceneGraphNode.prototype.draw = function(g) {
// This method should be CALLED to draw the object
// represented by this SceneGraphNode. It should NOT
// ordinarily be overridden in subclasses.
graphics.save();
if (this.fillColor) {
g.fillStyle = this.fillColor;
}
if (this.strokeColor) {
g.strokeStyle = this.strokeColor;
}
this.doDraw(g);
graphics.restore();
}
SceneGraphNode.prototype.setFillColor = function(color) {
// Sets fillColor for this node to color.
// Color should be a legal CSS color string, or null.
this.fillColor = color;
return this;
}
SceneGraphNode.prototype.setStrokeColor = function(color) {
// Sets strokeColor for this node to color.
// Color should be a legal CSS color string, or null.
this.strokeColor = color;
return this;
}
SceneGraphNode.prototype.setColor = function(color) {
// Sets both the fillColor and strokeColor to color.
// Color should be a legal CSS color string, or null.
this.fillColor = color;
this.strokeColor = color;
return this;
}
/**
* Defines a subclass, CompoundObject, of SceneGraphNode to represent
* an object that is made up of sub-objects. Initially, there are no
* sub-objects.
*/
function CompoundObject() {
SceneGraphNode.call(this); // do superclass initialization
this.subobjects = []; // the list of sub-objects of this object
}
CompoundObject.prototype = new SceneGraphNode(); // (makes it a subclass!)
CompoundObject.prototype.add = function(node) {
// Add node a subobject of this object. Note that the
// return value is a reference to this node, to allow chaining
// of method calls.
this.subobjects.push(node);
return this;
}
CompoundObject.prototype.doDraw = function(g) {
// Just call the sub-objects' draw() methods.
for (var i = 0; i < this.subobjects.length; i++)
this.subobjects[i].draw(g);
}
/**
* Define a subclass, TransformedObject, of SceneGraphNode that
* represents an object along with a modeling transformation to
* be applied to that object. The object must be specified in
* the constructor. The transformation is specified by calling
* the setScale(), setRotate() and setTranslate() methods. Note that
* each of these methods returns a reference to the TransformedObject
* as its return value, to allow for chaining of method calls.
* The modeling transformations are always applied to the object
* in the order scale, then rotate, then translate.
*/
function TransformedObject(object) {
SceneGraphNode.call(this); // do superclass initialization
this.object = object;
this.rotationInDegrees = 0;
this.scaleX = 1;
this.scaleY = 1;
this.translateX = 0;
this.translateY = 0;
}
TransformedObject.prototype = new SceneGraphNode(); // (makes it a subclass!)
TransformedObject.prototype.setRotation = function(angle) {
// Set the angle of rotation, measured in DEGREES. The rotation
// is always about the origin.
this.rotationInDegrees = angle;
return this;
}
TransformedObject.prototype.setScale = function(sx, sy) {
// Sets scaling factors.
this.scaleX = sx;
this.scaleY = sy;
return this;
}
TransformedObject.prototype.setTranslation = function(dx,dy) {
// Set translation mounts.
this.translateX = dx;
this.translateY = dy;
return this;
}
TransformedObject.prototype.doDraw = function(g) {
// Draws the object, with its modeling transformation.
g.save();
if (this.translateX != 0 || this.translateY != 0) {
g.translate(this.translateX, this.translateY);
}
if (this.rotationInDegrees != 0) {
g.rotate(this.rotationInDegrees/180*Math.PI);
}
if (this.scaleX != 1 || this.scaleY != 1) {
g.scale(this.scaleX, this.scaleY);
}
this.object.draw(g);
g.restore();
}
/**
* A subclass of SceneGraphNode representing filled triangles.
* The constructor specifies the vertices of the triangle:
* (x1,y1), (x2,y2), and (x3,y3).
*/
function Triangle(x1,y1,x2,y2,x3,y3) {
SceneGraphNode.call(this);
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.x3 = x3;
this.y3 = y3;
}
Triangle.prototype = new SceneGraphNode();
Triangle.prototype.doDraw = function(g) {
g.beginPath();
g.moveTo(this.x1,this.y1);
g.lineTo(this.x2,this.y2);
g.lineTo(this.x3,this.y3);
g.closePath();
g.fill();
}
/**
* Directly create a line object as a SceneGraphNode with a
* custom doDraw() method. line is of length 1 and
* extends along the x-axis from (0,0) to (1,0).
*/
var line = new SceneGraphNode();
line.doDraw = function(g) {
g.beginPath();
g.moveTo(0,0);
g.lineTo(1,0);
g.stroke();
}
/**
* Directly create a filled rectangle object as a SceneGraphNode with a
* custom doDraw() method. filledRect is a square with side 1, centered
* at (0,0), with corners at (-0.5,-0.5) and (0.5,0.5).
*/
var filledRect = new SceneGraphNode();
filledRect.doDraw = function(g) {
g.fillRect(-0.5,-0.5,1,1);
}
/**
* Directly create a rectangle object as a SceneGraphNode with a
* custom doDraw() method. rect is a square with side 1, centered
* at (0,0), with corners at (-0.5,-0.5) and (0.5,0.5). Only the
* outline of the square is drawn.
*/
var rect = new SceneGraphNode();
rect.doDraw = function(g) {
g.strokeRect(-0.5,-0.5,1,1);
}
/**
* Directly create a filled circle object as a SceneGraphNode with a
* custom doDraw() method. filledCircle is a circle with radius 0.5
* (diameter 1), centered at (0,0).
*/
var filledCircle = new SceneGraphNode();
filledCircle.doDraw = function(g) {
g.beginPath();
g.arc(0,0,0.5,0,2*Math.PI);
g.fill();
}
var clickHere = new SceneGraphNode();
clickHere.doDraw = function(g) {
g.fillText("click here :)",0,0)
}
/**
* Directly create a circle object as a SceneGraphNode with a
* custom doDraw() method. filledCircle is a circle with radius 0.5
* (diameter 1), centered at (0,0). Only the outline of the circle
* is drawn.
*/
var circle = new SceneGraphNode();
circle.doDraw = function(g) {
g.beginPath();
g.arc(0,0,0.5,0,2*Math.PI);
g.stroke();
}
var dog = new SceneGraphNode();
dog.doDraw = function(g) {
g.drawImage(images["dog-walking11"],-2, 2);
alert(images["dog-walking11"].name);
}
// -------------------- Specific to this application ----------------------------
/*
* Define two extra basic objects as SceneGraphNodes with custom doDraw() methods.
* One represents the ground, the other a vane for a windmill.
*/
var ground = new SceneGraphNode();
ground.doDraw = function(g) {
g.beginPath();
g.moveTo(0,-1);
g.lineTo(0,0.8);
g.lineTo(1.5,1.65);
g.lineTo(1.8,1.3);
g.lineTo(3,2.1);
g.lineTo(4.7,0.7);
g.lineTo(6.1,1.2);
g.lineTo(7,0.8);
g.lineTo(7,-1);
g.closePath();
g.fill();
}
var windmillVane = new SceneGraphNode();
windmillVane.doDraw = function(g) {
g.beginPath();
g.moveTo(0,0);
g.lineTo(0.5,0.1);
g.lineTo(1.5,0);
g.lineTo(0.5,-0.1);
g.closePath();
g.fill();
}
var world; // A SceneGraphNode representing the entire picture. This should
// be created in the createWorld() method.
var pixelSize; // The size of one pixel, in the transformed coordinates.
// This is used as the default width of a stroke.
var background = "#C8C8FF"; // A CSS color string giving the background color.
// the draw() function fills the canvas with this color.
var xleft = 0; // The requested xy-limits on the canvas, after the
var xright = 7; // coordinate transformation has been applied.
var ybottom = -1; // The transformation is applied in the draw() function.
var ytop = 4;
var frameNumber = 0; // Current frame number.
var cart; // TransformedObjects that are animated.
var wheel;
var sun;
var clickText1;
var clickText2;
var rotor;
/**
* Create the scene graph data structure. The global variable world must
* refer to the root node of the scene graph. This function is called in
* the init() function.
*/
function createWorld() {
pageLayout();
var i;
var sunTemp = new CompoundObject();
sunTemp.setColor("yellow"); // color for filled circle and light rays
for (i = 0; i < 12; i++) { // add the 12 light rays, with different rotations
sunTemp.add( new TransformedObject(line).setScale(0.75,0.75).setRotation(i*30) );
}
sunTemp.add( filledCircle ); // the face of the sun
sunTemp.add( new TransformedObject(circle).setColor("#B40000") ); // outlines the face
sun = new TransformedObject(sunTemp);
clickText1 = new TransformedObject(clickHere).setColor("#B40000").setScale(0.01,-0.01);
var wheelTemp = new CompoundObject();
wheelTemp.setColor("black"); // color for all but one of the subobjects
wheelTemp.add( new TransformedObject(filledCircle).setScale(2,2) );
wheelTemp.add( new TransformedObject(filledCircle).setScale(1.6,1.6).setColor("#CCCCCC") );
wheelTemp.add( new TransformedObject(filledCircle).setScale(0.4,0.4) );
for (i = 0; i < 12; i++) { // add the 12 spokes
wheelTemp.add( new TransformedObject(line).setRotation(i*30) );
}
wheel = new TransformedObject(wheelTemp);
var cartTemp = new CompoundObject();
cartTemp.setColor("red"); // color for the rectangular body of the cart
cartTemp.add( new TransformedObject(wheel).setScale(0.8,0.8).setTranslation(1.5,-0.1) );
cartTemp.add( new TransformedObject(wheel).setScale(0.8,0.8).setTranslation(-1.5,-0.1) );
cartTemp.add( new TransformedObject(filledRect).setScale(5,2).setTranslation(0,1) ); // the body of the cart
cart = new TransformedObject(cartTemp).setScale(0.3,0.3);
clickText2 = new TransformedObject(clickHere).setColor("yellow").setScale(0.01,-0.01);
var rotorTemp = new CompoundObject(); // a "rotor" consisting of three vanes
rotorTemp.setColor( "#C86464" ); // color for all of the vanes
rotorTemp.add( windmillVane );
rotorTemp.add( new TransformedObject(windmillVane).setRotation(120) );
rotorTemp.add( new TransformedObject(windmillVane).setRotation(240) );
rotor = new TransformedObject(rotorTemp);
var windmill = new CompoundObject();
windmill.setColor("#E0C8C8"); // color for the pole
windmill.add( new TransformedObject(filledRect).setScale(0.1,3).setTranslation(0,1.5) ); // the pole
windmill.add( new TransformedObject(rotor).setTranslation(0,3) ); // the rotating vanes
world = new CompoundObject();
world.setColor("#00961E"); // color used for the ground only
world.add(ground);
//world.add( new TransformedObject(filledRect).setScale(7,0.8).setTranslation(3.5,0).setColor("#646496") ); // road
//world.add( new TransformedObject(filledRect).setScale(7,0.06).setTranslation(3.5,0).setColor("white") ); // line in road
world.add( new TransformedObject(windmill).setScale(0.6,0.6).setTranslation(0.75,1) );
world.add( new TransformedObject(windmill).setScale(0.4,0.4).setTranslation(2.2,1.3) );
world.add( new TransformedObject(windmill).setScale(0.7,0.7).setTranslation(3.7,0.8) );
world.add( new TransformedObject(sun).setTranslation(5.5,3.3) );
world.add( new TransformedObject(clickText1).setTranslation(5.25,3.3) );
world.add( cart );
world.add( clickText2 );
//alert(2);
}
/**
* This will be called before each frame is drawn.
*/
function updateFrame() {
frameNumber++;
if (frameNumber>= 312){
frameNumber = 0;
frameNumber2 = 1;
}
cart.setTranslation(-3 + 13*(frameNumber % 300) / 300.0, 0);
clickText2.setTranslation(-3.3 + 13*(frameNumber % 300) / 300.0, 0.25);
if (typeof(pcoords[5]) != 'undefined') {
pcoords[5][0] = (-3.3 + 13*(frameNumber % 300) / 300.0-xleft)*canvas.width / (xright-xleft);
pcoords[5][1] = (0.25-ytop)*canvas.height / (ybottom-ytop);
}
wheel.setRotation(-frameNumber*3.1);
sun.setRotation(-frameNumber);
rotor.setRotation(frameNumber * 2.7);
}
// ------------------------------- graphics support functions --------------------------
/**
* Draw one frame of the animation. Probably doesn't need to be changed,
* except maybe to change the setting of preserveAspect in applyLimits().
*/
function draw() {
graphics.save(); // to make sure changes don't carry over from one call to the next
graphics.fillStyle = "transparent"; // background color
graphics.fillRect(0,0,wWidth, wHeight);
graphics.fillStyle = "black";
applyLimits(graphics,xleft,xright,ytop,ybottom,true);
graphics.lineWidth = pixelSize;
world.draw(graphics);
graphics.drawImage(images["dog-walking11"],200,200);
graphics.restore();
}
/**
* Applies a coordinate transformation to the graphics context, to map
* xleft,xright,ytop,ybottom to the edges of the canvas. This is called
* by draw(). This does not need to be changed.
*/
//pcoords[0][0] =
//pcoords[0][1]=
function applyLimits(g, xleft, xright, ytop, ybottom, preserveAspect) {
var width = canvas.width; // The width of this drawing area, in pixels.
var height = canvas.height; // The height of this drawing area, in pixels.
var k = portals.length;
var j;
var i = 0, widthheight, myradius;
var localerror = false;
if (pcoords.length < k) {
while (portals[i]){
j = i + 1;
if (width > 100){
var rWidth = width/(k + 1);
rWidth= Math.floor(rWidth);
} else {
var lWidth = 0;
var rWidth = 0;
}
if (height > 100){
var bHeight = height/(k + 1);
bHeight= Math.floor(bHeight);
} else {
var tHeight = 0;
var bHeight = 0;
}
var myleft = leftj[i] * rWidth - 50;
var mytop = j * bHeight - 50;
pcoords[i]= new Array;
pcoords[i][0] = myleft;
pcoords[i][1] = mytop;
i = i + 1;
}
}
if (preserveAspect) {
// Adjust the limits to match the aspect ratio of the drawing area.
var displayAspect = Math.abs(height / width);
var requestedAspect = Math.abs(( ybottom-ytop ) / ( xright-xleft ));
var excess;
if (displayAspect > requestedAspect) {
excess = (ybottom-ytop) * (displayAspect/requestedAspect - 1);
ybottom += excess/2;
ytop -= excess/2;
}
else if (displayAspect < requestedAspect) {
excess = (xright-xleft) * (requestedAspect/displayAspect - 1);
xright += excess/2;
xleft -= excess/2;
}
}
var pixelWidth = Math.abs(( xright - xleft ) / width);
var pixelHeight = Math.abs(( ybottom - ytop ) / height);
pixelSize = Math.min(pixelWidth,pixelHeight);
if (frameNumber == 4 || frameNumber == 5){
pcoords.push([(5.25-xleft)*width / (xright-xleft),(3.3-ytop)*height / (ybottom-ytop)]);
pcoords.push([(-3.3 + 13*(frameNumber % 300) / 300.0-xleft)*width / (xright-xleft), (0.25-ytop)*height / (ybottom-ytop)]);
}
g.scale( width / (xright-xleft), height / (ybottom-ytop) );
g.translate( -xleft, -ytop );
// if (frameNumber < 3)
}
//------------------ Animation framework ------------------------------
var animationTimeout = null; // A null value means the animation is off.
// Otherwise, this is the timeout ID.
function frame() {
// Draw one frame of the animation, and schedule the next frame.
updateFrame();
draw();
canvas.addEventListener("mousedown", doMouseDown, false);
animationTimeout = setTimeout(frame, 33);
}
function setAnimationRunning(run) {
if ( run ) {
if (animationTimeout == null) {
// If the animation is not already running, start
// it by scheduling a call to frame().
animationTimeout = setTimeout(frame, 33);
}
}
else {
if (animationTimeout != null) {
// If the animation is running, stop it by
// canceling the next scheduled call to frame().
clearTimeout(animationTimeout);
}
animationTimeout = null; // Indicates that animation is off.
}
}
//----------------------- initialization -------------------------------
function init() {
try {
canvas = document.getElementById("theCanvas");
if(typeof G_vmlCanvasManager != 'undefined') {
canvas = G_vmlCanvasManager.initElement(canvas);
}
graphics = canvas.getContext("2d");
}
catch (e) {
document.getElementById("message").innerHTML =
"Sorry, this page requires canvas graphics, but<br>" +
"it looks like your browser does not support it<br>" +
"Reported error: " + e;
return;
}
// add any other necessary initialization
document.getElementById("animateCheck").checked = true; // Make sure box is checked!
loadImage("dog-walking11");
createWorld();
setAnimationRunning(true); // start the animation
}
</script>
</head>
<body onload="init()" style="background-color: rgb(220,220,220)">
<div id="messagediv">
<h2>Hierarchical Modeling Example</h2>
<!-- For error reporting: the contents of the noscript tag are
shown only if JavaScript is not available. The paragraph with
id="message" is for reporting errors using JavaScript.-->
<noscript><b>This page requires JavaScript, which is<br>
not enabled in your browser.</b></noscript>
<p id="message" style="color:red"></p>
<p><input type="checkbox" id="animateCheck" onchange="setAnimationRunning(this.checked)">
<label for="animateCheck">Run Animation</label>
</p>
</div>
<div id="canvasdiv">
<canvas id="theCanvas" width= "400" height= "300"
style="background-color: transparent"></canvas>
</div>
</body>
</html>
It seems that I had to use graphics.drawImage() after using graphics.restore().
Though I was trying to draw the image in the correct order (after), compared to drawing the rectangles, circles etc, the shapes did not come out of the buffer onto the page until after the restore function().
When I was calling drawImage, I assumed that it was loading it onto the buffered canvas with the rest of the things, ready to be drawn in the correct order, when in fact it was putting it straight onto the page.
It seems strange since it was being called with the graphics context, so I thought it would be added to the rest of the canvas graphics, but i appear to be wrong.
I'm trying to blend two ImageData objects into a single object in order to obtain result similar to the pictures shown in this link
The following is the Javascript code that has the two ImageData
var redImage = copy.getImageData((SCREEN_WIDTH - VIDEO_WIDTH)/2,(SCREEN_HEIGHT - VIDEO_HEIGHT)/2,VIDEO_WIDTH,VIDEO_HEIGHT);
var bluImage = copy.getImageData((SCREEN_WIDTH - VIDEO_WIDTH)/2,(SCREEN_HEIGHT - VIDEO_HEIGHT)/2,VIDEO_WIDTH,VIDEO_HEIGHT);
var redData = redImage.data;
var blueData = blueImage.data;
// Colorize red
for(var i = 0; i < redData.length; i+=4) {
redData[i] -= (redData[i] - 255);
}
redImage.data = redData;
// Draw the pixels onto the visible canvas
disp.putImageData(redImage,(SCREEN_WIDTH - VIDEO_WIDTH)/2 - 25,(SCREEN_HEIGHT - VIDEO_HEIGHT)/2);
// Colorize cyan
for(var i = 1; i < blueData.length; i+=4) {
blueData[i] -= (blueData[i] - 255);
blueData[i+1] -= (blueData[i+1] - 255);
}
blueImage.data = blueData;
// Draw the pixels onto the visible canvas
disp.putImageData(blueImage,(SCREEN_WIDTH - VIDEO_WIDTH)/2 + 25,(SCREEN_HEIGHT - VIDEO_HEIGHT)/2);
How do i merge/blend the redData and blueData before putting it on the canvas ?
The formula you can use to mix two images is fairly simple:
newPixel = imageMainPixel * mixFactor + imageSecPixel * (1 - mixFactor)
Example assuming both buffers are of equal length:
var mixFactor = 0.5; //main image is dominant
//we're using the red buffer as main buffer for this example
for(var i = 0; i < redData.length; i+=4) {
redData[i] = redData[i] * mixFactor + blueData[i] * (1 - mixFactor);
redData[i+1] = redData[i+1] * mixFactor + blueData[i+1] * (1 - mixFactor);
redData[i+2] = redData[i+2] * mixFactor + blueData[i+2] * (1 - mixFactor);
}
Now your red buffer contains the mixed image.
To add an offset you can simply redraw the images with an offset value, for example:
var offset = 20; //pixels
copy.drawImage(originalImage, -offset, 0); // <--
var redImage = copy.getImageData( /*...*/ );
copy.drawImage(originalImage, offset, 0); // -->
var bluImage = copy.getImageData( /*...*/ );
If you have not onlyImageDataobjects, but also sourcecanvaselements, you can use this method.
You can obtain base64-encoded image data by callingtoDataURLcanvas method. Then you can createImageelement from that data and then paste that image to destination canvas viadrawImage.
Example code:
function mergeImageData(callback, sources) {
var canvas = document.createElement('canvas'),
context,
images = Array.prototype.slice.call(arguments, 1).map(function(canvas) {
var img = new Image();
img.onload = onLoad;
img.src = canvas.toDataURL();
return img;
}
),
imgCounter = 0,
widths = [],
heights = [];
function onLoad() {
widths.push(this.width);
heights.push(this.height);
if (++imgCounter == images.length) {
merge();
};
};
function merge() {
canvas.width = Math.max.apply(null, widths);
canvas.height = Math.max.apply(null, heights);
context = canvas.getContext('2d');
images.forEach(function(img) {
context.drawImage(img, 0, 0, img.width, img.height);
}
);
callback(context.getImageData(0, 0, canvas.width, canvas.height));
};
};
what about functions of setting the transmission format 3d - from format full side by side to anaglyph, alternating rows, alternating columns, chessboard, original side by side and 2d from 3d ?
I have a small Box2D thing (using box2dweb.js), but despite setting gravity to (0,0), and no forces/impulse being imparted on any objects, the only dynamic shape I have in the scene moves when I start the draw loop. I have no idea why O_O
Would anyone know why http://pomax.nihongoresources.com/downloads/temp/box2d/physics.html has the "ball" moving after hitting start?
The relevant bits of code are:
// shortcut aliasses
var d = Box2D.Dynamics,
v = Box2D.Common.Math,
s = Box2D.Collision.Shapes;
var ball,
gravity = new v.b2Vec2(0,0);
world = new d.b2World(gravity, true);
// setup the world box
var setupWorldBox = function(worldbox) {
var fixDef = new d.b2FixtureDef;
fixDef.density = 0;
fixDef.friction = 0;
fixDef.restitution = 0;
var bodyDef = new d.b2BodyDef;
bodyDef.type = d.b2Body.b2_staticBody;
bodyDef.position.x = worldbox.width/2;
bodyDef.position.y = worldbox.height/2;
fixDef.shape = new s.b2PolygonShape;
fixDef.shape.SetAsBox(worldbox.width/2, worldbox.height/2);
world.CreateBody(bodyDef).CreateFixture(fixDef);
}
// draw loop
var drawFrame = function() {
world.Step(1/60,10,10);
world.ClearForces();
ball.update(); // only updates the ball's DOM element position
requestAnimFrame(drawFrame);
};
// start the game
function start() {
var worldParent = document.querySelector("#world");
setupWorldBox(worldParent.getBoundingClientRect());
ball = new Ball(worldParent, document.querySelector(".ball"), d,v,s, world);
drawFrame();
}
For the main body, and the following code for defining the "ball":
var Ball = function(gamediv, element, d,v,s, world) {
var pbbox = gamediv.getBoundingClientRect();
var bbox = element.getBoundingClientRect();
this.el = element;
this.width = bbox.width;
this.height = bbox.height;
var bodyDef = new d.b2BodyDef;
bodyDef.type = d.b2Body.b2_dynamicBody;
var fixDef = new d.b2FixtureDef;
fixDef.shape = new s.b2PolygonShape;
fixDef.shape.SetAsBox(bbox.width/2, bbox.height/2);
bodyDef.position.x = bbox.left - pbbox.left;
bodyDef.position.y = bbox.top - pbbox.top;
this.b2 = world.CreateBody(bodyDef);
this.b2.CreateFixture(fixDef);
};
Ball.prototype = {
el: null,
b2: null,
width: 0, height: 0,
// Box2D position for the ball
center: function() { return this.b2.GetWorldCenter(); },
// update the DOM element based on Box2D position
update: function() {
var c = this.center();
this.el.style.left = c.x + "px";
this.el.style.top = c.y + "px";
}
};
Ball.prototype.constructor = Ball;
Neither of these bits of code introduces forces, as far as I can tell, so if anyone knows why the coordinates for the ball change anyway, please let me know, so I can turn this into something useful instead of something confusing =)
It turns out my code was creating a solid object as game world, which meant Box2D was trying to perform collision resolution because the "ball" was located inside another solid object.
The solution (based on http://box2d-js.sourceforge.net but with box2dweb API calls) was this:
// setup the world box
var setupWorldBox = function(worldbox) {
var worldAABB = new Box2D.Collision.b2AABB;
worldAABB.lowerBound.Set(0,0);
worldAABB.upperBound.Set(worldbox.width, worldbox.height);
var gravity = new b2Vec2(0, 0);
var doSleep = true;
var world = new b2World(worldAABB, gravity, doSleep);
[....]
}