I want to develop a game "Cut the Shape" in JavaScript. Several components with convex polygons that will need to be clipped depending on the number of convex freedom faces. Lines and polygons are displayed on the Canvas. The Paper.js graphics load to use.
But then the question arose, which concerns the correct separation of the polygons from each other relative to the drawn line, I just can’t think of a way to do this.
Here is an example on simple polygons (the lines can be absolutely any, the user draws them himself):
I got to the points of dividing the polygon into other polygons using small dots with a line:
var polygon = new Path.RegularPolygon(new Point(200, 300), 4, 100);
polygon.strokeColor = 'blue';
polygon.fullySelected = true;
var shapesArray = [];
shapesArray.push(polygon);
function splitShape(path1, path2){
var shapesArrayCopy = path1.slice(0);
shapesArray = [];
for(var i = 0; i < shapesArrayCopy.length; i++){
var intersections = shapesArrayCopy[i].getIntersections(path2);
if(intersections.length >= 2){
var p1 = shapesArrayCopy[i].split(shapesArrayCopy[i].getNearestLocation(intersections[0].point));
var p2 = shapesArrayCopy[i].split(shapesArrayCopy[i].getNearestLocation(intersections[1].point));
p1.closed = true;
p2.closed = true;
shapesArray.push(Object.assign(p1));
shapesArray.push(Object.assign(p2));
path2.visible = false;
}
else{
shapesArray.push(shapesArrayCopy[i])
}
}
var myPath;
function onMouseDown(event) {
myPath = new Path();
myPath.strokeColor = 'black';
myPath.add(event.point);
myPath.add(event.point);
}
function onMouseDrag(event) {
myPath.segments.pop();
myPath.add(event.point);
}
function onMouseUp(event) {
splitShape(shapesArray, myPath)
myPath.visible = false;
}
In 2D You just displace the cuts in perpendicular direction to the cut line. If your cut line endpoints are: p0(x0,y0),p1(x1,y1) then the line direction is:
dp = p1-p0 = (x1-x0,y1-y0)
make it unit:
dp /= sqrt((dp.x*dp.x)+(dp.y*dp.y)
make it equal to half of the gap between cuts:
dp *= 0.5*gap
now tere are two perpendicular directions:
d0 = (-dp.y,+dp.x)
d1 = (+dp.y,-dp.x)
so now just add d0 to all vertexes of one cut, and d1 to the other one. Which use for which is simply you take point that does not lie on the cutting line (for example avg point of your cut) p and compute (only once for polygon cut):
t = dot(p-p0,d0) = ((p.x-x0)*d0.x)+((p.y-y0)*d0.y)
if (t>0) use d0, if (t<0) use d1 and if (t==0) you chose wrong point p as it lies on cutting line.
Related
I have a graphics class that can draw lines and bezier curves. It stores the points of the shapes.
export default function Graphics() {
let points = [];
this.clear = () => points = [];
this.points = () => points;
this.point = point => {
points.push(point);
};
this.line = line => {
let a = line.A,
b = line.B,
direction = v.csub(b, a),
steps = v.length(direction),
step = v.cscale(direction, 1/steps);
let cur = v.copy(a);
for (let i = 0; i < steps; i++) {
let point = v.copy(cur);
point[0] = Math.round(point[0]);
point[1] = Math.round(point[1]);
points.push(point);
v.add(cur, step);
}
};
}
Now I can draw a rectangle by drawing four lines:
let g = new Graphics();
g.bent(ab, wave);
g.bent(bc, wave);
g.bent(cd, wave);
g.bent(da, wave);
bent function draws a slightly bent line so it's not straight. I can draw outlines fine, now I want to fill this shape I have drawn. Basically I need the points that fills this shape. How can I calculate it? I only need integer points.
I see there are a lot of complicated ways of doing this, but there should be a simple solution for a simple shape like a rectangle with bent edges and rounded corners.
I wanted to make a snapping functionality to snap to my mesh vertices. I experimented with several solutions.
One solution is to add THREE.Sprite instances for all vertices in my scene and then using a rayCaster to decide whether there is a snap point in the intersects array. It works pretty well; here is a fiddle with a demo.
The idea is to hide the sprites in the final solution so they won't be rendered, but my scenes are pretty big so it would still mean adding lots of sprites to my scene (for every vertex one so possibly thousands of sprites) to detect snap points with my rayCaster.
var intersects = rayCaster.intersectObject(scene, true);
var snap = null;
if (intersects.length > 0) {
var index = 0;
var intersect = intersects[index];
while (intersect && intersect.object.name === 'snap') {
snap = sprite.localToWorld(sprite.position.clone());
index++
intersect = intersects[index];
}
if (intersect) {
var face = intersect.face;
var point = intersect.point;
var object = intersect.object;
mouse3D.copy(point);
}
}
if (snap) {
renderer.domElement.style.cursor = 'pointer';
} else {
renderer.domElement.style.cursor = 'no-drop';
}
I also thought of an alternative solution by doing the math using results from the rayCaster. That solution is demonstrated in this fiddle.
The idea here is to test all vertices from the geometry of the object (mesh) that is intersected and then check whether the distance between the intersect point and those vertices from the geometry is smaller then the snap threshold.
var intersects = rayCaster.intersectObject(mesh, true);
if (intersects.length > 0) {
var distance, intersect = intersects[0];
var face = intersects[0].face;
var point = intersects[0].point;
var object = intersects[0].object;
var snap = null;
var test = object.worldToLocal(point);
var points = object.geometry.vertices;
for (var i = 0, il = points.length; i < il; i++) {
distance = points[i].distanceTo(test);
if (distance > threshold) {
continue;
}
snap = object.localToWorld(points[i]);
}
if (snap) {
sphereHelper.position.copy(snap);
sphereHelper.visible = true;
renderer.domElement.style.cursor = 'pointer';
} else {
sphereHelper.visible = false;
renderer.domElement.style.cursor = 'no-drop';
}
}
The sad thing is that in the second solution snap will only work when the mouse is moved from the surface of the intersected object towards a vertex. In case the mouse is moved from outside the object (so there is no intersection) the snapping won't work. In that respect the first solution with sprites is much more usable...
My question, am I overcomplicating things and is there a better/simpler/more efficient way to do this? Any suggestions for alternative approaches are welcome.
I looked into #meepzh his suggestion of using an octree and made the following solution using this threeoctree repository from github. The THREE.Octree class did not solve all my problems out-of-the-box so
I added custom method findClosestVertex to the THREE.Octree class that can be used like this.
var snap = octree.findClosestVertex(position, radius);
snap is null in case no vertices within the radius of position and returns the closest point (THREE.Vector3) in world space otherwise.
I made a Pull-Request here on github for the new method.
Here is a demo in a fiddle
I have a sphere (globe) with objects (pins) on the surface with DOM elements (labels) what are calculated from the pin position to 2d world.
My problem is that when the pins go behind the globe (with mouse dragging or animation) then I need to hide labels which are in DOM so that the text label isn’t visible without the pin.
My logic is that if I can get the pin which is in 3D world to tell me if it’s behind the globe then I can hide the label associated with the pin.
Codepen with whole the code.
The function that I have researched together:
function checkPinVisibility() {
var startPoint = camera.position.clone();
for (var i = 0; i < pins.length; i++) {
var direction = pins[i].position.clone();
var directionVector = direction.sub(startPoint);
raycaster.set(startPoint, directionVector.clone().normalize());
var intersects = raycaster.intersectObject(pins[i]);
if (intersects.length > 0) {
// ?
}
}
}
I have researched through many posts but can’t really get the result needed:
ThreeJS: How to detect if an object is rendered/visible
Three.js - How to check if an object is visible to the camera
http://soledadpenades.com/articles/three-js-tutorials/object-picking/
I have gotten it work by mouse XY position as a ray, but can’t really get a working solution with constant rendering for all the pins.
You want to know which points on the surface of a sphere are visible to the camera.
Imagine a line from the camera that is tangent to the sphere. Let L be the length of the line from the camera to the tangent point.
The camera can only see points on the sphere that are closer to the camera than L.
The formula for L is L = sqrt( D^2 - R^2 ), where D is the distance from the camera to the sphere center, and R is the sphere radius.
WestLangley's solution in code form. Please give him the accepted answer if you feel his answer the best.
function checkPinVisibility() {
var cameraToEarth = earth.position.clone().sub(camera.position);
var L = Math.sqrt(Math.pow(cameraToEarth.length(), 2) - Math.pow(earthGeometry.parameters.radius, 2));
for (var i = 0; i < pins.length; i++) {
var cameraToPin = pins[i].position.clone().sub(camera.position);
if(cameraToPin.length() > L) {
pins[i].domlabel.style.visibility = "hidden";
} else {
pins[i].domlabel.style.visibility = "visible";
}
}
}
Oddly enough it is still susceptible to that camera pan error. Very weird, but it's still better than my Projection-onto-LOOKAT solution.
MY OLD ANSWER:
I would have assumed its something like this, but this doesn't seem to work as expected.
if (intersects.length > 0) {
pins[i].domlabel.style.visibility = "visible";
} else {
pins[i].domlabel.style.visibility = "hidden";
}
I got close with this solution, but its still not perfect. What the code below does is it finds the distance along the LOOKAT direction of the camera to a pin (cameraToPinProjection) and compares it with the distance along the LOOKAT direction to the earth (cameraToEarthProjection).
If cameraToPinProjection > cameraToEarthProjection it means the pin is behind the centre of the earth along the LOOKAT direction (and then I hide the pin).
You will realise there's a "0.8" factor I multiply the cameraToEarth projection by. This is to make it slightly shorter. Experiment with it.
Its not perfect because as you rotate the Earth around you will notice that sometimes labels don't act the way you'd like them, I'm not sure how to fix.
I hope this helps.
function checkPinVisibility() {
var LOOKAT = new THREE.Vector3( 0, 0, -1 );
LOOKAT.applyQuaternion( camera.quaternion );
var cameraToEarth = earth.position.clone().sub(camera.position);
var angleToEarth = LOOKAT.angleTo(cameraToEarth);
var cameraToEarthProjection = LOOKAT.clone().normalize().multiplyScalar(0.8 * cameraToEarth.length() * Math.cos(angleToEarth));
var startPoint = camera.position.clone();
for (var i = 0; i < pins.length; i++) {
var cameraToPin = pins[i].position.clone().sub(camera.position);
var angleToPin = LOOKAT.angleTo(cameraToPin);
var cameraToPinProjection = LOOKAT.clone().normalize().multiplyScalar(cameraToPin.length() * Math.cos(angleToPin));
if(cameraToPinProjection.length() > cameraToEarthProjection.length()) {
pins[i].domlabel.style.visibility = "hidden";
} else {
pins[i].domlabel.style.visibility = "visible";
}
}
}
Hi i'm starting to work with webGL using Three.js and I need to detect if a click on a sphere is within a certain section of it's surface.
Currently i can detect if the sphere was clicked and get the coords of the point clicked. Now what i need is to detect if that click was in a certain region of that sphere, based on a array of 3D points from that sphere (another suggestion is ok).
The sphere is in the center point, and the point is garrantied to be on the surface of the sphere. Now i need to calculate if it's just within a section section. Any suggestions? My problem seams to be more mathematical.
Also i prefere a generic way to do this because the sections may be just a triangle or may be more complex figures.
My first idea was to project your 3D points on to the screen coordinates (ie from world coordinates to view coordinates, exactly as in drawing the shape on the screen). This gives you the visual region that corresponds to the surface-of-interest. This would be a simple 3D-to-2D projection using your view, and then you can see if the click location lies in the 2D polygon.
Then I realised a problem with this approach, which is that it won't work if your region-of-interest goes around onto the back-surface of the sphere.
If this is a problem, you will need to construct the projection of your mouse click along the camera direction. If you are using an isometric camera, this should be possible...
Draw a (great-circle) ray from the point. Find the nearest intersection with a segment of the curve. The point is inside the curve if and only if the segment crosses the ray from right to left.
One solution is to render a pseudocolor image where your areas each have a color of their own to a texture. Then sample the image, use the pesudocolor as array index. * for parctical reason the encoding should spread values around a bit.
I've ended using a different method then the suggestions.
I'm using matrix determinants where: (T1, T2, T3) are points that form a triangle and X is the point i want to know if it's inside this triangle, then i simple calculate 3 determinants where:
d1 = det([T1 T2 X])
d2 = det([T1 X T3])
d3 = det([T1 T2 X])
If all determinants are the same sign, then the point is inside the triangle.
Now i form a list of triangles based on the selection area and check if the point is inside one of those triangles.
this.Detector.triangleDetector = function(position, triangleArray){
for(var idxString in triangleArray){
var index = parseInt(idxString);
if(this.pointInTriangle(position, triangleArray[index].coords1, triangleArray[index].coords2, triangleArray[index].coords3))
return true;
}
return false;
}
The function pointInTriangle(x,t1,t2,t3) does the determinant verification.
this.Detector.pointInTriangle = function(x,T1,T2,T3){
var array1 = [coord1.x ,coord1.y ,coord1.z];
var array2 = [coord2.x ,coord2.y ,coord2.z];
var array3 = [coord3.x ,coord3.y ,coord3.z];
var zero = 0;
var A = [[zero,zero,zero],[zero,zero,zero],[zero,zero,zero]];
var d1,d2,d3;
A[0][0] = position.x;
A[0][1] = position.y;
A[0][2] = position.z;
A[1][0] = array2[0];
A[1][1] = array2[1];
A[1][2] = array2[2];
A[2][0] = array3[0];
A[2][1] = array3[1];
A[2][2] = array3[2];
d1 = MyMath.determinant(A,3);
A[0][0] = array1[0];
A[0][1] = array1[1];
A[0][2] = array1[2];
A[1][0] = position.x;
A[1][1] = position.y;
A[1][2] = position.z;
d2 = MyMath.determinant(A,3);
A[1][0] = array2[0];
A[1][1] = array2[1];
A[1][2] = array2[2];
A[2][0] = position.x;
A[2][1] = position.y;
A[2][2] = position.z;
d3 = MyMath.determinant(A,3);
if((d1>=0 && d2 >=0 && d3>=0) || (d1<=0 && d2 <=0 && d3<=0)){
return true;
}
return false;
};
For one of my application I would need to draw a dashed curves on the bezier path in Html5 canvas... The dash' length and gaps in between should be variable... It is achivable in JavaFx, see this link... I would like to achieve same effect using Html5 canvas. I know how to draw dashed straight lines, but not curved lines along the bezier...
Though I am not an expert, I know the bezier drawing algorithm, problem I see with this algorithm is, it allows you to identify coordinates on the bezier using the time parameter which ranges from 0 to 1...
This is not sufficient because to draw a dashed bezier, I would need to draw many small beziers, with specified length parameter and at given gap distance, on the main bezier path. There must be some algorithm which is used by JavaFx. If anyone can help me out that would be great.
I would presume that JavaFX is using a general technique for drawing any dashed curve and just happens to be using it on a bezier in that example.
The hard part is figuring out where to start and stop each dash, which requires knowing the arc length of your bezier curve at various points along it.
There is an analytic approach, but I would suggest the following:
var bezier = function(controlPoints, t) {
/* your code here, I'll presume it returns a 2-element array of x and y. */
};
//just figure out the coordinates of all the points in each dash, don't draw.
//returns an array of arrays, each sub-array will have an even number of nu-
//merical elements, to wit, x and y pairs.
//Argument dashPattern should be an array of alternating dash and space
//lengths, e.g., [10, 10] would be dots, [30, 10] would be dashes,
//[30, 10, 10, 10] would be 30-length dash, 10-length spaces, 10-length dash
// and 10-length space.
var calculateDashedBezier = function(controlPoints, dashPattern) {
var step = 0.001; //this really should be set by an intelligent method,
//rather than using a constant, but it serves as an
//example.
//possibly gratuitous helper functions
var delta = function(p0, p1) {
return [p1[0] - p0[0], p1[1] - p0[1]];
};
var arcLength = function(p0, p1) {
var d = delta(p0, p1);
return Math.sqrt(d[0]*d[0] + d[1] * d[1]);
};
var subPaths = [];
var loc = bezier(controlPoints, 0);
var lastLoc = loc;
var dashIndex = 0;
var length = 0;
var thisPath = [];
for(var t = step; t <= 1; t += step) {
loc = bezier(controlPoints, t);
length += arcLength(lastLoc, loc);
lastLoc = loc;
//detect when we come to the end of a dash or space
if(length >= dashPattern[dashIndex]) {
//if we are on a dash, we need to record the path.
if(dashIndex % 2 == 0)
subPaths.push(thisPath);
//go to the next dash or space in the pattern
dashIndex = (dashIndex + 1) % dashPattern.length;
//clear the arclength and path.
thisPath = [];
length = 0;
}
//if we are on a dash and not a space, add a point to the path.
if(dashIndex % 2 == 0) {
thisPath.push(loc[0], loc[1]);
}
}
if(thisPath.length > 0)
subPaths.push(thisPath);
return subPaths;
};
//take output of the previous function and build an appropriate path
var pathParts = function(ctx, pathParts) {
for(var i = 0; i < pathParts.length; i++) {
var part = pathParts[i];
if(part.length > 0)
ctx.moveTo(part[0], part[1]);
for(var j = 1; j < part.length / 2; j++) {
ctx.lineTo(part[2*j], part[2*j+1]);
}
}
};
//combine the above two functions to actually draw a dashed curve.
var drawDashedBezier = function(ctx, controlPoints, dashPattern) {
var dashes = calculateDashedBezier(controlPoints, dashPattern);
ctx.beginPath();
ctx.strokeStyle = /* ... */
ctx.lineWidth = /* ... */
pathParts(ctx, dashes);
ctx.stroke();
};
The main problem with this approach is its unintelligent granularity. When step is too big for your (small) dashes or (big) curve, the step size will not work well and dash boundaries will not fall exactly where you want them to. When step is too small, you may end up doing lineTo()s on points that are a sub-pixel distance away from each other, making for AA artifacts sometimes. Filtering out sub-pixel distance coordinates is not hard, but it is inefficient to generate more 'vertices' than you really need. Coming up with a better step size is actually something I'd consider attacking more analytically.
There is one bonus to using this approach: if you replace bezier(controlPoints, t) with anything else that evaluates to a curve, you'll be drawing dashed whatevers!-- again with the same potential problems listed in the previous paragraph. But a really good solution to the granularity problem could work for all 'well-behaved' curves.
In the future we might be able to use context.setLineDash(segments) :
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#line-styles