Related
When i use the "w" key to move my object and then pres the "x" key to rotate it, my object rotates around the world origin rather than its own center. How do I update the objects pivot after the move? I've seen a few questions and answers on this where its stated to use geometry.center(), however, this doesn't work for me.
var tMatrix = new THREE.Matrix4()
var radian = 5*Math.PI/180
function keyDown(event){
if(event.key == 'x'){
r5 = Math.cos(radian);
r6 = -Math.sin(radian);
r8 = Math.sin(radian);
r9 = Math.cos(radian);
tMatrix.set( 1, 0, 0, 0,
0, r5, r6, 0,
0, r8, r9, 0,
0, 0, 0, 1 );
mesh.applyMatrix4(tMatrix);
}
if(event.key == 'w'){
tMatrix.set( 1, 0, 0, 0,
0, 1, 0, 1,
0, 0, 1, 0,
0, 0, 0, 1 );
mesh.applyMatrix4(tMatrix);
}
}
var pivot = new THREE.Object3D();
let loader = new THREE.STLLoader();
var material = new THREE.MeshLambertMaterial({color: 0x818181});
loader.load('STL/test.stl', function (geometry) {
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});
window.addEventListener("keypress", keyDown);
Okay, I solved the problem by just getting the position of the object before I rotate it, and then set the position to its original after the new matrix has been set.
if(event.key == 'x'){
x = mesh.position.x;
y = mesh.position.y;
z = mesh.position.z;
r5 = Math.cos(radian);
r6 = -Math.sin(radian);
r8 = Math.sin(radian);
r9 = Math.cos(radian);
tMatrix.set( 1, 0, 0, 0,
0, r5, r6, 0,
0, r8, r9, 0,
0, 0, 0, 1 );
mesh.applyMatrix4(tMatrix);
mesh.position.set(x,y,z);
}
A 5 by 5 pixel image data is something like this in linearized imagedata array-
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 255, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
So, the 3x3 pixel data is- 0 0 0 255. How can I get the adjacent pixel positions? Left and right adjacent ones are easy, just minus 4 and plus 4 respectively.
Accessing pixel data
The pixel data from .getImageData().data is a TypedArray of type Uint8ClampedArray. When reading the values they will be in the range 0-255 and in the order Red, Green, Blue, Alpha. If the value of alpha is zero then red, green, and blue will also be zero.
To get the index of a pixel
const imageData = ctx.getImageData(0,0,ctx.canvas.width, ctx.canvas.height);
var index = (x + y * imageData.width) * 4;
const red = imageData.data[index];
const green = imageData.data[index + 1];
const blue = imageData.data[index + 2];
const alpha = imageData.data[index + 3];
To move down one pixel
index += imageData.width * 4;
To move up one
index -= imageData.width * 4;
To move left.
index -= 4;
To move right
index += 4;
If you are on the left or right edge and you move in the direction of the edge you will wrap around, on the line above and to the right if moving left and the line below and on the left if moving down.
When setting the image data the values will be floored and clamped to 0-255
imageData.data[index] = 29.5
console.log(imageData.data[index]); // 29
imageData.data[index] = -283
console.log(imageData.data[index]); // 0
imageData.data[index] = 283
console.log(imageData.data[index]); // 255
If you set an index that is outside the array size it will be ignored
imageData.data[-100] = 255;
console.log(imageData.data[-100]); // Undefined
imageData.data[imageData.data.length + 4] = 255;
console.log(imageData.data[imageData.data.length + 4]); // Undefined
You can speed up access and processing by using different array types. For example all of a pixel's channels as one value using Uint32Array
const imageData = ctx.getImageData(0,0,ctx.canvas.width, ctx.canvas.height);
const pixels = new Uint32Array(imageData.data.buffer);
var index32 = x + y * imageData.width; // note there is no 4*;
const pixel = pixels[index32];
The channels are stored in bits 31-24 Alpha, 23-16 Blue, 15-8 Green, 7-0 Red.
You can set a pixel using a hex value
pixels[x + y * imageData.width] = 0xFF0000FF; // red
pixels[x + y * imageData.width] = 0xFF00FF00; // Green
pixels[x + y * imageData.width] = 0xFFFF0000; // Blue
pixels[x + y * imageData.width] = 0xFF000000; // Black
pixels[x + y * imageData.width] = 0xFFFFFFFF; // White
pixels[x + y * imageData.width] = 0; // Transparent
You can set all the pixels in a single call
pixels.fill(0xFF000000); // all pixels black
You can copy array data onto the array with
// set 3 pixels in a row at x,y Red, Yellow, White
pixels.set([0xFF0000FF,0xFF00FFFF,0xFFFFFFFF], x+y * imageData.width);
Warning
If the canvas has any pixel/s that are from an untrusted source it will be tainted and you will not be able to read the pixel data. Trusted sources are same domain or images served with the appropriate CORS header information. Images that are on the file system can not have their pixels accessed. Once a canvas is tainted it can not be cleaned.
A tainted canvas will throw an error when you call ctx.getImageData(0,0,1,1,) MDN does not list this exception for some reason. You will see "SecurityError" DOMException; in the DevTools console and there are plenty of answered question here in StackOverflow on the subject.
You could calculate the index with the width of the matrix and the length of one unit of 4.
The access is zero based.
function getPos(array, x, y, width) {
var p = 4 * (x + y * width);
return array.slice(p, p + 4);
}
var array = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 255, 0, 0, 0, 240, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 241, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];
// element above
console.log(JSON.stringify(getPos(array, 2, 1, 5))); // [0, 0, 0, 240]
// actual element
console.log(JSON.stringify(getPos(array, 2, 2, 5))); // [0, 0, 0, 255]
// element below
console.log(JSON.stringify(getPos(array, 2, 3, 5))); // [0, 0, 0, 241]
I'm very much an amateur enthusiast trying to make a basic 2d game map with html canvas. I've done this before by using arrays to create div/img tags and position them. I'm now trying to do this with canvas, not with images but simply drawing squares with fillRect().
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var map =
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 1, 1, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 1, 0, 0]
[1, 1, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0];
window.addEventListener("load", function()
{
update();
}, false);
function update()
{
window.requestAnimationFrame(update, canvas);
render();
}
function render()
{
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var row = 0; row < map.length; row++)
{
for(var column = 0; column < map[0].length; column++)
{
switch(map[row][column])
{
case 0:
ctx.fillStyle = #ffffff;
ctx.fillRect
(
row * 64, column * 64, 64, 64
);
break;
case 1:
ctx.fillStyle = #009900;
ctx.fillRect
(
row * 64, column * 64, 64, 64
);
break;
}
}
}
}
I'm getting the error: 'Uncaught SyntaxError: Unexpected token ILLEGAL' on line 46 which is 'ctx.fillStyle = #ffffff;'.
I'm a bit stuck as to why it's giving this error, I'm wondering if you can't use the context methods in this way with an array but I can't understand why.
If anyone has any advice, I would be very grateful.
I have a spinning 3D letter "F". For some reason, the "F" is not drawn in the top 20% of the canvas. See this jsFiddle: http://jsfiddle.net/c948jos0/.
The render function:
var rX = 0;
var rY = 0;
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
gl.canvas.width = 400;
gl.canvas.height = 200;
gl.clearColor(0.5,0.5,0.5,1.0);
function render(){
rX += Math.PI/64;
rY += Math.PI/128;
var projection = make2DProjection(gl.canvas.clientWidth,gl.canvas.clientHeight,400);
var translation = makeTranslation(200,50,0);
var rotX = makeXRotation(rX);
var rotY = makeYRotation(rY);
var mat = matrixMultiply(rotX,rotY);
mat = matrixMultiply(mat,translation);
mat = matrixMultiply(mat,projection);
uniforms["u_matrix"](new Float32Array(mat));
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES,0, 16*6);
setTimeout(render,33);
}
render();
The matrices:
function makeTranslation(tx, ty, tz) {
return [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
tx, ty, tz, 1
];
}
function makeXRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1
];
}
function makeYRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1
];
}
function make2DProjection(width, height, depth) {
// Note: This matrix flips the Y axis so 0 is at the top.
return [
2 / width, 0, 0, 0,
0, -2 / height, 0, 0,
0, 0, 2 / depth, 0,
-1, 1, 0, 1
];
}
I have been following http://webglfundamentals.org/webgl/lessons/webgl-3d-orthographic.html so I figure I must have changed something I shouldn't have or made a typo. The problem is I cant figure out what. I think its the make2DProjection matrix not setting the origin to the top left corner of canvas, but the same code is working in the tutorial so I dont know.
The canvas element is initialized with 300x150 dimensions and so is your viewport.
When changing the canvas size after you acquired the webgl context you also need to set the viewport using
gl.viewport(x, y, width, height)
I've adjusted your fiddle and added a second canvas with an outline so you can see the difference.
Unless you have a specific need to only cover part of the canvas a recommended way to do this is.
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
so far ive got:
var Items = new Array(5); // this array contains the items
BodyPaint = new Array(1, 0, 1, 0, 0, 0, 0, 0, 0); // this array gives stats for the item
WolfFur = new Array(3, 0, 1, 0, 0, 0, 0, 0, 0); // ^^
BearFur = new Array(4, 0, 1, 0, 0, 0, 0, 0, 0); // ^^
WolfSkin = new Array(6, 0, 0, 0, 0, 0, 0, 0, 0); // ^^
BearSkin = new Array(7, 1, 0, 0, 0, 0, 0, 0, 0); // ^^
would this array work? im relatively new to programing and my friend suggested this is how i should do it, but i looked around and everywhere else recommends a more complex way of doing it that might make sense were they to explain it.
if this array would work please tell me how i would access values in the second array
so far to access the 1st stat for wolf fur ive got this:
var example = Items.2.1
would this (^^) work?
and if it wouldnt work, please tell me how to do it using the above names in the example and explaining why you do everything you do and what it does.
thx.
Im now using an object, ive got:
var Items = {
BodyPaint : new Array(1, 0, 1, 0, 0, 0, 0, 0, 0),
FurCloak : new Array(3, 0, 1, 0, 0, 0, 0, 0, 0),
WolfSkin : new Array(5, 0, 0, 0, 0, 0, 0, 0, 0)};
there are more arrays but i ended it here to save space
i get the following errors:
Unknown identifier: BodyPaint
Unknown identifier: FurCloak
Unknown identifier: WolfSkin
and for all of my other arrays
if i replace a name with a number the error goes but it cant be good for the code and i dont really want my items to be refered to as numbers here, i wont b able 2 know which is which
No. It's that's not how you get values from an array.
Items[2][1] would work if your array were something like this;
var BodyPaint = new Array(1, 0, 1, 0, 0, 0, 0, 0, 0); // this array gives stats for the item
var WolfFur = new Array(3, 0, 1, 0, 0, 0, 0, 0, 0); // ^^
var BearFur = new Array(4, 0, 1, 0, 0, 0, 0, 0, 0); // ^^
var WolfSkin = new Array(6, 0, 0, 0, 0, 0, 0, 0, 0); // ^^
var BearSkin = new Array(7, 1, 0, 0, 0, 0, 0, 0, 0); // ^^
Items = [BodyPaint, WolfFur, BearFur, WolfSkin, BearSkin];
Items[2][1] would return 0.
Edit: You might be interested in using an object too.
var Items = {
BodyPaint: new Array(1, 0, 1, 0, 0, 0, 0, 0, 0),
WolfFur: new Array(3, 0, 1, 0, 0, 0, 0, 0, 0),
BearFur: new Array(4, 0, 1, 0, 0, 0, 0, 0, 0),
WolfSkin: new Array(6, 0, 0, 0, 0, 0, 0, 0, 0),
BearSkin: new Array(7, 1, 0, 0, 0, 0, 0, 0, 0)
}
This would allow you to write some code like this:
Items.BearFur[1] would return 0 and is the same as Items[2][1] above.
You only have multiple 1D arrays, you should combine them into 2D, like :
var Items = new Array
BodyPaint = new Array(1, 0, 1, 0, 0, 0, 0, 0, 0);
WolfFur = new Array(3, 0, 1, 0, 0, 0, 0, 0, 0);
BearFur = new Array(4, 0, 1, 0, 0, 0, 0, 0, 0);
WolfSkin = new Array(6, 0, 0, 0, 0, 0, 0, 0, 0);
BearSkin = new Array(7, 1, 0, 0, 0, 0, 0, 0, 0);
Items[0] = BodyPaint;
alert(Items[0][2]);
In that case you have 2D, and with the alert example you can access the items
It would not work because you aren't linking Items in any way to any of the other arrays. You would have to write:
var BodyPaint = new Array(1, 0, 1, 0, 0, 0, 0, 0, 0);
var WolfFur = new Array(3, 0, 1, 0, 0, 0, 0, 0, 0);
var BearFur = new Array(4, 0, 1, 0, 0, 0, 0, 0, 0);
var WolfSkin = new Array(6, 0, 0, 0, 0, 0, 0, 0, 0);
var BearSkin = new Array(7, 1, 0, 0, 0, 0, 0, 0, 0);
var Items = new Array(BodyPaint, WolfFur, BearFur, WolfSkin, BearSkin);
Then this would work:
var example = Items[2][1];
I would recommend that you use a JavaScript Object instead of arrays. In JSON, this would be:
var Items = {
BodyPaint : Array(1, 0, 1, 0, 0, 0, 0, 0, 0),
WolfFur : new Array(3, 0, 1, 0, 0, 0, 0, 0, 0),
BearFur : new Array(4, 0, 1, 0, 0, 0, 0, 0, 0),
WolfSkin : new Array(6, 0, 0, 0, 0, 0, 0, 0, 0),
BearSkin : new Array(7, 1, 0, 0, 0, 0, 0, 0, 0)};
You can then add new items like this:
Items.AnotherItem = new Array(8, 0, 0, 0, 0, 0, 0, 0, 0);
Or:
Items['AnotherItem'] = new Array(8, 0, 0, 0, 0, 0, 0, 0, 0);
And get a property like:
var example = Items.WolfSkin[5];
JavaScript Objects are very flexible an I recommend you take some time to really get to know them.