2D top-down minimap - javascript

I'd like to make a minimap of my rpg game.
Is making a minimap as simple as dividing all object dimensions, velocities, and coordinates by however large you want the minimap?
For example below... You have a size of 1000x1000px, a canvas (viewport) of 500x500px, the player is located in the center of the viewport... If you wanted a minimap half the size of the actual world, you would do:
Player/Viewport x,y velocity/2
Player/Viewport x,y coordinates/2
Canvas, world, and all objects' width and height are divided by 2
etc...
That way the rendering of the minimap on the world and the velocities are scaled accurately? Am I missing anything?
Thanks!
EDIT: Something like this?
function miniMap() {
$(".minimapHolder").show();
$("#mini_map").text("hide minimap");
var minicanvas = document.getElementById("miniMap");
ministage = new createjs.Stage("miniMap");
minicam = new createjs.Shape();
minicam.graphics.beginStroke("white").drawRoundRect(0, 0, 100, 40, 5);
//blip representation of Player
player_blip = new createjs.Shape();
player_blip.graphics.beginFill("yellow").drawRoundRect(0, 0, 11.2, 12, 1);
animal_blip = new createjs.Shape();
animal_blip.graphics.beginFill("red").drawRoundRect(0, 0, 24.4, 21.6, 1);
player_blip.x = players_Array[0].x/5;
player_blip.y = players_Array[0].y/5;
animal_blip.x = animalContainer.x/5;
animal_blip.y = animalContainer.y/5;
minicam.x = players_Array[0].x-110;
minicam.y = players_Array[0].y-110;
ministage.addChild(player_blip, animal_blip, minicam);
ministage.update();
}
function updateMiniMap() {
player_blip.x = players_Array[0].x/5;
player_blip.y = players_Array[0].y/5;
if (ContainerOfAnimals.children[0] != null) {
var pt = ContainerOfAnimals.localToGlobal(ContainerOfAnimals.children[0].x, ContainerOfAnimals.children[0].y);
console.log(pt.x);
animal_blip.x = pt.x/5;
animal_blip.y = pt.y/5;
} else {
ministage.removeChild(animal_blip);
}
minicam.x = player_blip.x-40;
minicam.y = player_blip.y-15;
ministage.update();
}
Gives:

Short anwswer: "It will(most likely) work." ... but:
What you are trying to achieve is just scaling the stage/container, so you could also just use a copy of everything and put it into a container and scale it down to 0.5, but that is not the purpose of a minimap.
Objects of the minimap should only be a representation of the object in the 'real' world and should therefore not have any velocity ect.(that should especially not be updated separately from the 'real' world) - while your approach will probably work, you'd allways have to keep track and update every property, this will get messy quickly or even lead to differences if you miss some tiny things.
A more 'clean'(and simple) approach to this would be, that each minimap-object has a reference to the object in the 'real' world and on each tick, it just reads the x/y-coordinates and updates its' own coordinates based on the minimap-scale.
Another thing is the graphics: Scaling-operations can be costly(performance wise), especially when they are done each frame, so IF you use the same graphics for the minimap you should at least used a cached DisplayObject and not have the graphics scaled each frame.

Related

Rotating comet around planet

I'm trying to make a simple game. Tell me how to make object (comet) fly and rotate around orbit of another object (planet). I could only make the comet turn and fly toward the planet, but it is necessary that when a certain distance is reached, the comet starts to rotate around the planet’s orbit.
IMAGE
this.mesh.translateZ(this.speed);
if (this.target.position.distanceTo(this.mesh.position) >= 100) {
let targetQuaternion = new THREE.Quaternion();
let rotationMatrix = new THREE.Matrix4();
rotationMatrix.lookAt(this.mesh.position, this.target.position, this.mesh.up);
targetQuaternion.setFromRotationMatrix(rotationMatrix);
if (!this.mesh.quaternion.equals(targetQuaternion)) {
let step = 0.01;
this.mesh.quaternion.rotateTowards(targetQuaternion, step);
}
} else {
//how to make a comet(this.mesh) fly around the orbit of the planet(this.target)?
}
The simplest way to make one object orbit another is to utilize parent-child relationships. If you add a child to an object it shares a transformation, so when the parent is rotated the child is rotated with it - and when the child has a large translation offset, it effectively "orbits" the parent.
Very simple example of this:
init() {
this.planet = new Mesh(new SphereBufferGeometry(100))
this.satellite = new Mesh(new SphereBufferGeometry(5))
this.satelliteCenter = new Object3D()
this.satellite.position.set(1000,0,0)
this.satelliteCenter.add(this.satellite)
this.scene.add(this.planet)
this.scene.add(this.satelliteCenter)
}
update() {
this.satelliteCenter.rotateY(Math.PI / 2000) // one full revolution every 1000 frames
}

mouse position to isometric tile including height

Struggeling translating the position of the mouse to the location of the tiles in my grid. When it's all flat, the math looks like this:
this.position.x = Math.floor(((pos.y - 240) / 24) + ((pos.x - 320) / 48));
this.position.y = Math.floor(((pos.y - 240) / 24) - ((pos.x - 320) / 48));
where pos.x and pos.y are the position of the mouse, 240 and 320 are the offset, 24 and 48 the size of the tile. Position then contains the grid coordinate of the tile I'm hovering over. This works reasonably well on a flat surface.
Now I'm adding height, which the math does not take into account.
This grid is a 2D grid containing noise, that's being translated to height and tile type. Height is really just an adjustment to the 'Y' position of the tile, so it's possible for two tiles to be drawn in the same spot.
I don't know how to determine which tile I'm hovering over.
edit:
Made some headway... Before, I was depending on the mouseover event to calculate grid position. I just changed this to do the calculation in the draw loop itself, and check if the coordinates are within the limits of the tile currently being drawn. creates some overhead tho, not sure if I'm super happy with it but I'll confirm if it works.
edit 2018:
I have no answer, but since this ha[sd] an open bounty, help yourself to some code and a demo
The grid itself is, simplified;
let grid = [[10,15],[12,23]];
which leads to a drawing like:
for (var i = 0; i < grid.length; i++) {
for (var j = 0; j < grid[0].length; j++) {
let x = (j - i) * resourceWidth;
let y = ((i + j) * resourceHeight) + (grid[i][j] * -resourceHeight);
// the "+" bit is the adjustment for height according to perlin noise values
}
}
edit post-bounty:
See GIF. The accepted answer works. The delay is my fault, the screen doesn't update on mousemove (yet) and the frame rate is low-ish. It's clearly bringing back the right tile.
Source
Intresting task.
Lets try to simplify it - lets resolve this concrete case
Solution
Working version is here: https://github.com/amuzalevskiy/perlin-landscape (changes https://github.com/jorgt/perlin-landscape/pull/1 )
Explanation
First what came into mind is:
Just two steps:
find an vertical column, which matches some set of tiles
iterate tiles in set from bottom to top, checking if cursor is placed lower than top line
Step 1
We need two functions here:
Detects column:
function getColumn(mouseX, firstTileXShiftAtScreen, columnWidth) {
return (mouseX - firstTileXShiftAtScreen) / columnWidth;
}
Function which extracts an array of tiles which correspond to this column.
Rotate image 45 deg in mind. The red numbers are columnNo. 3 column is highlighted. X axis is horizontal
function tileExists(x, y, width, height) {
return x >= 0 & y >= 0 & x < width & y < height;
}
function getTilesInColumn(columnNo, width, height) {
let startTileX = 0, startTileY = 0;
let xShift = true;
for (let i = 0; i < columnNo; i++) {
if (tileExists(startTileX + 1, startTileY, width, height)) {
startTileX++;
} else {
if (xShift) {
xShift = false;
} else {
startTileY++;
}
}
}
let tilesInColumn = [];
while(tileExists(startTileX, startTileY, width, height)) {
tilesInColumn.push({x: startTileX, y: startTileY, isLeft: xShift});
if (xShift) {
startTileX--;
} else {
startTileY++;
}
xShift = !xShift;
}
return tilesInColumn;
}
Step 2
A list of tiles to check is ready. Now for each tile we need to find a top line. Also we have two types of tiles: left and right. We already stored this info during building matching tiles set.
function getTileYIncrementByTileZ(tileZ) {
// implement here
return 0;
}
function findExactTile(mouseX, mouseY, tilesInColumn, tiles2d,
firstTileXShiftAtScreen, firstTileYShiftAtScreenAt0Height,
tileWidth, tileHeight) {
// we built a set of tiles where bottom ones come first
// iterate tiles from bottom to top
for(var i = 0; i < tilesInColumn; i++) {
let tileInfo = tilesInColumn[i];
let lineAB = findABForTopLineOfTile(tileInfo.x, tileInfo.y, tiles2d[tileInfo.x][tileInfo.y],
tileInfo.isLeft, tileWidth, tileHeight);
if ((mouseY - firstTileYShiftAtScreenAt0Height) >
(mouseX - firstTileXShiftAtScreen)*lineAB.a + lineAB.b) {
// WOHOO !!!
return tileInfo;
}
}
}
function findABForTopLineOfTile(tileX, tileY, tileZ, isLeftTopLine, tileWidth, tileHeight) {
// find a top line ~~~ a,b
// y = a * x + b;
let a = tileWidth / tileHeight;
if (isLeftTopLine) {
a = -a;
}
let b = isLeftTopLine ?
tileY * 2 * tileHeight :
- (tileX + 1) * 2 * tileHeight;
b -= getTileYIncrementByTileZ(tileZ);
return {a: a, b: b};
}
Please don't judge me as I am not posting any code. I am just suggesting an algorithm that can solve it without high memory usage.
The Algorithm:
Actually to determine which tile is on mouse hover we don't need to check all the tiles. At first we think the surface is 2D and find which tile the mouse pointer goes over with the formula OP posted. This is the farthest probable tile mouse cursor can point at this cursor position.
This tile can receive mouse pointer if it's at 0 height, by checking it's current height we can verify if this is really at the height to receive pointer, we mark it and move forward.
Then we find the next probable tile which is closer to the screen by incrementing or decrementing x,y grid values depending on the cursor position.
Then we keep on moving forward in a zigzag fashion until we reach a tile which cannot receive pointer even if it is at it's maximum height.
When we reach this point the last tile found that were at a height to receive pointer is the tile that we are looking for.
In this case we only checked 8 tiles to determine which tile is currently receiving pointer. This is very memory efficient in comparison to checking all the tiles present in the grid and yields faster result.
One way to solve this would be to follow the ray that goes from the clicked pixel on the screen into the map. For that, just determine the camera position in relation to the map and the direction it is looking at:
const camPos = {x: -5, y: -5, z: -5}
const camDirection = { x: 1, y:1, z:1}
The next step is to get the touch Position in the 3D world. In this certain perspective that is quite simple:
const touchPos = {
x: camPos.x + touch.x / Math.sqrt(2),
y: camPos.y - touch.x / Math.sqrt(2),
z: camPos.z - touch.y / Math.sqrt(2)
};
Now you just need to follow the ray into the layer (scale the directions so that they are smaller than one of your tiles dimensions):
for(let delta = 0; delta < 100; delta++){
const x = touchPos.x + camDirection.x * delta;
const y = touchPos.y + camDirection.y * delta;
const z = touchPos.z + camDirection.z * delta;
Now just take the tile at xz and check if y is smaller than its height;
const absX = ~~( x / 24 );
const absZ = ~~( z / 24 );
if(tiles[absX][absZ].height >= y){
// hanfle the over event
}
I had same situation on a game. first I tried with mathematics, but when I found that the clients wants to change the map type every day, I changed the solution with some graphical solution and pass it to the designer of the team. I captured the mouse position by listening the SVG elements click.
the main graphic directly used to capture and translate the mouse position to my required pixel.
https://blog.lavrton.com/hit-region-detection-for-html5-canvas-and-how-to-listen-to-click-events-on-canvas-shapes-815034d7e9f8
https://code.sololearn.com/Wq2bwzSxSnjl/#html
Here is the grid input I would define for the sake of this discussion. The output should be some tile (coordinate_1, coordinate_2) based on visibility on the users screen of the mouse:
I can offer two solutions from different perspectives, but you will need to convert this back into your problem domain. The first methodology is based on coloring tiles and can be more useful if the map is changing dynamically. The second solution is based on drawing coordinate bounding boxes based on the fact that tiles closer to the viewer like (0, 0) can never be occluded by tiles behind it (1,1).
Approach 1: Transparently Colored Tiles
The first approach is based on drawing and elaborated on here. I must give the credit to #haldagan for a particularly beautiful solution. In summary it relies on drawing a perfectly opaque layer on top of the original canvas and coloring every tile with a different color. This top layer should be subject to the same height transformations as the underlying layer. When the mouse hovers over a particular layer you can detect the color through canvas and thus the tile itself. This is the solution I would probably go with and this seems to be a not so rare issue in computer visualization and graphics (finding positions in a 3d isometric world).
Approach 2: Finding the Bounding Tile
This is based on the conjecture that the "front" row can never be occluded by "back" rows behind it. Furthermore, "closer to the screen" tiles cannot be occluded by tiles "farther from the screen". To make precise the meaning of "front", "back", "closer to the screen" and "farther from the screen", take a look at the following:
.
Based on this principle the approach is to build a set of polygons for each tile. So firstly we determine the coordinates on the canvas of just box (0, 0) after height scaling. Note that the height scale operation is simply a trapezoid stretched vertically based on height.
Then we determine the coordinates on the canvas of boxes (1, 0), (0, 1), (1, 1) after height scaling (we would need to subtract anything from those polygons which overlap with the polygon (0, 0)).
Proceed to build each boxes bounding coordinates by subtracting any occlusions from polygons closer to the screen, to eventually get coordinates of polygons for all boxes.
With these coordinates and some care you can ultimately determine which tile is pointed to by a binary search style through overlapping polygons by searching through bottom rows up.
It also matters what else is on the screen. Maths attempts work if your tiles are pretty much uniform. However if you are displaying various objects and want the user to pick them, it is far easier to have a canvas-sized map of identifiers.
function poly(ctx){var a=arguments;ctx.beginPath();ctx.moveTo(a[1],a[2]);
for(var i=3;i<a.length;i+=2)ctx.lineTo(a[i],a[i+1]);ctx.closePath();ctx.fill();ctx.stroke();}
function circle(ctx,x,y,r){ctx.beginPath();ctx.arc(x,y,r,0,2*Math.PI);ctx.fill();ctx.stroke();}
function Tile(h,c,f){
var cnv=document.createElement("canvas");cnv.width=100;cnv.height=h;
var ctx=cnv.getContext("2d");ctx.lineWidth=3;ctx.lineStyle="black";
ctx.fillStyle=c;poly(ctx,2,h-50,50,h-75,98,h-50,50,h-25);
poly(ctx,50,h-25,2,h-50,2,h-25,50,h-2);
poly(ctx,50,h-25,98,h-50,98,h-25,50,h-2);
f(ctx);return ctx.getImageData(0,0,100,h);
}
function put(x,y,tile,image,id,map){
var iw=image.width,tw=tile.width,th=tile.height,bdat=image.data,fdat=tile.data;
for(var i=0;i<tw;i++)
for(var j=0;j<th;j++){
var ijtw4=(i+j*tw)*4,a=fdat[ijtw4+3];
if(a!==0){
var xiyjiw=x+i+(y+j)*iw;
for(var k=0;k<3;k++)bdat[xiyjiw*4+k]=(bdat[xiyjiw*4+k]*(255-a)+fdat[ijtw4+k]*a)/255;
bdat[xiyjiw*4+3]=255;
map[xiyjiw]=id;
}
}
}
var cleanimage;
var pickmap;
function startup(){
var water=Tile(77,"blue",function(){});
var field=Tile(77,"lime",function(){});
var tree=Tile(200,"lime",function(ctx){
ctx.fillStyle="brown";poly(ctx,50,50,70,150,30,150);
ctx.fillStyle="forestgreen";circle(ctx,60,40,30);circle(ctx,68,70,30);circle(ctx,32,60,30);
});
var sheep=Tile(200,"lime",function(ctx){
ctx.fillStyle="white";poly(ctx,25,155,25,100);poly(ctx,75,155,75,100);
circle(ctx,50,100,45);circle(ctx,50,80,30);
poly(ctx,40,70,35,80);poly(ctx,60,70,65,80);
});
var cnv=document.getElementById("scape");
cnv.width=500;cnv.height=400;
var ctx=cnv.getContext("2d");
cleanimage=ctx.getImageData(0,0,500,400);
pickmap=new Uint8Array(500*400);
var tiles=[water,field,tree,sheep];
var map=[[[0,0],[1,1],[1,1],[1,1],[1,1]],
[[0,0],[1,1],[1,2],[3,2],[1,1]],
[[0,0],[1,1],[2,2],[3,2],[1,1]],
[[0,0],[1,1],[1,1],[1,1],[1,1]],
[[0,0],[0,0],[0,0],[0,0],[0,0]]];
for(var x=0;x<5;x++)
for(var y=0;y<5;y++){
var desc=map[y][x],tile=tiles[desc[0]];
put(200+x*50-y*50,200+x*25+y*25-tile.height-desc[1]*20,
tile,cleanimage,x+1+(y+1)*10,pickmap);
}
ctx.putImageData(cleanimage,0,0);
}
var mx,my,pick;
function mmove(event){
mx=Math.round(event.offsetX);
my=Math.round(event.offsetY);
if(mx>=0 && my>=0 && mx<cleanimage.width && my<cleanimage.height && pick!==pickmap[mx+my*cleanimage.width])
requestAnimationFrame(redraw);
}
function redraw(){
pick=pickmap[mx+my*cleanimage.width];
document.getElementById("pick").innerHTML=pick;
var ctx=document.getElementById("scape").getContext("2d");
ctx.putImageData(cleanimage,0,0);
if(pick!==0){
var temp=ctx.getImageData(0,0,cleanimage.width,cleanimage.height);
for(var i=0;i<pickmap.length;i++)
if(pickmap[i]===pick)
temp.data[i*4]=255;
ctx.putImageData(temp,0,0);
}
}
startup(); // in place of body.onload
<div id="pick">Move around</div>
<canvas id="scape" onmousemove="mmove(event)"></canvas>
Here the "id" is a simple x+1+(y+1)*10 (so it is nice when displayed) and fits into a byte (Uint8Array), which could go up to 15x15 display grid already, and there are wider types available too.
(Tried to draw it small, and it looked ok on the snippet editor screen but apparently it is still too large here)
Computer graphics is fun, right?
This is a special case of the more standard computational geometry "point location problem". You could also express it as a nearest neighbour search.
To make this look like a point location problem you just need to express your tiles as non-overlapping polygons in a 2D plane. If you want to keep your shapes in a 3D space (e.g. with a z buffer) this becomes the related "ray casting problem".
One source of good geometry algorithms is W. Randolf Franklin's website and turf.js contains an implementation of his PNPOLY algorithm.
For this special case we can be even faster than the general algorithms by treating our prior knowledge about the shape of the tiles as a coarse R-tree (a type of spatial index).

Canvas getImageData() For optimal performance. To pull out all data or one at a time?

I need to scan through every pixel in a canvas image and do some fiddling with the colors etc. For optimal performance, should I grab all the data in one go and work on it through the array? Or should I call each pixel as I work on it.
So basically...
data = context.getImageData(x, y, height, width);
VS
data = context.getImageData(x, y, 1, 1); //in a loop height*width times.
You'll get much higher performances by grabbing the image all at once since :
a) a (contiguous) acces to an array is way faster than a function call.
b) especially when this function isa method of a DOM object having some overhead.
c) and there might be buffer refresh issues that might delay response (if canvas is
on sight... or not depending on double buffering implementation).
So go for a one-time grab.
I'll suggest you look into Javascript Typed Arrays to get the most of the
imageData result.
If i may quote myself, look at how you can handle pixels fast in this old post of mine
(look after 2) ):
Nice ellipse on a canvas?
(i quoted the relevant part below : )
You can get a UInt32Array view on your ImageData with :
var myGetImageData = myTempCanvas.getImageData(0,0,sizeX, sizeY);
var sourceBuffer32 = new Uint32Array(myGetImageData.data.buffer);
then sourceBuffer32[i] contains Red, Green, Blue, and transparency packed into one unsigned 32 bit int. Compare it to 0 to know if pixel is non-black ( != (0,0,0,0) )
OR you can be more precise with a Uint8Array view :
var myGetImageData = myTempCanvas.getImageData(0,0,sizeX, sizeY);
var sourceBuffer8 = new Uint8Array(myGetImageData.data.buffer);
If you deal only with shades of grey, then R=G=B, so watch for
sourceBuffer8[4*i]>Threshold
and you can set the i-th pixel to black in one time using the UInt32Array view :
sourceBuffer32[i]=0xff000000;
set to any color/alpha with :
sourceBuffer32[i]= (A<<24) | (B<<16) | (G<<8) | R ;
or just to any color :
sourceBuffer32[i]= 0xff000000 | (B<<16) | (G<<8) | R ;
(be sure R is rounded).
Listening to #Ken's comment, yes endianness can be an issue when you start fighting with bits 32 at a time.
Most computer are using little-endian, so RGBA becomes ABGR when dealing with them 32bits a once.
Since it is the vast majority of systems, if dealing with 32bit integer assume this is the case,
and you can -for compatibility- reverse your computation before writing the 32 bits results on Big endian systems.
Let me share those two functions :
function isLittleEndian() {
// from TooTallNate / endianness.js. https://gist.github.com/TooTallNate/4750953
var b = new ArrayBuffer(4);
var a = new Uint32Array(b);
var c = new Uint8Array(b);
a[0] = 0xdeadbeef;
if (c[0] == 0xef) { isLittleEndian = function() {return true }; return true; }
if (c[0] == 0xde) { isLittleEndian = function() {return false }; return false; }
throw new Error('unknown endianness');
}
function reverseUint32 (uint32) {
var s32 = new Uint32Array(4);
var s8 = new Uint8Array(s32.buffer);
var t32 = new Uint32Array(4);
var t8 = new Uint8Array(t32.buffer);
reverseUint32 = function (x) {
s32[0] = x;
t8[0] = s8[3];
t8[1] = s8[2];
t8[2] = s8[1];
t8[3] = s8[0];
return t32[0];
}
return reverseUint32(uint32);
};
Additionally to what GameAlchemist said, if you want to get or set all the colors of a pixel simultaneously, but you don't want to check endianness, you can use a DataView:
var data = context.getImageData(0, 0, canvas.width, canvas.height);
var view = new DataView(data.data.buffer);
// Read or set pixel (x,y) as #RRGGBBAA (big endian)
view.getUint32(4 * (x + y*canvas.width));
view.setUint32(4 * (x + y*canvas.width), 0xRRGGBBAA);
// Read or set pixel (x,y) as #AABBGGRR (little endian)
view.getUint32(4 * (x + y*canvas.width), true);
view.setUint32(4 * (x + y*canvas.width), 0xAABBGGRR, true);
// Save changes
ctx.putImageData(data, 0, 0);
It depends on what exactly you're doing, but I'd suggest grabbing it all at once, and then looping through it.
Grabbing it all at once is faster than grabbing it pixel by pixel, since searching through an array is a lot faster than searching through a canvas, once for each pixel.
If you're really in need of speed, look into web workers. You can set each one to grab a specific section of the canvas, and since they can run simultaneously, they'll make much better use out of your CPU.
getImageData() isn't really slow enough for you to notice the difference if you were to grab it all at once or individually, in my experiences using the function.

three.js - canvas texture on tube geometry for ipad

I've a 3d model of a tube geometry. There are 18000 co-ordinates on production side. I am taking every 9th co-ordinate so that actually plotting 9000 co-ordinates to build a tube geometry. I've to use CanvasRenderer only.
Now when I use vertexColors: THREE.VertexColors in WebGLRenderer, the model displays different color on each face. When I change it to CanvasRenderer, the model turns into white color only. Even I change vertexColors: THREE.FaceColors, the result is same.
Please find below the link of jsfiddle and link of my previous where mrdoob added support for material.vertexColors = THREE.FaceColors to CanvasRenderer.
support for vertex color in canvas rendering
tube in canvas rendering
Please find below the image to apply colors based on values.
As shown in the image there are 12 values at 12 different degrees for every co-ordinate. So I've created a tube with radius segment of 12. Then I've stored these values into JSON file but as there 18000 points, the file becomes to heavy. Even though I am plotting 2000 points it takes too much time. For 2000 segments and each segment has 12 faces, there are 24000 faces on a tube.
Please find below the programming logic to apply color based on value of a parameter.
// get res values & apply color
var lblSeg=0; var pntId; var d=0; var faceLength=tube.faces.length;
var degrees = [ '30', '60', '90', '120', '150', '180', '210', '240', '270', '300', '330' ];
var faces = tube.faces; var degreeCntr=0; var degreeProp;
//console.log(faces);
var res30=0,res60=0,res90=0,res120=0,res150=0,res180=0,res210=0,res240=0,res270=0,res300=0,res330=0;
var res; var resDegree; var pnt=0;
// fetching json data of resistivity values at different degree as //shown in the image
var result = getResValue();
for(var k=0; k<faceLength; k++){
resDegree = degrees[degreeCntr];
degreeProp = "r"+resDegree;
res = result.resistivity[pnt][degreeProp];
objects.push(result.resistivity[pnt]);
f = faces[k];
color = new THREE.Color( 0xffffff );
if(res<5){
color.setRGB( 197/255, 217/255, 241/255);
}
else if(res>=5 && res<50){
color.setRGB( 141/255, 180/255, 226/255);
}
else if(res>=50 && res<100){
color.setRGB( 83/255, 141/255, 213/255);
}
else if(res>=100 && res<200){
color.setRGB( 22, 54, 92);
}
else if(res>=200 && res<300){
color.setRGB( 15/255,36/255,62/255);
}
else if(res>=300 && res<400){
color.setRGB( 220/255, 230/255, 241/255);
}
else if(res>=400 && res<700){
color.setRGB( 184/255, 204/255, 228/255);
}
else if(res>=700 && res<1200){
color.setRGB( 149/255, 179/255, 215/255);
}
else if(res>=1200 && res<1500){
color.setRGB( 54/255, 96/255, 146/255);
}
else if(res>=1700 && res<1800){
color.setRGB( 36/255, 84/255, 98/255);
}
else if(res>1900){
color.setRGB( 128/255, 128/255, 128/255);
}
for(var j=0;j<4;j++)
{
tube.vertices.push(f.centroid);
vertexIndex = f[ faceIndices[ j ] ];
p = tube.vertices[ vertexIndex ];
f.vertexColors[ j ] = color;
}
degreeCntr++;
if(degreeCntr==10){
degreeCntr=0;
}
if(k%12==0 && k!=0){
pnt++;
}
}
This logic takes too much time to render the model and the model becomes too heavy and we can't perform other operations. The FPS on android drops at 2-3 FPS. Actually I've to render this model on iPad so have to use canvas renderer only.
So, how do I make this model lighter to load and works smoothly on iPad ? and is there any other way to apply colors on every face ? If canvas map as texture can be applied to make the model lighter, how do I build that map with all the colors based on value ?
Update:
After changing library version to r53, vertexColors: THREE.FaceColors and face.color.setRGB( Math.random(), Math.random(), Math.random()), the model displays random color for each face on canvas rendering.
So now the issue is applying colors as per requirements (either by canvas map or any feasible solution) and to make the model lighter to load it smoothly on iPad.
I believe this will give you a little bit better performance + if you could come up with some automated method of calculating colors for each angle offset, that you could set hex color directly:
for ( var i = 0; i < tube.faces.length; i ++ ) {
tube.faces[ i ].color.setHex( Math.random() * 0xffffff );
}
As I explained to you in the previous message - three.js - text next to line, using canvas textures will only increase load to you fps if you'll attempt to render so many faces.
If you really want to render 24,000 faces on canvas renderer and still hope that it gonna show up good on an iPad – you are out of your mind!))
Here is the only solution that I can think of for now:
1) Set your tube to only 1 segment.
2) Create 12 canvas elements (for every radius segment) with Width equal to your tube length (see my link above).
3) Now imagine that your 2000 segments you are going to create inside of each canvas. So, you divide your canvas length by 2000 and for every one of the portion of this division you set your calculated color!!! (Just like the Stats() FPS bar shows it’s bar, but you are going to have each bar different color).
4) Then you just apply your colored-bars-canvas-texture to each one of your 12 radius segments and you are good to go!!
This way you’ll only get initial page load (calculating 'em 24,000 colored-bars) and YOUR WHOLE TUBE ONLY GONNA BE 12 FACES!!!
Now, I know your next question is going to be: How I'll pick my faces to show my lines with tag text?
Well, very simple! Just take current face (1 of 12) pick position coordinates and translate them back to your JSON, just the same way you would do with 24,000 faces;)
Hope that helps!

HTML canvas double buffering frame-rate issues

I have a full-screen canvas with 3 images drawn on it. When I resize the window, these images change position; however, it appears to be very glitchy, more so in Firefox.
I've been reading that double-buffering should resolve this issue, but I'm wondering how I would double buffer when the next position is unknown. That is to say, I cannot determine what should be buffered in the future, so how would this be possible?
Here is one source that seems doable, but I do not fully understand the concept Fedor is trying to explain.
Does HTML5/Canvas Support Double Buffering?
So far I have,
$canvas = $('#myclouds')[0];
$canvas_buffer = $('canvas')[0].insertAfter($canvas).css('visibility', 'hidden');
context = $canvas.getContext('2d');
context_buffer = $canvas_buffer.getContext('2d');
clouds_arr = [$canvas, $canvas_buffer];
$(window).resize(function () {
drawCanvas();
};
function initCanvas() {
// Sources for cloud images
var cloud1 = '/js/application/home/images/cloud1.png',
cloud2 = '/js/application/home/images/cloud2.png',
cloud3 = '/js/application/home/images/cloud3.png';
// add clouds to be drawn
// parameters are as follows:
// image source, x, y, ratio, adjustment)
addCloud(cloud1, null, 125, .03);
addCloud(cloud2, null, 75, .15);
addCloud(cloud3, null, 50, .55);
addCloud(cloud1, null, 125, .97, 300);
addCloud(cloud2, null, 70, .85, 300);
addCloud(cloud3, null, 45, .5, 300);
// Draw the canvas
drawCanvas();
}
function drawCanvas() {
// Reset
$canvas.attr('height', $window.height()).attr('width', $window.width());
// draw the clouds
var l = clouds.length;
for (var i = 0; i < l; i++) {
clouds[i].x = ($window.width() * clouds[i].ratio) - clouds[i].offset;
drawimage(context, clouds[i]);
}
}
function Cloud() {
this.x = 0;
this.y = 0;
}
function addCloud(path, x, y, ratio, offset) {
var c = new Cloud;
c.x = x;
c.y = y;
c.path = path;
c.ratio = ratio || 0;
c.offset = offset || 0;
clouds.push(c);
}
function drawimage(ctx, image) {
var clouds_obj = new Image();
clouds_obj.src = image.path;
clouds_obj.onload = function() {
ctx.drawImage(clouds_obj, image.x, image.y);
};
}
I think maybe you are misunderstanding what double buffering is. Its a technique for smooth real-time rendering of graphics on a display.
The concept is you have two buffers. Only one is visible at any one time. When you go to draw the elements that make up a frame you draw them to the invisible buffer. In you case the clouds. Then you flip the buffers making the hidden one visible and the visible one hidden. Then on the next frame you draw to the now newly hidden buffer. Then at the end of drawing you flip back.
What this does is stop the user seeing partial rendering of elements before a frame is complete. On gaming systems this would also be synced up with the vertical refresh of the display to be really smooth and stop artefacts such as tearing to occur.
Looking at you code above you seem to have created the two canvas elements, but you're only using the first Context object. I assume this is incomplete as no flipping is taking place.
Its also worth noting that the window resize event can fire continuously when dragging which can cause frantic rendering. I usually create a timer on the resize event to actually re-render. This way the re-render only happens once the user stops resizing for a few milliseconds.
Also, your draw routine is creating new Image objects every time which you don't need to do. You can use one image object and render to the canvas multiple times. This will speed up your render considerably.
Hope this helps.

Categories

Resources