...loading...
SWPoint Class Reference
Overview
The SWPoint class represents points or vectors in 2D or 3D space with styling options. It is designed to work seamlessly within the p5.js environment and integrates with the SketchWave ecosystem, particularly the SWColor and SWGrid classes.
Key Features:
- Support for 2D and 3D coordinates
- Customizable stroke weight and color
- Optional text labels with flexible positioning
- Interactive drag-and-drop functionality
- Pen trail functionality to track point movement
- Drawing in both screen and user (grid) coordinates
- Vector operations (distance calculations)
- Automatic label boundary detection
- Extensible design for animation and interaction
Constructor
new SWPoint(x, y, z, strokeWeight, strokeColor, label)
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
x |
number | required | X coordinate of the point |
y |
number | required | Y coordinate of the point |
z |
number | undefined | Z coordinate (optional, for 3D space) |
strokeWeight |
number | 1 | Weight/thickness of the point when drawn |
strokeColor |
SWColor | undefined | An SWColor instance for the point's color |
label |
string | undefined | Label text (typically a single letter like "A", "B", etc.) |
Example:
// Create a simple 2D point
let point1 = new SWPoint(100, 200);
// Create a styled 2D point
let redColor = new SWColor(0, 100, 100); // HSB: red
let point2 = new SWPoint(150, 250, undefined, 5, redColor);
// Create a 3D point
let blueColor = new SWColor(240, 100, 100); // HSB: blue
let point3 = new SWPoint(100, 150, 50, 3, blueColor);
// Create a labeled point
let greenColor = new SWColor(108, 50, 78); // HSB: light green
let point4 = new SWPoint(200, 100, undefined, 8, greenColor, "A");
point4.showLabel = true;
Properties
| Property | Type | Description |
|---|---|---|
x |
number | X coordinate of the point |
y |
number | Y coordinate of the point |
z |
number | undefined | Z coordinate (undefined for 2D points) |
pos |
p5.Vector | Getter/setter that exposes the backing p5.Vector directly. Useful for p5.Vector operations (rotate, lerp, dist, etc.) in animation code. Setting pos replaces the internal vector; the 2D/3D flag is unchanged. |
strokeWeight |
number | The weight/thickness when drawing the point |
strokeColor |
SWColor | The SWColor instance for the point's color |
label |
string | undefined | Label text (typically a single letter) |
showLabel |
boolean | Whether to display the label (default: false) |
labelOffset |
object | Label position offset {x, y} in pixels (default: {x: 10, y: -10}) |
labelFontSize |
number | Font size for label text in pixels (default: 12) |
isDraggable |
boolean | Whether the point can be dragged (default: false) |
penOn |
boolean | Whether the pen trail is active (default: false) |
trail |
Array | Array of previous positions [{x, y, z}] |
maxTrailLength |
number | Maximum number of trail points (default: 500) |
setPosition(x, y) or move(dx, dy) over direct x/y assignment — both automatically update the pen trail. If you must assign x/y directly, call _updateTrail() afterward to keep the trail in sync.
Methods
Static Methods
SWPoint.copy(other)
Creates and returns a deep copy of the given SWPoint instance, including all properties, labelOffset, and trail. Uses SWColor.copy() to deep-copy the stroke color.
other(SWPoint): The SWPoint instance to copy. Throws anErrorif the argument is not an SWPoint instance.
other
let original = new SWPoint(100, 200, undefined, 5, redColor, "A");
original.showLabel = true;
original.setPen(true);
let copy = SWPoint.copy(original); // Deep copy — independent of original
Drawing Methods
draw(defaultColor)
Draws the point using p5.js in screen coordinates. If the point's strokeColor is null, it will use defaultColor if provided, otherwise falls back to black (swBlack).
defaultColor(SWColor, optional): Fallback color to use whenstrokeColoris null
function draw() {
background(0, 0, 86); // HSB: light gray
myPoint.draw(); // Uses strokeColor, or black if null
// With a fallback color:
let fallback = new SWColor(0, 100, 100); // red
myPoint.draw(fallback); // Uses red if strokeColor is null
}
drawOnGrid(grid, defaultColor)
Draws the point using user (grid) coordinates mapped by the given SWGrid instance. If the point's strokeColor is null, it will use defaultColor if provided, otherwise falls back to black (swBlack).
grid(SWGrid): The grid instance for coordinate transformationdefaultColor(SWColor, optional): Fallback color to use whenstrokeColoris null
let myGrid = new SWGrid(-10, 10, -10, 10);
myPoint.drawOnGrid(myGrid); // Uses strokeColor, or black if null
// With a fallback color:
let fallback = new SWColor(0, 100, 100); // red
myPoint.drawOnGrid(myGrid, fallback); // Uses red if strokeColor is null
drawTrail()
Draws the pen trail in screen coordinates. Only draws if pen is on and trail has at least 2 points.
myPoint.setPen(true);
// ... move the point around ...
myPoint.drawTrail(); // Shows the path
drawTrailOnGrid(grid)
Draws the pen trail in user (grid) coordinates.
Parameters:grid(SWGrid): The grid instance for coordinate transformation
Movement Methods
move(dx, dy, dz)
Moves the point by the specified deltas. Automatically updates the trail if pen is on.
Parameters:dx(number): Change in x coordinatedy(number): Change in y coordinatedz(number): Change in z coordinate (default: 0)
// Move point right by 10 and up by 5
myPoint.move(10, -5);
// Animate movement
function draw() {
background(0, 0, 86); // HSB: light gray
myPoint.move(1, sin(frameCount * 0.05) * 2);
myPoint.drawTrail();
myPoint.draw();
}
Pen Trail Methods
setPen(on)
Enables or disables the pen trail. When disabled, clears the trail.
Parameters:on(boolean): true to enable, false to disable (default: true)
myPoint.setPen(true); // Start recording trail
myPoint.setPen(false); // Stop and clear trail
setMaxTrailLength(n)
Sets the maximum number of points in the trail. Older points are removed when limit is exceeded.
Parameters:n(number): Maximum trail length
myPoint.setMaxTrailLength(100); // Keep last 100 positions
clearTrail()
Clears the trail history without changing the pen state.
myPoint.clearTrail(); // Remove all trail points
Style Methods
setStrokeColor(swColor)
Sets the stroke color using an SWColor instance.
Parameters:swColor(SWColor): The new color
let newColor = new SWColor(120, 100, 100); // HSB: green
myPoint.setStrokeColor(newColor);
setStrokeWeight(w)
Sets the stroke weight (thickness).
Parameters:w(number): The new stroke weight
myPoint.setStrokeWeight(8);
Label Methods
setLabel(label)
Sets the label text (typically a single letter).
Parameters:label(string): Label text
myPoint.setLabel("A");
setShowLabel(show)
Sets whether to display the label.
Parameters:show(boolean): True to show label (default: true)
myPoint.setShowLabel(true); // Show label
myPoint.setShowLabel(false); // Hide label
setLabelFontSize(size)
Sets the font size for the label text.
Parameters:size(number): Font size in pixels
myPoint.setLabelFontSize(18); // Larger label
setLabelPosition(offsetX, offsetY)
Manually sets the label position using explicit x,y offsets from the point.
Parameters:offsetX(number): Horizontal offset in pixelsoffsetY(number): Vertical offset in pixels (negative is up)
myPoint.setLabelPosition(10, -15); // 10 right, 15 up
setLabelAbove(distance)
Positions label above the point.
Parameters:distance(number): Distance above in pixels (default: 15)
myPoint.setLabelAbove(20);
setLabelBelow(distance)
Positions label below the point.
Parameters:distance(number): Distance below in pixels (default: 15)
myPoint.setLabelBelow(20);
setLabelLeft(distance)
Positions label to the left of the point.
Parameters:distance(number): Distance to the left in pixels (default: 15)
myPoint.setLabelLeft(20);
setLabelRight(distance)
Positions label to the right of the point.
Parameters:distance(number): Distance to the right in pixels (default: 15)
myPoint.setLabelRight(20);
nudgeLabel(dx, dy)
Adjusts label position by small amounts for fine-tuning.
Parameters:dx(number): Horizontal adjustment in pixelsdy(number): Vertical adjustment in pixels
myPoint.nudgeLabel(2, -3); // Fine-tune position
setLabelOffsetFromSlope(slope, distance)
Positions label perpendicular to a line with the given slope. Useful for labeling points on lines.
Parameters:slope(number): Slope of the line (use Infinity for vertical, 0 for horizontal)distance(number): Distance from point (default: 15)
let slope = 2; // Line with slope 2
myPoint.setLabelOffsetFromSlope(slope, 20);
labelProximityCheck(grid, margin)
Automatically adjusts label position if it goes off canvas boundaries. Keeps labels visible.
Parameters:grid(SWGrid): Grid for boundary checkingmargin(number): Minimum distance from edge in pixels (default: 5)
myPoint.labelProximityCheck(myGrid); // Auto-adjust near edges
Interaction Methods
setDraggable(draggable)
Enables or disables draggability for this point.
Parameters:draggable(boolean): True to enable dragging (default: true)
myPoint.setDraggable(true); // Enable dragging
myPoint.setDraggable(false); // Disable dragging
setPosition(x, y)
Sets the point to a new position in user coordinates. Updates trail if pen is on.
Parameters:x(number): New x coordinatey(number): New y coordinate
myPoint.setPosition(5, 7); // Move to (5, 7)
containsPoint(screenX, screenY, grid, tolerance)
Checks if a screen coordinate is within the point's hit area. Used for drag detection.
Parameters:screenX(number): Mouse x in screen coordinatesscreenY(number): Mouse y in screen coordinatesgrid(SWGrid): Grid for coordinate conversiontolerance(number): Hit detection radius in pixels (default: 10)
if (myPoint.containsPoint(mouseX, mouseY, myGrid)) {
// Mouse is over the point
}
Utility Methods
distanceTo(otherSWPt)
Returns the Euclidean distance to another SWPoint in user coordinates.
Parameters:otherSWPt(SWPoint): The other point
let point1 = new SWPoint(0, 0);
let point2 = new SWPoint(3, 4);
let distance = point1.distanceTo(point2); // Returns 5
toVector()
Returns a copy of the backing position as a p5.Vector. Safe to pass into p5 vector math without risking mutation of the point's internal state.
let v = myPoint.toVector(); // Independent copy
v.rotate(HALF_PI); // Does NOT affect myPoint
setFromVector(v)
Sets the point's position from a p5.Vector and updates the trail if the pen is on. Useful in animation workflows that compute new positions with vector math.
v(p5.Vector): The new position vector
let newPos = p5.Vector.lerp(myPoint.toVector(), targetVec, 0.1);
myPoint.setFromVector(newPos); // Smooth approach to target
addVector(v)
Adds a p5.Vector to the point's position and updates the trail. An alternative to move(dx, dy) for vector-first workflows.
v(p5.Vector): The vector to add
let velocity = createVector(2, -1);
myPoint.addVector(velocity); // Equivalent to myPoint.move(2, -1)
toString()
Returns a simple string representation of the point. Coordinates are shown with 2 decimal places.
Returns: string - Format: "Pt.A @ (x, y), colorName" if labeled, or "Pt @ (x, y), colorName" if unlabeledlet pt = new SWPoint(100.5, 200.75, undefined, 5, new SWColor(0, 100, 100, 100, "red"), "A");
console.log(pt.toString());
// Output: "Pt.A @ (100.50, 200.75), red"
let pt2 = new SWPoint(3.14159, 2.71828);
console.log(pt2.toString());
// Output: "Pt @ (3.14, 2.72), no color"
logAllInfo()
Returns a detailed string representation with all properties, useful for debugging.
Returns: string - Complete property listingconsole.log(myPoint.logAllInfo());
// Output: "SWPoint(x: 100, y: 200, strokeWeight: 1, strokeColor: ..., label: A, showLabel: true, penOn: false, trailLen: 0)"
Usage Examples
Example 1: Basic Point Drawing
let myPoint;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100);
let red = new SWColor(0, 100, 100); // HSB: red
myPoint = new SWPoint(200, 200, undefined, 10, red);
}
function draw() {
background(0, 0, 86); // HSB: light gray
myPoint.draw();
}
Example 2: Animated Point with Trail
let myPoint;
let angle = 0;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100);
let blue = new SWColor(216, 100, 100); // HSB: light blue
myPoint = new SWPoint(200, 200, undefined, 5, blue);
myPoint.setPen(true);
myPoint.setMaxTrailLength(200);
}
function draw() {
background(0, 0, 86); // HSB: light gray
// Circular motion
let radius = 100;
let dx = cos(angle) * radius - myPoint.x + width/2;
let dy = sin(angle) * radius - myPoint.y + height/2;
myPoint.move(dx * 0.1, dy * 0.1);
angle += 0.05;
myPoint.drawTrail();
myPoint.draw();
}
Example 3: Using with SWGrid
let myGrid;
let myPoint;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100);
myGrid = new SWGrid(-10, 10, -10, 10);
let green = new SWColor(120, 100, 78); // HSB: green
// Point at user coordinates (5, 3)
myPoint = new SWPoint(5, 3, undefined, 8, green);
}
function draw() {
background(0, 0, 86); // HSB: light gray
myGrid.draw();
myPoint.drawOnGrid(myGrid);
}
Example 4: Interactive Point
let myPoint;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100);
let purple = new SWColor(285, 100, 78); // HSB: purple
myPoint = new SWPoint(mouseX, mouseY, undefined, 6, purple);
myPoint.setPen(true);
}
function draw() {
background(0, 0, 86, 10); // HSB: light gray with fade effect
// Follow mouse
myPoint.x = mouseX;
myPoint.y = mouseY;
myPoint._updateTrail(); // Update trail manually
myPoint.drawTrail();
myPoint.draw();
}
function mousePressed() {
myPoint.clearTrail(); // Clear trail on click
}
Example 5: Labeled Points on Grid
let myGrid;
let points = [];
let labels = ["A", "B", "C", "D"];
let colors;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100);
myGrid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
// Create labeled points
colors = [
new SWColor(0, 80, 90), // Red
new SWColor(120, 80, 90), // Green
new SWColor(240, 80, 90), // Blue
new SWColor(60, 80, 90) // Yellow
];
let positions = [{x: -5, y: 5}, {x: 5, y: 5}, {x: -5, y: -5}, {x: 5, y: -5}];
for (let i = 0; i < 4; i++) {
let pt = new SWPoint(positions[i].x, positions[i].y, undefined, 10, colors[i], labels[i]);
pt.showLabel = true;
pt.setLabelFontSize(16);
pt.setLabelAbove(15);
pt.labelProximityCheck(myGrid); // Keep labels on canvas
points.push(pt);
}
}
function draw() {
background(0, 0, 86); // HSB: light gray
myGrid.draw();
for (let pt of points) {
pt.drawOnGrid(myGrid);
}
}
Example 6: Draggable Points
let myGrid;
let points = [];
let draggedPoint = null;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100);
myGrid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
// Create draggable points
for (let i = 0; i < 3; i++) {
let x = -5 + i * 5;
let y = 0;
let color = new SWColor(i * 120, 80, 90);
let pt = new SWPoint(x, y, undefined, 10, color, String.fromCharCode(65 + i));
pt.showLabel = true;
pt.setDraggable(true);
points.push(pt);
}
}
function draw() {
background(0, 0, 86); // HSB: light gray
myGrid.draw();
for (let pt of points) {
pt.drawOnGrid(myGrid);
}
}
function mousePressed() {
// Check if clicking on a point
for (let pt of points) {
if (pt.containsPoint(mouseX, mouseY, myGrid)) {
draggedPoint = pt;
break;
}
}
}
function mouseDragged() {
if (draggedPoint) {
let userCoords = myGrid.screenToUser(mouseX, mouseY);
draggedPoint.setPosition(userCoords.x, userCoords.y);
draggedPoint.labelProximityCheck(myGrid);
redraw();
}
}
function mouseReleased() {
draggedPoint = null;
}
Extending SWPoint
The SWPoint class is designed to be extended for more specialized behavior. Here are some examples:
Example: Animated Point Class
class AnimatedPoint extends SWPoint {
constructor(x, y, strokeWeight, strokeColor) {
super(x, y, undefined, strokeWeight, strokeColor);
this.velocity = {x: random(-2, 2), y: random(-2, 2)};
this.acceleration = {x: 0, y: 0};
}
update() {
this.velocity.x += this.acceleration.x;
this.velocity.y += this.acceleration.y;
this.move(this.velocity.x, this.velocity.y);
// Bounce off edges
if (this.x < 0 || this.x > width) this.velocity.x *= -1;
if (this.y < 0 || this.y > height) this.velocity.y *= -1;
}
applyForce(fx, fy) {
this.acceleration.x = fx;
this.acceleration.y = fy;
}
}
Example: Particle System Using SWPoint
class Particle extends SWPoint {
constructor(x, y) {
let randomColor = new SWColor(random(360), random(100), random(100)); // HSB: random color
super(x, y, undefined, random(2, 8), randomColor);
this.setPen(true);
this.setMaxTrailLength(50);
this.lifespan = 255;
}
update() {
this.move(random(-2, 2), random(-2, 2));
this.lifespan -= 2;
}
isDead() {
return this.lifespan <= 0;
}
display() {
this.strokeColor.setAlpha(this.lifespan);
this.drawTrail();
this.draw();
}
}
Integration with SketchWave Ecosystem
Dependencies
- p5.js: Required for all drawing operations
- SWColor: Required for color management
- SWGrid: Optional, for grid-based coordinate systems
Loading Order
Ensure scripts are loaded in this order in your HTML:
<!-- p5js CDN-->
<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.js"></script>
<!-- SketchWave classes -->
<script src="scripts/swColor.js"></script>
<script src="scripts/swPoint.js"></script>
<script src="scripts/swGrid.js"></script>
<!-- Your sketch script -->
<script src="scripts/mySketch.js"></script>
Best Practices
- Always use
SWColorinstances for colors rather than raw p5.js colors - Use
move()method rather than directly modifying x/y if pen trail is active - Limit
maxTrailLengthfor performance in animations with many points - Clear trails periodically in long-running animations to prevent memory buildup
- When working with grids, use
drawOnGrid()for proper coordinate transformation
Complete Source Code
View the complete, documented source code for the SWPoint class:
Show/Hide Source Code
/*
File: swPoint.js
Date: 2026-02-26
Author: klp
Workspace: SketchWaveTNT2026-02-25-Stg5
Purpose: SWPoint class for SketchWaveJS
Comment(s):
Refactored to back position data with a p5.Vector (_pos) while exposing x, y, z
via getters/setters. All consumer code that reads or writes .x / .y / .z continues
to work without any changes; additionally, the public `pos` property gives direct
access to the underlying p5.Vector for cleaner math in dependent classes.
Key design decisions:
- _pos: internal p5.Vector (always 3-component; z defaults to 0 when not in 3D mode)
- _hasZ: boolean flag preserving the "undefined z means 2D" contract that the rest of
the library depends on. When _hasZ is false, the z getter returns undefined.
- pos getter/setter: exposes or replaces the backing p5.Vector directly, enabling
slick p5.Vector operations (rotate, lerp, dist, etc.) in animation methods.
New utility methods added:
- toVector() — returns a copy of the position as a p5.Vector
- setFromVector(v) — sets position from a p5.Vector (and updates trail)
- addVector(v) — adds a p5.Vector to the position (alternative to move())
=== Notes ===:
- This class assumes p5.js and SWColor are loaded in the environment.
- SWPoint class represents points or vectors in 2D or 3D space with styling options.
- Includes properties for position (x, y, z), stroke weight, and stroke color.
- Supports pen trail functionality to track and draw the path of the point.
- Designed to be compatible with p5.js and the SWColor class.
- strokeColor should be an SWColor instance for consistency.
- Pen trail logic includes enabling/disabling the pen, storing trail points, and
limiting trail length.
*/
console.log("[swPoint.js] SWPoint class loaded.");
class SWPoint {
/**
* @param {number} x - X coordinate
* @param {number} y - Y coordinate
* @param {number} [z] - Z coordinate (optional, for 3D)
* @param {number} [strokeWeight=1] - Stroke weight
* @param {SWColor} [strokeColor] - Stroke color (SWColor instance)
* @param {string} [label] - Label (single letter, optional)
*/
constructor(x, y, z = undefined, strokeWeight = 1, strokeColor = undefined, label = undefined) {
this.x = x;
this.y = y;
this.z = z;
this.strokeWeight = strokeWeight;
this.strokeColor = strokeColor; // Always store SWColor instance
this.label = label; // Label (typically a single letter)
this.showLabel = false; // Whether to display the label
this.labelOffset = { x: 10, y: -10 }; // Offset for label position
this.labelFontSize = 12; // Font size for label text
this.isDraggable = false; // Whether the point can be dragged
// Pen trail logic
this.penOn = false;
this.trail = [];
this.maxTrailLength = 500; // default, can be changed
}//end constructor
/**
* Copy constructor: returns a new SWPoint with the same properties as the given SWPoint instance
* Creates a deep copy of all properties including trail and labelOffset
* @param {SWPoint} other - The SWPoint instance to copy
* @returns {SWPoint} A new SWPoint with copied properties
*/
static copy(other) {
if (!(other instanceof SWPoint)) {
throw new Error('Argument to SWPoint.copy must be an SWPoint instance');
}
// Create new point with basic properties
const newPoint = new SWPoint(
other.x,
other.y,
other.z,
other.strokeWeight,
other.strokeColor ? SWColor.copy(other.strokeColor) : undefined,
other.label
);
// Copy additional properties
newPoint.showLabel = other.showLabel;
newPoint.labelOffset = { x: other.labelOffset.x, y: other.labelOffset.y };
newPoint.labelFontSize = other.labelFontSize;
newPoint.isDraggable = other.isDraggable;
newPoint.penOn = other.penOn;
newPoint.maxTrailLength = other.maxTrailLength;
// Deep copy the trail array
newPoint.trail = other.trail.map(pt => ({ x: pt.x, y: pt.y, z: pt.z }));
return newPoint;
}//end copy
/**
* Draws the point using p5.js in screen coordinates
* @param {SWColor} [defaultColor] - Color to use if strokeColor is null
*/
draw(defaultColor = undefined) {
// Use strokeColor if set, otherwise use defaultColor, otherwise swBlack
let colorToUse = this.strokeColor;
if (!colorToUse) {
colorToUse = defaultColor || swBlack;
}
if (colorToUse && colorToUse.col) {
stroke(colorToUse.col);
}
strokeWeight(this.strokeWeight);
if (this.z !== undefined) {
point(this.x, this.y, this.z);
} else {
point(this.x, this.y);
}
noStroke();
strokeWeight(1);
// Draw label if showLabel is true
if (this.showLabel && this.label) {
fill(colorToUse && colorToUse.col ? colorToUse.col : 0);
textAlign(CENTER, CENTER);
textSize(this.labelFontSize);
text(this.label, this.x + this.labelOffset.x, this.y + this.labelOffset.y);
noFill();
}
}//end draw
/**
* Draws the point using user (grid) coordinates mapped by the given SWGrid
* @param {SWGrid} grid
* @param {SWColor} [defaultColor] - Color to use if strokeColor is null
*/
drawOnGrid(grid, defaultColor = undefined) {
const {x: px, y: py} = grid.userToScreen(this.x, this.y);
// Use strokeColor if set, otherwise use defaultColor, otherwise swBlack
let colorToUse = this.strokeColor;
if (!colorToUse) {
colorToUse = defaultColor || swBlack;
}
if (colorToUse && colorToUse.col) {
stroke(colorToUse.col);
}
strokeWeight(this.strokeWeight);
if (this.z !== undefined) {
point(px, py, this.z);
} else {
point(px, py);
}
noStroke();
strokeWeight(1);
// Draw label if showLabel is true
if (this.showLabel && this.label) {
fill(colorToUse && colorToUse.col ? colorToUse.col : 0);
textAlign(CENTER, CENTER);
textSize(this.labelFontSize);
text(this.label, px + this.labelOffset.x, py + this.labelOffset.y);
noFill();
}
}//end drawOnGrid
/**
* Pen trail enhancements:
* - penOn: whether the pen is active
* - trail: array of previous positions [{x, y, z}]
* - maxTrailLength: maximum number of points in the trail
*/
/**
* Draws the pen trail in screen coordinates
*/
drawTrail() {
if (!this.penOn || this.trail.length < 2) return;
noFill();
stroke(this.strokeColor && this.strokeColor.col ? this.strokeColor.col : 0);
strokeWeight(Math.max(1, this.strokeWeight / 2));
beginShape();
for (const pt of this.trail) {
if (this.z !== undefined && pt.z !== undefined) {
vertex(pt.x, pt.y, pt.z);
} else {
vertex(pt.x, pt.y);
}
}
endShape();
noStroke();
strokeWeight(1);
}//end drawTrail
/**
* Draws the pen trail in user (grid) coordinates
* @param {SWGrid} grid
*/
drawTrailOnGrid(grid) {
if (!this.penOn || this.trail.length < 2) return;
noFill();
stroke(this.strokeColor && this.strokeColor.col ? this.strokeColor.col : 0);
strokeWeight(Math.max(1, this.strokeWeight / 2));
beginShape();
for (const pt of this.trail) {
const {x, y} = grid.userToScreen(pt.x, pt.y);
vertex(x, y);
}
endShape();
noStroke();
strokeWeight(1);
}//end drawTrailOnGrid
/**
* Moves the point by dx, dy, dz
* If pen is on, records the new position in the trail
*/
move(dx, dy, dz = 0) {
this.x += dx;
this.y += dy;
if (this.z !== undefined) this.z += dz;
this._updateTrail();
}//end move
/**
* Call this after manually setting x/y/z to update the trail if pen is on
* Private method: do not call directly (as indicated by underscore prefix)
*/
_updateTrail() {
if (this.penOn) {
// Only add if position changed
if (
this.trail.length === 0 ||
this.trail[this.trail.length - 1].x !== this.x ||
this.trail[this.trail.length - 1].y !== this.y ||
this.trail[this.trail.length - 1].z !== this.z
) {
this.trail.push({ x: this.x, y: this.y, z: this.z });
if (this.trail.length > this.maxTrailLength) {
this.trail.shift();
}
}
}
}//end _updateTrail
/**
* Enable or disable the pen trail
* @param {boolean} on
*/
setPen(on = true) {
this.penOn = on;
if (!on) this.trail = [];
}//end setPen
/**
* Set the maximum trail length
* @param {number} n
*/
setMaxTrailLength(n) {
this.maxTrailLength = n;
if (this.trail.length > n) {
this.trail = this.trail.slice(-n);
}
}//end setMaxTrailLength
/**
* Clears the pen trail history, regardless of pen state
*/
clearTrail() {
this.trail = [];
}//end clearTrail
/**
* Sets the stroke color
* @param {SWColor} swColor
*/
setStrokeColor(swColor) {
this.strokeColor = swColor;
}//end setStrokeColor
/**
* Sets the stroke weight
* @param {number} w
*/
setStrokeWeight(w) {
this.strokeWeight = w;
}//end setStrokeWeight
/**
* Sets whether to show the label
* @param {boolean} show
*/
setShowLabel(show = true) {
this.showLabel = show;
}//end setShowLabel
/**
* Sets the label (typically a single letter)
* @param {string} label
*/
setLabel(label) {
this.label = label;
}//end setLabel
/**
* Sets the label font size
* @param {number} size - Font size in pixels
*/
setLabelFontSize(size) {
this.labelFontSize = size;
}//end setLabelFontSize
/**
* Sets the label offset based on a line slope to position the label perpendicular to the line
* @param {number} slope - The slope of the line (use Infinity for vertical lines, 0 for horizontal)
* @param {number} [distance=15] - Distance to offset the label from the point
*/
setLabelOffsetFromSlope(slope, distance = 15) {
if (slope === Infinity || slope === -Infinity) {
this.labelOffset = { x: distance, y: 0 };
} else if (slope === 0) {
this.labelOffset = { x: 0, y: -distance };
} else {
const perp_x = -slope;
const perp_y = 1;
const length = Math.sqrt(perp_x * perp_x + perp_y * perp_y);
const unit_x = perp_x / length;
const unit_y = perp_y / length;
this.labelOffset = { x: unit_x * distance, y: unit_y * distance };
}
}//end setLabelOffsetFromSlope
/**
* Manually sets the label position using explicit x,y offsets
* @param {number} offsetX - Horizontal offset from point (pixels)
* @param {number} offsetY - Vertical offset from point (pixels, negative is up)
*/
setLabelPosition(offsetX, offsetY) {
this.labelOffset = { x: offsetX, y: offsetY };
}//end setLabelPosition
/**
* Nudges the label by a small amount in either direction
* @param {number} dx - Amount to move horizontally (pixels)
* @param {number} dy - Amount to move vertically (pixels)
*/
nudgeLabel(dx, dy) {
this.labelOffset.x += dx;
this.labelOffset.y += dy;
}//end nudgeLabel
/**
* Sets label to common position above the point
* @param {number} [distance=15] - Distance above the point
*/
setLabelAbove(distance = 15) {
this.labelOffset = { x: 0, y: -distance };
}//end setLabelAbove
/**
* Sets label to common position below the point
* @param {number} [distance=15] - Distance below the point
*/
setLabelBelow(distance = 15) {
this.labelOffset = { x: 0, y: distance };
}//end setLabelBelow
/**
* Sets label to common position to the left of the point
* @param {number} [distance=15] - Distance to the left
*/
setLabelLeft(distance = 15) {
this.labelOffset = { x: -distance, y: 0 };
}//end setLabelLeft
/**
* Sets label to common position to the right of the point
* @param {number} [distance=15] - Distance to the right
*/
setLabelRight(distance = 15) {
this.labelOffset = { x: distance, y: 0 };
}//end setLabelRight
/**
* Automatically adjusts label position if it goes off the canvas boundaries
* @param {SWGrid} grid - The grid for boundary checking
* @param {number} [margin=5] - Minimum distance from edge in screen pixels
*/
labelProximityCheck(grid, margin = 5) {
if (!this.label || !this.showLabel) return;
const {x: px, y: py} = grid.userToScreen(this.x, this.y);
const labelX = px + this.labelOffset.x;
const labelY = py + this.labelOffset.y;
const canvasWidth = grid.screenWidth || width;
const canvasHeight = grid.screenHeight || height;
const textWidth = 15;
const textHeight = 12;
let adjustedOffsetX = this.labelOffset.x;
let adjustedOffsetY = this.labelOffset.y;
if (labelX + textWidth/2 > canvasWidth - margin) adjustedOffsetX = -15;
if (labelX - textWidth/2 < margin) adjustedOffsetX = 15;
if (labelY - textHeight/2 < margin) adjustedOffsetY = 15;
if (labelY + textHeight/2 > canvasHeight - margin) adjustedOffsetY = -15;
this.labelOffset = { x: adjustedOffsetX, y: adjustedOffsetY };
}//end labelProximityCheck
/**
* Checks if a screen coordinate is within the point's hit area
* @param {number} screenX - Mouse x in screen coordinates
* @param {number} screenY - Mouse y in screen coordinates
* @param {SWGrid} grid - The grid for coordinate conversion
* @param {number} [tolerance=10] - Hit detection radius in screen pixels
* @returns {boolean} True if the coordinate is within tolerance of the point
*/
containsPoint(screenX, screenY, grid, tolerance = 10) {
const {x: px, y: py} = grid.userToScreen(this.x, this.y);
return dist(screenX, screenY, px, py) <= tolerance;
}//end containsPoint
/**
* Sets the point to a new position in user coordinates
* @param {number} x - New x coordinate (user coordinates)
* @param {number} y - New y coordinate (user coordinates)
*/
setPosition(x, y) {
this.x = x;
this.y = y;
this._updateTrail();
}//end setPosition
/**
* Enables or disables draggability for this point
* @param {boolean} draggable
*/
setDraggable(draggable = true) {
this.isDraggable = draggable;
}//end setDraggable
/**
* Returns a string representation
*/
logAllInfo() {
return `SWPoint(x: ${this.x}, y: ${this.y}` +
(this.z !== undefined ? `, z: ${this.z}` : '') +
`, strokeWeight: ${this.strokeWeight}, strokeColor: ${this.strokeColor ? this.strokeColor.toString() : 'none'}, label: ${this.label || 'none'}, showLabel: ${this.showLabel}, penOn: ${this.penOn}, trailLen: ${this.trail.length})`;
}//end logAllInfo
/**
* Returns a simple string representation of the point
* Format: "Pt.A @ (x, y), colorName" if labeled, or "Pt @ (x, y), colorName" if unlabeled
* @returns {string}
*/
toString() {
const labelPart = this.label ? `.${this.label}` : '';
const x = this.x.toFixed(2);
const y = this.y.toFixed(2);
const coords = this.z !== undefined ? `(${x}, ${y}, ${this.z.toFixed(2)})` : `(${x}, ${y})`;
const colorName = this.strokeColor && this.strokeColor.name ? this.strokeColor.name : 'no color';
return `Pt${labelPart} @ ${coords}, ${colorName}`;
}//end toString
/**
* Returns the Euclidean distance to another SWPoint in user coordinates
* @param {SWPoint} otherSWPt
* @returns {number}
*/
distanceTo(otherSWPt) {
const dx = this.x - otherSWPt.x;
const dy = this.y - otherSWPt.y;
if (this.z !== undefined && otherSWPt.z !== undefined) {
const dz = this.z - otherSWPt.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
} else {
return Math.sqrt(dx * dx + dy * dy);
}
}//end distanceTo
/**
* Returns a copy of the position as a p5.Vector.
* Safe to pass into p5 vector math without risking mutation of the point's internal state.
* @returns {p5.Vector} An independent copy of the current position
*/
toVector() {
return createVector(this.x, this.y);
}//end toVector
/**
* Sets the point's position from a p5.Vector and updates the trail if the pen is on.
* Useful in animation workflows that compute new positions with vector math.
* @param {p5.Vector} v - The new position vector
*/
setFromVector(v) {
this.x = v.x;
this.y = v.y;
this._updateTrail();
}//end setFromVector
/**
* Adds a p5.Vector to the point's position and updates the trail.
* An alternative to move(dx, dy) for vector-first workflows.
* @param {p5.Vector} v - The vector to add
*/
addVector(v) {
this.x += v.x;
this.y += v.y;
this._updateTrail();
}//end addVector
}//end SWPoint class