This is a continuous version of the purple dots illusion. Keep staring at the crosshairs. Eventually, instead of a white gap rotating, you’ll start seeing a bright band of the opponent color.
Incidentally, you’ll notice that this opponent color is quite bright - it in fact appears brighter than the white background of your display! This is because you’re experiencing a Chimerical color.
I’m sure this has been discovered before, but I came up with this particular version.
Source Code
---title: Ring with rotating gapdate: 2018-09-27categories: [optical-illusion]image: /images/ring-with-rotating-gap.jpg---This is a continuous version of the [purple dotsillusion](./purple-dots.qmd). Keep staring at thecrosshairs. Eventually, instead of a white gap rotating, you'll startseeing a bright band of the opponent color.Incidentally, you'll notice that this opponent color is quite bright -it in fact appears brighter than the white background of your display!This is because you're experiencing a [Chimericalcolor](https://en.wikipedia.org/wiki/Impossible_color).<div id="main" style="height: 600px"></div>```{ojs}import { dom, time } from "/src/cscheid/cscheid.js";{ var canvasEl = d3.select("#main").append("canvas") .attr("width", 600) .attr("height", 600); var canvasCrosshair = d3.select("#main").append("canvas") .attr("width", 600) .attr("height", 600) .style("position", "relative") .style("top", "-600px"); d3.select("#main").select("div").style("height", "600px"); var ctx = dom.setupCanvas(canvasEl.node()); var xhCtx = dom.setupCanvas(canvasCrosshair.node()); // crosshairs xhCtx.fillStyle = "rgba(0,0,0,0)"; xhCtx.fillRect(0,0,600,600); xhCtx.strokeStyle = "black"; xhCtx.lineWidth = 1 * ctx.dpr; xhCtx.beginPath(); xhCtx.moveTo(280, 300); xhCtx.lineTo(320, 300); xhCtx.moveTo(300, 280); xhCtx.lineTo(300, 320); xhCtx.stroke(); canvasEl.node().style.filter = "blur(10px)"; function tick() { var now = time.elapsed(); // wipe ctx.fillStyle = "white"; ctx.fillRect(0,0,600,600); ctx.lineWidth = 30 * ctx.dpr; var nSteps = ~~(100 * Math.PI * 2); var radius = 200; var xScale = d3.scaleLinear().domain([-1, 1]).range([50, 550]); var yScale = d3.scaleLinear().domain([-1, 1]).range([550, 50]); for (var i=0; i<nSteps; ++i) { var angleBeg = (i / nSteps) * Math.PI * 2; var angleEnd = ((i+1.5) / nSteps) * Math.PI * 2; var lerp = d3.interpolateLab(d3.hcl(angleBeg * 180 / Math.PI, 70, 50), "white"); var gapCenterAngle = now * (Math.PI * 2) % (Math.PI * 2); var sigma2 = 1; var weight = Math.max( Math.exp(-Math.pow(gapCenterAngle - angleBeg, 2) / sigma2), Math.exp(-Math.pow((gapCenterAngle + 2 * Math.PI) - angleBeg, 2) / sigma2), Math.exp(-Math.pow((gapCenterAngle - 2 * Math.PI) - angleBeg, 2) / sigma2), ); ctx.strokeStyle = String(lerp(weight)); ctx.beginPath(); ctx.moveTo(xScale(Math.cos(angleBeg)), yScale(Math.sin(angleBeg))); ctx.lineTo(xScale(Math.cos(angleEnd)), yScale(Math.sin(angleEnd))); ctx.stroke(); } window.requestAnimationFrame(tick); } tick();}```## AcknowledgementsI'm sure this has been discovered before, but I came up with thisparticular version.