Algorithm "NoObtuse" in javascript.
I have to implement this algo below in a canvas.
The user put a set of points and click on the button to call the function "noObtuse" and I have to draw the graph (see image).
How can I do it ?
No obtuse algoritm
EDIT:
I change the code with the informations of "MBo" but I don't get what I need, the nextPoint is not the right one (Neither in CW nor in CCW).
My code:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
this.col = 'red';
}
drawDot() {
fill(this.col);
ellipse(this.x, this.y, 8, 8);
}
tagNotExtreme() {
this.col = 'blue';
}
setColor(color) {
this.col = color;
}
}
var points;
var vertices = [];
var rightMost= null;
var tableau;
var angle1;
var angle2;
var a ;
var b;
var p1 ;
var indice;
var pointSize= 0;
var canvas;
var button_CH;
var button_clear;
var isEnter;
var times_clicked = 0;
function setup() {
canvas = createCanvas(500, 500);
background(255);
pointSize = 8;
points = [];
tableau = [];
rightMost = {x: 0, y:0};
a = {x: 0, y:0};
b = {x: 0, y:0};
p1 = {x: 0, y:0};
indice = 2;
isEnter = -1;
angle1 = 0;
angle2 = 0;
canvas.parent('candiv');
}
function draw() {
}
function contient(point, tableau){
var oui = -1;
for (var i = 0; i < tableau.length;i++){
if (point == tableau[i]){
oui = 1;
}
}
return oui;
}
function NoObtuse(){
p1 = rightMost;
angle1 = 0;
angle2 = 0;
a = points[0];
for (var k=0; k < points.length; k++){
angle = Math.atan2(points[k].y - rightMost.y, points[k].x - rightMost.x);
if (angle < angle1){
angle1 = angle;
a = points[k];
}
}
tableau[0] = p1;
tableau[1] = a;
var flag = -1;
var n = points.length-2;
var i;
for (i = 0; i <= 0; i++){
if (contient(points[i],tableau) == -1){
b = nextPoint(points[i], a, flag);
if (Math.degrees(find_angle(points[i],a,b)) <= 90 ){
points[i+1] = a;
a = b;
} else {
points[i+1] = b;
flag = -flag;
}
tableau[indice] = points[i];
indice = indice+1;
// ...
} else{
if(i == 0){
strokeWeight(2);
line(p1.x, p1.y, a.x, a.y);
}
}
}
points[points.length - 1] = a;
}
Math.degrees = function(radians) {
return radians * 180 / Math.PI;
};
function find_angle(A,B,C) {
var AB = Math.sqrt(Math.pow(B.x-A.x,2)+ Math.pow(B.y-A.y,2));
var BC = Math.sqrt(Math.pow(B.x-C.x,2)+ Math.pow(B.y-C.y,2));
var AC = Math.sqrt(Math.pow(C.x-A.x,2)+ Math.pow(C.y-A.y,2));
return Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB));
}
function nextPoint(pi, a, flag){
/* HELP : Let ρ be a ray attached to a and continuing the directed segment pia*/
fill('blue');
strokeWeight(2);
line(pi.x, pi.y, a.x, a.y);
b = a; /* HELP : Let b be the first point encountered by ρ when rotating it around a in the direction indicated by flag*/
return b;
}
function mousePressed() {
if(isEnter == -1){
newPointN = new Point(mouseX,mouseY);
if (rightMost && (rightMost.x < newPointN.x)) {
rightMost = newPointN;
}
points.push(newPointN);
newPointN.drawDot();
}else{
setup();
}
}
function keyPressed() {
if (keyCode == 82) {
setup();
} else if (keyCode == ENTER){
NoObtuse();
isEnter = 1;
} else if (keyCode == 77){
rightMost.setColor('green');
rightMost.drawDot();
}
}
To get the first point from the rightmost, choose one with the smallest value of
angle = Math.atan2(P[i].Y - rightmost.Y, P[i].X - rightmost.X)
To get the next point that goes after current A and B points in CCW order, you can compare angles A-B-C, calculated through atan2, cross-product and dot product of vectors.
angle = Math.atan2(cross(AB, BC), dot(AB, BC))
Related
//Editable Vars
let cols = 35;
let rows = 35;
let fps = 5;
//Declarations
let canvas;
let ctx;
let background;
let grid = new Array(cols);
let w;
let h;
let pathfinder;
let target;
let timer;
let renderQueue = [];
let canPathfind = true;
//Space Class
class Space{
constructor(x,y,c='lightgrey'){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.c = c;
}
draw(){
ctx.fillStyle = this.c;
ctx.strokeStyle = 'black';
ctx.fillRect(this.x * w, this.y * h, this.w, this.h);
ctx.rect(this.x * w, this.y * h, this.w, this.h);
ctx.stroke();
}
move(x,y){
if(x < 0 || x > cols-1 || y < 0|| y > rows-1){
return;
}
grid[this.x][this.y] = new Space(this.x,this.y);
renderQueue.push(grid[this.x][this.y]);
this.x = x;
this.y = y;
grid[this.x][this.y] = this;
renderQueue.push(grid[this.x][this.y]);
}
}
//Game-Run code
function gameStart(){
canvas = document.getElementById('gameCanvas');
ctx = canvas.getContext('2d');
w = canvas.width / cols;
h = canvas.height / rows;
createGrid();
pathfinder = new Space(randomInt(cols-1),randomInt(rows-1),'green');
grid[pathfinder.x][pathfinder.y] = pathfinder;
target = new Space(randomInt(cols-1),randomInt(rows-1),'red');
grid[target.x][target.y] = target;
drawGrid();
timer = setInterval(updateScreen, 1000/fps);
}
function restartGame(){
clearInterval(timer);
gameStart();
}
//Starts loading process on windows load
window.onload = gameStart();
//Checks all 8 possible move directions and calls pathfinder.move for best move
function pathfind(pathfinder, target){
if(!canPathfind) return;
let p = {x: pathfinder.x, y: pathfinder.y};
let t = {x: target.x, y: target.y};
let move = {x : 0, y : 0};
// 0,0 9,9
//if(p.x == t.x && p.y == t.y){
//restartGame();
//}
if(t.x - p.x >= 1){
move.x = 1;
}else if(t.x - p.x <= -1){
move.x = -1;
}else{
move.x = 0;
}
if(t.y - p.y >= 1){
move.y = 1;
}else if(t.y - p.y <= -1){
move.y = -1;
}else{
move.y = 0;
}
pathfinder.move(pathfinder.x + move.x, pathfinder.y + move.y);
}
function updateScreen(){
pathfind(pathfinder,target);
drawUpdatedSpaces();
}
function drawUpdatedSpaces(){
for(let i = 0; i < renderQueue.length; i++){
renderQueue[i].draw();
}
renderQueue = [];
}
function drawGrid(){
for(let i = 0; i < grid.length; i++){
for(let j = 0; j < grid[i].length; j++){
grid[i][j].draw();
}
}
}
//Creates grid and instantiates Space in every cell
function createGrid(){
for(let i = 0; i < grid.length; i++){
grid[i] = new Array(rows);
}
for(let i = 0; i < grid.length; i++){
for(let j = 0; j < grid[i].length; j++){
grid[i][j] = new Space(i,j);
}
}
}
// Returns distance to target from specified coords
function distanceFromTarget(x, y) {
return (Math.sqrt(Math.pow(Math.abs(x - target.x), 2) + (Math.pow(Math.abs(y - target.y), 2))));
}
// Returns random Integer between 0 and Max
function randomInt(max) {
return Math.floor(Math.random() * max);
}
It runs as expected which is great, but performance is super slow. That may be because I'm using jsfiddle to work on this while away from my personal PC setup, but is there a way to make this perform better? As of right now I can't move the grid size to >50 without it running extremely slow. I would love to eventually move to a 4k x 4k grid and create an auto-generating maze for the 'pathfinder' to pathfind through.
Thoughts/things I'm considering for performance:
Using HTML grid and updating via CSS instead of HTML5 Canvas
Only re-drawing cells that have changed (Implemented in the Space.move() function with array renderQueue)
literally re-doing everything in python :D
I'm developing a tool for modifying different geometrical shapes from an assortment of templates. The shapes are basic ones that could be found in rooms.
For example: L-shape, T-shape, Hexagon, Rectangle etc.
What I need to do is making the shape conform all necessary edges as to keep the shape's symmetry and bounding dimensions intact when the user modifies an edge.
A shape is simply implemented like this, with the first node starting in the upper left corner and going around the shape clockwise (I use TypeScript):
public class Shape {
private nodes: Array<Node>;
private scale: number; // Scale for calculating correct coordinate compared to given length
... // A whole lot of transformation methods
Which then is drawn as a graph, connecting each node to the next in the array. (See below)
If I, for example, would change the length of edge C to 3m from 3.5m, then I'd also want either edge E or G to change their length to keep the side to 12m and also push down E so that edge D is still fully horizontal.
If I instead would change side D to 2m, then B would have to change its length to 10m and so on.
(I do have shapes which have slanted angles as well, like a rectangle with one of its corners cut off)
The problem
I have the following code for modifying the specific edge:
public updateEdgeLength(start: Point, length: number): void {
let startNode: Node;
let endNode: Node;
let nodesSize = this.nodes.length;
// Find start node, and then select end node of selected edge.
for (let i = 0; i < nodesSize; i++) {
if (this.nodes[i].getX() === start.x && this.nodes[i].getY() === start.y) {
startNode = this.nodes[i];
endNode = this.nodes[(i + 1) % nodesSize];
break;
}
}
// Calculate linear transformation scalar and create a vector of the edge
let scaledLength = (length * this.scale);
let edge: Vector = Vector.create([endNode.getX() - startNode.getX(), endNode.getY() - startNode.getY()]);
let scalar = scaledLength / startNode.getDistance(endNode);
edge = edge.multiply(scalar);
// Translate the new vector to its correct position
edge = edge.add([startNode.getX(), startNode.getY()]);
// Calculate tranlation vector
edge = edge.subtract([endNode.getX(), endNode.getY()]);
endNode.translate({x: edge.e(1), y: edge.e(2)});
}
Now I need a more general case for finding the corresponding edges that will also need to be modified. I have begun implementing shape-specific algorithms as I know which nodes correspond to the edges of the shape, but this won't be very extensible for the future.
For example, the shape above could be implemented somewhat like this:
public updateSideLength(edge: Position): void {
// Get start node coordinates
let startX = edge.start.getX();
let startY = edge.start.getY();
// Find index of start node;
let index: num;
for (let i = 0; i < this.nodes.length; i++) {
let node: Node = this.nodes[i];
if(node.getX() === startX && node.getY() === startY) {
index = i;
break;
}
}
// Determine side
let side: number;
if (index === 0 || index === 2) {
side = this.TOP;
}
else if (index === 1 || index === 3 || index === 5) {
side = this.RIGHT;
}
else if (index === 4 || index === 6) {
side = this.BOTTOM;
}
else if (index === 7) {
side = this.LEFT;
}
adaptSideToBoundingBox(index, side); // adapts all other edges of the side except for the one that has been modified
}
public adaptSideToBoundingBox(exceptionEdge: number, side: number) {
// Modify all other edges
// Example: C and G will be modified
Move C.end Y-coord to D.start Y-coord;
Move G.start Y-coord to D.end Y-coord;
}
And so on.. But implementing this for each shape (5 atm.) and for future shapes would be very time consuming.
So what I'm wondering is if there is a more general approach to this problem?
Thanks!
Keep a list of point pairs and the key that constrains them and use that to overwrite coordinates on update.
This works with the example you gave:
var Point = (function () {
function Point(x, y, connectedTo) {
if (connectedTo === void 0) { connectedTo = []; }
this.x = x;
this.y = y;
this.connectedTo = connectedTo;
}
return Point;
}());
var Polygon = (function () {
function Polygon(points, constrains) {
if (constrains === void 0) { constrains = []; }
this.points = points;
this.constrains = constrains;
}
return Polygon;
}());
var Sketch = (function () {
function Sketch(polygons, canvas) {
if (polygons === void 0) { polygons = []; }
if (canvas === void 0) { canvas = document.body.appendChild(document.createElement("canvas")); }
this.polygons = polygons;
this.canvas = canvas;
this.canvas.width = 1000;
this.canvas.height = 1000;
this.ctx = this.canvas.getContext("2d");
this.ctx.fillStyle = "#0971CE";
this.ctx.strokeStyle = "white";
this.canvas.onmousedown = this.clickHandler.bind(this);
this.canvas.onmouseup = this.clickHandler.bind(this);
this.canvas.onmousemove = this.clickHandler.bind(this);
requestAnimationFrame(this.draw.bind(this));
}
Sketch.prototype.clickHandler = function (evt) {
if (evt.type == "mousedown") {
if (this.selectedPoint != void 0) {
this.selectedPoint = null;
}
else {
var score = null;
var best = null;
for (var p = 0; p < this.polygons.length; p++) {
var polygon = this.polygons[p];
for (var pi = 0; pi < polygon.points.length; pi++) {
var point = polygon.points[pi];
var dist = Math.abs(point.x - evt.offsetX) + Math.abs(point.y - evt.offsetY);
if (score == null ? true : dist < score) {
score = dist;
best = point;
}
}
}
this.selectedPoint = best;
}
}
if (evt.type == "mousemove" && this.selectedPoint != void 0) {
this.selectedPoint.x = Math.round(evt.offsetX / 5) * 5;
this.selectedPoint.y = Math.round(evt.offsetY / 5) * 5;
for (var pi = 0; pi < this.polygons.length; pi++) {
var polygon = this.polygons[pi];
if (polygon.points.indexOf(this.selectedPoint) < 0) {
continue;
}
for (var pa = 0; pa < polygon.constrains.length; pa++) {
var constrain = polygon.constrains[pa];
if (constrain.a == this.selectedPoint || constrain.b == this.selectedPoint) {
constrain.a[constrain.key] = this.selectedPoint[constrain.key];
constrain.b[constrain.key] = this.selectedPoint[constrain.key];
if (constrain.offset != void 0) {
if (constrain.a == this.selectedPoint) {
constrain.b[constrain.key] += constrain.offset;
}
else {
constrain.a[constrain.key] -= constrain.offset;
}
}
}
}
}
}
requestAnimationFrame(this.draw.bind(this));
};
Sketch.prototype.draw = function () {
var ctx = this.ctx;
//clear
ctx.fillStyle = "#0971CE";
ctx.fillRect(0, 0, 1000, 1000);
//grid
ctx.strokeStyle = "rgba(255,255,255,0.25)";
for (var x = 0; x <= this.canvas.width; x += 5) {
ctx.beginPath();
ctx.moveTo(x, -1);
ctx.lineTo(x, this.canvas.height);
ctx.stroke();
ctx.closePath();
}
for (var y = 0; y <= this.canvas.height; y += 5) {
ctx.beginPath();
ctx.moveTo(-1, y);
ctx.lineTo(this.canvas.width, y);
ctx.stroke();
ctx.closePath();
}
ctx.strokeStyle = "white";
ctx.fillStyle = "white";
//shapes
for (var i = 0; i < this.polygons.length; i++) {
var polygon = this.polygons[i];
for (var pa = 0; pa < polygon.points.length; pa++) {
var pointa = polygon.points[pa];
if (pointa == this.selectedPoint) {
ctx.beginPath();
ctx.fillRect(pointa.x - 2, pointa.y - 2, 4, 4);
ctx.closePath();
}
ctx.beginPath();
for (var pb = 0; pb < pointa.connectedTo.length; pb++) {
var pointb = pointa.connectedTo[pb];
if (polygon.points.indexOf(pointb) < pa) {
continue;
}
ctx.moveTo(pointa.x, pointa.y);
ctx.lineTo(pointb.x, pointb.y);
}
ctx.stroke();
ctx.closePath();
}
}
};
return Sketch;
}());
//==Test==
//Build polygon 1 (House)
var poly1 = new Polygon([
new Point(10, 10),
new Point(80, 10),
new Point(80, 45),
new Point(130, 45),
new Point(130, 95),
new Point(80, 95),
new Point(80, 135),
new Point(10, 135),
]);
//Connect dots
for (var x = 0; x < poly1.points.length; x++) {
var a = poly1.points[x];
var b = poly1.points[(x + 1) % poly1.points.length];
a.connectedTo.push(b);
b.connectedTo.push(a);
}
//Setup constrains
for (var x = 0; x < poly1.points.length; x++) {
var a = poly1.points[x];
var b = poly1.points[(x + 1) % poly1.points.length];
poly1.constrains.push({ a: a, b: b, key: x % 2 == 1 ? 'x' : 'y' });
}
poly1.constrains.push({ a: poly1.points[1], b: poly1.points[5], key: 'x' }, { a: poly1.points[2], b: poly1.points[5], key: 'x' }, { a: poly1.points[1], b: poly1.points[6], key: 'x' }, { a: poly1.points[2], b: poly1.points[6], key: 'x' });
//Build polygon 2 (Triangle)
var poly2 = new Polygon([
new Point(250, 250),
new Point(300, 300),
new Point(200, 300),
]);
//Connect dots
for (var x = 0; x < poly2.points.length; x++) {
var a = poly2.points[x];
var b = poly2.points[(x + 1) % poly2.points.length];
a.connectedTo.push(b);
b.connectedTo.push(a);
}
//Setup constrains
poly2.constrains.push({ a: poly2.points[0], b: poly2.points[1], key: 'x', offset: 50 }, { a: poly2.points[0], b: poly2.points[1], key: 'y', offset: 50 });
//Generate sketch
var s = new Sketch([poly1, poly2]);
<!-- TYPESCRIPT -->
<!--
class Point {
constructor(public x: number, public y: number, public connectedTo: Point[] = []) {
}
}
interface IConstrain {
a: Point,
b: Point,
key: string,
offset?: number
}
class Polygon {
constructor(public points: Point[], public constrains: IConstrain[] = []) {
}
}
class Sketch {
public ctx: CanvasRenderingContext2D;
constructor(public polygons: Polygon[] = [], public canvas = document.body.appendChild(document.createElement("canvas"))) {
this.canvas.width = 1000;
this.canvas.height = 1000;
this.ctx = this.canvas.getContext("2d");
this.ctx.fillStyle = "#0971CE";
this.ctx.strokeStyle = "white";
this.canvas.onmousedown = this.clickHandler.bind(this)
this.canvas.onmouseup = this.clickHandler.bind(this)
this.canvas.onmousemove = this.clickHandler.bind(this)
requestAnimationFrame(this.draw.bind(this))
}
public selectedPoint: Point
public clickHandler(evt: MouseEvent) {
if (evt.type == "mousedown") {
if (this.selectedPoint != void 0) {
this.selectedPoint = null;
} else {
let score = null;
let best = null;
for (let p = 0; p < this.polygons.length; p++) {
let polygon = this.polygons[p];
for (let pi = 0; pi < polygon.points.length; pi++) {
let point = polygon.points[pi];
let dist = Math.abs(point.x - evt.offsetX) + Math.abs(point.y - evt.offsetY)
if (score == null ? true : dist < score) {
score = dist;
best = point;
}
}
}
this.selectedPoint = best;
}
}
if (evt.type == "mousemove" && this.selectedPoint != void 0) {
this.selectedPoint.x = Math.round(evt.offsetX / 5) * 5;
this.selectedPoint.y = Math.round(evt.offsetY / 5) * 5;
for (let pi = 0; pi < this.polygons.length; pi++) {
let polygon = this.polygons[pi];
if (polygon.points.indexOf(this.selectedPoint) < 0) {
continue;
}
for (let pa = 0; pa < polygon.constrains.length; pa++) {
let constrain = polygon.constrains[pa];
if (constrain.a == this.selectedPoint || constrain.b == this.selectedPoint) {
constrain.a[constrain.key] = this.selectedPoint[constrain.key]
constrain.b[constrain.key] = this.selectedPoint[constrain.key]
if (constrain.offset != void 0) {
if (constrain.a == this.selectedPoint) {
constrain.b[constrain.key] += constrain.offset
} else {
constrain.a[constrain.key] -= constrain.offset
}
}
}
}
}
}
requestAnimationFrame(this.draw.bind(this))
}
public draw() {
var ctx = this.ctx;
//clear
ctx.fillStyle = "#0971CE";
ctx.fillRect(0, 0, 1000, 1000)
//grid
ctx.strokeStyle = "rgba(255,255,255,0.25)"
for (let x = 0; x <= this.canvas.width; x += 5) {
ctx.beginPath()
ctx.moveTo(x, -1)
ctx.lineTo(x, this.canvas.height)
ctx.stroke();
ctx.closePath()
}
for (let y = 0; y <= this.canvas.height; y += 5) {
ctx.beginPath()
ctx.moveTo(-1, y)
ctx.lineTo(this.canvas.width, y)
ctx.stroke();
ctx.closePath()
}
ctx.strokeStyle = "white"
ctx.fillStyle = "white";
//shapes
for (let i = 0; i < this.polygons.length; i++) {
let polygon = this.polygons[i];
for (let pa = 0; pa < polygon.points.length; pa++) {
let pointa = polygon.points[pa];
if (pointa == this.selectedPoint) {
ctx.beginPath();
ctx.fillRect(pointa.x - 2, pointa.y - 2, 4, 4)
ctx.closePath();
}
ctx.beginPath();
for (var pb = 0; pb < pointa.connectedTo.length; pb++) {
var pointb = pointa.connectedTo[pb];
if (polygon.points.indexOf(pointb) < pa) {
continue;
}
ctx.moveTo(pointa.x, pointa.y)
ctx.lineTo(pointb.x, pointb.y)
}
ctx.stroke();
ctx.closePath();
}
}
}
}
//==Test==
//Build polygon 1 (House)
var poly1 = new Polygon([
new Point(10, 10),
new Point(80, 10),
new Point(80, 45),
new Point(130, 45),
new Point(130, 95),
new Point(80, 95),
new Point(80, 135),
new Point(10, 135),
])
//Connect dots
for (let x = 0; x < poly1.points.length; x++) {
let a = poly1.points[x];
let b = poly1.points[(x + 1) % poly1.points.length]
a.connectedTo.push(b)
b.connectedTo.push(a)
}
//Setup constrains
for (let x = 0; x < poly1.points.length; x++) {
let a = poly1.points[x];
let b = poly1.points[(x + 1) % poly1.points.length]
poly1.constrains.push({ a: a, b: b, key: x % 2 == 1 ? 'x' : 'y' })
}
poly1.constrains.push(
{ a: poly1.points[1], b: poly1.points[5], key: 'x' },
{ a: poly1.points[2], b: poly1.points[5], key: 'x' },
{ a: poly1.points[1], b: poly1.points[6], key: 'x' },
{ a: poly1.points[2], b: poly1.points[6], key: 'x' }
)
//Build polygon 2 (Triangle)
var poly2 = new Polygon([
new Point(250, 250),
new Point(300, 300),
new Point(200, 300),
])
//Connect dots
for (let x = 0; x < poly2.points.length; x++) {
let a = poly2.points[x];
let b = poly2.points[(x + 1) % poly2.points.length]
a.connectedTo.push(b)
b.connectedTo.push(a)
}
//Setup constrains
poly2.constrains.push(
{ a: poly2.points[0], b: poly2.points[1], key: 'x', offset: 50 },
{ a: poly2.points[0], b: poly2.points[1], key: 'y', offset: 50 },
)
//Generate sketch
var s = new Sketch([poly1, poly2])
-->
UPDATE - Constrain offsets
Based on feedback in the comments i added a "offset" key in the constrains to handle uneven relationships.
The Triangles top-right-most edge (at least initially) is constrained with an offset.
At my school I am learning how to code in JS using a site called codehs.com. After a while I learned about graphics with JS. There was this one point where I had to create a circle:
var circle = new Circle(50);
circle.setPosition(100,100);
add(circle);
After a few days I came across another website that was teaching students how code using JS. The website was called khanacademy.org I was interested and saw that the first lesson was making drawings. I looked at the video provided and it had a different code to make a circle.
ellipse(203, 197, 300, 350);
I am confused on how to make a circle using JS since I just started.
I'm one of the founders of CodeHS. CodeHS uses a custom JavaScript library on top of regular JavaScript. Khan Academy uses Processing JS, which is a different library (You can use Processing on CodeHS as well if you like).
You can see the documentation for everything in the CodeHS JS library at https://codehs.com/docs and learn how to use it in the Intro CS in JavaScript course.
We have designed this library to be great for learning -- it gives you experience using Object Oriented Programming while making it simple to create and manipulate shapes for programs like a Helicopter Game.
Additionally, you can include the library on an HTML page that runs JavaScript by adding this script tag to your page.
<script type="text/javascript" src="https://static.codehs.com/gulp/3d065bc81d3b7edf21e928ce2c764374a03c5cd6/chs-js-lib/chs.js"></script>
Here's an example of a full HTML page that runs JavaScript and uses the CodeHS library on it to draw a circle.
<html>
<head>
<title>Circle Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript" src="https://static.codehs.com/gulp/3d065bc81d3b7edf21e928ce2c764374a03c5cd6/chs-js-lib/chs.js"></script>
<style>
canvas {
border: 1px solid black;
display: inline-block;
vertical-align: top;
}
pre {
border: 1px solid black;
display: inline-block;
width: 400px;
height: 500px;
background-color: #F5F5F5;
}
</style>
</head>
<body>
<h1>Circle Example</h1>
<canvas
width="400"
height="500"
class="codehs-editor-canvas"></canvas>
<script>
window.onload = function() {
var circle = new Circle(50);
circle.setPosition(100,100);
add(circle);
};
</script>
</body>
</html>
Looks like KHAN ACADEMY uses ProcessingJS to draw the circle
I was unable to check what is the library CodeHS uses to draw a circle, but has to be a different one. But the fact is that there are so many good libraries developed in javascript to make whatever you can imagine. They're generally different one from another but their goal is to make our life easier.
JavaScript library | Wikipedia
What's a JS library? | KHAN ACADEMY
I tried using the processing platform for CodeHs, just copying and pasting this code:
/**
* This program finds the shortest path through a series of obstacles using A* search,
* smooths the path, then uses PID control to drive a robot to the goal.
**/
// You can play around with these constants---
var NUM_BLOCKS = 20;
var OBSTACLE_PROBABILITY = 0.2;
var MOVE_NOISE = 0.1;
var STEERING_NOISE = 0.1;
var ROBOT_SPEED = 0.5;
var MAX_STEERING_ANGLE = Math.PI/4.0;
var tP = 2.0;
var tI = 0.0001;
var tD = 16.0;
var weightData = 0.1;
var weightSmooth = 0.1;
// Search types
var A_STAR = 0;
var GREEDY = 1;
var BREADTH_FIRST = 2;
var DEPTH_FIRST = 3;
var SEARCH_MODE = A_STAR;
// --------------------------------------------
var BLOCK_SIZE = width/NUM_BLOCKS;
var START = 2;
var GOAL_X = NUM_BLOCKS-1;
var GOAL_Y = NUM_BLOCKS-1;
var GOAL = 3;
var START_COLOR = color(23, 33, 176);
var GOAL_COLOR = color(199, 188, 68);
var FRONTIER_COLOR = color(105, 179, 105);
var EXPLORED_COLOR = color(117, 117, 117, 100);
var PATH_COLOR = color(166, 53, 53);
var SMOOTH_PATH_COLOR = color(53, 68, 166);
var PLAN = 0;
var SMOOTH = 1;
var CALIBRATE = 2;
var NAVIGATE = 3;
var DELTA = [[-1, 0], [1, 0], [0, -1], [0, 1]];
var frontier = [[0, 0, 0, 0]];
var explored = [];
var predecessors = [];
var path = [];
var smoothPath = [];
var mode = PLAN;
var index = 0;
var cte = 0;
var sigma = 0;
angleMode = "radians";
frameRate(60);
// Initialize world
var world = [];
for (var i = 0; i < NUM_BLOCKS; i++) {
var row = [];
for (var j = 0; j < NUM_BLOCKS; j++) {
var r = random();
if (r < OBSTACLE_PROBABILITY) {
row.push(1);
}
else {
row.push(0);
}
}
world.push(row);
}
world[0][0] = START;
world[GOAL_Y][GOAL_X] = GOAL;
for (var i = 0; i < NUM_BLOCKS; i++) {
var row = [];
for (var j = 0; j < NUM_BLOCKS; j++) {
row.push(false);
}
explored.push(row);
}
explored[0][0] = true;
for (var i = 0; i < NUM_BLOCKS; i++) {
var row = [];
for (var j = 0; j < NUM_BLOCKS; j++) {
row.push(null);
}
predecessors.push(row);
}
var Robot = function(x, y, size) {
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.orientation = 0;
this.size = size;
// Thanks to Sebastian Thrun for this code:
// https://www.udacity.com/course/viewer#!/c-cs373/l-48696626/e-48403941/m-48729137
// My code for this movement can be found at:
// https://www.khanacademy.org/computer-programming/bicycle-model/5496951953031168
this.move = function(d, theta) {
var generator = new Random(millis());
var moveDistance = (generator.nextGaussian() * MOVE_NOISE) + d;
var turnAngle = (generator.nextGaussian() * STEERING_NOISE) + theta;
turnAngle = min(turnAngle, MAX_STEERING_ANGLE);
turnAngle = max(turnAngle, -MAX_STEERING_ANGLE);
var turn = tan(turnAngle)*moveDistance/this.size;
// Approximately straight motion
if (abs(turn) < 0.001) {
this.x += moveDistance*cos(this.orientation);
this.y += moveDistance*sin(this.orientation);
this.orientation = (this.orientation+turn)%(2.0*Math.PI);
}
// Move using the bicyle model
else {
var radius = moveDistance/turn;
var cx = this.x-(sin(this.orientation)*radius);
var cy = this.y+(cos(this.orientation)*radius);
this.orientation = (this.orientation+turn)%(2.0*Math.PI);
this.x = cx + (sin(this.orientation)*radius);
this.y = cy - (cos(this.orientation)*radius);
}
};
this.draw = function() {
pushMatrix();
translate(this.x, this.y);
rotate(this.orientation);
fill(128, 27, 27);
stroke(255, 0, 0);
rect(-this.size/2, -(this.size*0.75*0.5), this.size, this.size*0.75);
popMatrix();
};
};
var robot;
var addToFrontier = function(node) {
// Insert the node into the frontier
// Order by lowest cost
var i = frontier.length-1;
if (SEARCH_MODE === A_STAR) {
while (i > 0 && node[2]+node[3] < frontier[i][2]+frontier[i][3]) {
i--;
}
}
else if (SEARCH_MODE === GREEDY) {
while (i > 0 && node[3] < frontier[i][3]) {
i--;
}
}
else if (SEARCH_MODE === BREADTH_FIRST) {
frontier.push(node);
}
else if (SEARCH_MODE === DEPTH_FIRST) {
frontier.splice(0, 0, node);
}
frontier.splice(i+1, 0, node);
};
var distance = function(x1, y1, x2, y2) {
return sqrt(pow(x1-x2, 2) + pow(y1-y2, 2));
};
var manhattanDistance = function(x1, y1, x2, y2) {
return abs(x1-x2) + abs(y1-y2);
};
var drawWorld = function() {
background(255, 255, 255);
for (var i = 0; i < world.length; i++) {
for (var j = 0; j < world[0].length; j++) {
if (world[i][j] === 1) {
stroke(0, 0, 0);
fill(0, 0, 0);
}
else if (world[i][j] === START) {
fill(START_COLOR);
stroke(START_COLOR);
}
else if (world[i][j] === GOAL) {
fill(GOAL_COLOR);
stroke(GOAL_COLOR);
}
else {
fill(255, 255, 255);
noStroke();
}
rect(j*BLOCK_SIZE, i*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
}
};
var simulate = function(steps) {
var error = 0;
var crosstrackError = 0;
var r = new Robot(0, 1, BLOCK_SIZE/2);
var sigma = 0;
var diff = 0;
for (var i = 0; i < steps*2; i++) {
// Compute cte
diff = r.y-crosstrackError;
crosstrackError = r.y;
sigma += crosstrackError;
if (i > steps) {
error += pow(crosstrackError, 2);
}
// Update robot
r.move(ROBOT_SPEED, -tP*crosstrackError - tD*diff - tI*sigma);
}
return error;
};
draw = function() {
drawWorld();
// Use A* to find a path to the goal
if (mode === PLAN) {
// Draw explored
fill(EXPLORED_COLOR);
noStroke();
for (var i = 0; i < explored.length; i++) {
for (var j = 0; j < explored[0].length; j++) {
if (explored[i][j]) {
rect(j*BLOCK_SIZE, i*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
}
}
// Draw frontier
fill(FRONTIER_COLOR);
noStroke();
for (var i = 0; i < frontier.length; i++) {
rect(frontier[i][0]*BLOCK_SIZE, frontier[i][1]*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
// A*
if (frontier.length > 0) {
// Remove a node from the frontier
var x = frontier[0][0];
var y = frontier[0][1];
var cost = frontier[0][2];
//println(cost + ", " + frontier[0][3]);
frontier.splice(0, 1);
// Goal check
if (world[y][x] === GOAL) {
mode = SMOOTH;
}
else {
// Add all adjacent unexplored nodes
for (var i = 0; i < DELTA.length; i++) {
// If the new position is in the world
var x2 = x + DELTA[i][0];
var y2 = y + DELTA[i][1];
if (x2 >= 0 && x2 < world[0].length && y2 >= 0 && y2 < world.length) {
// If the position is unexplored
if (!explored[y2][x2] && world[y2][x2] !== 1) {
explored[y2][x2] = true;
predecessors[y2][x2] = [x, y];
addToFrontier([x2, y2, cost+1, dist(x2, y2, GOAL_X, GOAL_Y)]);
}
}
}
}
}
else {
mode = -1;
println("No possible path to goal.");
}
}
// Smooth the path
else if (mode === SMOOTH) {
// Build path
if (path.length === 0) {
var x = GOAL_X;
var y = GOAL_Y;
path.splice(0, 0, [x, y]);
smoothPath.splice(0, 0, [x, y]);
while (x !== 0 || y !== 0) {
var newX = predecessors[y][x][0];
var newY = predecessors[y][x][1];
x = newX;
y = newY;
path.splice(0, 0, [x, y]);
smoothPath.splice(0, 0, [x, y]);
}
}
// Draw the original path
stroke(PATH_COLOR);
strokeWeight(5);
for (var i = 0; i < path.length; i++) {
point(BLOCK_SIZE*path[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*path[i][1]+BLOCK_SIZE/2);
}
strokeWeight(1);
for (var i = 0; i < path.length-1; i++) {
line(BLOCK_SIZE*path[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*path[i][1]+BLOCK_SIZE/2, BLOCK_SIZE*path[i+1][0]+BLOCK_SIZE/2, BLOCK_SIZE*path[i+1][1]+BLOCK_SIZE/2);
}
// Draw the new path
stroke(SMOOTH_PATH_COLOR);
strokeWeight(5);
for (var i = 0; i < smoothPath.length; i++) {
point(BLOCK_SIZE*smoothPath[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i][1]+BLOCK_SIZE/2);
}
strokeWeight(1);
for (var i = 0; i < smoothPath.length-1; i++) {
line(BLOCK_SIZE*smoothPath[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i][1]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i+1][0]+BLOCK_SIZE/2,
BLOCK_SIZE*smoothPath[i+1][1]+BLOCK_SIZE/2);
}
// Perform gradient descent
var update;
var diff = 0;
for (var i = 1; i < smoothPath.length-1; i++) {
update = [0, 0];
for (var j = 0; j < smoothPath[0].length; j++) {
update[j] += weightData * (path[i][j] - smoothPath[i][j]);
update[j] += weightSmooth * (smoothPath[(i+1)%smoothPath.length][j] + smoothPath[(i-1+smoothPath.length)%smoothPath.length][j] - 2*smoothPath[i][j]);
}
// Simulataneous update
for (var j = 0; j < smoothPath[0].length; j++) {
smoothPath[i][j] += update[j];
diff += abs(update[j]);
}
}
if (diff < 0.000001) {
robot = new Robot(BLOCK_SIZE/2, BLOCK_SIZE/2, BLOCK_SIZE/2);
mode = NAVIGATE;
}
}
else if (mode === CALIBRATE) {
var steps = 100;
var error = simulate(steps);
var dp = [1.0, 1.0, 1.0];
var params = [0, 0, 0];
if (error/steps > 0.04) {
for (var i = 0; i < dp.length; i++) {
params[i] += dp[i];
var newError = simulate(steps);
if (newError < error) {
error = newError;
dp[i] *= 1.1;
}
else {
params[i] -= 2*dp[i];
newError = simulate(steps);
if (newError < error) {
error = newError;
dp[i] *= -1.1;
}
else {
params[i] += dp[i];
dp[i] *= 0.9;
}
}
}
tP = params[0];
tD = params[1];
tI = params[2];
println(error/steps);
}
else {
println(params);
tP = params[0];
tD = params[1];
tI = params[2];
mode = NAVIGATE;
}
}
// Use PID control to follow the path
else if (mode === NAVIGATE) {
// Draw path
stroke(SMOOTH_PATH_COLOR);
strokeWeight(5);
for (var i = 0; i < smoothPath.length; i++) {
point(BLOCK_SIZE*smoothPath[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i][1]+BLOCK_SIZE/2);
}
strokeWeight(1);
for (var i = 0; i < smoothPath.length-1; i++) {
line(BLOCK_SIZE*smoothPath[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i][1]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i+1][0]+BLOCK_SIZE/2,
BLOCK_SIZE*smoothPath[i+1][1]+BLOCK_SIZE/2);
}
// Draw robot
robot.draw();
// Compute cte
var diff = -cte;
var x1 = smoothPath[index][0]*BLOCK_SIZE+BLOCK_SIZE/2;
var y1 = smoothPath[index][1]*BLOCK_SIZE+BLOCK_SIZE/2;
var x2 = smoothPath[index+1][0]*BLOCK_SIZE+BLOCK_SIZE/2;
var y2 = smoothPath[index+1][1]*BLOCK_SIZE+BLOCK_SIZE/2;
var dx = x2-x1;
var dy = y2-y1;
var d = sqrt(pow(dx, 2) + pow(dy, 2));
var u = ((robot.x-x1)*dx + (robot.y-y1)*dy)/(pow(dx, 2) + pow(dy, 2));
var Px = x1 + d*u*cos(atan2(dy, dx));
var Py = y1 + d*u*sin(atan2(dy, dx));
cte = ((robot.y-y1)*dx-(robot.x-x1)*dy)/(pow(dx, 2) + pow(dy, 2));
sigma += cte;
diff += cte;
if (u > 1) {
index++;
index = min(index, smoothPath.length-2);
}
// Update robot
robot.move(ROBOT_SPEED, -tP*cte - tD*diff - tI*sigma);
//println(index);
}
};
but all that happens is that a grey screen shows up
For the last step of this project, I want the growing circle to stop when it collides with another circle. The isOnCircle function already checks for this successfully when creating a new circle. However, when adding the condition !isOnCircle to my grow() function (line 61) it prevents any new circles from being added.
function grow() {
var a = circles[circles.length - 1];
if (!isOnCircle(a)){
a.radius += 1;
}}
Perhaps the circle is being created first, then in the check for collision, it's colliding with itself. Where else could I put the !isOnCircle check so that it gets checked at every radius increase and stops the grow function then?
check this
//set up canvas
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var circles = [];
//create circle
function create(location) {
circles.push({
x: location.x,
y: location.y,
radius: 10,
color: '#' + Math.floor(Math.random() * 16777215).toString(16)
});
}
//figure out mouse position
var rect = document.getElementById("canvas").getBoundingClientRect();
// Get canvas offset on page
var offset = {
x: rect.left,
y: rect.top
};
function isOnCanvas(a) {
if ((a.x >= 0 && a.x <= rect.width) && (a.y >= 0 && a.y <= rect.height)) {
return true;
}
return false;
}
function isOnCircle(a) {
var i = 0,
l = circles.length,
x, y, d, c;
for (; i < l; ++i) {
c = circles[i];
x = a.x - c.x;
y = a.y - c.y;
d = (a.radius || 10) + c.radius;
if (x * x + y * y <= d * d) {
return true;
}
}
return false;
}
// draw all circles
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < circles.length; i++) {
var p = circles[i];
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI);
ctx.fillStyle = p.color;
ctx.fill();
}
}
//make last drawn circle 1px bigger
function grow() {
var a = circles[circles.length - 1];
a.radius += 1;
}
//find percentage of canvas filled in
var totalSpace = canvas.width * canvas.height;
var totalFilled = function () {
total = 0;
for (var i = 0; i < circles.length; i++) {
var p = circles[i];
total += Math.PI * Math.pow(p.radius, 2);
}
return total;
console.log(total);
}
function findPercentage() {
return (totalFilled() / totalSpace) * 100;
}
function updateInfo() {
percentage = findPercentage();
document.getElementById("percentage").innerHTML = "You've filled in " + percentage.toFixed(1) + "%";
}
//do all the stuff
var animate = function () {
grow();
draw();
updateInfo();
}
//put this outside function so we can stop it later
var growLoop;
window.onmousedown = function (e) {
// get event location on page offset by canvas location
var location = {
x: e.pageX - offset.x,
y: e.pageY - offset.y
};
if (isOnCanvas(location) && !isOnCircle(location)) {
create(location);
draw();
updateInfo();
growLoop = setInterval(animate, 100);
}
};
window.onmouseup = function () {
clearInterval(growLoop);
}
window.onmouseout = function () {
clearInterval(growLoop);
}
it's colliding with itself.
Probably. You definitely should avoid that in the collision detection:
function isOnCircle(a) {
var l = circles.length,
x, y, d, c;
for (var i = 0; i < l; ++i) {
c = circles[i];
if (a == c) // add these
continue; // two lines!
x = a.x - c.x;
y = a.y - c.y;
d = (a.radius || 10) + c.radius;
if (x * x + y * y <= d * d) {
return true;
}
}
return false;
}
It is colliding with itself. Since you know the current circle will always be the last one in circles you can modify isOnCircle like this:
l = circles.length - 1,
so that it won't check against the current circle.
http://jsfiddle.net/SeAGU/91/
function randomXToY(minVal,maxVal,floatVal)
{
var randVal = minVal+(Math.random()*(maxVal-minVal));
return typeof floatVal=='undefined'?Math.round(randVal):randVal.toFixed(floatVal);
}
Ball = (function() {
// constructor
function Ball(x,y,radius,color){
this.center = {x:x, y:y};
this.radius = radius;
this.color = color;
this.dx = 2;
this.dy = 2;
this.boundaryHeight = $('#ground').height();
this.boundaryWidth = $('#ground').width();
this.dom = $('<p class="circle"></p>').appendTo('#ground');
// the rectange div a circle
this.dom.width(radius*2);
this.dom.height(radius*2);
this.dom.css({'border-radius':radius,background:color});
this.placeAtCenter(x,y);
}
// Place the ball at center x, y
Ball.prototype.placeAtCenter = function(x,y){
this.dom.css({top: Math.round(y- this.radius), left: Math.round(x - this.radius)});
this.center.x = Math.round(x);
this.center.y = Math.round(y);
};
Ball.prototype.setColor = function(color) {
if(color) {
this.dom.css('background',color);
} else {
this.dom.css('background',this.color);
}
};
// move and bounce the ball
Ball.prototype.move = function(){
var diameter = this.radius * 2;
var radius = this.radius;
if (this.center.x - radius < 0 || this.center.x + radius > this.boundaryWidth ) {
this.dx = -this.dx;
}
if (this.center.y - radius < 0 || this.center.y + radius > this.boundaryHeight ) {
this.dy = -this.dy;
}
this.placeAtCenter(this.center.x + this.dx ,this.center.y +this.dy);
};
return Ball;
})();
var number_of_balls = 5;
var balls = [];
$('document').ready(function(){
for (i = 0; i < number_of_balls; i++) {
var boundaryHeight = $('#ground').height();
var boundaryWidth = $('#ground').width();
var y = randomXToY(30,boundaryHeight - 50);
var x = randomXToY(30,boundaryWidth - 50);
var radius = randomXToY(15,30);
balls.push(new Ball(x,y,radius, '#'+Math.floor(Math.random()*16777215).toString(16)));
}
loop();
});
loop = function(){
for (var i = 0; i < balls.length; i++){
balls[i].move();
}
setTimeout(loop, 8);
};
I have never used in oops concepts in javascript. How do I change the ball color when the balls touches each other?
This is the link : http://jsbin.com/imofat/1/edit
You currently don't have any interaction with the balls. What you can do is checking whether two balls are "inside" each other, and change colors in that case: http://jsbin.com/imofat/1491/.
// calculates distance between two balls
var d = function(a, b) {
var dx = b.center.x - a.center.x;
var dy = b.center.y - a.center.y;
return Math.sqrt(dx*dx + dy*dy);
};
and:
// for each ball
for(var i = 0; i < balls.length; i++) {
// check against rest of balls
for(var j = i + 1; j < balls.length; j++) {
var a = balls[i];
var b = balls[j];
// distance is smaller than their radii, so they are inside each other
if(d(a, b) < a.radius + b.radius) {
// set to some other color using your random color code
a.setColor('#'+Math.floor(Math.random()*16777215).toString(16));
b.setColor('#'+Math.floor(Math.random()*16777215).toString(16));
}
}
}
Still, there are things for improvement:
Balls are changing colors as long as they are inside each other, not just once.
If you want them to "touch", you might want to implement some kind of bouncing effect to make it more realistic.