Complex path is not cut - three.js

I need to cut out the complex profile. The circle is cut, and California is not cut. Here's an example. This does not work:
squareShape.holes.push( californiaPath );
And it - works:
var Radius = 190, x0 = 1440, y0 = 1260;
var CirclePath = new THREE.Path();
CirclePath.moveTo( x0 + Radius, y0 );
CirclePath.absarc( x0, y0, Radius, 0, Math.PI*2, true );
CirclePath.closePath();
squareShape.holes.push( CirclePath );
look here. What happens?

Related

Create a plane with curved edges using PlaneGeometry - Three.js

I'm trying to create a 2D square with curved/round edges. As I understand it using planes is the way to go. I'm struggling to figure out how exactly to do this though. It seems like it should be simple. Can't seem to find any straightforward answers online either so I would really appreciate the help.
// Create plane
let geometry = new THREE.PlaneGeometry(1, 1)
// Round the edges somehow?
this.mesh = new THREE.Mesh(geometry, material)
this.mesh.rotation.x = -Math.PI / 2
this.container.add(this.mesh)
Managed to get it working based off #prisoner849 suggestion to use THREE.Shape() and THREE.ShapeBufferGeometry(). Posting my answer but its mostly the same as the found here here
let x = 1; let y = 1; let width = 50; let height = 50; let radius = 20
let shape = new THREE.Shape();
shape.moveTo( x, y + radius );
shape.lineTo( x, y + height - radius );
shape.quadraticCurveTo( x, y + height, x + radius, y + height );
shape.lineTo( x + width - radius, y + height );
shape.quadraticCurveTo( x + width, y + height, x + width, y + height - radius );
shape.lineTo( x + width, y + radius );
shape.quadraticCurveTo( x + width, y, x + width - radius, y );
shape.lineTo( x + radius, y );
shape.quadraticCurveTo( x, y, x, y + radius );
let geometry = new THREE.ShapeBufferGeometry( shape );
this.mesh = new THREE.Mesh(geometry, material)
this.mesh.rotation.x = -Math.PI / 2
this.container.add(this.mesh)

How to rotate cartesian coordinates relative to a vector?

I'm building a fractal tree in three dimensions. I need to draw each generation of branches at an angle relative to the previous generation. The branches are currently drawn at the same angle and are growing "straight up". I know I need to do a rotation of some kind, but not sure if it's quaternions or if I need to take a completely different approach.
Here's a jsfiddle of the fractal tree with the branches growing "straight up".
https://jsfiddle.net/degraeve/xa8m5Lcj/59/
Here's a 2D image of what I'm trying to achieve with the branch angles: https://i.imgur.com/uVK4Dx6.png
code that appears in the jsfiddle:
function draw_tree_branch(x, y, z, phi, theta, radius) {
// use sperical coordinate system
// https://en.wikipedia.org/wiki/Spherical_coordinate_system
var phi_in_degrees = phi * (180 / Math.PI);
var material = new THREE.LineBasicMaterial({
color: 0x00ffff,
linewidth: 1
});
// draw 3 lines at 120 degrees to each other
var angle_between_branches = 120;
var num_branches = 360 / angle_between_branches;
for (var temp_count = 1; temp_count <= num_branches; temp_count++) {
phi_in_degrees += angle_between_branches;
phi = (phi_in_degrees) * Math.PI / 180;
// compute Cartesian coordinates
var x2 = x + (radius * Math.sin(theta) * Math.sin(phi));
var y2 = y + (radius * Math.cos(theta));
var z2 = z + (radius * Math.sin(theta) * Math.cos(phi));
// ????????
// How do I rotate this line so the angles are "relative" to the parent line instead of growing "straight up?"
// Quaternion ???
// example of what I'm trying to achieve, but in 3D:
// https://www.codheadz.com/2019/06/30/Trees-with-Turtle-in-Python/simple_tree.png
// ????????
var points = [];
var vector_1 = new THREE.Vector3(x, y, z);
points.push(vector_1);
var vector_2 = new THREE.Vector3(x2, y2, z2);
points.push(vector_2);
var geometry = new THREE.BufferGeometry().setFromPoints(points);
var line = new THREE.Line(geometry, material);
scene.add(line);
// keep drawing branches until the branch is "too short"
if (radius > 2) {
draw_tree_branch(x2, y2, z2, phi, theta, radius * 0.5);
}
}
}
I may not even be asking the right question. Any pointers in the right direction are appreciated.
You're very close. The only problem is that theta is the same on each iteration, so you'll always get a sub-branch that's 30ยบ from vertical. A simple way to solve this is by keeping track of the iteration you're in, and multiply that by tree_theta so you get an increasing number of degrees: 30, 60, 90, 120, etc...
function draw_tree_branch(x, y, z, phi, tree_theta, radius, iteration) {
var theta = tree_theta * iteration;
// ... perform all calculations
// Draw next branch with iteration + 1
if (radius > 2) {
draw_tree_branch(x2, y2, z2, phi, tree_theta, radius * 0.5, iteration + 1);
}
}
Here's an updated version of your JSFiddle: https://jsfiddle.net/marquizzo/r2w7oz6x/

Ying and Yang P5.js

I'm currently trying to make a Ying and Yang symbol spin using a circular path. SO far I have made the medium and smaller ones rotate just fine. However, the stationary arc's are wrecking the illusion. Here is an open link to see my current code.
https://editor.p5js.org/Nathan65bmx/sketches/PAu3xx6Bd
Just looking for someone to help me make it look like it is rotating properly.
Draw all shapes from a common central point, then use the rotate() function. https://p5js.org/reference/#/p5/rotate Here's the link.
Do ask if you need help modifying the code.
[EDIT]
Here's the working version
function setup() {
createCanvas(600, 600);
angleMode(DEGREES);
a = 0;
x = 180;
}
let ANGLE = 0
let a;
let x;
function draw() {
background(180, 13, 123);
//Big Circle
noStroke();
//Change starts from here
push();
translate(300, 300);
rotate(a);
fill("black");
arc(0, 0, 300, 300, 0, x);
fill("white")
arc(0, 0, 300, 300, x,0);
pop();
a+=2;
//Till here
// Medium Circles
fill("black");
let CENTRE_X4 = width / 2;
let CENTRE_Y4 = height / 2;
let RADIUS4 = 75;
let X4 = RADIUS4 * cos(ANGLE);
let Y4 = RADIUS4 * sin(ANGLE);
ellipse(CENTRE_X4 + X4, CENTRE_Y4 + Y4, 150);
fill("white");
let CENTRE_X3 = width / 2;
let CENTRE_Y3 = height / 2;
let RADIUS3 = 75;
let X3 = RADIUS3 * cos(ANGLE);
let Y3 = RADIUS3 * sin(ANGLE);
ellipse(CENTRE_X3 - X3, CENTRE_Y3 - Y3, 150);
// Small Circles
fill("white");
let CENTRE_X = width / 2;
let CENTRE_Y = height / 2;
let RADIUS = 75;
let X = RADIUS * cos(ANGLE);
let Y = RADIUS * sin(ANGLE);
ellipse(CENTRE_X + X, CENTRE_Y + Y, 50);
fill("black");
let CENTRE_X2 = width / 2;
let CENTRE_Y2 = height / 2;
let RADIUS2 = 75;
let X2 = RADIUS2 * cos(ANGLE);
let Y2 = RADIUS2 * sin(ANGLE);
ellipse(CENTRE_X2 - X2, CENTRE_Y2 - Y2, 50);
ANGLE = ANGLE + 2;
}
All the edits have been done using the push() & pop() and rotate() functions.
Hope this has helped!
My answer is not adding anything new to Ruskin's great answer suggesting rotate() as well as push()/pop(), but wanted to mention that you could isolate the drawing instructions into a re-usable function and additionally simply reduce some of complexity and repetition (see D.R.Y):
function setup() {
createCanvas(600, 600);
angleMode(DEGREES);
}
function draw() {
background (200, 13, 123);
// isolate coordinate system
push();
// move everything to the center
translate(width / 2, height / 2);
// rotate everything from the center
rotate(frameCount % 360);
// draw ying Yang
drawYingYang(300);
// return to the original coordinate system (0,0 = top left)
pop();
}
function drawYingYang(outerDiameter){
let innerYOffset = outerDiameter / 4;
let outerRadius = outerDiameter / 2;
let innerDiameter = innerYOffset / 1.5;
// Big Circle
noStroke();
fill("black");
arc(0, 0, outerDiameter, outerDiameter, -90, -270);
fill("white")
arc(0, 0, outerDiameter, outerDiameter, 90, 270);
// Medium Circles
fill("black");
ellipse(0, innerYOffset, outerRadius);
fill("white");
ellipse(0, - innerYOffset, outerRadius);
// Small Circles
fill("white");
ellipse(0, innerYOffset, innerDiameter);
fill("black");
ellipse(0, - innerYOffset, innerDiameter);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>
If that's the only thing you want to draw, removing push()/pop() won't make a difference visually, however, if you want to draw other shapes it will much easier to have independent control over where each shape is drawn

Cartesian coordinates algorithmb question

On the 2D grid, there is a cartesian coordinate C(Cx,Cy) which is a center of square and it has 'b' of radius.
And there are two points P1(x1,y1), P2(x2,y2). When I connect P1 with P2 directly by line, there should be a straight line.
I wanna make the pseudo code that checking whether the straight line between P1 and P2 is on the square area or not.
The argument would be center point and two different points and radius.
Square center: (Cx,Cy)
Two points: P1(x1,y1), P2(x2,y2)
Radius: 'b'
function Straight ((x1,y1),(x2,y2),(Cx,Cy),b)
If the straight line is not on the square area, it should return true, if it is on the square area, it should return false.
You can convert this problem (by projecting the coordinates) to a square at (0, 0) with "radius" 1.
For determining whether the line segment (p1, p2) crosses the top side of the square, you can first test these conditions:
(y2 - 1) * (y2 - 1) > 0. If this is true, it means that the line segment is completely above the top of the square, or completely below it.
y1 = y2. If this is true, the line segment is parallel with the square.
In all other cases, the x-coordinate of the intersection point is:
x = x1 - y1 * (x2 - x1) / (y2 - y1)
If this x is not in the range [-1, 1], the line segment does not intersect the top of the square.
A similar operation can be done for the three other sides.
If any of them gives an intersection coordinate in the range [-1, 1], the function straight should return true, and false otherwise.
Here is an interactive JavaScript implementation with which you can draw a line segment (by "dragging" the mouse) near a square. The square will highlight when the call to straight returns true:
function intersectionWithXaxis(x1, y1, x2, y2) {
if (y1 * y2 > 0 || y1 === y2) return Infinity; // No intersection
return x1 - y1 * (x2 - x1) / (y2 - y1); // x-coordinate of intersection
}
function straight(x1, y1, x2, y2, cx, cy, b) {
// Project the coordinates so the square is at (0, 0) with "radius" 1
x1 = (x1-cx)/b;
y1 = (y1-cy)/b;
x2 = (x2-cx)/b;
y2 = (y2-cy)/b;
let z;
// Get intersections with top, bottom, left and right side of box:
z = intersectionWithXaxis(x1, y1-1, x2, y2-1);
if (Math.abs(z) <= 1) return true;
z = intersectionWithXaxis(x1, y1+1, x2, y2+1);
if (Math.abs(z) <= 1) return true;
// We can use the same function for vertical line intersections by swapping x and y
z = intersectionWithXaxis(y1, x1-1, y2, x2-1);
if (Math.abs(z) <= 1) return true;
z = intersectionWithXaxis(y1, x1+1, y2, x2+1);
if (Math.abs(z) <= 1) return true;
return false;
}
let cx = 100;
let cy = 60;
let b = 30;
let x1, y1, x2, y2;
// I/O handling
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
ctx.fillStyle = "yellow";
let isMouseDown = false;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.rect(cx-b, cy-b, 2*b, 2*b);
ctx.stroke();
if (!isMouseDown) return;
// Call the main function. If true, highlight the square
if (straight(x1, y1, x2, y2, cx, cy, b)) ctx.fill();
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
canvas.addEventListener("mousedown", function(e) {
x1 = e.clientX - this.offsetLeft;
y1 = e.clientY - this.offsetTop;
isMouseDown = true;
});
canvas.addEventListener("mousemove", function(e) {
if (!isMouseDown) return;
x2 = e.clientX - this.offsetLeft;
y2 = e.clientY - this.offsetTop;
draw();
});
canvas.addEventListener("mouseup", function(e) {
isMouseDown = false;
});
draw();
<canvas width="400" height="180"></canvas>

How to make a crescent moon shape in HTML canvas

I need to make the following shape in HTML5 canvas. I have tried using cubic bezier arcs and also clipping two circles.
How can I make this shape?
Here's my work in progress, just cant get it right
https://codepen.io/matt3224/pen/oeXbdg?editors=1010
var canvas = document.getElementById("canvas1");
var ctx1 = canvas.getContext("2d");
ctx1.lineWidth = 2;
ctx1.beginPath();
ctx1.bezierCurveTo(4, 42, 0, 0, 42, 4);
ctx1.moveTo(4, 42);
ctx1.bezierCurveTo(4, 42, 0, 84, 42, 84);
ctx1.stroke();
var canvas = document.getElementById("canvas2");
var ctx2 = canvas.getContext("2d");
ctx2.lineWidth = 2;
ctx2.beginPath();
ctx2.arc(55, 75, 50, 0, Math.PI * 2, true);
ctx2.moveTo(165, 75);
ctx2.arc(75, 75, 50, 0, Math.PI * 2, true);
ctx2.fill();
Circle circle boolean operation.
Incase anyone is interested in a programmatic solution the example below finds the intercept points of the two circles and uses those points to calculate the start and end angles for the outer and inner circle.
This is a little more flexible than a masking solution as it give you a path.
Snippet shows circle, move mouse over circle to see crescent solution. Not the stroke that would not be available if using a masking solution.
const PI2 = Math.PI * 2;
const ctx = canvas.getContext("2d");
canvas.height = canvas.width = 400;
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
const m = mouse;
const bounds = canvas.getBoundingClientRect();
m.x = e.pageX - bounds.left - scrollX;
m.y = e.pageY - bounds.top - scrollY;
m.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : m.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
// generic circle circle intercept function. Returns undefined if
// no intercept.
// Circle 1 is center x1,y1 and radius r1
// Circle 2 is center x2,y2 and radius r2
// If points found returns {x1,y1,x2,y2} as two points.
function circleCircleIntercept(x1,y1,r1,x2,y2,r2){
var x = x2 - x1;
var y = y2 - y1;
var dist = Math.sqrt(x * x + y * y);
if(dist > r1 + r2 || dist < Math.abs(r1-r2)){
return; // no intercept return undefined
}
var a = (dist * dist - r1 * r1 + r2 *r2) / ( 2 * dist);
var b = Math.sqrt(r2 * r2 - a * a);
a /= dist;
x *= a;
y *= a;
var mx = x2 - x;
var my = y2 - y;
dist = b / Math.sqrt(x * x + y * y);
x *= dist;
y *= dist;
return {
x1 : mx-y,
y1 : my+x,
x2 : mx+y,
y2 : my-x,
};
}
// draws a crescent from two circles if possible
// If not then just draws the first circle
function drawCrescent(x1,y1,r1,x2,y2,r2){
// The circle circle intercept finds points
// but finding the angle of the points does not consider
// the rotation direction and you end up having to do a lot of
// checking (if statments) to determin the correct way to draw each circle
// the following normalises the direction the circle are from each other
// thus making the logic a lot easier
var dist = Math.hypot(x2-x1,y2-y1);
var ang = Math.atan2(y2-y1,x2-x1);
var intercepts = circleCircleIntercept(x1,y1,r1,x1 + dist,y1,r2);
if(intercepts === undefined){
ctx.beginPath();
ctx.arc(x1, y1, r1, 0, PI2);
if(dist < r1){
ctx.moveTo(x2 + r2, y2);
ctx.arc(x2, y2, r2, 0, PI2, true);
}
ctx.fill();
ctx.stroke();
return;
}
// get the start end angles for outer then inner circles
const p = intercepts;
var startA1 = Math.atan2(p.y1 - y1, p.x1 - x1) + ang;
var endA1 = Math.atan2(p.y2 - y1, p.x2 - x1) + ang;
var startA2 = Math.atan2(p.y1 - y1, p.x1 - (x1 + dist)) + ang;
var endA2 = Math.atan2(p.y2 - y1, p.x2 - (x1 + dist)) + ang;
ctx.beginPath();
if(endA1 < startA1){
ctx.arc(x1, y1, r1, startA1, endA1);
ctx.arc(x2, y2, r2, endA2, startA2, true);
}else{
ctx.arc(x2, y2, r2, endA2, startA2);
ctx.arc(x1, y1, r1, startA1, endA1,true);
}
ctx.closePath();
ctx.fill();
ctx.stroke();
}
const outerRadius = 100;
const innerRadius = 80;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.lineJoin = "round";
ctx.lineWidth = 8;
ctx.strokeStyle = "#999";
// main update function
function mainLoop(timer){
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.fillStyle = "black";
ctx.fillRect(0,0,w,h);
ctx.fillStyle = "white";
ctx.fillText("Move mouse over circle",cw,40);
drawCrescent(cw, ch-40, outerRadius, mouse.x, mouse.y, innerRadius);
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
canvas { border : 2px solid black; }
<canvas id="canvas"></canvas>
Solved it using globalCompositeOperation
https://codepen.io/matt3224/pen/oeXbdg?editors=1010

Resources