Flip an SKPath using SkiaSharp - image

I'm using slightly modified resize code based on example I found. However, on resize the everything is flipped. I would like to either flip it back or prevent it from flipping in the first place.
Here is my resize code:
private static void ResizePath(SKPath buildingPath, IEnumerable<Room> rooms)
{
var info = new SKImageInfo(512, 600, SKImageInfo.PlatformColorType, SKAlphaType.Premul);
var drawSpaceRect = SKRect.Create(info.Size);
//I need to find the size of the path
var buildingPathRect = buildingPath.TightBounds;
//I want to find the largest rectangle that can fit on my canvas maintaining the path's aspect ratio
var sketchRect = drawSpaceRect.AspectFit(buildingPathRect.Size);
//Now I need to transform the path to draw within the sketchRect
//First translate original path to its own origin
var firstTranslateM = SKMatrix.MakeTranslation(-buildingPathRect.Left, -buildingPathRect.Top);
//Next handle scaling. Since I maintained aspect ratio, I should be able to use either
//width or height to figure out scaling factor
var scalingFactor = sketchRect.Width/buildingPathRect.Width;
var scaleM = SKMatrix.MakeScale(scalingFactor, scalingFactor);
//Next I need to handle translation so path is centered on canvas
var secondTranslateM = SKMatrix.MakeTranslation(sketchRect.Left, sketchRect.Top);
//Finally I need to handle transforming the path to rotate 180 degrees
var rotationMatrix = SKMatrix.MakeRotationDegrees(180, sketchRect.MidX, sketchRect.MidY);
//Now combine the translation, scaling, and rotation into a single matrix by matrix multiplication/concatentation
var transformM = SKMatrix.MakeIdentity();
SKMatrix.PostConcat(ref transformM, firstTranslateM);
SKMatrix.PostConcat(ref transformM, scaleM);
SKMatrix.PostConcat(ref transformM, secondTranslateM);
SKMatrix.PostConcat(ref transformM, rotationMatrix);
//Now apply the transform to the path
foreach (var r in rooms)
{
r.Path.Transform(transformM);
}
}
Here is an example of what I want (ignore the line numbers):
Flipped to:
Any help would be appreciated.

This transformation should do what you are looking for. The terminology would be flip horizontal or reflect horizontal.
var Ma = new SKMatrix {Values = new float[] {-1, 0, 0, 1, 0, 0, 0, 0, 0}};
pathToFlip.Transform(Ma);

Related

Three js: How to normalize a mesh generated by vertices

I'm somewhat new to Three js, and my linear algebra days were back in the 90s so I don't recall much about quarternions. My issue is I have 8 vertices for a cube that I can use to create a custom geometry mesh from, but it doesn't set the position / rotation / scale info for its world matrix. Therefor it can not be used cleanly by other three js modules like controls. I can look up the math and calculate what position / scale / rotation (rotation gets a bit hairy with some fun acos stuff) should be and create a standard boxgeometry from that. But it seems like there should be some way to do it via three js objects if I can generate the proper matrix to apply to it. The quarternion setFromUnitVectors looked interesting, but I'd still have to do some work to generate the vectors. Any ideas would be appreciated thanks
Edit: :) So let me try and simplify. I have 8 vertices, I want to create a box geometry. But box geometry doesn't take vertices. It takes width, height, depth (relatively easy to calculate) and then you set the position/scale/rotation. So here's my code thus far:
5____4
1/___0/|
| 6__|_7
2/___3/
const box = new Box3();
box.setFromPoints(points);
const width = points[1].distanceTo(points[0]);
const height = points[3].distanceTo(points[0]);
const depth = points[4].distanceTo(points[0]);
const geometry = new BoxGeometry(width, height, depth);
mesh = new Mesh(geometry, material);
const center = box.getCenter(new Vector3());
const normalizedCorner = points[0].clone().sub(center);
const quarterian = new Quaternion();
quarterian.setFromUnitVectors(geometry.vertices[0], normalizedCorner);
mesh.setRotationFromQuaternion(quarterian);
mesh.position.copy(center);
The problem being my rotation element is wrong (besides my vectors not being unit vectors). I'm apparently not getting the correct quarternion to rotate my mesh correctly.
Edit: From WestLangley's suggestion, I'm creating a rotation matrix. However, while it rotates in the correct plane, the angle is off. Here's what I have added:
const matrix = new Matrix4();
const widthVector = new Vector3().subVectors(points[6], points[7]).normalize();
const heightVector = new Vector3().subVectors(points[6], points[5]).normalize();
const depthVector = new Vector3().subVectors(points[6], points[2]).normalize();
matrix.set(
widthVector.x, heightVector.x, depthVector.x, 0,
widthVector.y, heightVector.y, depthVector.y, 0,
widthVector.z, heightVector.z, depthVector.z, 0,
0, 0, 0, 1,
);
mesh.quaternion.setFromRotationMatrix(matrix);
Per WestLangley's comments I wasn't creating my matrix correctly. The correct matrix looks like:
const matrix = new Matrix4();
const widthVector = new Vector3().subVectors(points[7], points[6]).normalize();
const heightVector = new Vector3().subVectors(points[5], points[6]).normalize();
const depthVector = new Vector3().subVectors(points[2], points[6]).normalize();
matrix.set(
widthVector.x, heightVector.x, depthVector.x, 0,
widthVector.y, heightVector.y, depthVector.y, 0,
widthVector.z, heightVector.z, depthVector.z, 0,
0, 0, 0, 1,
);
mesh.quaternion.setFromRotationMatrix(matrix);

Fit to screen from different Orthographic camera positions

Made a simple jsFiddle example to illustrate a problem.
I'm trying to fit object's bounding box to screen from different camera positions. In example in dat.GUI panel you can change camera position and then click button fit to screen.
When changing y and z (positive) camera positions to find camera's top and bottom properties code below is used
var top = boundingBox.max.y * Math.cos(angleToZAxis) + boundingBox.max.z * Math.sin(angleToZAxis); // line 68
var bottom boundingBox.min.y * Math.cos(angleToZAxis) + boundingBox.min.z * Math.sin(angleToZAxis);
I would like to know how I can include camera's x position and negative positions in this calculation, what is the math behind this. Should I use rotation matrix and how to use it?
Or maybe it can be achieved in some simple way with threejs methods, can't figure out, tried the code below but something is wrong:
var matrix = new THREE.Matrix4();
matrix.lookAt ( this.camera.position, new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 1, 0) );
var bbMax = boundingBox.max.clone().applyMatrix4(matrix);
var bbMin = boundingBox.min.clone().applyMatrix4(matrix)
;
to fit an orthographic camera you have to simply change its zoom and position
you can calculate zoom from the bounding box of your object
(I used the boxes from geometry, but you will have to take in account matrices of the objects in group; I used them because .setFromObject was not returning consistent value)
Canvas3D.prototype.fitToScreen = function() {
this.group.children[0].geometry.computeBoundingBox();
var boundingBox = this.group.children[0].geometry.boundingBox.clone();
this.group.children[1].geometry.computeBoundingBox();
boundingBox.union(this.group.children[1].geometry.boundingBox);
var rotation = new THREE.Matrix4().extractRotation(this.camera.matrix);
boundingBox.applyMatrix4(rotation);
this.camera.zoom = Math.min(this.winWidth / (boundingBox.max.x - boundingBox.min.x),
this.winHeight / (boundingBox.max.y - boundingBox.min.y)) * 0.95;
this.camera.position.copy(boundingBox.center());
this.camera.updateProjectionMatrix();
this.camera.updateMatrix();
};
using this will not work in your fiddle because you are using OrbitControls and they rotate camera on update based on their own state - so either update that state or create your own controls
also either move camera back after
this.camera.position.copy(boundingBox.center());
or set near plane to -1000 to avoid having cut object
this.camera = new THREE.OrthographicCamera(this.winWidth / -2,
this.winWidth / 2 , this.winHeight / 2, this.winHeight / -2, -10000, 10000);
EDIT
now i see that you dont want to just fit the object but the whole box...
to do so an easy way is to project the points of the box and get the distances of extremes in pixels, then you can set ortho camera directly
boundingBox = new THREE.Box3().setFromObject(this.group);
//take all 8 vertices of the box and project them
var p1 = new THREE.Vector3(boundingBox.min.x,boundingBox.min.y,boundingBox.min.z).project(this.camera);
var p2 = new THREE.Vector3(boundingBox.min.x,boundingBox.min.y,boundingBox.max.z).project(this.camera);
var p3 = new THREE.Vector3(boundingBox.min.x,boundingBox.max.y,boundingBox.min.z).project(this.camera);
var p4 = new THREE.Vector3(boundingBox.min.x,boundingBox.max.y,boundingBox.max.z).project(this.camera);
var p5 = new THREE.Vector3(boundingBox.max.x,boundingBox.min.y,boundingBox.min.z).project(this.camera);
var p6 = new THREE.Vector3(boundingBox.max.x,boundingBox.min.y,boundingBox.max.z).project(this.camera);
var p7 = new THREE.Vector3(boundingBox.max.x,boundingBox.max.y,boundingBox.min.z).project(this.camera);
var p8 = new THREE.Vector3(boundingBox.max.x,boundingBox.max.y,boundingBox.max.z).project(this.camera);
//fill a box to get the extremes of the 8 points
var box = new THREE.Box3();
box.expandByPoint(p1);
box.expandByPoint(p2);
box.expandByPoint(p3);
box.expandByPoint(p4);
box.expandByPoint(p5);
box.expandByPoint(p6);
box.expandByPoint(p7);
box.expandByPoint(p8);
//take absolute value because the points already have the correct sign
var top = box.max.y * Math.abs(this.camera.top);
var bottom = box.min.y * Math.abs(this.camera.bottom);
var right = box.max.x * Math.abs(this.camera.right);
var left = box.min.x * Math.abs(this.camera.left);
this.updateCamera(left, right, top, bottom);
this code also stretches the view to fit exactly into the window so you will have to check for the aspect ratio and change one size accordingly, but that should be trivial

Three.js - Arranging cubes in a grid

I would like to position cubes in a rectangular/square like grid. I'm having trouble trying to create some methodology in depending on what I pick through an HTML form input (checkboxes) to have it arrange left to right and up to down, a series of cubes, in a prearranged grid all on the same plane.
What measurement units is three.js in? Right now, I'm setting up my shapes using the built-in geometries, for instance.
var planeGeometry = new THREE.PlaneGeometry(4, 1, 1, 1);
The 4 and 1; I'm unsure what that measures up to in pixels, although I do see it rendered. I'm resorting to eyeballing it (guess and checking) every time so that it looks acceptable.
Without a fair bit of extra math THREE is not measured in pixels.
To make a simple grid (I leave optimizations, colors, etc for future refinements) try something like:
var hCount = from_my_web_form('horiz'),
vCount = from_my_web_form('vert'),
size = 1,
spacing = 1.3;
var grid = new THREE.Object3d(); // just to hold them all together
for (var h=0; h<hCount; h+=1) {
for (var v=0; v<vCount; v+=1) {
var box = new THREE.Mesh(new THREE.BoxGeometry(size,size,size),
new THREE.MeshBasicMaterial());
box.position.x = (h-hCount/2) * spacing;
box.position.y = (v-vCount/2) * spacing;
grid.add(box);
}
}
scene.add(grid);

Emgu CV draw rotated rectangle

I'm looking for few days a solution to draw rectangle on image frame. Basically I'm using CvInvoke.cvRectangle method to draw rectangle on image because I need antialiased rect.
But problem is when I need to rotate a given shape for given angle. I can't find any good solution.
I have tryed to draw rectangle on separate frame then rotate hole frame and apply this new image on top of my base frame. But in this solution there is a problem with antialiasing. It's not working.
I'm working on simple application that should allow draw few kinds of shape, resize them and rotation for given angle.
Any idea how to achive this?
The best way I found to draw a minimum enclosing rectangle on the contour is using the Polylines() function which uses vertices that are returned from MinAreaRect() function. There are surely other ways to do it as well. Here is the code walk down:
// Find contours
var contours = new Emgu.CV.Util.VectorOfVectorOfPoint();
Mat hierarchy = new Mat();
CvInvoke.FindContours(image, contours, hierarchy, RetrType.Tree, ChainApproxMethod.ChainApproxSimple);
// According to your metric, get an index of the contour you want to find the min enclosing rectangle for
int index = 2; // Say, 2nd index works for you.
var rectangle = CvInvoke.MinAreaRect(contours[index]);
Point[] vertices = Array.ConvertAll(rectangle.GetVertices(), Point.Round);
CvInvoke.Polylines(image, vertices, true, new MCvScalar(0, 0, 255), 5);
The result can be visualized in the image below, in red is the minimum enclosing rectangle.
I use C# and EMGU.CV(4.1), and I think this code will not be difficult to transfer to any platform.
Add function in the in your helper:
public static Mat DrawRect(Mat input, RotatedRect rect, MCvScalar color = default(MCvScalar),
int thickness = 1, LineType lineType = LineType.EightConnected, int shift = 0)
{
var v = rect.GetVertices();
var prevPoint = v[0];
var firstPoint = prevPoint;
var nextPoint = prevPoint;
var lastPoint = nextPoint;
for (var i = 1; i < v.Length; i++)
{
nextPoint = v[i];
CvInvoke.Line(input, Point.Round(prevPoint), Point.Round(nextPoint), color, thickness, lineType, shift);
prevPoint = nextPoint;
lastPoint = prevPoint;
}
CvInvoke.Line(input, Point.Round(lastPoint), Point.Round(firstPoint), color, thickness, lineType, shift);
return input;
}
This draws roteted rectangle by points. Here used rounding points by method Point.Round becose RotatedRect has points in float coordinates and CvInvoke.Line takes points as integer.
Use:
var mat = Mat.Zeros(200, 200, DepthType.Cv8U, 3);
mat.GetValueRange();
var rRect = new RotatedRect(new PointF(100, 100), new SizeF(100, 50), 30);
DrawRect(mat, rRect,new MCvScalar(255,0,0));
var brect = CvInvoke.BoundingRectangle(new VectorOfPointF(rRect.GetVertices()));
CvInvoke.Rectangle(mat, brect, new MCvScalar(0,255,0), 1, LineType.EightConnected, 0);
Result:
You should read the OpenCV documentation.
There is a RotatedRectangle class that you can use for your task. You can specify the angle by which the rectangle will be rotated.
Here is a sample code (taken from the docs) for drawing a rotated rectangle:
Mat image(200, 200, CV_8UC3, Scalar(0));
RotatedRect rRect = RotatedRect(Point2f(100,100), Size2f(100,50), 30);
Point2f vertices[4];
rRect.points(vertices);
for (int i = 0; i < 4; i++)
line(image, vertices[i], vertices[(i+1)%4], Scalar(0,255,0));
Rect brect = rRect.boundingRect();
rectangle(image, brect, Scalar(255,0,0));
imshow("rectangles", image);
waitKey(0);
Here is the result:

How to change only width of path(rect, circle) while mouse drag using paper.js

I have stucked at resizing only width of an item in the canvas using paper.js
I have done in following ways to resize , but it results in resizing both left and right sides from the center of rectangle/circle.
function onMouseDrag(event){
(selectedItem.bounds.contains(event.point)) ?
selectedItem.scale(0.9871668311944719,1) : selectedItem.scale(1.013, 1);
}
above code resizes in both x-directions.
Help me to resize width only in one direction.
thanks,
suribabu.
You can center the scale operation at any point by using the form:
scale(hor, ver, point)
So in your case, if you want to scale from the left-center of your selected item, you could use:
function onMouseDrag(event){
(selectedItem.bounds.contains(event.point)) ?
selectedItem.scale(0.9871668311944719, 1, selectedItem.bounds.left) : selectedItem.scale(1.013, 1, selectedItem.bounds.left);
}
I am not sure what you mean with scale only the width. If you want to have thicker path than changing the strokeWidth instead might do what you want.
If you are wondering why the scaled path expands in both directions on the x-axis than you might check the location of the paths local center. If some nodes of the path have negative coordinates relative to this local center, scaling them with a positive value decreases their coordinates even more.
Perhaps you should normalize all the vertices, means move them directly so that the smallest x and y value of all vertices is 0.
Greetings and good luck
Try this:
function onMouseDown(e) {
var cx = e.point.x;
var cy = e.point.y;
var rectangle = new Rectangle(e.point, new Size(1,1));
path = new Path.Ellipse(rectangle);
path.strokeColor = 'red';
}
function onMouseDrag(e) {
path.remove();
var x = Math.min(e.point.x, cx),
y = Math.min(e.point.y, cy),
w = Math.abs(e.point.x -cx),
h = Math.abs(e.point.y -cy);
var rectangle = new Rectangle(x,y,w,h);
path = new Path.Ellipse(rectangle);
path.strokeColor = 'red'
}

Categories

Resources