<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>http://groupkos.com/dev/index.php?action=history&amp;feed=atom&amp;title=P5js_Visual_Torus_Knot</id>
	<title>P5js Visual Torus Knot - Revision history</title>
	<link rel="self" type="application/atom+xml" href="http://groupkos.com/dev/index.php?action=history&amp;feed=atom&amp;title=P5js_Visual_Torus_Knot"/>
	<link rel="alternate" type="text/html" href="http://groupkos.com/dev/index.php?title=P5js_Visual_Torus_Knot&amp;action=history"/>
	<updated>2026-04-16T01:31:02Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.39.3</generator>
	<entry>
		<id>http://groupkos.com/dev/index.php?title=P5js_Visual_Torus_Knot&amp;diff=5047&amp;oldid=prev</id>
		<title>XenoEngineer: Created page with &quot;&lt;pre style=&quot;margin-left:3em; font:normal 14px terminal;&quot;&gt;/*  * Torus Knot Phase Array Visualizer – Enhanced Version  *  * This sketch draws an array of half-knots derived from a standard torus knot  * parametrization using the correct roles of p and q. A background torus can be  * toggled on/off to put the visualization in context.  *   * Improvements:  * - Added line thickness control  * - Added ability to control how much of the knot is rendered (knotFraction)  * - A...&quot;</title>
		<link rel="alternate" type="text/html" href="http://groupkos.com/dev/index.php?title=P5js_Visual_Torus_Knot&amp;diff=5047&amp;oldid=prev"/>
		<updated>2025-04-18T09:58:40Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;&amp;lt;pre style=&amp;quot;margin-left:3em; font:normal 14px terminal;&amp;quot;&amp;gt;/*  * Torus Knot Phase Array Visualizer – Enhanced Version  *  * This sketch draws an array of half-knots derived from a standard torus knot  * parametrization using the correct roles of p and q. A background torus can be  * toggled on/off to put the visualization in context.  *   * Improvements:  * - Added line thickness control  * - Added ability to control how much of the knot is rendered (knotFraction)  * - A...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&amp;lt;pre style=&amp;quot;margin-left:3em; font:normal 14px terminal;&amp;quot;&amp;gt;/*&lt;br /&gt;
 * Torus Knot Phase Array Visualizer – Enhanced Version&lt;br /&gt;
 *&lt;br /&gt;
 * This sketch draws an array of half-knots derived from a standard torus knot&lt;br /&gt;
 * parametrization using the correct roles of p and q. A background torus can be&lt;br /&gt;
 * toggled on/off to put the visualization in context.&lt;br /&gt;
 * &lt;br /&gt;
 * Improvements:&lt;br /&gt;
 * - Added line thickness control&lt;br /&gt;
 * - Added ability to control how much of the knot is rendered (knotFraction)&lt;br /&gt;
 * - Added interactive controls via sliders&lt;br /&gt;
 * - Improved performance handling&lt;br /&gt;
 * - Added option to show/hide the background torus&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
const PHI = (1 + Math.sqrt(5)) / 2;&lt;br /&gt;
&lt;br /&gt;
const CONFIG = {&lt;br /&gt;
  // Torus profile&lt;br /&gt;
  R: Math.pow(PHI, 4),            // Major radius (set by Φ⁴)&lt;br /&gt;
  r: Math.pow(PHI, 4) - 1,        // Tube radius (balanced proportion)&lt;br /&gt;
  scale: 25,                      // Scaling factor for a human-viewable size&lt;br /&gt;
&lt;br /&gt;
  // Knot Parameters – The Actors&lt;br /&gt;
  p: 13,                          // Primary winding number&lt;br /&gt;
  q: 8,                           // Secondary winding number&lt;br /&gt;
  steps: 2400,                    // Resolution for sampling the curve&lt;br /&gt;
  lineWeight: 2,                  // Line thickness&lt;br /&gt;
&lt;br /&gt;
  // Rendering Control&lt;br /&gt;
  knotFraction: 1.0,              // Fraction of knot to render (0 to 1)&lt;br /&gt;
&lt;br /&gt;
  // Phase Configuration – The Choreography&lt;br /&gt;
  phases: 3,                      // How many phase copies to display&lt;br /&gt;
&lt;br /&gt;
  // Visual Guides&lt;br /&gt;
  showTorus: false,               // Whether to show the background torus&lt;br /&gt;
  halfKnotColors: {&lt;br /&gt;
    first: &amp;#039;#FF3366&amp;#039;,             // Color for the first half-knot&lt;br /&gt;
    second: &amp;#039;#3366FF&amp;#039;,            // Color for the second half-knot&lt;br /&gt;
    terminals: &amp;#039;#44FF00&amp;#039;          // Terminal points accent&lt;br /&gt;
  }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
// UI Elements&lt;br /&gt;
let pSlider, qSlider, phasesSlider, knotFractionSlider, lineWeightSlider, torusCheckbox;&lt;br /&gt;
let pInput, qInput, phasesInput, knotFractionInput, lineWeightInput;&lt;br /&gt;
&lt;br /&gt;
// Torus knot parametrization:&lt;br /&gt;
// For t in [0, 1], the curve is traced by p rotations around and q rotations along the tube.&lt;br /&gt;
function torusKnot(t, p, q) {&lt;br /&gt;
  const theta = 2 * Math.PI * p * t;  // p full rotations (longitudinal)&lt;br /&gt;
  const phi   = 2 * Math.PI * q * t;  // q rotations (meridional)&lt;br /&gt;
  const x = (CONFIG.R + CONFIG.r * Math.cos(phi)) * Math.cos(theta);&lt;br /&gt;
  const y = (CONFIG.R + CONFIG.r * Math.cos(phi)) * Math.sin(theta);&lt;br /&gt;
  const z = CONFIG.r * Math.sin(phi);&lt;br /&gt;
  return { x: x * CONFIG.scale, y: y * CONFIG.scale, z: z * CONFIG.scale };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Generate a half-knot segment with an additional phase rotation.&lt;br /&gt;
function drawHalfKnot(phase, isFirstHalf) {&lt;br /&gt;
  const start = isFirstHalf ? 0   : 0.5;&lt;br /&gt;
  const end   = isFirstHalf ? 0.5 : 1;&lt;br /&gt;
  &lt;br /&gt;
  // Apply knotFraction to scale the end point&lt;br /&gt;
  const scaledEnd = start + (end - start) * CONFIG.knotFraction;&lt;br /&gt;
  &lt;br /&gt;
  const phaseOffset = (phase / CONFIG.phases) * 2 * Math.PI;&lt;br /&gt;
  &lt;br /&gt;
  let points = [];&lt;br /&gt;
  &lt;br /&gt;
  // Calculate the number of steps needed for the fraction of the knot&lt;br /&gt;
  const stepsNeeded = isFirstHalf ? &lt;br /&gt;
    Math.floor((CONFIG.steps / 2) * CONFIG.knotFraction) : &lt;br /&gt;
    Math.floor((CONFIG.steps / 2) * CONFIG.knotFraction);&lt;br /&gt;
  &lt;br /&gt;
  for (let i = 0; i &amp;lt;= stepsNeeded; i++) {&lt;br /&gt;
    const t_local = start + (scaledEnd - start) * (i / stepsNeeded);&lt;br /&gt;
    const point = torusKnot(t_local, CONFIG.p, CONFIG.q);&lt;br /&gt;
    // Rotate the point in the XY plane to distribute half-knots around the circle.&lt;br /&gt;
    const rotated = {&lt;br /&gt;
      x: point.x * Math.cos(phaseOffset) - point.y * Math.sin(phaseOffset),&lt;br /&gt;
      y: point.x * Math.sin(phaseOffset) + point.y * Math.cos(phaseOffset),&lt;br /&gt;
      z: point.z&lt;br /&gt;
    };&lt;br /&gt;
    points.push(rotated);&lt;br /&gt;
  }&lt;br /&gt;
  return points;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Draw the curve using p5.js vertex commands.&lt;br /&gt;
function drawPath(points, col) {&lt;br /&gt;
  stroke(col);&lt;br /&gt;
  strokeWeight(CONFIG.lineWeight);&lt;br /&gt;
  noFill();&lt;br /&gt;
  beginShape();&lt;br /&gt;
  for (let pt of points) {&lt;br /&gt;
    vertex(pt.x, pt.y, pt.z);&lt;br /&gt;
  }&lt;br /&gt;
  endShape();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Mark the starting and ending positions of a half-knot.&lt;br /&gt;
function markTerminals(start, end) {&lt;br /&gt;
  push();&lt;br /&gt;
  fill(CONFIG.halfKnotColors.terminals);&lt;br /&gt;
  noStroke();&lt;br /&gt;
  // Small spheres at the terminal points.&lt;br /&gt;
  push();&lt;br /&gt;
  translate(start.x, start.y, start.z);&lt;br /&gt;
  sphere(4);&lt;br /&gt;
  pop();&lt;br /&gt;
  &lt;br /&gt;
  // Only draw end sphere if we&amp;#039;re showing the full knot&lt;br /&gt;
  if (CONFIG.knotFraction &amp;gt; 0.99) {&lt;br /&gt;
    push();&lt;br /&gt;
    translate(end.x, end.y, end.z);&lt;br /&gt;
    sphere(4);&lt;br /&gt;
    pop();&lt;br /&gt;
  }&lt;br /&gt;
  pop();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Draw all phase copies of the two half-knots and mark their endpoints.&lt;br /&gt;
function visualize() {&lt;br /&gt;
  for (let phase = 0; phase &amp;lt; CONFIG.phases; phase++) {&lt;br /&gt;
    const firstHalf = drawHalfKnot(phase, true);&lt;br /&gt;
    if (firstHalf.length &amp;gt; 0) {&lt;br /&gt;
      drawPath(firstHalf, CONFIG.halfKnotColors.first);&lt;br /&gt;
      &lt;br /&gt;
      // Mark starting point&lt;br /&gt;
      markTerminals(firstHalf[0], firstHalf[firstHalf.length - 1]);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    const secondHalf = drawHalfKnot(phase, false);&lt;br /&gt;
    if (secondHalf.length &amp;gt; 0) {&lt;br /&gt;
      drawPath(secondHalf, CONFIG.halfKnotColors.second);&lt;br /&gt;
      &lt;br /&gt;
      // Mark terminal points&lt;br /&gt;
      markTerminals(secondHalf[0], secondHalf[secondHalf.length - 1]);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setupUI() {&lt;br /&gt;
  const uiY = 20;&lt;br /&gt;
  const spacing = 40;&lt;br /&gt;
  const labelWidth = 100;&lt;br /&gt;
  const sliderWidth = 120;&lt;br /&gt;
  const inputWidth = 50;&lt;br /&gt;
  const padding = 10;&lt;br /&gt;
  &lt;br /&gt;
  // Create sliders and text fields for interactive control&lt;br /&gt;
  // p value&lt;br /&gt;
  createP(&amp;#039;p value:&amp;#039;).position(10, uiY).style(&amp;#039;color&amp;#039;, &amp;#039;white&amp;#039;);&lt;br /&gt;
  pSlider = createSlider(1, 20, CONFIG.p, 0.1);&lt;br /&gt;
  pSlider.position(labelWidth, uiY);&lt;br /&gt;
  pSlider.style(&amp;#039;width&amp;#039;, sliderWidth + &amp;#039;px&amp;#039;);&lt;br /&gt;
  pSlider.input(() =&amp;gt; { pInput.value(pSlider.value()); });&lt;br /&gt;
  &lt;br /&gt;
  pInput = createInput(CONFIG.p.toString());&lt;br /&gt;
  pInput.position(labelWidth + sliderWidth + padding, uiY);&lt;br /&gt;
  pInput.style(&amp;#039;width&amp;#039;, inputWidth + &amp;#039;px&amp;#039;);&lt;br /&gt;
  pInput.input(() =&amp;gt; { &lt;br /&gt;
    const val = parseFloat(pInput.value());&lt;br /&gt;
    if (!isNaN(val) &amp;amp;&amp;amp; val &amp;gt;= 1 &amp;amp;&amp;amp; val &amp;lt;= 20) {&lt;br /&gt;
      pSlider.value(val);&lt;br /&gt;
    }&lt;br /&gt;
  });&lt;br /&gt;
  &lt;br /&gt;
  // q value&lt;br /&gt;
  createP(&amp;#039;q value:&amp;#039;).position(10, uiY + spacing).style(&amp;#039;color&amp;#039;, &amp;#039;white&amp;#039;);&lt;br /&gt;
  qSlider = createSlider(1, 20, CONFIG.q, 0.1);&lt;br /&gt;
  qSlider.position(labelWidth, uiY + spacing);&lt;br /&gt;
  qSlider.style(&amp;#039;width&amp;#039;, sliderWidth + &amp;#039;px&amp;#039;);&lt;br /&gt;
  qSlider.input(() =&amp;gt; { qInput.value(qSlider.value()); });&lt;br /&gt;
  &lt;br /&gt;
  qInput = createInput(CONFIG.q.toString());&lt;br /&gt;
  qInput.position(labelWidth + sliderWidth + padding, uiY + spacing);&lt;br /&gt;
  qInput.style(&amp;#039;width&amp;#039;, inputWidth + &amp;#039;px&amp;#039;);&lt;br /&gt;
  qInput.input(() =&amp;gt; { &lt;br /&gt;
    const val = parseFloat(qInput.value());&lt;br /&gt;
    if (!isNaN(val) &amp;amp;&amp;amp; val &amp;gt;= 1 &amp;amp;&amp;amp; val &amp;lt;= 20) {&lt;br /&gt;
      qSlider.value(val);&lt;br /&gt;
    }&lt;br /&gt;
  });&lt;br /&gt;
  &lt;br /&gt;
  // Phases&lt;br /&gt;
  createP(&amp;#039;Phases:&amp;#039;).position(10, uiY + spacing * 2).style(&amp;#039;color&amp;#039;, &amp;#039;white&amp;#039;);&lt;br /&gt;
  phasesSlider = createSlider(1, 8, CONFIG.phases, 1);&lt;br /&gt;
  phasesSlider.position(labelWidth, uiY + spacing * 2);&lt;br /&gt;
  phasesSlider.style(&amp;#039;width&amp;#039;, sliderWidth + &amp;#039;px&amp;#039;);&lt;br /&gt;
  phasesSlider.input(() =&amp;gt; { phasesInput.value(phasesSlider.value()); });&lt;br /&gt;
  &lt;br /&gt;
  phasesInput = createInput(CONFIG.phases.toString());&lt;br /&gt;
  phasesInput.position(labelWidth + sliderWidth + padding, uiY + spacing * 2);&lt;br /&gt;
  phasesInput.style(&amp;#039;width&amp;#039;, inputWidth + &amp;#039;px&amp;#039;);&lt;br /&gt;
  phasesInput.input(() =&amp;gt; { &lt;br /&gt;
    const val = parseInt(phasesInput.value());&lt;br /&gt;
    if (!isNaN(val) &amp;amp;&amp;amp; val &amp;gt;= 1 &amp;amp;&amp;amp; val &amp;lt;= 8) {&lt;br /&gt;
      phasesSlider.value(val);&lt;br /&gt;
    }&lt;br /&gt;
  });&lt;br /&gt;
  &lt;br /&gt;
  // Knot Fraction&lt;br /&gt;
  createP(&amp;#039;Knot Fraction:&amp;#039;).position(10, uiY + spacing * 3).style(&amp;#039;color&amp;#039;, &amp;#039;white&amp;#039;);&lt;br /&gt;
  knotFractionSlider = createSlider(0.01, 1, CONFIG.knotFraction, 0.01);&lt;br /&gt;
  knotFractionSlider.position(labelWidth, uiY + spacing * 3);&lt;br /&gt;
  knotFractionSlider.style(&amp;#039;width&amp;#039;, sliderWidth + &amp;#039;px&amp;#039;);&lt;br /&gt;
  knotFractionSlider.input(() =&amp;gt; { knotFractionInput.value(knotFractionSlider.value()); });&lt;br /&gt;
  &lt;br /&gt;
  knotFractionInput = createInput(CONFIG.knotFraction.toString());&lt;br /&gt;
  knotFractionInput.position(labelWidth + sliderWidth + padding, uiY + spacing * 3);&lt;br /&gt;
  knotFractionInput.style(&amp;#039;width&amp;#039;, inputWidth + &amp;#039;px&amp;#039;);&lt;br /&gt;
  knotFractionInput.input(() =&amp;gt; { &lt;br /&gt;
    const val = parseFloat(knotFractionInput.value());&lt;br /&gt;
    if (!isNaN(val) &amp;amp;&amp;amp; val &amp;gt;= 0.01 &amp;amp;&amp;amp; val &amp;lt;= 1) {&lt;br /&gt;
      knotFractionSlider.value(val);&lt;br /&gt;
    }&lt;br /&gt;
  });&lt;br /&gt;
  &lt;br /&gt;
  // Line Weight&lt;br /&gt;
  createP(&amp;#039;Line Weight:&amp;#039;).position(10, uiY + spacing * 4).style(&amp;#039;color&amp;#039;, &amp;#039;white&amp;#039;);&lt;br /&gt;
  lineWeightSlider = createSlider(1, 5, CONFIG.lineWeight, 0.1);&lt;br /&gt;
  lineWeightSlider.position(labelWidth, uiY + spacing * 4);&lt;br /&gt;
  lineWeightSlider.style(&amp;#039;width&amp;#039;, sliderWidth + &amp;#039;px&amp;#039;);&lt;br /&gt;
  lineWeightSlider.input(() =&amp;gt; { lineWeightInput.value(lineWeightSlider.value()); });&lt;br /&gt;
  &lt;br /&gt;
  lineWeightInput = createInput(CONFIG.lineWeight.toString());&lt;br /&gt;
  lineWeightInput.position(labelWidth + sliderWidth + padding, uiY + spacing * 4);&lt;br /&gt;
  lineWeightInput.style(&amp;#039;width&amp;#039;, inputWidth + &amp;#039;px&amp;#039;);&lt;br /&gt;
  lineWeightInput.input(() =&amp;gt; { &lt;br /&gt;
    const val = parseFloat(lineWeightInput.value());&lt;br /&gt;
    if (!isNaN(val) &amp;amp;&amp;amp; val &amp;gt;= 1 &amp;amp;&amp;amp; val &amp;lt;= 5) {&lt;br /&gt;
      lineWeightSlider.value(val);&lt;br /&gt;
    }&lt;br /&gt;
  });&lt;br /&gt;
  &lt;br /&gt;
  // Create checkbox for showing/hiding the torus&lt;br /&gt;
  createP(&amp;#039;Show Torus:&amp;#039;).position(10, uiY + spacing * 5).style(&amp;#039;color&amp;#039;, &amp;#039;white&amp;#039;);&lt;br /&gt;
  torusCheckbox = createCheckbox(&amp;#039;&amp;#039;, CONFIG.showTorus);&lt;br /&gt;
  torusCheckbox.position(labelWidth, uiY + spacing * 5);&lt;br /&gt;
  torusCheckbox.style(&amp;#039;color&amp;#039;, &amp;#039;white&amp;#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateConfigFromUI() {&lt;br /&gt;
  // Get values from sliders (which are synced with input fields)&lt;br /&gt;
  CONFIG.p = parseFloat(pSlider.value());&lt;br /&gt;
  CONFIG.q = parseFloat(qSlider.value());&lt;br /&gt;
  CONFIG.phases = parseInt(phasesSlider.value());&lt;br /&gt;
  CONFIG.knotFraction = parseFloat(knotFractionSlider.value());&lt;br /&gt;
  CONFIG.lineWeight = parseFloat(lineWeightSlider.value());&lt;br /&gt;
  CONFIG.showTorus = torusCheckbox.checked();&lt;br /&gt;
  &lt;br /&gt;
  // Update input fields to show current values (in case they were changed by slider)&lt;br /&gt;
  pInput.value(CONFIG.p);&lt;br /&gt;
  qInput.value(CONFIG.q);&lt;br /&gt;
  phasesInput.value(CONFIG.phases);&lt;br /&gt;
  knotFractionInput.value(CONFIG.knotFraction);&lt;br /&gt;
  lineWeightInput.value(CONFIG.lineWeight);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setup() {&lt;br /&gt;
  createCanvas(windowWidth, windowHeight, WEBGL);&lt;br /&gt;
  setupUI();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function draw() {&lt;br /&gt;
  background(0);&lt;br /&gt;
  updateConfigFromUI();&lt;br /&gt;
  &lt;br /&gt;
  // Display current p:q ratio with precise values&lt;br /&gt;
  push();&lt;br /&gt;
  textSize(16);&lt;br /&gt;
  fill(255);&lt;br /&gt;
  text(`Torus Knot (${CONFIG.p.toFixed(2)}:${CONFIG.q.toFixed(2)}) - ${(CONFIG.knotFraction * 100).toFixed(1)}% rendered`, -width/2 + 10, -height/2 + 20);&lt;br /&gt;
  pop();&lt;br /&gt;
  &lt;br /&gt;
  // Position the 3D view in the center-right of the screen&lt;br /&gt;
  translate(100, 0, 0);&lt;br /&gt;
  &lt;br /&gt;
  orbitControl(); // Allows interactive rotation with mouse input.&lt;br /&gt;
  &lt;br /&gt;
  // A subtle continuous rotation of the entire scene.&lt;br /&gt;
  rotateY(frameCount * 0.00015);&lt;br /&gt;
  &lt;br /&gt;
  // Render a background torus if enabled&lt;br /&gt;
  if (CONFIG.showTorus) {&lt;br /&gt;
    push();&lt;br /&gt;
    noFill();&lt;br /&gt;
    stroke(55, 45, 55);&lt;br /&gt;
    strokeWeight(0.5);&lt;br /&gt;
    // Adjust the parameters to match our CONFIG scale.&lt;br /&gt;
    // Note: p5.js torus() expects: torus(radius, tubeRadius, detailX, detailY)&lt;br /&gt;
    torus(CONFIG.R * CONFIG.scale, CONFIG.r * CONFIG.scale, 24, 12);&lt;br /&gt;
    pop();&lt;br /&gt;
  }&lt;br /&gt;
  &lt;br /&gt;
  // Draw the torus knot phase array.&lt;br /&gt;
  visualize();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function windowResized() {&lt;br /&gt;
  resizeCanvas(windowWidth, windowHeight);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>XenoEngineer</name></author>
	</entry>
</feed>