| name | geometry-microsim |
| description | Refactoring patterns and best practices for creating maintainable geometry MicroSims using p5.js. Provides guidelines for local coordinate systems, push/pop/translate/scale patterns, mathematical accuracy, and clean code structure. |
Geometry MicroSim Refactoring Patterns
Overview
This skill provides best practices for creating and refactoring geometry MicroSims in p5.js. These patterns make code more maintainable, mathematically accurate, and easier to debug.
When to Use This Skill
Use this skill when:
- Creating new geometry MicroSims with multiple shapes or drawing regions
- Refactoring existing geometry code that has complex manual positioning
- Debugging geometry visualizations with incorrect labels or positioning
- Building MicroSims with shapes that need to be scaled, rotated, or transformed
File Naming Convention
ALWAYS use main.html as the HTML filename for all MicroSims. This ensures consistent iframe embedding across the project.
Standard Directory Structure
Each MicroSim should have this structure:
docs/sims/[sim-name]/
āāā main.html # REQUIRED: The HTML file (always named main.html)
āāā [sim-name].js # JavaScript file with p5.js code
āāā index.md # Documentation page
āāā [sim-name].png # Screenshot/preview image
āāā metadata.json # Optional: MicroSim metadata
Live Server Preview URL
After creating or modifying a MicroSim, use this URL pattern for Live Server preview:
http://127.0.0.1:5500/docs/sims/[sim-name]/main.html
Iframe Embedding
Always use main.html in iframe references:
<iframe src="main.html" width="100%" height="450px" scrolling="no"></iframe>
For external embedding:
<iframe src="https://dmccreary.github.io/geometry-course/sims/[sim-name]/main.html" width="100%" height="450px" scrolling="no"></iframe>
Core Principle: Local Coordinate Systems
Always draw geometric shapes in local coordinates centered at the origin, then use transformations to position them.
The Push/Pop/Translate/Scale Pattern
Instead of calculating global positions for every vertex, use this pattern:
function drawShape(centerX, centerY, scaleFactor) {
push();
translate(centerX, centerY);
scale(scaleFactor);
triangle(verts.A.x, verts.A.y, verts.B.x, verts.B.y, verts.C.x, verts.C.y);
drawAngleLabels(verts);
drawVertexLabels(verts);
pop();
}
Benefits of This Approach
- Separation of concerns: Geometry calculation is separate from positioning
- Easier debugging: Shapes work correctly at origin before positioning
- Reusability: Same shape function works at any position/scale
- Cleaner code: No scattered offset calculations throughout code
- Accurate labels: Labels stay associated with correct vertices through transforms
Scale Factor Compensation
When scaling, compensate for stroke weights and text sizes:
push();
translate(centerX, centerY);
scale(scaleFactor);
strokeWeight(2 / scaleFactor);
textSize(12 / scaleFactor);
pop();
Geometry Calculation Best Practices
Separate Vertex Calculation from Rendering
Create a function that calculates vertices in normalized coordinates:
function calculateTriangleVertices(angles, baseLength) {
let angleA = radians(angles[0]);
let angleB = radians(angles[1]);
let angleC = radians(angles[2]);
let sideC = baseLength;
let k = sideC / sin(angleC);
let sideA = k * sin(angleA);
let sideB = k * sin(angleB);
let Cx = 0, Cy = 0;
let Bx = sideA, By = 0;
let Ax = sideB * cos(angleC);
let Ay = -sideB * sin(angleC);
let centX = (Ax + Bx + Cx) / 3;
let centY = (Ay + By + Cy) / 3;
return {
A: { x: Ax - centX, y: Ay - centY },
B: { x: Bx - centX, y: By - centY },
C: { x: Cx - centX, y: Cy - centY }
};
}
Calculate Bounding Box for Auto-Scaling
function drawShapePanel(centerX, centerY, panelWidth, panelHeight) {
let verts = calculateVertices();
let minX = min(verts.A.x, verts.B.x, verts.C.x);
let maxX = max(verts.A.x, verts.B.x, verts.C.x);
let minY = min(verts.A.y, verts.B.y, verts.C.y);
let maxY = max(verts.A.y, verts.B.y, verts.C.y);
let shapeWidth = maxX - minX;
let shapeHeight = maxY - minY;
let scaleX = (panelWidth - 40) / shapeWidth;
let scaleY = (panelHeight - 40) / shapeHeight;
let scaleFactor = min(scaleX, scaleY) * 0.85;
push();
translate(centerX, centerY);
scale(scaleFactor);
pop();
}
Label Positioning
Vertex Labels
Position labels radially outward from centroid:
function drawVertexLabels(verts, scaleFactor) {
let centX = (verts.A.x + verts.B.x + verts.C.x) / 3;
let centY = (verts.A.y + verts.B.y + verts.C.y) / 3;
let labelDist = 16 / scaleFactor;
let dirX = verts.A.x - centX;
let dirY = verts.A.y - centY;
let len = sqrt(dirX * dirX + dirY * dirY);
text('A', verts.A.x + labelDist * dirX / len,
verts.A.y + labelDist * dirY / len);
}
Angle Labels
Position angle measurements along the angle bisector:
function drawAngleLabel(vertex, adj1, adj2, angleDeg, scaleFactor) {
let a1 = atan2(adj1.y - vertex.y, adj1.x - vertex.x);
let a2 = atan2(adj2.y - vertex.y, adj2.x - vertex.x);
let midAngle = (a1 + a2) / 2;
let labelDist = 35 / scaleFactor;
let labelX = vertex.x + labelDist * cos(midAngle);
let labelY = vertex.y + labelDist * sin(midAngle);
text(angleDeg + "°", labelX, labelY);
}
Common Patterns
Multiple Panels with Shapes
When displaying multiple shapes in panels (like triangle classifications):
function drawPanels() {
let panelWidth = (canvasWidth - 80) / 3;
let startX = 30;
for (let i = 0; i < 3; i++) {
let x = startX + i * (panelWidth + 10);
let centerX = x + panelWidth / 2;
let centerY = panelY + 130;
drawShapePanel(centerX, centerY, shapes[i], panelWidth);
}
}
Right Angle Indicators
function drawRightAngle(vertex, adj1, adj2, scaleFactor) {
let size = 12 / scaleFactor;
let dir1 = atan2(adj1.y - vertex.y, adj1.x - vertex.x);
let dir2 = atan2(adj2.y - vertex.y, adj2.x - vertex.x);
let p1 = { x: vertex.x + size * cos(dir1), y: vertex.y + size * sin(dir1) };
let p2 = { x: vertex.x + size * cos(dir2), y: vertex.y + size * sin(dir2) };
let corner = {
x: vertex.x + size * cos(dir1) + size * cos(dir2),
y: vertex.y + size * sin(dir1) + size * sin(dir2)
};
line(p1.x, p1.y, corner.x, corner.y);
line(p2.x, p2.y, corner.x, corner.y);
}
Checklist for Geometry MicroSims
Before finalizing a geometry MicroSim, verify:
File Structure
Geometry Code Quality
Anti-Patterns to Avoid
Manual Offset Calculations
Bad:
let ax = cx - baseLen / 2 + offsetX + panelX;
let ay = cy + verticalOffset + panelY - 10;
Good:
push();
translate(panelCenterX, panelCenterY);
let ax = -baseLen / 2;
let ay = 0;
pop();
Rotation Breaking Label Associations
Bad:
rotateVertices();
drawAngle(verts.A, angleA);
Good:
let verts = calculateFromAngles(angles);
Mathematical Accuracy
For geometry education, mathematical accuracy is critical:
- Use law of sines/cosines for triangle calculations
- Verify angle sums (triangle: 180°, quadrilateral: 360°)
- Test with known values (30-60-90, 45-45-90 triangles)
- Display angles that match the actual geometric shape
Related Skills
microsim-generator/references/p5-guide.md - General p5.js MicroSim patterns
microsim-utils - Quality validation and standardization
Examples
See these MicroSims for implementation examples:
docs/sims/triangle-classification-angles/ - Triangle types by angles
docs/sims/triangle-classification-sides/ - Triangle types by sides (if exists)
- Other geometry sims in
docs/sims/