{"id":"5a5ab60b2251893d","slug":"six-seven-eight","trashed":false,"description":"","likes":11,"publish_level":"public","forks":1,"fork_of":null,"has_importers":false,"update_time":"2020-02-02T20:18:52.374Z","first_public_version":null,"paused_version":null,"publish_time":"2020-01-31T15:00:03.228Z","publish_version":670,"latest_version":670,"thumbnail":"b62844cbb861f65649bcbf0de3a3ce9aba95f610be6789a4eaf9382c17ba7125","default_thumbnail":"9c0dc7506254b2d614c1d994e74abd0bf7dd0e606b7d89e01984b53e53bdc87a","roles":[],"sharing":null,"owner":{"id":"898d35f8860c7f03","avatar_url":"https://avatars.observableusercontent.com/avatar/0d7defa821f38094c03bad23b9b360a5364e6e97e21fc238c39ddc48db7994ad","login":"mootari","name":"Fabian Iwand","bio":"Web dev and tinkerer.","home_url":"https://twitter.com/mootari","type":"team","tier":"starter_2024"},"creator":{"id":"07362516b5994994","avatar_url":"https://avatars.observableusercontent.com/avatar/0d7defa821f38094c03bad23b9b360a5364e6e97e21fc238c39ddc48db7994ad","login":"mootari","name":"Fabian Iwand","bio":"Web dev and tinkerer.","home_url":"https://mootari.de/","tier":"pro"},"authors":[{"id":"07362516b5994994","avatar_url":"https://avatars.observableusercontent.com/avatar/0d7defa821f38094c03bad23b9b360a5364e6e97e21fc238c39ddc48db7994ad","name":"Fabian Iwand","login":"mootari","bio":"Web dev and tinkerer.","home_url":"https://mootari.de/","tier":"pro","approved":true,"description":""}],"collections":[{"id":"414312a67ea30ac3","type":"public","slug":"generative","title":"Generative","description":"Stuff with knobs to make it look more or less pretty.","update_time":"2019-08-24T08:59:06.382Z","pinned":true,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"79bb0336ecb16ea71310e28b7f5ffc1c077b45f4fdc27ada83d98653018a8daf","thumbnail":"79bb0336ecb16ea71310e28b7f5ffc1c077b45f4fdc27ada83d98653018a8daf","listing_count":59,"parent_collection_count":0,"owner":{"id":"898d35f8860c7f03","avatar_url":"https://avatars.observableusercontent.com/avatar/0d7defa821f38094c03bad23b9b360a5364e6e97e21fc238c39ddc48db7994ad","login":"mootari","name":"Fabian Iwand","bio":"Web dev and tinkerer.","home_url":"https://twitter.com/mootari","type":"team","tier":"starter_2024"}},{"id":"aa0dd33c29664cca","type":"public","slug":"optical-illusions","title":"Optical Illusions","description":"","update_time":"2019-06-04T21:37:51.882Z","pinned":true,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"68a948b80da7edfa5d32542b02e2ec393bcb7833dec401c3f18c9c9b1f38a99d","thumbnail":"68a948b80da7edfa5d32542b02e2ec393bcb7833dec401c3f18c9c9b1f38a99d","listing_count":5,"parent_collection_count":0,"owner":{"id":"898d35f8860c7f03","avatar_url":"https://avatars.observableusercontent.com/avatar/0d7defa821f38094c03bad23b9b360a5364e6e97e21fc238c39ddc48db7994ad","login":"mootari","name":"Fabian Iwand","bio":"Web dev and tinkerer.","home_url":"https://twitter.com/mootari","type":"team","tier":"starter_2024"}}],"files":[],"comments":[],"commenting_lock":null,"suggestion_from":null,"suggestions_to":[],"version":670,"title":"Six, Seven, Eight","license":null,"copyright":"","nodes":[{"id":0,"value":"md`# Six, Seven, Eight\n\nhttps://twitter.com/mootari/status/1223253181955805190\n\nIdea based on https://twitter.com/jezzamonn/status/1222909681435152386\n`","pinned":false,"mode":"js","data":null,"name":null},{"id":562,"value":"viewof gif = Object.assign(html`<label><input type=checkbox style=\"vertical-align:baseline\"> Render a GIF`, {\n  value: false,\n  oninput(e) { this.value = e.target.checked }\n})","pinned":false,"mode":"js","data":null,"name":null},{"id":664,"value":"{\n  const duration = 3000, draw = render({duration});\n  // Render a gif.\n  if(gif) return yield* renderGif(invalidation, t => draw(t), duration, {\n    fps: 40, preview: draw(0),\n    filename: `six-seven-eight-${Date.now()/1000|0}`,\n  });\n  // Draw a live preview. We offset the date so that animations\n  // will always start at the same offset.\n  const to = Date.now();\n  while(true) yield draw(((Date.now() - to) / duration) % 1);\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":4,"value":"function render(options = {}) {\n  const {\n    size = 600,\n    stroke = 10,\n    strokeColor = 'hsl(30,40%,30%)',\n    fillColor = 'hsl(30,40%,98%)',\n    duration = 3000,\n    ease = easeInOut(3),\n  } = options;\n  const boxSize = 50 - stroke / 2 / (2**.5) - .5, boxOffset = 100;\n\n  const PI = Math.PI, PIQ = PI / 2;\n  const ctx = DOM.context2d(size, size, 1);\n  const illo = new Zdog.Illustration({\n    element: ctx.canvas,\n    zoom: 2,\n    rotate: { x: -PI/5.1 , y:-PI/4 },\n  });\n  // Force a pixel ratio of 1 and reset canvas size.\n  illo.pixelRatio = 1;\n  ctx.canvas.width = illo.width = size;\n  ctx.canvas.height = illo.height = size;\n  // Override Zdog inline styles to make canvas responsive.\n  ctx.canvas.style.maxWidth = '100%';\n  ctx.canvas.style.height = 'auto';\n  \n  // Creates a set of two boxes (one for the outline, one for the fill).\n  function createCube([x, y, z]) {\n      const origin = new Zdog.Anchor({\n        translate: {x: x*boxOffset, y: y*boxOffset, z: z*boxOffset}});\n      // The groups are required because internally Zdog boxes are rect shapes.\n      new Zdog.Box({\n        addTo: new Zdog.Group({addTo: origin, updateSort: true}),\n        width: boxSize, height: boxSize, depth: boxSize,\n        stroke, fill: false, color: strokeColor,\n      });\n      new Zdog.Box({\n        addTo: new Zdog.Group({addTo: origin, updateSort: true}),\n        width: boxSize, height: boxSize, depth: boxSize,\n        color: fillColor,\n      });\n      return origin;    \n  }\n  // Turns a list of translation offsets into a set of cubes.\n  function createCubeSet(offsets) {\n    const root = new Zdog.Anchor();\n    const boxes = offsets.map(createCube);\n    for(let b of boxes) root.addChild(b);\n    return {root, boxes};\n  }\n  \n  // Our two box sets, each having a \"root\" element, a list of \"boxes\", and a rotation vector.\n  const sets = [\n    {\n      rotate: [0, -1, 0],\n      // 7 Boxes, 2 on opposite ends of each axis, one at center.\n      ...createCubeSet([-1, 1].map(s => [0, 1, 2].map(o => [0, 1, 2].map(i => i === o ? s : 0))).flat().concat([[0,0,0]])),\n    },\n    {\n      rotate: [-1, 0, 0],\n      // 8 Boxes, each on a corner.\n      ...createCubeSet([-.5, .5].map(x => [-.5, .5].map(y => [-.5, .5].map(z => [x, y, z]))).flat(2)),\n    },\n  ];\n  // Container to which we'll attach \"invisible\" sets during animation.\n  const inactive = new Zdog.Anchor();\n  // Zdog clears the canvas on every draw. Instead of setting a rect in the background,\n  // we simply override our context's clearRect to instead perform a fill.\n  ctx.clearRect = function(x, y, w, h) {\n    this.fillStyle = fillColor;\n    this.fillRect(x, y, w, h);\n  };\n  \n  // Main render callback.\n  return t => {\n    // Remove all sets from the scene.\n    for(let s of sets) inactive.addChild(s.root);\n    // Fetch the current cube set.\n    const {root, rotate} = sets[t * sets.length | 0];\n    // Reattach the current set to the scene.\n    illo.addChild(root);\n    // Rotate the cube set anchor.\n    const [x, y, z] = rotate, a = ease((t * sets.length) % 1) * PIQ;\n    root.rotate = { x: x * a, y: y * a, z: z * a };\n    \n    illo.updateRenderGraph();\n    return illo.element;\n  }\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":93,"value":"function easeInOut(a) {\n  return t => t ** a / (t ** a + (1 - t) ** a);\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":610,"value":"md`---\n## Dependencies`","pinned":false,"mode":"js","data":null,"name":null},{"id":2,"value":"Zdog = require('zdog@1.1.1/dist/zdog.dist.min.js')","pinned":false,"mode":"js","data":null,"name":null},{"id":489,"value":"import {renderGif} from '88a8246df5677bae'","pinned":false,"mode":"js","data":null,"name":null}],"resolutions":[],"schedule":null,"last_view_time":null}