SkiaSharp: Unexpected results with ArcTo() and Close() - xamarin.forms

I'm drawing some lines and arcs using SkiaSharp on a SKCanvasView (Xamarin.Forms) under Android 11.
I create the SKPath, and it mostly renders as expected, but I'm finding that the Close() stroke always ends at the start-point of the last-added Arc rather than at the 1st point in the path.
Code:
SKPoint pt = new SKPoint(600, 400); // Random location
float r = 100; // Arc radius
float d = 2 * r; // Side of arc rect
SKRect rc = new SKRect(pt.X, pt.Y, pt.X + d, pt.Y + d);
SKPath path = new SKPath();
path.MoveTo(rc.Right + 50, rc.Top);
path.LineTo(rc.Right, rc.MidY); // Line "S"
path.ArcTo(rc, 0, 90, true); // Arc "A"
path.ArcTo(rc, 90, 90, true); // Arc "B"
path.ArcTo(rc, 180, 90, true); // Arc "C"
path.Close(); // Line "K"
using (var skp = new SKPaint() { Style=SKPaintStyle.Stroke, IsAntialias=true, Color=SKColors.Red, StrokeWidth=6 }) {
canv.DrawPath(path, skp);
}
And here's the result (non-red items added for illustration).
The thing that I don't understand is why path.Close() causes line "K" to end at the B/C juncture rather than at the origin of "S". I would have expected Close() to generate line "G" (in red, of course) rather than "K".
Any insight would be much appreciated!

Close goes to the start of the current contour.
Instead of
path.ArcTo(rc, 0, 90, true);
You want
path.ArcTo(rc, 0, 90);
OR
path.ArcTo(rc, 0, 90, false);
Make this change on all of those ArcTo calls.
Reason:
final parameter "true" told it to start a new contour!
As described in Three ways to Draw an Arc:
public void ArcTo (SKRect oval, Single startAngle, Single sweepAngle, Boolean forceMoveTo)
That last argument is called forceMoveTo, and it effectively causes a MoveTo call at the beginning of the arc. That begins a new contour.

Related

Transforming a vector to adhere to a complimentary vector with ThreeJS

I'm facing a troublesome problem while trying to create a game engine in threeJS.
It is a math problem, but also a programming problem.
I've implemented a velocity based movement system for the player's avatar - I've used a tank in this example.
Currently, when the player hits a wall, regardless of the angle, the tank invariably stops dead.
However, I want it to be the case that the tank's velocity changes, having been coerced to follow the angle of the wall, and also reduced by a magnitude that is related to that angle.
For example, in FIG A, upon hitting the wall, the Tank continues to try and move forwards, but it's velocity is altered so that it now moves forwards, and sideways, at a reduced rate.
In FIG B, the tank hits the wall dead-on, and its overall velocity reaches 0.
In FIG C, the tank glances off the wall, and its overall velocity is only reduced by a small amount.
I've realised that I need to somehow combine the Tank's velocity vector with the wall's normal vector, to produce the adjusted vector, but I am struggling with how to represent this mathematically / programmatically.
I've tried using: tank.velocity.multiply(wallFaceNormal); (both tank.velocity and wallFaceNormal are Vector3 objects.) but this only seems to work as intended when the wall is either at angles of 0, 90, 180 or 270.
since a tank will not jump or fly, you should be fine with just a 2D-System for your calculation?
i found a link describing the physics of car hitting a solid brick wall.
http://colgatephys111.blogspot.com/2017/12/guardrail-lessens-force-of-impact.html
hope thats gonna help you a bit!
edit:
so, out of curiosity, i asked an theoretical physicist over the phone about your issue.
you got 2 seperate problems to solve:
1. P1 what is the velocity v' while hitting the wall?
2. P2 what is the new angle of the vehicel?
P2 should be fairly easy, considering your tank is adapting the angle of the wall you only need to calculate in which direction the wall is "pointing".
P1 in physics, we would talk about the reduced force and not the velocity, but given a constant limit to the force F1 (eg. your engine) resulting in a constant maxspeed,
and with a given force the wall has on the vehicel F2
v = F1
v' = F1'
F1' = F1 - F2
i think
https://www.thoughtco.com/what-is-the-physics-of-a-car-collision-2698920
explains what to do
Some code provided by a Physicist, which partly worked when I converted it to Javascript and applied it to the program:
Vector3 wallNormal = new Vector3(-0.5, 0.0, 0.5);
Vector3 incomingVelocity = new Vector3(0.0, 0.0, -1.0);
double magnitudeProduct = wallNormal.Length() * incomingVelocity.Length();
double angleBetweenVelocityAndWall = ((-incomingVelocity).Dot(wallNormal)) / (magnitudeProduct);
double newVelocityMagnitude = incomingVelocity.Length() * Math.Sin(angleBetweenVelocityAndWall);
Vector3 upVector =incomingVelocity.Cross(wallNormal);
Vector3 newDirection = wallNormal.Cross(upVector);
Vector3 newVelocity = newDirection.Normalise() * newVelocityMagnitude;
I've done some work on this problem and produced a mini game "framework" that includes an environment collision and movement attenuation utility.
I've written an article that explains how it works, which can be found here. http://www.socket-two.com/main/resource/hdoc-tutorial
But for the sake of the integrity of the thread, here's an adaptation of the portion that describes one of the approaches that can be used to attenuate motion in a ThreeJS simulation:
...
Crucially, my interest has not been to create games that involve large amount of physics, but just to create games where:
A player cannot walk through walls
A player cannot fall through floors
I've made a handful of attempts at implementing a system that would achieve this behaviour, but none of them have really worked satisfactorily. Until now.
In terms of how the ECS fits into the app architecture, it is a utility class. This is its API shape:
class Planeclamp {
constructor({ floors /*Mesh[]*/, walls /*Mesh[]*/ })
getSafePosition(startingPositionIn /*Vector3*/, intendedPositionIn /*Vector3*/) // Returns safePosition, which is a Vector3
}
As you can see, its a class that accepts two arrays of meshes in its constructor: Meshes that should be treated as floors, and meshes that should be treated as walls. Now of course in reality, there is no clear distinction between a steep floor and a shallow-angled wall, but for the purposes of the simulation, the distinction has a very reasonable integrity, and will simplify the environment collision system logic greatly.
Once you've constructed an instance of the Planeclamp class, you can then invoke it's getSafePosition method, to transform a starting position and an intended position into an attenuated position. Being the discerning reader that you are, you will have deduced that the attenuated position is the intended position, having been changed a bit if any collisions have been detected by the utility.
This is how it can be used in the game loop, to ensure a player does not pass through walls or floors:
const planeclamp = new Planeclamp({
floors: [someFloorMesh, someOtherMesh],
walls: [houseMesh, perimeterMesh, truckMesh],
});
const player = new Player();
console.log(player.cage); // Object3D
let playerPreviousPosition = player.cage.position; // Vector3
function gameLoop(delta) {
const playerIntendedPosition = new Three.Vector3(
playerPreviousPosition.x,
playerPreviousPosition.y + (10 * delta), // i.e. Gravity
playerPreviousPosition.z + (1 * delta), // i.e. Walking forwards
);
let {
safePosition, // Vector3
grounded, // Boolean
groundMaterial, // String
} = planeclamp.getSafePosition(playerPreviousPosition, playerIntendedPosition);
player.cage.position.copy(safePosition);
playerPreviousPosition = player.cage.position; // Vector3
}
And thats about it! If you would like to use this utility, you can find it in the repository. But if you would like to know more about the logic behind its workings, read on.
The Planeclamp.getSafePosition method works out a safe position in two stages. Firstly, it uses a vertical raycaster to take a look at what is underneath the player, to then see if it should stop the player from moving downwards any further. Secondly, it uses horizontal raycasters to see if it should stop the player from moving horizontally. Lets look at the vertical constraint procedure first - this is the more simple of the two steps.
// Before we do anything, create a variable called "gated".
// This will contain the safe new position that we will return at the end of
// the function. When creating it, we let it default to the
// intended position. If collisions are detected throughout the lifecycle
// of this function, these values will be overwritten.
let gated = {
x: intendedPosition.x,
y: intendedPosition.y,
z: intendedPosition.z,
};
// Define the point in 3D space where we will shoot a ray from.
// For those who haven't used raycasters before, a ray is just a line with a direction.
// We use the player's intended position as the origin of the ray, but we
// augment this by moving the origin up a little bit (backStepVert) to prevent tunneling.
const start = intendedPosition.clone().sub(new Three.Vector3(
0,
(backStepVert * -1) - (heightOffset / 2),
0)
);
// Now, define the direction of the ray, in the form of a vector.
// By giving the vector X and Z values of 0, and a Y value of -1,
// the ray shoots directly downwards.
const direction = new Three.Vector3(0, -1, 0).normalize();
// We now set the origin and direction of a raycaster that we instantiated
// in the class constructor method.
this.raycasters.vert.set(start, direction);
// Now, we use the `intersectObjects` method of the ray.
// This will return to us an array, filled with information about each
// thing that the ray collided with.
const dirCollisions = this.raycasters.vert.intersectObjects(this.floors, false);
// Initialise a distanceToGround, a grounded variable, and a groundMaterial variable.
let distanceToGround = null;
let grounded = false;
let groundMaterial = null;
// If the dirCollisions array has at least one item in it, the
// ray passed through one of our floor meshes.
if (dirCollisions.length) {
// ThreeJS returns the nearest intersection first in the collision
// results array. As we are only interested in the nearest collision,
// we pluck it out, and ignore the rest.
const collision = dirCollisions[0];
// Now, we work out the distance between where the players feet
// would be if the players intended position became the players
// actual position, and the collided object.
distanceToGround = collision.distance - backStepVert - heightOffset;
// If the distance is less than 0, then the player will pass through
// the groud if their intended position is allowed to become
// their actual position.
if (distanceToGround < 0) {
// We dont want that to hapen, so lets set the safe gated.y coordinate
// to the y coordinate of the point in space at which the collision
// happened. In other words, exactly where the ground is.
gated.y = intendedPosition.y - distanceToGround;
// Make a note that the player is now grounded.
// We return this at the end of the function, along with
// the safe position.
grounded = true;
// If the collided object also has a groundMaterial set inside
// its userData (the place that threeJS lets us attach arbitrary
// info to our objects), also set the groundMaterial. This is
// also returned at the end of the function alongside the grounded
// variable.
if (collision.object.userData.groundMaterial) {
groundMaterial = collision.object.userData.groundMaterial;
}
}
}
And thats it for vertical environment constraints. Simples!
The horizontal environment constraint system is a bit more complex. But in its essence, what it does is:
Work out the horizontal direction the player is travelling in. In olde worlde terms, this can be thought of as North, South, SouthEast, SouthSouthWest etc, but in ThreeJS it is represented by a Vector.
Cast a ray in the direction that the player is travelling in.
Use the ray to find out if allowing the players intended position would cause the player to pass through any of the wall meshes.
And it is at this point that the horizontal ECS becomes more complex than the vertical ECS. With the vertical ECS, if a collision happens, we can just set the players Y position to the Y position of the point at which the collision happened - effectively halting the players Y movement. However, it we did this for horizontal movement, it would make for a very frustrating game experience.
If the player was running head on into a wall, and was stopped dead in their tracks, this would be fine. But if the player moved into the wall at a very shallow angle, and merely grazed it, it would appear that they had "gotten stuck" on the wall, and would find themselves having to reverse away from it, and take care not to touch it again.
What we actually want to happen, is have the player's horizontal velocity attenuated, so that they move along the wall. Therefore, the horizontal ECS proceeds as follows:
Obtain the normal of the surface that was collided with. (For our purposes, a normal can be described as the direction that the wall is facing)
Inspect the difference between the wall normal direction, and the player's movement direction.
Use the difference to work out a safe position, which is the point in space that the collision happened, incremented by a vector that is horizontally perpendicular to the wall normal, multiplied by the cross product of the players input direction and the wall normal.
...
Here is the final utility class, in full:
import * as Three from '../../../vendor/three/three.module.js';
class Planeclamp {
constructor({
scene,
floors = [],
walls = [],
drawRays = true,
} = {}) {
this.drawRays = drawRays;
this.floors = [];
this.walls = [];
this.scene = scene;
this.objects = [];
// Init collidable mesh lists
this.addFloors(floors);
this.addWalls(walls);
// Create rays
this.raycasters = {
vert: new Three.Raycaster(),
horzLeft: new Three.Raycaster(),
horzRight: new Three.Raycaster(),
correction: new Three.Raycaster(),
};
}
setDrawRays(draw) {
this.drawRays = draw;
}
addFloor(floor) {
this.floors.push(floor);
}
removeFloor(floor) {
this.floors = this.floors.filter(thisFloor => thisFloor !== floor);
}
addFloors(floors) {
floors.forEach(floor => this.addFloor(floor));
}
resetFloors() {
this.floors = [];
}
addWall(wall) {
this.walls.push(wall);
}
removeWall(wall) {
this.walls = this.walls.filter(thisWall => thisWall !== wall);
}
addWalls(walls) {
walls.forEach(wall => this.addWall(wall));
}
resetWalls() {
this.walls = [];
}
getSafePosition(startingPositionIn, intendedPositionIn, {
collisionPadding = .5,
heightOffset = 0,
} = {}) {
// ------------------ Setup -------------------
// Parse args
const startingPosition = startingPositionIn.clone();
const intendedPosition = intendedPositionIn.clone();
let grounded = false;
let groundMaterial = null;
// Augmenters
const backStepVert = 50;
const backStepHorz = 5;
const backStepCorrection = 5;
// Prepare output
let gated = {
x: intendedPosition.x,
y: intendedPosition.y,
z: intendedPosition.z,
};
// Clean up previous debug visuals
this.objects.map(object => this.scene.remove(object));
this.objects = [];
// ------------------ Vertical position gating -------------------
// Adjust vertical position in gated.y.
// Store grounded status in grounded.
const start = intendedPosition.clone().sub(new Three.Vector3(
0,
(backStepVert * -1) - (heightOffset / 2),
0)
);
const direction = new Three.Vector3(0, -1, 0).normalize();
this.raycasters.vert.set(start, direction);
const dirCollisions = this.raycasters.vert.intersectObjects(this.floors, false);
if (this.drawRays) {
const arrowColour = dirCollisions.length ? 0xff0000 : 0x0000ff;
const arrow = new Three.ArrowHelper(this.raycasters.vert.ray.direction, this.raycasters.vert.ray.origin, 300, arrowColour);
this.objects.push(arrow);
}
let distanceToGround = null;
if (dirCollisions.length) {
const collision = dirCollisions[0];
distanceToGround = collision.distance - backStepVert - heightOffset;
if (distanceToGround < 0) {
gated.y = intendedPosition.y - distanceToGround;
grounded = true;
if (collision.object.userData.groundMaterial) {
groundMaterial = collision.object.userData.groundMaterial;
}
}
}
// ------------------ Horizontal position gating -------------------
const horizontalOutputPosition = (() => {
// Init output position
const outputPosition = new Three.Vector3(intendedPosition.x, 0, intendedPosition.z);
// Store normalised input vector
const startingPos = startingPosition.clone();
const intendedPos = intendedPosition.clone();
startingPos.y = startingPositionIn.y + .5;
intendedPos.y = startingPositionIn.y + .5;
let inputVector = intendedPos.clone().sub(startingPos).normalize();
// Work out distances
const startingIntendedDist = startingPos.distanceTo(intendedPos);
const inputSpeed = startingIntendedDist;
// Define function for moving ray left and right
function adj(position, offset) {
const rayAdjuster = inputVector
.clone()
.applyAxisAngle(new Three.Vector3(0, 1, 0), Math.PI / 2)
.multiplyScalar(.5)
.multiplyScalar(offset);
return position.clone().add(rayAdjuster);
}
// Work out intersections and collision
let collisions = {
left: {
collision: null
},
right: {
collision: null
}
};
Object.keys(collisions).forEach(side => {
const rayOffset = side === 'left' ? -1 : 1;
const rayStart = adj(startingPos.clone().sub(inputVector.clone().multiplyScalar(2)), rayOffset);
const startingPosSide = adj(startingPos, rayOffset);
const intendedPosSide = adj(intendedPos, rayOffset);
const startingIntendedDistSide = startingPosSide.distanceTo(intendedPosSide);
const rayKey = 'horz' + _.startCase(side);
this.raycasters[rayKey].set(rayStart, inputVector);
const intersections = this.raycasters[rayKey].intersectObjects(this.walls, true);
for (let i = 0; i < intersections.length; i++) {
if (collisions[side].collision) break;
const thisIntersection = intersections[i];
const startingCollisionDist = startingPosSide.distanceTo(thisIntersection.point);
if (startingCollisionDist - collisionPadding <= startingIntendedDistSide) {
collisions[side].collision = thisIntersection;
collisions[side].offset = rayOffset;
}
}
if (inputSpeed && this.drawRays) {
this.objects.push(new Three.ArrowHelper(this.raycasters[rayKey].ray.direction, this.raycasters[rayKey].ray.origin, 300, 0x0000ff));
}
});
const [ leftCollision, rightCollision ] = [ collisions.left.collision, collisions.right.collision ];
const collisionData = (leftCollision?.distance || Infinity) < (rightCollision?.distance || Infinity) ? collisions.left : collisions.right;
if (collisionData.collision) {
// Var shorthands
const collision = collisionData.collision;
const normalVector = collision.face.normal.clone();
normalVector.transformDirection(collision.object.matrixWorld);
normalVector.normalize();
// Give output a baseline position that is the same as the collision position
let paddedCollision = collision.point.clone().sub(inputVector.clone().multiplyScalar(collisionPadding));
paddedCollision = adj(paddedCollision, collisionData.offset * -1);
outputPosition.x = paddedCollision.x;
outputPosition.z = paddedCollision.z;
if (leftCollision && rightCollision && leftCollision.face !== rightCollision.face) {
return startingPos;
}
// Work out difference between input vector and output / normal vector
const iCAngleCross = inputVector.clone().cross(normalVector).y; // -1 to 1
// Work out output vector
const outputVector = (() => {
const ivn = inputVector.clone().add(normalVector);
const xMultiplier = ivn.x > 0 ? 1 : -1;
const zMultiplier = ivn.z > 0 ? 1 : -1;
return new Three.Vector3(
Math.abs(normalVector.z) * xMultiplier,
0,
Math.abs(normalVector.x) * zMultiplier,
).normalize();
})();
if (inputSpeed && this.drawRays) {
this.objects.push(new Three.ArrowHelper(normalVector, startingPos, 300, 0xff0000));
}
// Work out output speed
const outputSpeed = inputSpeed * Math.abs(iCAngleCross) * 0.8;
// Increment output position with output vector X output speed
outputPosition.add(outputVector.clone().multiplyScalar(outputSpeed));
}
// ------------------ Done -------------------
return outputPosition;
})();
gated.x = horizontalOutputPosition.x;
gated.z = horizontalOutputPosition.z;
// ------------------ Culmination -------------------
// Add debug visuals
this.objects.map(object => this.scene.add(object));
// Return gated position
const safePosition = new Three.Vector3(gated.x, gated.y, gated.z);
return { safePosition, grounded, groundMaterial };
}
}
export default Planeclamp;

Processing: Draw vector instead of pixels

I have a simple Processing Sketch, drawing a continuous line of ellipses with a 20px diameter. Is there a way to modify the sketch so that it draws vector shapes instead of pixels?
void setup() {
size(900, 900);
background(110, 255, 94);
}
void draw() {
ellipse(mouseX, mouseY, 20, 20);
fill(255);
}
Thanks to everyone who can provide some helpful advice.
Expanding my comment above, there a couple of things to tackle:
drawing a continuous line of ellipses with a 20px diameter
draws vector shapes
Currently you're drawing ellipses based on mouse movement.
A side effect is that if you move the mouse fast enough you will have gaps in between ellipses.
To fill the gaps you can work out the distance between every two ellipses.
If the distance is greater than the sizes of these two ellipses you can draw some in between.
The PVector class provides a lerp() function that allows you easily interpolate between two points.
You can read more on this and run some examples here
Using the ratio between these distance of two points and the ellipse size the number of points needed in between.
Here is an example that stores mouse locations to a list of PVectors as you drag the mouse:
//create an array list to store points to draw
ArrayList<PVector> path = new ArrayList<PVector>();
//size of each ellipse
float size = 20;
//how tight will the extra ellipses be drawn together
float tightness = 1.25;
void setup() {
size(900, 900);
}
void draw() {
background(110, 255, 94);
fill(255);
//for each point in the path, starting at 1 (not 0)
for(int i = 1; i < path.size(); i++){
//get a reference to the current and previous point
PVector current = path.get(i);
PVector previous = path.get(i-1);
//calculate the distance between them
float distance = previous.dist(current);
//work out how many points will need to be added in between the current and previous points to keep the path continuous (taking the ellipse size into account)
int extraPoints = (int)(round(distance/size * tightness));
//draw the previous point
ellipse(previous.x,previous.y,size,size);
//if there are any exta points to be added, compute and draw them:
for(int j = 0; j < extraPoints; j++){
//work out a normalized (between 0.0 and 1.0) value of where each extra point should be
//think of this as a percentage along a line: 0.0 = start of line, 0.5 = 50% along the line, 1.0 = end of the line
float interpolation = map(j,0,extraPoints,0.0,1.0);
//compute the point in between using PVector's linear interpolation (lerp()) functionality
PVector inbetween = PVector.lerp(previous,current,interpolation);
//draw the point in between
ellipse(inbetween.x,inbetween.y,size,size);
}
}
//draw instructions
fill(0);
text("SPACE = clear\nLEFT = decrease tightness\nRIGHT = increase tightness\ntightness:"+tightness,10,15);
}
void mouseDragged(){
path.add(new PVector(mouseX,mouseY));
}
void keyPressed(){
if(keyCode == LEFT) tightness = constrain(tightness-0.1,0.0,3.0);
if(keyCode == RIGHT) tightness = constrain(tightness+0.1,0.0,3.0);
if(key == ' ') path.clear();
}
Note that the interpolation between points is linear.
It's the simplest, but as the name implies, it's all about lines:
it always connects two points in a straight line, not curves.
I've added the option to control how tight interpolated ellipses will be packed together. Here are a couple of screenshots with different tightness levels. You'll notice as tightness increases, the lines will become more evident:
You run the code bellow:
//create an array list to store points to draw
var path = [];
//size of each ellipse
var ellipseSize = 20;
//how tight will the extra ellipses be drawn together
var tightness = 1.25;
function setup() {
createCanvas(900, 900);
}
function draw() {
background(110, 255, 94);
fill(255);
//for each point in the path, starting at 1 (not 0)
for(var i = 1; i < path.length; i++){
//get a reference to the current and previous point
var current = path[i];
var previous = path[i-1];
//calculate the distance between them
var distance = previous.dist(current);
//work out how many points will need to be added in between the current and previous points to keep the path continuous (taking the ellipse size into account)
var extraPoints = round(distance/ellipseSize * tightness);
//draw the previous point
ellipse(previous.x,previous.y,ellipseSize,ellipseSize);
//if there are any exta points to be added, compute and draw them:
for(var j = 0; j < extraPoints; j++){
//work out a normalized (between 0.0 and 1.0) value of where each extra point should be
//think of this as a percentage along a line: 0.0 = start of line, 0.5 = 50% along the line, 1.0 = end of the line
var interpolation = map(j,0,extraPoints,0.0,1.0);
//compute the point in between using PVector's linear interpolation (lerp()) functionality
var inbetween = p5.Vector.lerp(previous,current,interpolation);
//draw the point in between
ellipse(inbetween.x,inbetween.y,ellipseSize,ellipseSize);
}
}
//draw instructions
fill(0);
text("BACKSPACE = clear\n- = decrease tightness\n+ = increase tightness\ntightness:"+tightness,10,15);
}
function mouseDragged(){
path.push(createVector(mouseX,mouseY));
}
function keyPressed(){
if(keyCode == 189) tightness = constrain(tightness-0.1,0.0,3.0);
if(keyCode == 187) tightness = constrain(tightness+0.1,0.0,3.0);
if(keyCode == BACKSPACE) path = [];
}
//https://stackoverflow.com/questions/40673192/processing-draw-vector-instead-of-pixels
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.4/p5.min.js"></script>
If you want smoother lines you will need to use a different interpolation such as quadratic or cubic interpolation. You can start with existing Processing functions for drawing curves such as curve() or bezier(),and you'll find some helpful resources unrelated to Processing here,here and here.
On vector shapes
You're not directly working with pixels[], you're drawing shapes.
These shapes can easily be saved to PDF using Processing's PDF library
Check out the Single Frame from an Animation (With Screen Display) example.
Here is a version that saves to PDF when pressing the 's' key:
import processing.pdf.*;
//create an array list to store points to draw
ArrayList<PVector> path = new ArrayList<PVector>();
//size of each ellipse
float size = 20;
//how tight will the extra ellipses be drawn together
float tightness = 1.25;
//PDF saving
boolean record;
void setup() {
size(900, 900);
}
void draw() {
background(110, 255, 94);
fill(255);
//if we need to save the current frame to pdf, begin recording drawing instructions
if (record) {
// Note that #### will be replaced with the frame number. Fancy!
beginRecord(PDF, "frame-####.pdf");
}
//for each point in the path, starting at 1 (not 0)
for(int i = 1; i < path.size(); i++){
//get a reference to the current and previous point
PVector current = path.get(i);
PVector previous = path.get(i-1);
//calculate the distance between them
float distance = previous.dist(current);
//work out how many points will need to be added in between the current and previous points to keep the path continuous (taking the ellipse size into account)
int extraPoints = (int)(round(distance/size * tightness));
//draw the previous point
ellipse(previous.x,previous.y,size,size);
//if there are any exta points to be added, compute and draw them:
for(int j = 0; j < extraPoints; j++){
//work out a normalized (between 0.0 and 1.0) value of where each extra point should be
//think of this as a percentage along a line: 0.0 = start of line, 0.5 = 50% along the line, 1.0 = end of the line
float interpolation = map(j,0,extraPoints,0.0,1.0);
//compute the point in between using PVector's linear interpolation (lerp()) functionality
PVector inbetween = PVector.lerp(previous,current,interpolation);
//draw the point in between
ellipse(inbetween.x,inbetween.y,size,size);
}
}
//once what we want to save has been recorded to PDF, stop recording (this will skip saving the instructions text);
if (record) {
endRecord();
record = false;
println("pdf saved");
}
//draw instructions
fill(0);
text("SPACE = clear\nLEFT = decrease tightness\nRIGHT = increase tightness\ntightness:"+tightness+"\n's' = save PDF",10,15);
}
void mouseDragged(){
path.add(new PVector(mouseX,mouseY));
}
void keyPressed(){
if(keyCode == LEFT) tightness = constrain(tightness-0.1,0.0,3.0);
if(keyCode == RIGHT) tightness = constrain(tightness+0.1,0.0,3.0);
if(key == ' ') path.clear();
if(key == 's') record = true;
}
In addition to George's great answer (which I've +1'd), I wanted to offer a more basic option:
The problem, like George said, is that when you move the mouse, you actually skip over a bunch of pixels. So if you only draw ellipses or points at mouseX, mouseY then you'll end up with gaps.
The dumb fix: the pmouseX and pmouseY variables hold the previous position of the cursor.
That might not sound very useful, but they allow you to solve exactly your problem. Instead of drawing ellipses or points at the current mouse position, draw a line from the previous position to the current position. This will eliminate any gaps in your lines.
void draw(){
line(pmouseX, pmouseY, mouseX, mouseY);
}
Shameless self-promotion: I've written a tutorial on getting user input in Processing available here.
Note: This dumb solution will only work if you aren't redrawing the background every frame. If you need to redraw everything every frame, then George's answer is the way to go.

Visual Clock: New to Processing

Back again with my visual clock. I need a bit of guidance. I am trying to make a "clock" graph that counts down the time (in hours) until event times that a user inputs, such as until they eat dinner, sleep etc. In my sketch, "now" is the dark grey line on the left, and I would like to create a system that counts down these input times, in relation to real time. The white ticks signify 24 hrs in a day. I also want the gradient to change in relation to "now" depending on the lightness outside. Mapping was suggested, but I don't even know where to start. Here is my sketch and code: any help you be appreciated!
My Sketch in Illustrator
PFont din;
PFont din2;
color blue = color(0, 80, 200);
color orange = color(255, 150, 50);
int hr = hour();
float w, h, angle;
void setup () {
size (1100, 600, P2D);
din = loadFont("DIN-Medium-30.vlw");
din2 = loadFont("DIN-Medium-15.vlw");
}
void draw() {
background(255);
gradientRect(90, 470, 850, 50, blue, orange);
fill(0, 102, 153);
textFont(din);
if (hr > 12) {
hr=hour()-12;
text("pm", 220, 55);
} else {
text ("am", 220, 55);
}
text(nf(hr, 2)+":", 86, 55);
text(nf(minute(), 2)+":", 126, 55);
text(nf(second(), 2), 166, 55);
textFont(din2);
text("SLEEP", 25, 350);
stroke(255);
textFont(din2);
text("TEST", 25, 250);
textFont(din2);
text("DINNER", 25, 150);
//GREY RECT
strokeWeight(0);
fill(209);
rect(90, 70, 850, 400);
//DINNER
strokeWeight(2);
stroke(255);
noFill();
rect(90, 130, 850, 30);
//TEST
strokeWeight(2);
stroke(255);
noFill();
rect(90, 230, 850, 30);
//SLEEP
strokeWeight(2);
stroke(255);
noFill();
rect(90, 330, 850, 30);
//NOW
stroke(150);
strokeWeight(5);
line(90, 470, 90, 75);
//24 HRS
stroke(255);
strokeWeight(2);
translate(90, 230);
// TIME
angle = millis();
w = hr=hour()-12;
h = 30;
fill(255);
rect(0, 0, w, 100);
strokeWeight(0);
}
//Gradiant
void gradientRect(int x, int y, int w, int h, color c1, color c2) {
beginShape();
fill(c1);
vertex(x, y);
vertex(x, y+h);
fill(c2);
vertex(x+w, y+h);
vertex(x+w, y);
endShape();
}
//input, output - calculations, get second();
//map();
It's really hard to answer "how do I do this?" or "how do I start?" type questions, so they're generally considered off-topic on Stack Overflow. Stack Overflow is more for specific "I tried X, expected Y, but got Z instead" type questions.
That being said, your question is "How do I start programming a sketch like this?", and I'll try to answer that.
The golden rule to starting a programming project is: start smaller. Try to break your end goal down into much smaller individual steps, and then try to do those steps one at a time. You've already got a sketch that does the step of drawing a display, and that's great. Now create a separate sketch that only shows the current time. This might seem dumb, or smaller than what you're interested in, but that's good. That's how you start a big programming project: by breaking it down into pieces that seem too small to be interesting.
In creating these separate sketches that only do one small piece of your big main goal, the Processing reference is your best friend. Check out the Time & Date section of the reference for some useful functions. Here's a little example sketch that just shows the current time:
void draw() {
background(0);
int h = hour();
int m = minute();
int s = second();
String time = h + ":" + m + ":" + s;
text(time, 10, 50);
}
From there, it's easier for you to create a countdown timer in this small example than if you keep focusing on your big goal. Here's one that counts down until midnight:
void draw() {
background(0);
int h = 24-hour();
int m = 60-minute();
int s = 60-second();
String time = h + ":" + m + ":" + s;
text(time, 10, 50);
}
Now that you've got that working, it'll be easier to make small, incremental changes to get closer and closer to your goal. The next thing you might do is show a simple visualization of the time instead of text. Again, the reference is your best friend: the reference for the minute() function shows an example that shows the time in a simple visualization:
void draw() {
background(204);
int s = second(); // Values from 0 - 59
int m = minute(); // Values from 0 - 59
int h = hour(); // Values from 0 - 23
line(s, 0, s, 33);
line(m, 33, m, 66);
line(h, 66, h, 100);
}
And we might try to add that type of logic to our small countdown sketch. Something like this:
void draw() {
background(128);
int h = 24-hour();
int m = 60-minute();
int s = 60-second();
line(s, 0, s, 33);
line(m, 33, m, 66);
line(h, 66, h, 100);
}
From there, keep asking yourself: what is the absolute, smallest, simplest thing I know I need to do next? If something seems confusing or too big, then break it down even further. Create a bunch of tiny sketches that only do one thing, and only think about combining them when you have them working by themselves. That way, when you get stuck, you'll be able to ask more specific questions by posting the small sketch that you're stuck on.
Instead of focusing on the big picture and trying to do it all at once, break it down into smaller pieces, and focus on only one small piece at a time.
Good luck, and happy coding!

How do i continue translate transition?

I want to make the node go further instead of starting from initial coordinates and going a certain distance can anyone help me?
public void sky(Node node, double xDest, double yDest) {
TranslateTransition tTrans = new TranslateTransition(
Duration.millis(4000), node);
tTrans.setFromX(0);
tTrans.setFromY(0);
tTrans.setToY(yDest);
tTrans.setToX(xDest);
tTrans.setRate(2);
tTrans.setInterpolator(Interpolator.LINEAR);
tTrans.play();
}
The method above moves a node with xDest and yDest.
xDest = x coordinate and yDest = y coordinate
If the initial coordinates are 100, 100 and xDest = 50 and yDest = 50 then when i press the button the node will go to 150, 150 but if i press the button again the animation shows the node starting from 100, 100 and going to 150, 150. I want it to go to 200, 200 at second mouse click, any ideas please ?
Use the byX and byY properties instead of the toX and toY properties:
public void sky(Node node, double deltaX, double deltaY) {
TranslateTransition tTrans = new TranslateTransition(
Duration.millis(4000), node);
tTrans.setFromX(node.getTranslateX());
tTrans.setFromY(node.getTranslateY());
tTrans.setByY(deltaY);
tTrans.setByX(deltaX);
tTrans.setRate(2);
tTrans.setInterpolator(Interpolator.LINEAR);
tTrans.play();
}
Now each time you call this with deltaX=50 and deltaY=50 it will translate it by an additional 50 pixels along both axes.

Rotate 3D object to mouse with Three.js

I want to rotate an object in 3D space, so that the front side always looks to the mouse.
function onMouseMove(event){
mouse3D = projector.unprojectVector(
new THREE.Vector3( event.clientX, event.clientY, 0.5 ), camera );
}
var angle = ??;
box.rotation.y = angle;
First is the unprojection correct ? And secondly how to calculate the angle ? Is it just tan(mouseX/mouseY) ? I'm trying to get more into the 3D mathematics, so a little bit explanation would be nice.
Thanks in advance.
// Direction we are already facing (without rotation)
var forward = new Vector3(0,0,-1);
// Direction we want to be facing (towards mouse pointer)
var target = new Vector3().sub(mouse3D, box.position).normalize();
// Axis and angle of rotation
var axis = new Vector3().cross(forward, target);
var sinAngle = axis.length(); // |u x v| = |u|*|v|*sin(a)
var cosAngle = forward.dot(target); // u . v = |u|*|v|*cos(a)
var angle = Math.atan2(sinAngle, cosAngle); // atan2(sin(a),cos(a)) = a
axis.normalize();
// Overwrite rotation
box.rotation.makeRotationAxis(axis, angle);
Alternatively, you could use quaternions:
// Overwrite rotation
box.useQuaternion = true;
box.quaternion.setFromAxisAngle(axis, angle);

Resources