Recently I've been working on creating a 3D graphics engine in Javascript. Currently, the camera can move in the x, y, and z axis (working on learning how to rotate the camera) and shapes can be rotated. Shapes are generated based on groups of vertices that make up a face.
An example of the drawing code for the shapes is as below:
function renderShapes() {
for (i = 0; i < objects.length; i++) { // For each object
for (j = 0; j < objects[i].faces.length; j++) { // For each face
var face = objects[i].faces[j];
var P = project(face[0],objects[i].type);
if (P == false) { // project the coordinates based on camera.
continue;
}
ctx.beginPath();
ctx.moveTo(P.x + cx, - P.y + cy); // Start line at first vertex
// Draw the other vertices that make up the face
for (l = 0; l < face.length; l++) {
P = project(face[l],objects[i].type);
if (P == false) {
continue;
}
ctx.lineTo(P.x + cx, -P.y + cy); // Draw line to the next vertex
}
// Close the path and draw the face
ctx.closePath();
ctx.stroke();
if (objects[i].color != undefined) { // Fill in a color if the object has color property
ctx.fillStyle = objects[i].color;
ctx.fill();
}
}
}
}
This above code, generates something roughly like this depending on how I rotate it:
Everything works perfectly, except that all faces are drawn, and based on their order in the object array, may overlap one or the other. The red cube is an example of my problem. The blue diamond is an example of what I want to achieve, clean faces that are only rendered based on what the camera can see. Instead of rendering all 8 faces of the diamond, it should only render the 4 visible ones. Instead of rendering all 6 faces of the cube, it should only render the 2 visible ones.
I have researched into it to find back-face culling, although the examples were either non-existent, in the wrong language, or simply hard to understand or fit into my case.
Can anyone explain to me, in layman's terms how back-face culling works? And if possible, a Javascript example of it that could be implemented into my code?
Alternatively, is there a different word or phrase for what I'm trying to achieve or a different process or algorithm that would be better suited?
Thanks
Related
i am making a virtual Hama beads tool online. (its a kids toy where you put plastic beads on a matrix and iron them to make them stick together and form a solid shape)like this one below (real hama)
So far i managed to make this work (you can try it out there : https://static.club1.fr/alixturcq/hama%20dev/ ) but i would like to make it more elaborate and have the beads on the edges more realistic with round corners where there are no neighbors (virtual hama)
This would mean having a special image for each of theses neighbor cases like this (neighbor_cases)
So each bead is an object, and i have added a "neighbor" parameter to display the correct image.
Every object is automatically placed on the matrix at start but "isOn" is false, so nothing is displayed. When I click on the matrix, I change the 'isOn' boolean to true and the hama bead displays.
The objects are stored in a 1D array called hamas[], but are being displayed as a 2D array, so i can manipulate it as 2D.
class hama {
constructor(isOn, posx, posy, color, neighbor) {
this.posx = posx;
this.posy = posy;
this.color = color;
this.isOn = isOn;
this.neighbor = neighbor;
this.variation = int(random(0,6));
map
}
display() {
if (this.isOn == true) {
if (ironed == true) {
image(hamas_img[this.color][this.neighbor], this.posx, this.posy, CELL_SIZE, CELL_SIZE);
}
else {
image(ironed_hamas_img[this.color][this.neighbor], this.posx, this.posy, CELL_SIZE, CELL_SIZE);
}
}
}
}
Every time I place a bead i need the program to check every objects if they are On, and also what is the 'isOn' state of each of the neighbors in the matrix.
So far I've done this in pseudo-code but I hope there are smarter ways.
// creating an array of neighbor indexes (clockwise around the object)
checkAround[(+1); (width+1); (width); (width-1); (-1); (-width-1); (-width); (-width+1)]
// creating an empty string that is going to be the neighbor code
neighborCode= "";
// check around object to get neighborstate
for (int i=0; i<checkAround.count; i++){
if (hammas[hammaIndex+checkAround[i]].isOn==true){
neighborCode += "O"
}
else {
neighborCode += "X"
}
}
Then I get neighbourCode strings looking like "XOOXOXOO" and map all these code to appropriate neighbour state number and display the correct image.
So this is rather twisted, I was hoping someone could help me find a smarter solution !
And also I have to find a way to avoid checking non existing indexes on the edges of the matrix, but this should not be to hard to find.
Thanks in advance
Al
What you are looking for is edge detection on a pixel array and has been solved many times before. You can solve it yourself and it's fun to figure out yourself.
When a problem exists in 2D space, it often helps to think in 2D.
But your data is structured in 1D (which is fine, and the convention)
Each pixel/bead has a position in the 1D array -let's call that i- and a position in 2D space, defined by x and y.
The relationship between those is like this: i = x + y * w
( Where w is the width of the image)
With this information you can traverse the 1D array through x and y quite easily and decide for each pixel what its neighbours are doing.
example:
for(var x = 0; x < width; x++){
for(var y = 0; y < height; y++){
var i = x + y * width; // id of the current pixel being checked.
// Calculate neighbour ids:
var iNorth = x + (y - 1) * width;
var iSouth = x + (y + 1) * width;
var iEast = x + 1 + y * width;
var iWest = x - 1 + y * width;
var iNorthWest = (x - 1) + (y - 1) * width; // haha Kanye
// etc.. you get the point
// Now encode all those vars in one single byte
// Use that byte to choose the right image for the bead at position I
// Don't forget to deal with your edge cases
}
}
You can optimize later. (eg. You don't have to update the whole image each time you change one pixel, only its neighbors are affected.)
Your "OXOOXOOO" string is a perfectly acceptable way of storing the different states of a bead. A more nerdy approach would be to just store bits in a 8 bit byte.(because each pixel has only 8 neighbours..)
Those bytes can be stored in a buffer with the same exact structure as your actual pixel data.
I think this should get you started without spoiling too much of the fun?
I'm trying to create a hexagonal map with the list of terrain types.
At the moment I have the map that is drawn from the sprites that uses as a base texture the graphics with the shape of a hexagon.
I need to put a different images on them, but can't find a solution how to do it.
Here's the demo of what I have: https://codepen.io/cuddlemeister/pen/rPvwZw
I've tryied to do it in this way:
const texture = PIXI.Texture.fromImage(img);
const s = new PIXI.Sprite(texture);
s.mask = graphics;
But I get only one hexagon that mask being applyied to. And if I put graphics in a loop, I get performance issues.
Maybe I should just cut the images to get hexagons and simply draw sprites made from these images?
Here's what I want to achieve: http://i.imgur.com/xXLTK.jpg
Basically, I need to replace that white hexagons with some textures. How can I get this?
how about that:
https://jsfiddle.net/vxt5eqk4/
for each tile you use a clipmask
var scale = 8;
for (y = 0; y < 10; y++) {
for (x = 0; x < 10; x++) {
var offsetx = (y%2)*5 + x*10 - 6;
var offsety = y * 9 -3;
ctx.save();
ctx.beginPath();
ctx.moveTo(scale*(offsetx+5),scale*(offsety));
ctx.lineTo(scale*(offsetx+10),scale*(offsety+3));
ctx.lineTo(scale*(offsetx+10),scale*(offsety+9));
ctx.lineTo(scale*(offsetx+5),scale*(offsety+12));
ctx.lineTo(scale*offsetx,scale*(offsety+9));
ctx.lineTo(scale*offsetx,scale*(offsety+3));
ctx.closePath();
ctx.clip();
if((y%2 !== 0 || x%2 !== 0) && (y%2 === 0 || x%2 === 0)){
ctx.drawImage(img, scale*offsetx, scale*offsety, 10*scale, 12*scale);
}else{
ctx.drawImage(img2, scale*offsetx, scale*offsety, 10*scale, 12*scale);
}
ctx.restore();
}
a 10x12 grid seemed fine to me to draw a hexagon
the fiddle just shows a basic method to draw hexagonal tiles, the if part should be replaced by getting the proper image for the tile in the tilemap, should you use one
For my University project I am making a visual representation of data, specifically population. So far I have it so the top 50 countries are displayed as circles with the size being relative to the population amount. The position of the circles is completely random and because of this the circles cluster and the data unintelligible. What code do I have to add in order to make the circles not overlap. Thanks in advance and I apologise if I missed some details I am beginner in coding.
the code in its current state.
let population;
//The preload function is executed before initializing the code in setup
//Loads any related data or media files
function preload() {
population = loadJSON("worldpopulation.json");
}
function setup() {
console.log(population); //Return all JSON data
var canvas = createCanvas(1280, 720);
}
function draw(){
noLoop();
console.log(population[2].population / 1000000);
for (let i=0; i<50; i++){
let newSize = population[i].population / 3000000;
let newPosX = random(1280);
let newPosY = random(720);
var r = newSize;
fill(random(255), random(255), random(255));
ellipse(newPosX, newPosY, newSize, newSize);
fill(0);
textAlign(CENTER);
text(population[i].country, newPosX, newPosY-5);
text(population[i].population, newPosX, newPosY+5);
}
The simplest approach might be to keep an array of the rendered circles as you create them. Then only add valid (non-overlapping) ones to to the rendering layer.
A circle overlaps with another if the distance between their centers is less than the sum of their radii. In other words, only add Circle2 (with center (x2, y2) and radius r2) if it satisfies the following condition for all pre-existing circles (with centers (x1, y1) and radius r1).
( (x2-x1)^2 + (y2-y1)^2 ) ^ (1/2) > (r1 + r2)
If it doesn't, discard it and try generating another one.
i am making a webgl application i want to know is there any way of making objects(3D models made using blender) inside canvas clickable. So that when i click on them a pop up comes containing data.
I know (and have used) two major approaches.
The first one is to allocate a separate framebuffer and render interactive object to it with different colours. Then, upon a mouse event, you read a pixel corresponding to mouse position and find an object corresponding to the colour just read. For exapmle, it may look somewhat like this.
Textured and shaded scene:
Rendered for hit testing:
This approach is interesting due to it's simplicity. But it has some performance challenges and major ones among them are rendering the scene twice and reading pixel data back (its slow and synchronous). The first one was easy: just keep a dirty flag for the framebuffer and redraw it only upon a event and only if the flag is set (then of course reset it). The second one I've tackled by reading and caching from the framebuffer big chunks of pixels:
getPixel: function (x, y) {
var screenSize = this._screen.getCssSize();
x = x * HIT_TEST_BUFFER_SIZE[0] / screenSize[0] | 0;
y = y * HIT_TEST_BUFFER_SIZE[1] / screenSize[1] | 0;
var rx = x >> PIXEL_CACHE_BUCKET_IDX_SHIFT,
ry = y >> PIXEL_CACHE_BUCKET_IDX_SHIFT,
pixelCache = this._pixelCache,
bucket = pixelCache[[rx, ry]];
if (!bucket) {
this._framebuffer.bind();
bucket = pixelCache[[rx, ry]] = new Uint8Array(
4 * PIXEL_CACHE_BUCKET_SIZE[0] * PIXEL_CACHE_BUCKET_SIZE[1]
);
var gl = this._gl;
gl.readPixels(
rx << PIXEL_CACHE_BUCKET_IDX_SHIFT,
ry << PIXEL_CACHE_BUCKET_IDX_SHIFT,
PIXEL_CACHE_BUCKET_SIZE[0],
PIXEL_CACHE_BUCKET_SIZE[1],
gl.RGBA,
gl.UNSIGNED_BYTE,
bucket
);
this._framebuffer.unbind();
}
var bucketOffset = 4 * (
(y - ry * PIXEL_CACHE_BUCKET_SIZE[1]) * PIXEL_CACHE_BUCKET_SIZE[0] +
x - rx * PIXEL_CACHE_BUCKET_SIZE[0]
);
return bucket.subarray(bucketOffset, bucketOffset + 3);
}
The second major approach would be casting a ray to the scene. You take mouse position, construct a ray with it and cast it from a camera position into a scene to find which object it intersects with. That object would be the one mouse cursor pointing to. There is actually a decent implementation of that approach in Three.js, you can use it or take it as a reference to implement your own algorithm. The main challenge with that approach would be algorithmic complexity of searching an object the ray intersects with. It can be tackled with spacial indices built upon you scene.
Canvas is a simple graphics API. It draws pixels very well and nothing more. There are ways to 'fake' event handlers via mouse positions, but that takes more work. Basically you will register the location of mouse click, than match that up with the position of your 3D models to see if you have a match. You will not be able to attach event handlers directly to the 3d blender objects in canvas. In Scalable Vector Graphics (SVG) that would work fine. Just not in canvas.
function handleMouseDown(e) {
// tell the browser we'll handle this event
e.preventDefault();
e.stopPropagation();
// save the mouse position
// in case this becomes a drag operation
lastX = parseInt(e.clientX - offsetX);
lastY = parseInt(e.clientY - offsetY);
// hit all existing FrameControlPt of your blender objects
var hit = -1;
for (var i = 0; i < FrameControlPt.length; i++) {
var location = FrameControlPt[i];
var dx = lastX - location.x;
var dy = lastY - location.y;
if (dx * dx + dy * dy < stdRadius * stdRadius) {
hit = i;
}
}
// hit all existing buttons in the canvas
for (var i = 0; i < btn.length; i++) {
if ((lastX < (btn[i].x + btn[i].width)) &&
(lastX > btn[i].x) &&
(lastY < (btn[i].y + btn[i].height)) &&
(lastY > btn[i].y)) {
console.log("Button #" + (i + 1) + " has been clicked!!");
// execute button function
btn[i].action(); // execute a custom function
}
}
// if hit then set the isDown flag to start a drag
if (hit < 0) {
drawAll();
} else {
draggingCircle = FrameControlPt[hit];
isDown = true;
}
}
Obviously you'd have to handleMouseUp(event).. in this example, I was allowing the users to drag and drop elements within the canvas. You'd have to adjust your events to match your intended usage.
Code extract from this sample.
I'm trying implement A* Start path finding in my games(which are written with JavaScript, HTML5 Canvas). Library for A* Start found this - http://46dogs.blogspot.com/2009/10/star-pathroute-finding-javascript-code.html and now I'm using this library for path finding.
And with this library, I'm trying write a simple test, but stuck with one problem.
I'm now done when in HTML5 canvas screen click with mouse show path until my mouse.x and mouse.y. Here is a screenshot:
(Pink square: Player, Orange squares: path until my mouse.x/mouse.y)
Code how I'm drawing the orange squares until my mouse.x/mouse.y is:
for(var i = 0; i < path.length; i++) {
context.fillStyle = 'orange';
context.fillRect(path[i].x * 16, path[i].y * 16, 16, 16);
}
My problem is I do not understand how to move my player until path goal.
I've tried:
for(var i = 0; i < path.length; i++) {
player.x += path[i].x;
player.y += path[i].y;
}
But with this code my player is not beung drawn.(When I run the code, player.x and player.y are equals to 0 and when I click with the mouse I get the path player blink and disappear)
Maybe anyone know how to solve this problem?
And I'm very very very sorry for my bad English language. :)
My Working Fiddle
This is what I currently use which is based off of my a*. The concept should be the same though. The a* function should return the path as an array, then you just need to iterate through the path on each player update and move them.
// data holds the array of points returned by the a* alg, step is the current point you're on.
function movePlayer(data, step){
step++;
if(step >= data.length){
return false;
}
// set the player to the next point in the data array
playerObj.x = data[step].x;
playerObj.y = data[step].y;
// fill the rect that the player is on
ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect(playerObj.x*tileSize, playerObj.y*tileSize, tileSize, tileSize);
// do it again
setTimeout(function(){movePlayer(data,step)},10);
}