Back to PHICodepen Playground
Codepen Playground

Riemann Zeta

Complexe Function

live renderhtml
htmlriemann.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Riemann Zeta - Plain JS + Canvas (Smooth)</title>
  <style>
    html, body {
      margin: 0; 
      padding: 0; 
      width: 100%; 
      height: 100%;
      background: #000;
      overflow: hidden;
    }
    canvas {
      display: block;
      position: absolute; 
      left: 0; top: 0;
    }
  </style>
</head>
<body>
<canvas id="riemannCanvas"></canvas>

<script>
// ======================================================================
// 1) Basic Utility: map, random, minimal noise2D
// ======================================================================
function mapRange(value, inMin, inMax, outMin, outMax){
  return outMin + (outMax - outMin)*((value-inMin)/(inMax-inMin));
}
function lerp(a,b,t){ return a + (b-a)*t; }
function fade(t){ return t*t*t*(t*(t*6 -15)+10); }

function noise2D(x,y){
  // minimal "hash" approach
  let xi = Math.floor(x), yi= Math.floor(y);
  let xf= x-xi,        yf= y-yi;
  let r1 = randomCell2D(xi,   yi);
  let r2 = randomCell2D(xi+1, yi);
  let r3 = randomCell2D(xi,   yi+1);
  let r4 = randomCell2D(xi+1, yi+1);
  let u= fade(xf), v= fade(yf);
  let i1= lerp(r1, r2, u);
  let i2= lerp(r3, r4, u);
  return lerp(i1, i2, v);
}
function randomCell2D(ix,iy){
  let s = (ix*374761393 + iy*668265263)^0xBADC0FFE;
  s = (s<<13)^s;
  let r= (1.0 - ((s*(s*s*15731+789221)+1376312589)&0x7fffffff)/1073741824.0);
  return r;
}

// ======================================================================
// 2) Vec2 class (like p5's createVector) 
// ======================================================================
class Vec2 {
  constructor(x,y){this.x=x;this.y=y;}
  add(v){ return new Vec2(this.x+v.x, this.y+v.y);}
  sub(v){ return new Vec2(this.x-v.x, this.y-v.y);}
}

// ======================================================================
// 3) Complex & Zeta
// ======================================================================
function Complex(real, imag){
  if(real && real.real!==undefined && real.imag!==undefined) return real;
  return {real, imag: imag||0};
}
function complexNeg(a){ return Complex(-a.real, -a.imag);}
function complexMul(a,b){
  let re= a.real*b.real - a.imag*b.imag;
  let im= a.real*b.imag + a.imag*b.real;
  return Complex(re,im);
}
function complexAdd(a,b){
  return Complex(a.real+b.real, a.imag+b.imag);
}
function complexSub(a,b){
  return Complex(a.real-b.real, a.imag-b.imag);
}
function complexPow(a,b){
  //  (r e^{i\theta})^(x + i y) = ...
  let r= Math.sqrt(a.real*a.real + a.imag*a.imag);
  let theta= Math.atan2(a.imag,a.real);
  let br= b.real, bi= b.imag;
  let logR= Math.log(r);
  let x= Math.exp(br*logR - bi*theta);
  let phi= bi*logR + br*theta;
  return Complex(x*Math.cos(phi), x*Math.sin(phi));
}
function binomial(n, k){
  if(k>n)return 0;
  if(k> n-k)k=n-k;
  let r=1; 
  for(let i=0;i<k;i++){
    r*= (n-i)/(i+1);
  }
  return r;
}
function sign(k){ return (k%2)? -1: 1; }
function zeta3(s, t=150){
  // partial sum approach
  // s!=1
  // sum_{n=0..t} sum_{k=0..n} [ (-1)^k binomial(n,k) / (k+1)^s ] / 2^(n+1)
  // all divided by [1-2^(1-s)]
  // from your snippet
  let sum= Complex(0,0);
  for(let n=0;n<t;n++){
    let inn= Complex(0,0);
    for(let k=0;k<=n;k++){
      let p= complexPow(Complex(k+1,0), complexNeg(s));
      let sc= sign(k)* binomial(n,k);
      let tmp= complexMul(p, Complex(sc,0));
      inn= complexAdd(inn, tmp);
    }
    let factor= Math.pow(2, -(n+1));
    inn= Complex( inn.real*factor, inn.imag*factor );
    sum= complexAdd(sum, inn);
  }
  // factor = 1/(1- 2^(1-s))
  let two= Complex(2,0);
  let oneMinusS= Complex(1-s.real, -s.imag);
  let twoOneS= complexPow(two, oneMinusS);
  let denom= complexSub(Complex(1,0), twoOneS);
  // sum / denom
  // naive complexDiv
  let dnorm= denom.real*denom.real + denom.imag*denom.imag;
  let conjRe=  denom.real, conjIm= -denom.imag;
  let cross= complexMul(sum, {real: conjRe, imag: conjIm});
  return { 
    real: cross.real/dnorm,
    imag: cross.imag/dnorm
  };
}

// ======================================================================
// 4) Riemann Logic w/ Smooth Lines
// We store all zeta points in an array and draw one continuous smooth path
// ======================================================================
class RiemannApp {
  constructor(){
    this.canvas= document.getElementById('riemannCanvas');
    this.ctx= this.canvas.getContext('2d');
    this.width=0; this.height=0;
    this.resize();
    window.addEventListener('resize', ()=>this.resize());
    window.addEventListener('keydown', (e)=>this.keyDown(e));
    // store zeta coords
    this.points= [];
    this.index= -0.2;
    this.limit= 34;
    this.offset=200;
    this.start=0;

    requestAnimationFrame(()=>this.draw());
  }

  resize(){
    this.width= window.innerWidth;
    this.height= window.innerHeight;
    this.canvas.width= this.width;
    this.canvas.height= this.height;
    this.center= new Vec2(this.width*0.5, this.height*0.5);
  }

  keyDown(e){
    if(e.key==='c'){
      this.points= [];
      this.index= -0.2;
      this.limit=34;
    } else if(e.key==='p'){
      this.limit=64;
    }
  }

  draw(){
    requestAnimationFrame(()=>this.draw());
    // background
    this.ctx.fillStyle= "rgb(6,10,43)";
    this.ctx.fillRect(0,0,this.width,this.height);

    // generate next zeta point if index < limit
    if(this.index< this.limit){
      let s= Complex(0.5,this.index);
      let comp= zeta3(s);
      let x= mapRange(comp.real,-2,2, -this.offset, this.offset);
      let y= mapRange(comp.imag,-2,2, -this.offset, this.offset);
      // shift by center
      x+= this.center.x;
      y+= this.center.y;
      this.points.push({x,y});
      this.index+=0.05;
    }

    // draw circle etc. if you want
    this.ctx.strokeStyle="rgba(255,255,255,0.5)";
    this.ctx.beginPath();
    this.ctx.arc(this.center.x, this.center.y, 200, 0,Math.PI*2);
    this.ctx.stroke();

    // draw a smooth curve through all points
    if(this.points.length>2){
      this.drawCatmullRom();
    }

    this.start+= 0.0001;
  }

  drawCatmullRom(){
    // We'll do a standard Catmull–Rom approach for a “smooth function.”
    // p[i-1], p[i], p[i+1], p[i+2] => we create a segment
    let pts= this.points;
    this.ctx.lineWidth= 3;

    this.ctx.beginPath();
    // move to first
    this.ctx.moveTo(pts[0].x, pts[0].y);

    for(let i=1; i<pts.length-2; i++){
      let c1x= (pts[i].x + pts[i+1].x)/2;
      let c1y= (pts[i].y + pts[i+1].y)/2;
      this.ctx.quadraticCurveTo(pts[i].x, pts[i].y, c1x, c1y);
    }
    // last segment
    let penult= pts[pts.length-2];
    let last=   pts[pts.length-1];
    this.ctx.quadraticCurveTo(penult.x, penult.y, last.x, last.y);

    // color logic
    // we could pick a color from wave or from length
    // for now, a static color
    this.ctx.strokeStyle="rgba(255,200,100,0.8)";
    this.ctx.stroke();
  }
}

// let's go
document.addEventListener("DOMContentLoaded", ()=>{
  new RiemannApp();
});
</script>

</body>
</html>