triangulation messing up triangles - libgdx

I'm trying to triangulate a convex polygon.
On the left are the triangulated polygons, while on the right is the convex polygon without triangulation. (both are wrong) but the one on the right is in the correct position and besides the fact that it's missing the convex part, it's pretty ok...
I create the triangles and add them to an arraylist
float[] vertices = polygonObject.getPolygon().getTransformedVertices();
float[] worldVertices = new float[vertices.length];
for (int i = 0; i < vertices.length; ++i) {
System.out.println(vertices[i]);
worldVertices[i] = vertices[i] / ppt;
}
// Make triangles
Vector<float[]> trianglesVertices = new Vector<float[]>();
EarClippingTriangulator triangulator = new EarClippingTriangulator();
ArrayList<PolygonShape> triangles = new ArrayList<PolygonShape>();
ShortArray pointsCoords = triangulator.computeTriangles(worldVertices);
for (int i = 0; i < pointsCoords.size / 6; i++)
{
trianglesVertices.add(new float[] {
pointsCoords.get(i*6), pointsCoords.get(i*6 + 1),
pointsCoords.get(i*6 + 2), pointsCoords.get(i*6 + 3),
pointsCoords.get(i*6 + 4), pointsCoords.get(i*6 + 5),
});
PolygonShape triangleShape = new PolygonShape();
triangleShape.set(trianglesVertices.get(i));
triangles.add(triangleShape);
}
later I loop that arraylist and create bodies, but somehow libgdx(probably me, not libgdx) totally messes it up.
for(PolygonShape triangle:triangles){
BodyDef bd = new BodyDef();
bd.type = BodyDef.BodyType.StaticBody;
Body body = world.createBody(bd);
body.createFixture(triangle, 1);
bodies.add(body);
triangle.dispose();
}
Here is the shape I am trying to render in libgdx (created in Tiled)

I found another example and it worked!
https://github.com/MobiDevelop/maps-editor/blob/master/maps-basic/src/com/mobidevelop/maps/basic/BasicMapRenderer.java#L151
the way I was creating the triangles from this array was wrong
ShortArray pointsCoords = triangulator.computeTriangles(worldVertices);
here is the code that does create the triangles correctly,
for (int i = 0; i < pointsCoords.size; i += 3) {
int v1 = pointsCoords.get(i) * 2;
int v2 = pointsCoords.get(i + 1) * 2;
int v3 = pointsCoords.get(i + 2) * 2;
PolygonShape triangleShape = new PolygonShape();
triangleShape.set(
new float[]{
worldVertices[v1 + 0],
worldVertices[v1 + 1],
worldVertices[v2 + 0],
worldVertices[v2 + 1],
worldVertices[v3 + 0],
worldVertices[v3 + 1]
}
);
triangles.add(triangleShape);
}

Related

Draw Line Arrowhead Without Rotating in Canvas

Most code to drawing arrowheads in html canvas involves rotating the canvas context and drawing the lines.
My use case is to draw them using trigonometry without rotating the canvas. or is that vector algorithm you call it? Help is appreciated.
This is what I have (forgot where I got most of the code). Draws 2 arrowheads on start and end based on the last 2 parameters arrowStart and arrowEnd which are boolean.
drawLineArrowhead: function(context, arrowStart, arrowEnd) {
// Place start end points here.
var x1 = 0;
var y1 = 0;
var x2 = 0;
var y2 = 0;
var distanceFromLine = 6;
var arrowLength = 9;
var dx = x2 - x1;
var dy = y2 - y1;
var angle = Math.atan2(dy, dx);
var length = Math.sqrt(dx * dx + dy * dy);
context.translate(x1, y1);
context.rotate(angle);
context.beginPath();
context.moveTo(0, 0);
context.lineTo(length, 0);
if (arrowStart) {
context.moveTo(arrowLength, -distanceFromLine);
context.lineTo(0, 0);
context.lineTo(arrowLength, distanceFromLine);
}
if (arrowEnd) {
context.moveTo(length - arrowLength, -distanceFromLine);
context.lineTo(length, 0);
context.lineTo(length - arrowLength, distanceFromLine);
}
context.stroke();
context.setTransform(1, 0, 0, 1, 0, 0);
},
See the code below, just a bit of trigonometry.
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
ctx.lineCap = "round";
ctx.lineWidth = 5;
function drawLineArrowhead(p1, p2, startSize, endSize) {
ctx.beginPath()
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
if (startSize > 0) {
lineAngle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
delta = Math.PI/6
for (i=0; i<2; i++) {
ctx.moveTo(p1.x, p1.y);
x = p1.x + startSize * Math.cos(lineAngle + delta)
y = p1.y + startSize * Math.sin(lineAngle + delta)
ctx.lineTo(x, y);
delta *= -1
}
}
if (endSize > 0) {
lineAngle = Math.atan2(p1.y - p2.y, p1.x - p2.x);
delta = Math.PI/6
for (i=0; i<2; i++) {
ctx.moveTo(p2.x, p2.y);
x = p2.x + endSize * Math.cos(lineAngle + delta)
y = p2.y + endSize * Math.sin(lineAngle + delta)
ctx.lineTo(x, y);
delta *= -1
}
}
ctx.stroke();
}
drawLineArrowhead({x:10, y:10}, {x:100, y:20}, 0, 30)
drawLineArrowhead({x:20, y:25}, {x:140, y:120}, 20, 20)
drawLineArrowhead({x:140, y:20}, {x:80, y:50} , 20, 0)
drawLineArrowhead({x:150, y:20}, {x:150, y:90}, 20, 5)
drawLineArrowhead({x:180, y:90}, {x:180, y:20}, 20, 5)
drawLineArrowhead({x:200, y:10}, {x:200, y:140}, 10, 10)
drawLineArrowhead({x:220, y:140}, {x:220, y:10}, 10, 20)
<canvas id="canvas">
If you run it you should see a few samples.
The drawLineArrowhead has 4 parameters (p1, p2, startSize, endSize)
the first two are the starting-point and end-point of the line, the last two are arrow size, just to give some control to the final user over how big are those arrows at the end, if we want to remove them we set to 0.

Separate fill for each shape in Flex graphics

I am drawing a bunch of circles in 2 horizontal rows across a sprite and would like a gradient applied to each circle individually but flex is applying it to the entire graphics area. What do I need to change to draw each circle with its own gradient?
var s:Sprite = new Sprite();
var g:Graphics = s.graphics;
g.beginBitmapFill(bd, new Matrix(), false, false);
g.drawRect(0, 0, s.width, s.height);
g.endFill();
var m:Matrix = new Matrix();
g.lineStyle(1, 0x888888, 1);
for(var i:int = 0; i < numSpirals; i++) {
g.beginGradientFill(GradientType.LINEAR, [0x666666, 0xFFFFFF], [1, 1], [0, 255], m);
xc = i * spiralWidth + spiralHoleRadius;
yc = bdCenter - (spiralBoxHeight / 2) + (spiralHoleRadius / 2);
g.drawCircle(xc, yc, spiralHoleRadius);
g.endFill();
}
for(i = 0; i < numSpirals; i++) {
g.beginGradientFill(GradientType.LINEAR, [0x666666, 0xFFFFFF], [1, 1], [0, 255], m);
xc = i * spiralWidth + spiralHoleRadius;
yc = bdCenter + (spiralBoxHeight / 2) - (spiralHoleRadius / 2);
g.drawCircle(xc, yc, spiralHoleRadius);
g.endFill();
}
Thanks for your help!
Edit - I forgot this part (may or may not be relevant). Before drawing the circles I drew the BitmapData from an ImageSnapshot of some UI elements onto the sprite as well.
Solution:
I needed to create a new matrix and 'createGradientBox' for each circle individually
m = new Matrix();
m.createGradientBox(spiralHoleRadius * 2, spiralHoleRadius * 2, 0, xc - spiralHoleRadius / 2, yc - spiralHoleRadius / 2);
Following is a simular question on this site, maybe it helps (I cannot comment so I put this in a answer)
AS3: beginGradientFIll() doesn't make me a gradient!

Properly hovering over isometric tile sprite

I have four classes: Room, TileGrid, HoverTile, and Tile.
Room is composed of walls and a TileGrid. TileGrid is made out of Tile. Currently, I use this code to generate a TileGrid out of Tiles:
this.mapArray = [[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1, 1, 1, 1, 1, 1, 1]];
this._mapHeight = this.mapArray.length;
this._mapWidth = this.mapArray[0].length;
this._tileHeight = 23;
this._tileWidth = 46;
var initialX:Number = 260;
var initialY:Number = 150;
for (var isoY:int = 0; isoY < mapArray.length; isoY++)
{
for (var isoX:int = 0; isoX < mapArray[isoY].length; isoX++)
{
if (isoX == 0 && isoY == 0)
{
var _tile:Tile = new Tile();
_tile.x = initialX;
_tile.y = initialY;
this.addChild(_tile);
}
if (this.mapArray[isoY][isoX] == 1)
{
var _tile:Tile = new Tile();
_tile.x = initialX - (isoX * 20) - (isoY * 20);
_tile.y = initialY - (isoX * 10) + (isoY * 10);
addChild(_tile);
_tile.addEventListener(MouseEvent.MOUSE_OVER, updateHover);
}
}
}
My current issue is that I want to add a white square around the tile that a mouse is hovering over. The code I used to use wasn't sufficient, because transparent parts of the Tile sprite are still counted as part of it. So even if I'm pointing at another Tile2 (which is next to Tile1), for example, if I'm not far enough onto Tile2, it'll highlight Tile1.
So, here's the current code I'm using:
public function updateHover(e:MouseEvent):void
{
var mX:int = e.stageX - (_tileWidth / 2);
var tPoint:Point = pointToXY(mX, e.stageY);
var isoX = tPoint.x;
var isoY = tPoint.y;
if (isoX >= 0 && isoY >= 0)
{
if (isoY < mapArray.length)
{
if (isoX < mapArray[0].length)
{
tPoint = xyToPoint(isoX, isoY);
_tileHover.x = tPoint.x;
_tileHover.y = tPoint.y;
_tileHover.visible = true;
return;
}
}
}
_tileHover.visible = false;
}
public function pointToXY(x:int, y:int):Point
{
x -= 260;
y -= 150;
var pRatio:int = (_tileWidth / 2) / (_tileHeight / 2);
var tX:int = (y + x / pRatio) * (pRatio / 2) / (_tileWidth / 2);
var tY:int = (y - x / pRatio) * (pRatio / 2) / (_tileWidth / 2);
return new Point(tX, tY);
}
public function xyToPoint(x:int, y:int):Point
{
x -= 1;
var worldPoint:Point = new Point(0, 0);
worldPoint.x = (x * (_tileWidth / 2)) - (y * (_tileWidth / 2));
worldPoint.y = (x * (_tileHeight / 2)) + (y * (_tileHeight / 2));
worldPoint.x = worldPoint.x + (_tileWidth / 2);
worldPoint.y = worldPoint.y + (_tileHeight / 2);
worldPoint.x += 260;
worldPoint.y += 150;
return worldPoint;
}
Sorry I have to post so many code blocks. Now, 260 and 150 are the default starting point for the entire room. That said, I'm really confused on how to get the last two functions in particular to work so that they'll give me the correct answer. This is what I expected from using this code:
That would be perfect. But, again, I don't know why the code isn't working. The sizes are all correct and I believe the offset is, too. So, I'm
First, you should add the listener to this, not to _tile, because then you are locked to stage coordinates to determine the tile that's selected, which is not good. Second, your listener should be against MouseEvent.MOUSE_MOVE event, not over, this way you'll constantly get updated mouse coords to properly move your rectangle over tiles. And you have a minor error out there, you have a (0,0) tile created two times, one being inactive.
for (var isoY:int = 0; isoY < mapArray.length; isoY++)
{
for (var isoX:int = 0; isoX < mapArray[isoY].length; isoX++)
{
if (this.mapArray[isoY][isoX] == 1)
{
var _tile:Tile = new Tile();
_tile.x = initialX - (isoX * 20) - (isoY * 20);
_tile.y = initialY - (isoX * 10) + (isoY * 10);
addChild(_tile);
}
}
}
this.addEventListener(MouseEvent.MOUSE_MOVE, updateHover);
Also, it'll be better that you'd store (x,y) pairs on the array (as tiles, most likely), so that your initial array of zeroes and ones would transform into an array of Tile objects. To do that, you first do this:
this.tileArray=[];
for (var i:int=0;i<this.mapArray.length;i++)
this.tileArray.push(new Array(this.mapArray[i].length));
This will create an array of nulls that matches your mapArray by dimensions, that will serve as placeholder for created Tile objects. After you do this, you call this.tileArray[isoY][isoX]=_tile; to place the newly created tile to its place. After that, you can rewrite your listener to this:
public function updateHover(e:MouseEvent):void
{
var p:Point=pointToXY(e.localX,e.localY);
_tileHover.visible = false; // hide hover for now
if ((p.y<0) || (p.y>=tileArray.length)) return; // range error on Y
if ((p.x<0)||(p.x>=tileArray[p.y].length)) return; // range error on X
if (!tileArray[p.y][p.x]) return; // no tile
var _tile:Tile=tileArray[p.y][p.x];
_tileHover.x=_tile.x;
_tileHover.y=_tile.y; // no need to convert xyToPoint() we have coords stored in tile
_tileHover.visible=true;
}

Box2d working on dynamically created bodies

I'm trying to learn box2d but i've a problem. I have 3 bodies in my project, 3 lines actually. i'm trying to set each lines y coordinate to -7 when they arrive to y coordinate 14. my code for checking if they are over 14 is below:
for (int i = 0; i < groundsArray.size(); i++) {
Body body2 = groundsArray.get(i);
float x_deg;
x_deg = body2.getTransform().getPosition().x;
if (body2.getUserData().equals("grounds" + i) && body2.getTransform().getPosition().y > 14) {
body2.setTransform(x_deg, -7, 0);
}
}
How i create bodies :
public void createGrounds(int i) {
// for some random positions
int rnd = random.nextInt(10);
int constant = -20;
int new_y = i * 7;
int result_x1 = constant + rnd;
int result_x2 = result_x1 + 16;
BodyDef bodyDef = new BodyDef();
FixtureDef fixtureDef = new FixtureDef();
// GROUND LEFT
// body definition
bodyDef.type = BodyType.StaticBody;
bodyDef.position.set(0, 0);
// ground shape
ChainShape groundShape = new ChainShape();
groundShape.createChain(new Vector2[] { new Vector2(result_x1, -new_y),new Vector2(result_x2, -new_y) });
// fixture definition
fixtureDef.shape = groundShape;
fixtureDef.friction = 5f;
fixtureDef.restitution = 0;
grounds = world.createBody(bodyDef);
grounds.createFixture(fixtureDef);
grounds.setUserData("grounds" + i);
groundsArray.add(grounds);
groundShape.dispose();
}
How i call createGrounds() inside show():
for (int i = 0; i <= 2; i++) {
createGrounds(i);
}
How i update the world and positions inside render():
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 0);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
accelX = Gdx.input.getAccelerometerX();
Vector2 gravity = new Vector2(-accelX * 6, -60.81f);
world.setGravity(gravity);
world.step(TIMESTEP, VELOCITYITERATIONS, POSITIONITERATIONS);
batch.setProjectionMatrix(camera.combined);
batch.begin();
world.getBodies(tmpBodies);
for (Body body : tmpBodies) {
if (body.getUserData().equals(ballsprite)
&& body.getUserData() instanceof Sprite) {
Sprite sprite = (Sprite) body.getUserData();
sprite.setPosition(
body.getPosition().x - sprite.getWidth() / 2,
body.getPosition().y - sprite.getHeight() / 2);
sprite.setRotation(body.getAngle() * MathUtils.radiansToDegrees);
sprite.draw(batch);
}
float x_deg;
float y_deg;
x_deg = body.getTransform().getPosition().x;
y_deg = body.getTransform().getPosition().y;
y_deg = y_deg + game_speed;
body.setTransform(x_deg, y_deg, 0);
}
// The part i couldn't solve
for (int i = 0; i < groundsArray.size(); i++) {
Body body2 = groundsArray.get(i);
float x_deg;
x_deg = body2.getTransform().getPosition().x;
if (body2.getUserData().equals("grounds" + i)
&& body2.getTransform().getPosition().y > 14) {
body2.setTransform(x_deg, -7, 0);
}
}
batch.end();
debugRenderer.render(world, camera.combined);
}
Maybe change your for cycle to this
for(int i = groundsArray.size(); i > 0; i--){}

How can I implement Lanczos resampling after every canvas transform without having to make a new canvas?

UPDATE: Once I got this demo working... holy smokes, it's SLOW, like 12-16 seconds for only a level 2 render (when image is around 1000x2000 pixels). This is not even worth bothering with.
I found this really awesome and hopeful looking code in the top answer here: Resizing an image in an HTML5 canvas
//returns a function that calculates lanczos weight
function lanczosCreate(lobes){
return function(x){
if (x > lobes)
return 0;
x *= Math.PI;
if (Math.abs(x) < 1e-16)
return 1
var xx = x / lobes;
return Math.sin(x) * Math.sin(xx) / x / xx;
}
}
//elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius
function thumbnailer(elem, img, sx, lobes){
this.canvas = elem;
elem.width = img.width;
elem.height = img.height;
elem.style.display = "none";
this.ctx = elem.getContext("2d");
this.ctx.drawImage(img, 0, 0);
this.img = img;
this.src = this.ctx.getImageData(0, 0, img.width, img.height);
this.dest = {
width: sx,
height: Math.round(img.height * sx / img.width),
};
this.dest.data = new Array(this.dest.width * this.dest.height * 3);
this.lanczos = lanczosCreate(lobes);
this.ratio = img.width / sx;
this.rcp_ratio = 2 / this.ratio;
this.range2 = Math.ceil(this.ratio * lobes / 2);
this.cacheLanc = {};
this.center = {};
this.icenter = {};
setTimeout(this.process1, 0, this, 0);
}
thumbnailer.prototype.process1 = function(self, u){
self.center.x = (u + 0.5) * self.ratio;
self.icenter.x = Math.floor(self.center.x);
for (var v = 0; v < self.dest.height; v++) {
self.center.y = (v + 0.5) * self.ratio;
self.icenter.y = Math.floor(self.center.y);
var a, r, g, b;
a = r = g = b = 0;
for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
if (i < 0 || i >= self.src.width)
continue;
var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
if (!self.cacheLanc[f_x])
self.cacheLanc[f_x] = {};
for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
if (j < 0 || j >= self.src.height)
continue;
var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
if (self.cacheLanc[f_x][f_y] == undefined)
self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2) + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
weight = self.cacheLanc[f_x][f_y];
if (weight > 0) {
var idx = (j * self.src.width + i) * 4;
a += weight;
r += weight * self.src.data[idx];
g += weight * self.src.data[idx + 1];
b += weight * self.src.data[idx + 2];
}
}
}
var idx = (v * self.dest.width + u) * 3;
self.dest.data[idx] = r / a;
self.dest.data[idx + 1] = g / a;
self.dest.data[idx + 2] = b / a;
}
if (++u < self.dest.width)
setTimeout(self.process1, 0, self, u);
else
setTimeout(self.process2, 0, self);
};
thumbnailer.prototype.process2 = function(self){
self.canvas.width = self.dest.width;
self.canvas.height = self.dest.height;
self.ctx.drawImage(self.img, 0, 0);
self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
var idx, idx2;
for (var i = 0; i < self.dest.width; i++) {
for (var j = 0; j < self.dest.height; j++) {
idx = (j * self.dest.width + i) * 3;
idx2 = (j * self.dest.width + i) * 4;
self.src.data[idx2] = self.dest.data[idx];
self.src.data[idx2 + 1] = self.dest.data[idx + 1];
self.src.data[idx2 + 2] = self.dest.data[idx + 2];
}
}
self.ctx.putImageData(self.src, 0, 0);
self.canvas.style.display = "block";
}
...
img.onload = function() {
var canvas = document.createElement("canvas");
new thumbnailer(canvas, img, 188, 3); //this produces lanczos3
//but feel free to raise it up to 8. Your client will appreciate
//that the program makes full use of his machine.
document.body.appendChild(canvas);
}
However, this implementation loads an image and renders it, end of story.
I have been trying to re-implement this code so that it does the filtering every time an existing canvas is scaled (think, zooming in and out of an image or document) without having to load a new image or create a new canvas.
How can I adapt it to work this way? Or is that even possible?
What you want to do is something like a singleton to reuse your canvas object. This will let you save the cost of create a new canvas object each time and you will reuse the same object
function getCanvas(){
var canvas;
if (typeof canvas === "undefined"){ canvas = document.createElement("canvas");}
return canvas;
}
img.onload = function() {
var canvas = getCanvas("canvas");
.... THE REST OF YOUR CODE .......
}
.
However this is not what slows your code, image scaling Algorithms are really heavy algorithms with intensive cpu use "usually make use of gpu acceleration at a really low level", and use advanced techniques like multiple bufferr and so others. here is a interesting tutorial in java.net on how image scaling works, it is in java but you can interpolate to any language.
Javascript is not ready for this techniques, so I recommend you to use the transformations available in the canvas api, as in the tutorial you read the efficient way is using the canvas2Dcontext.
var ctx = canvas.getContext("2d");
ctx.scale(2,2);