Segmenting on Arcs from DWG File - autodesk-forge

I have an application using the Forge Viewer displaying converted ACAD dwg files. The short description is that I need to take specific polylines out of the dwg file source and use the Edit2D extension to draw them as polygons over the background. I have this working, but arcs are causing some issues right now. This doesn't have to be perfect but it should be decently the same shape. In most cases it is just drawing a line from the start to the end of the arc (and I understand why, see code below) but in other cases it's significantly segmenting the arc and I'm not sure why.
I start by finding the id's of the polylines based on their layer and then getting the fragment ids (this is working fine). Then I get the vertexes for the polyline like this:
export function getVertexesById(
viewer: Autodesk.Viewing.GuiViewer3D,
frags: Autodesk.Viewing.Private.FragmentList,
fragIds: number[],
dbId: number
): Point[] {
// We need to also get the center points of arcs as lines seem to be drawn to them in the callbacks for some
// reason. Center points should later be removed from the point array, so we don't get strange spikes on our shapes.
const polyPoints: Point[] = [];
const centers: Point[] = [];
fragIds.forEach((fid) => {
const mesh = frags.getVizmesh(fid);
const vbr = new Autodesk.Viewing.Private.VertexBufferReader(
mesh.geometry,
viewer.impl.use2dInstancing
);
vbr.enumGeomsForObject(dbId, {
onLineSegment(
x1: number,
y1: number,
x2: number,
y2: number,
_vpId: number
) {
checkAddPoint(polyPoints, { x: x1, y: y1, z: 0 });
checkAddPoint(polyPoints, { x: x2, y: y2, z: 0 });
},
onCircularArc: function (cx, cy, start, end, radius, _vpId) {
centers.push({ x: cx, y: cy, z: 0 });
},
onEllipticalArc: function (
cx,
cy,
start,
end,
major,
minor,
tilt,
_vpId
) {
centers.push({ x: cx, y: cy, z: 0 });
},
onOneTriangle: function (x1, y1, x2, y2, x3, y3, _vpId) {
checkAddPoint(polyPoints, { x: x1, y: y1, z: 0 });
checkAddPoint(polyPoints, { x: x2, y: y2, z: 0 });
checkAddPoint(polyPoints, { x: x3, y: y3, z: 0 });
},
onTexQuad: function (cx, cy, width, height, rotation, _vpId) {
centers.push({ x: cx, y: cy, z: 0 });
},
});
});
centers.forEach((c) => {
checkRemovePoint(polyPoints, { x: c.x, y: c.y, z: 0 });
});
return polyPoints;
}
The functions checkAddPoint and checkRemovePoint are just helper functions that make sure we don't duplicate points and take into account rounding (so we don't get two points that are say 0,0,0 and 0,0.00001,0.
I then use those points to draw with the Edit2D extension. So what I would expect here is that it creates a series of points that would draw along all the straight lines of the polyline and when it gets to an arc it just draws from one endpoint to the other. That is mostly what I see.
Here is an example file as it looks in ACAD:
Notice there are a handful of breaks in the arc around the outside of the room. What I get when I do the above process is this:
Notice all along the top I get what I would expect. However, along the bottom in 2 places I get a huge number of segments all along the line.
I looked back at the ACAD file and exploded the polyline and looked at it as much as I know how and I can't find anything different about those two segments vs. the other that would indicate why it acts differently.
What would be really awesome is if there is an easy way to just segment along an arc say every x units and have it return that but I'm not expecting that here, I just want to know why it is treating these differently.
Any help is greatly appreciated.
Edit
I should also mention that I have logged the creation routine and the only things in this that are ever hit are the onLineSegment and onCircularArc. As you see, the circular arc one only checks to make sure we don't have the center point in the list, so all of these extra points are for some reason being read in the line segment section.

Related

How can I get the viewer coordinates of AutoCAD geometry?

I am using the 2D Autodesk Forge Viewer, and I'm looking for a way to determine the X,Y coordinate of a block reference object from AutoCAD.
I have the dbID for the geometry element, and. I can get some information through NOP_VIEWER.getProperties() and NOP_VIEWER.getDimensions(), but neither of those have the X,Y coordinate.
With help from Xiaodong below, I was able to devise the following solution to get the X,Y coordinate of an object using its dbId
const geoList = NOP_VIEWER.model.getGeometryList().geoms;
const readers = [];
for (const geom of geoList) {
if (geom) {
readers.push(new Autodesk.Viewing.Private.VertexBufferReader(geom, NOP_VIEWER.impl.use2dInstancing));
}
}
const findObjectLocation = (objectId) => {
for (const reader of readers) {
let result;
reader.enumGeomsForObject(objectId, {
onLineSegment: (x, y) => {
result = { x, y };
},
});
if (result) {
return result;
}
}
throw new Error(`Unable to find requested object`);
};
As I remember, it is true the position data is not available with block entity. I will check with engineer team if there is any comment about the native position data of block. One alterative is to use Forge Design Automation of AutoCAD to extract the data yourself, while it would require additional more code.
After Forge translates the source DWG, the entities are converted to primitives. By API, it is feasible to get geometry info of the primitives, such as start point of line, center of circle. The two blogs tell in details:
https://forge.autodesk.com/blog/working-2d-and-3d-scenes-and-geometry-forge-viewer
https://forge.autodesk.com/blog/working-2d-and-3d-scenes-and-geometry-forge-viewer
Essentially, it uses the callback function:
VertexBufferReader.prototype.enumGeomsForObject = function(dbId, callback)
The callback object needs these optional functions:
• onLineSegment(x0, y0, x1, y1, viewport_id)
• onCircularArc(centerX, centerY, startAngle, endAngle, radius, viewport_id)
• onEllipticalArccenterX, centerY, startAngle, endAngle, major, minor, tilt, viewport_id)
• onTriangleVertex(x, y, viewport_id)
.

Libgdx is showing wrong (x, y) coordinates from tiled

When i remove a tile with the coords (example: X: 15, Y: 9) with
TiledMapTileLayer tiledMapTileLayer = (TiledMapTileLayer)map.getLayers().get(0);
tiledMapTileLayer.setCell(15, 9, null);
I notice that actually the wrong tile is removed from the map. Instead tile with the coords X:15 Y: 6 is being removed. What am i doing wrong?
I believe this would be due to libgdx inverting the map to match better with their coordinate system. If your map is 16 tiles high, trying to remove tile at Y: 9 will result in removal of tile at Y: 16 - 9 - 1 = 6.
If you want to copy a Y coordinate from Tiled and put it in your code, you'll in general need to apply the following conversion to turn it into the same location in libgdx:
int y = tileLayer.getHeight() - 1 - [Y coordinate from Tiled];

ClientToWorld value when not interacting with model

Is there a method similar to ClientToWorld, that can give me the X,Y world coords if I provide it with X,Y screen coords?
I know that ClientToWorld gives me a Z coord of where it interacts with the model, but I am happy to have no Z coord as it will not raycast to a point on the model.
How about Viewer3dImpl.clientToViewport?
let coords = viewer.impl.clientToViewport(client.x, client.y); //c.Vector3 {x: -0.9696521095484826, y: 0.9200779727095516, z: 1 (always 1)}
let finalCoords = coords.unproject(viewer.impl.camera) //c.Vector3 {x: -26.379134321221724, y: 5.162777223710702, z: 1.3846547842336627}
See unofficial doc (not authoritative & subject to change w/o notice) for this method here

Kinetic.JS - why does layer order change overwrite my colour?

I'm currently playing with Kinetic.JS. I have drawn a rather crude UFO-like shape in two parts, a hull (red) and a disc (grey).
Demo - JSBin
Question: how come when I later arrange the shape ordering so the hull is above the disc, the disc bizarrely goes from grey to the hull's red?
Uncomment the moveToTop() line at the bottom of my JSBin to see what I mean. Here's the pertinent (condensed) code.
//ship hull
var hull = new Kinetic.Shape({
drawFunc: function(ctx) {
ctx.arc(game_dims.w / 2, game_dims.h * 0.6, game_dims.h * 0.45, 0, Math.PI, true);
this.fill(ctx);
},
fill: 'red'
});
//ship disc
var disc = new Kinetic.Circle({
x: game_dims.w / 2,
y: game_dims.h * 0.6,
radius: {x: game_dims.w * 0.45, y: 30},
fill: '#888'
});
//draw
layer.add(hull);
layer.add(disc);
stage.add(layer);
//post-production
hull.moveToTop(); // <-- weirdness - changes disc colour!?
layer.draw();
I am aware I could draw the two shapes in reverse order to get the desired order, but that is not what I want with this question - I'm interested in rearrangement of order after drawing.
Thanks in advance
Your draw function of the hull needs to tell the context it's drawing a new path:
function(ctx) {
ctx.beginPath();
ctx.arc(...);
this.fill(ctx);
}
By adding the beginPath() command you are telling the context that you are not in fact adding to the previous path, but drawing a new one instead. This is also what makes this.fill() fill the previous shape with red, because in your example the context is still referring to the disc when it attempts to fill it

Drawing a dot on HTML5 canvas [duplicate]

This question already has answers here:
What's the best way to set a single pixel in an HTML5 canvas?
(14 answers)
Closed 7 years ago.
Drawing a line on the HTML5 canvas is quite straightforward using the context.moveTo() and context.lineTo() functions.
I'm not quite sure if it's possible to draw a dot i.e. color a single pixel. The lineTo function wont draw a single pixel line (obviously).
Is there a method to do this?
For performance reasons, don't draw a circle if you can avoid it. Just draw a rectangle with a width and height of one:
ctx.fillRect(10,10,1,1); // fill in the pixel at (10,10)
If you are planning to draw a lot of pixel, it's a lot more efficient to use the image data of the canvas to do pixel drawing.
var canvas = document.getElementById("myCanvas");
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var ctx = canvas.getContext("2d");
var canvasData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
// That's how you define the value of a pixel
function drawPixel (x, y, r, g, b, a) {
var index = (x + y * canvasWidth) * 4;
canvasData.data[index + 0] = r;
canvasData.data[index + 1] = g;
canvasData.data[index + 2] = b;
canvasData.data[index + 3] = a;
}
// That's how you update the canvas, so that your
// modification are taken in consideration
function updateCanvas() {
ctx.putImageData(canvasData, 0, 0);
}
Then, you can use it in this way :
drawPixel(1, 1, 255, 0, 0, 255);
drawPixel(1, 2, 255, 0, 0, 255);
drawPixel(1, 3, 255, 0, 0, 255);
updateCanvas();
For more information, you can take a look at this Mozilla blog post : http://hacks.mozilla.org/2009/06/pushing-pixels-with-canvas/
It seems strange, but nonetheless HTML5 supports drawing lines, circles, rectangles and many other basic shapes, it does not have anything suitable for drawing the basic point. The only way to do so is to simulate a point with whatever you have.
So basically there are 3 possible solutions:
draw point as a line
draw point as a polygon
draw point as a circle
Each of them has their drawbacks.
Line
function point(x, y, canvas){
canvas.beginPath();
canvas.moveTo(x, y);
canvas.lineTo(x+1, y+1);
canvas.stroke();
}
Keep in mind that we are drawing to South-East direction, and if this is the edge, there can be a problem. But you can also draw in any other direction.
Rectangle
function point(x, y, canvas){
canvas.strokeRect(x,y,1,1);
}
or in a faster way using fillRect because render engine will just fill one pixel.
function point(x, y, canvas){
canvas.fillRect(x,y,1,1);
}
Circle
One of the problems with circles is that it is harder for an engine to render them
function point(x, y, canvas){
canvas.beginPath();
canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
canvas.stroke();
}
the same idea as with rectangle you can achieve with fill.
function point(x, y, canvas){
canvas.beginPath();
canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
canvas.fill();
}
Problems with all these solutions:
it is hard to keep track of all the points you are going to draw.
when you zoom in, it looks ugly
If you are wondering, what is the best way to draw a point, I would go with filled rectangle. You can see my jsperf here with comparison tests
In my Firefox this trick works:
function SetPixel(canvas, x, y)
{
canvas.beginPath();
canvas.moveTo(x, y);
canvas.lineTo(x+0.4, y+0.4);
canvas.stroke();
}
Small offset is not visible on screen, but forces rendering engine to actually draw a point.
The above claim that "If you are planning to draw a lot of pixel, it's a lot more efficient to use the image data of the canvas to do pixel drawing" seems to be quite wrong - at least with Chrome 31.0.1650.57 m or depending on your definition of "lot of pixel". I would have preferred to comment directly to the respective post - but unfortunately I don't have enough stackoverflow points yet:
I think that I am drawing "a lot of pixels" and therefore I first followed the respective advice for good measure I later changed my implementation to a simple ctx.fillRect(..) for each drawn point, see http://www.wothke.ch/webgl_orbittrap/Orbittrap.htm
Interestingly it turns out the silly ctx.fillRect() implementation in my example is actually at least twice as fast as the ImageData based double buffering approach.
At least for my scenario it seems that the built-in ctx.getImageData/ctx.putImageData is in fact unbelievably SLOW. (It would be interesting to know the percentage of pixels that need to be touched before an ImageData based approach might take the lead..)
Conclusion: If you need to optimize performance you have to profile YOUR code and act on YOUR findings..
This should do the job
//get a reference to the canvas
var ctx = $('#canvas')[0].getContext("2d");
//draw a dot
ctx.beginPath();
ctx.arc(20, 20, 10, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();