{"id":"f68151506850fb40","slug":"bezier-surface","trashed":false,"description":"","likes":4,"publish_level":"public","forks":1,"fork_of":null,"has_importers":false,"update_time":"2020-08-04T11:40:08.230Z","first_public_version":null,"paused_version":null,"publish_time":"2019-12-08T01:32:59.755Z","publish_version":1635,"latest_version":1635,"thumbnail":"641ed6c033bcc74d182decd44d4fec156cd87fe0cfdee4eb3c89914ee9951b20","default_thumbnail":"641ed6c033bcc74d182decd44d4fec156cd87fe0cfdee4eb3c89914ee9951b20","roles":[],"sharing":null,"owner":{"id":"8e8363b14a130065","avatar_url":"https://avatars.observableusercontent.com/avatar/a3c8cec3733471c90bab096a990da9cfb3c5a02d8abcaaf3335550b6ff9e4234","login":"timhau","name":"tau","bio":"math animations, geometric visuals, always learning ","home_url":"https://twitter.com/_timhau","type":"team","tier":"starter_2024"},"creator":{"id":"98fc0e4faeb2889d","avatar_url":"https://avatars.observableusercontent.com/avatar/a3c8cec3733471c90bab096a990da9cfb3c5a02d8abcaaf3335550b6ff9e4234","login":"timhau","name":"tau","bio":"math animations, geometric visuals, always learning ","home_url":"https://twitter.com/_timhau","tier":"public"},"authors":[{"id":"98fc0e4faeb2889d","avatar_url":"https://avatars.observableusercontent.com/avatar/a3c8cec3733471c90bab096a990da9cfb3c5a02d8abcaaf3335550b6ff9e4234","name":"tau","login":"timhau","bio":"math animations, geometric visuals, always learning ","home_url":"https://twitter.com/_timhau","tier":"public","approved":true,"description":""}],"collections":[{"id":"ffea95499049c8c3","type":"public","slug":"learning-geometry","title":"learning Geometry","description":"","update_time":"2019-12-16T22:32:54.697Z","pinned":false,"ordered":true,"custom_thumbnail":null,"default_thumbnail":"bb5d49c11a990adef379b908080155f5931b4df1636cda28b5bda39338e5a588","thumbnail":"bb5d49c11a990adef379b908080155f5931b4df1636cda28b5bda39338e5a588","listing_count":42,"parent_collection_count":0,"owner":{"id":"8e8363b14a130065","avatar_url":"https://avatars.observableusercontent.com/avatar/a3c8cec3733471c90bab096a990da9cfb3c5a02d8abcaaf3335550b6ff9e4234","login":"timhau","name":"tau","bio":"math animations, geometric visuals, always learning ","home_url":"https://twitter.com/_timhau","type":"team","tier":"starter_2024"}}],"files":[],"comments":[],"commenting_lock":null,"suggestion_from":null,"suggestions_to":[],"version":1635,"title":"bézier surface","license":null,"copyright":"","nodes":[{"id":0,"value":"md`# bézier surface\n\nA [Bézier surface](https://en.wikipedia.org/wiki/B%C3%A9zier_surface) is given by ${tex`n \\times m`} control points. The parametric coordinates for the parameterization variables ${tex`u,v`} is given by:\n\n${tex.block`\n  \\begin{aligned}\n    p(u,v) = \\sum_{i=0}^{n}\\sum_{j=0}^{m}B_{i}^{n}(u)B_{j}^{m}(v)k_{ij}\n  \\end{aligned}\n`}\n\nwhere ${tex`B_i^n(u)`} is the ${tex`i`}-th [Bernstein polynomial](https://observablehq.com/@timhau/bernstein-poylnomial) given by\n\n${tex.block`\n  \\begin{aligned} \n    B_i^n(u) = \\binom{n}{i}u^i(1-u)^{n-i}\n  \\end{aligned}\n`}\n\nand ${tex`k_{ij}`} is the ${tex`i,j`}-th control point.\n\nfeel free to drag / send suggestions / correct my mistakes \n`","pinned":false,"mode":"js","data":null,"name":null},{"id":14,"value":"gl.canvas","pinned":false,"mode":"js","data":null,"name":null},{"id":1582,"value":"md`\n__Note__: unfortunately, i did not get [regl.js](http://regl.party/) 100% to work with Observable, so if you update the code you should reload the page. Suggestions are welcome as always.\n`","pinned":false,"mode":"js","data":null,"name":null},{"id":23,"value":"function draw(t) {\n  const view = camera.view();\n  drawGrid({ view });\n\n  const bPoints = [];\n  for (let points of curvePoints) {\n    drawCurve({\n      view,\n      count: points.length,\n      point: points,\n      color: [0.5, 0.5, 0.5, 0.8]\n    });\n    drawPoint({\n      view,\n      count: points.length,\n      point: points,\n      pointSize: 5.0\n    });\n    const [lastPoint] = drawBezier(points, n - 1, view, t);\n    bPoints.push(lastPoint);\n  }\n\n  const surface = [];\n  const num = 20;\n  const step = 1 / num;\n  for (let u = 0; u < 1; u += step) {\n    for (let v = 0; v < 1; v += step) {\n      const c = bezier_surface(curvePoints, [u, v]);\n      surface.push(c);\n    }\n  }\n\n  for (let i = 0; i < num; ++i) {\n    const color = [0.2, 0.2, 1, 0.8];\n    drawCurve({\n      view,\n      count: num,\n      point: surface.slice(i * num, i * num + num + 1),\n      color\n    });\n    drawCurve({\n      view,\n      count: num,\n      point: surface.filter((v, j) => j % num === i),\n      color\n    });\n  }\n\n  return bPoints;\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":1329,"value":"function bezier_surface(controlPoints, [u, v]) {\n  const n = controlPoints.length;\n  const m = controlPoints[0].length;\n\n  let res = [];\n  for (let i = 0; i < n; ++i) {\n    const i_poly = bernstein_poly(i, n - 1, u);\n    for (let j = 0; j < m; ++j) {\n      const [x, y, z] = controlPoints[i][j];\n      const j_Poly = bernstein_poly(j, m - 1, v);\n      res.push([i_poly * j_Poly * x, i_poly * j_Poly * y, i_poly * j_Poly * z]);\n    }\n  }\n\n  return res.reduce(([x1, y1, z1], [x2, y2, z2]) => [\n    x1 + x2,\n    y1 + y2,\n    z1 + z2\n  ]);\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":1278,"value":"function bezier_curve(controlPoints, t) {\n  const n = controlPoints.length;\n\n  let res = [];\n  for (let i = 0; i < controlPoints.length; ++i) {\n    const [x, y, z] = controlPoints[i];\n    const b_poly = bernstein_poly(i, n - 1, t);\n    res.push([b_poly * x, b_poly * y, b_poly * z]);\n  }\n\n  return res.reduce(([x1, y1, z1], [x2, y2, z2]) => [\n    x1 + x2,\n    y1 + y2,\n    z1 + z2\n  ]);\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":1068,"value":"function drawBezier(old, n, view, t) {\n  const current = [];\n  for (let i = 0; i < n; ++i) {\n    current.push(lerp3D(old[i], old[i + 1], t / 100));\n  }\n\n  if (n === 1) {\n    drawPoint({\n      view,\n      count: n,\n      point: current,\n      highlight: true,\n      pointSize: 5.0\n    });\n    return current;\n  }\n\n  drawPoint({\n    view,\n    count: current.length,\n    point: current,\n    pointSize: 5.0\n  });\n  drawCurve({\n    view,\n    count: current.length,\n    point: current,\n    color: [0.5, 0.5, 0.5, 0.8]\n  });\n\n  return drawBezier(current, n - 1, view, t);\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":654,"value":"{\n  gl.regl.frame(({ tick }) => {\n    camera.tick();\n    const time = (tick / 2) % 100;\n    const lastBPoints = draw(time);\n\n    const path = [];\n    for (let t = 0; t < 1; t += 0.05) {\n      const c = bezier_curve(lastBPoints, t);\n      path.push(c);\n    }\n  });\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":641,"value":"gl = createGL();","pinned":true,"mode":"js","data":null,"name":null},{"id":591,"value":"function positions(points) {\n  const startPos = [];\n  for (let i = 0; i < points.length; ++i) {\n    for (let j = 0; j < points[i].length - 1; ++j) {\n      startPos.push(points[i][j]);\n    }\n  }\n\n  const endPos = [];\n  for (let i = 0; i < points.length; ++i) {\n    for (let j = 1; j < points[i].length; ++j) {\n      endPos.push(points[i][j]);\n    }\n  }\n\n  return [startPos, endPos];\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":482,"value":"curvePoints = {\n  const res = [];\n\n  for (let i = 0; i < n; ++i) {\n    const curve = [];\n    for (let j = 0; j < n; ++j) {\n      const p = [\n        -w + i * ((2 * w) / (n - 1)),\n        Math.floor(Math.random() * randh),\n        -d + j * ((2 * d) / (n - 1))\n      ];\n      curve.push(p);\n    }\n    res.push(curve);\n  }\n\n  return res;\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":351,"value":"drawPoint = gl.regl({\n  vert: `\n    precision mediump float;\n\n    attribute vec3 point;\n  \n    uniform mat4 proj;\n    uniform mat4 view;\n    uniform float pointSize;\n  \n    void main () {\n      gl_PointSize = pointSize;\n      gl_Position = proj * view * vec4(point, 1.0);\n    }\n`,\n  frag: `\n    precision mediump float;\n\n    uniform bool highlight;\n\n    void main () {\n      vec4 color;\n      if(highlight) {\n        color = vec4(1, 0, 0, 1);\n      } else {\n        color = vec4(0.4, 0.4, 0.4, 0.7);\n      }\n      gl_FragColor = color;\n    }\n`,\n  attributes: {\n    point: gl.regl.prop('point')\n  },\n  uniforms: {\n    view: gl.regl.prop('view'),\n    proj: mat4.perspective([], Math.PI / 4, width / height, 0.001, 10000),\n    highlight: gl.regl.prop('highlight'),\n    pointSize: gl.regl.prop('pointSize') || 5.0\n  },\n  count: gl.regl.prop('count'),\n  primitive: 'points'\n})","pinned":true,"mode":"js","data":null,"name":null},{"id":428,"value":"drawCurve = gl.regl({\n  vert: `\n    precision mediump float;\n    attribute vec3 point;\n  \n    uniform mat4 proj;\n    uniform mat4 view;\n  \n    void main () {\n      gl_Position = proj * view * vec4(point, 1.0);\n    }\n`,\n  frag: `\n    precision mediump float;\n\n    uniform bool highlight;\n    uniform vec4 color;\n\n    void main () {\n      gl_FragColor = color;\n    }\n`,\n  attributes: {\n    point: gl.regl.prop('point')\n  },\n  uniforms: {\n    view: gl.regl.prop('view'),\n    proj: mat4.perspective([], Math.PI / 4, width / height, 0.001, 10000),\n    highlight: gl.regl.prop('highlight'),\n    color: gl.regl.prop('color')\n  },\n  count: gl.regl.prop('count'),\n  primitive: 'line strip'\n})","pinned":true,"mode":"js","data":null,"name":null},{"id":104,"value":"drawGrid = gl.regl({\n  vert: `\n    precision mediump float;\n    attribute vec3 point;\n  \n    uniform mat4 proj;\n    uniform mat4 view;\n  \n    void main () {\n      gl_Position = proj * view * vec4(point, 1.0);\n    }\n`,\n  frag: `\n    precision mediump float;\n    void main () {\n      gl_FragColor = vec4(0, 0, 0, 0.5);\n    }\n`,\n  attributes: {\n    point: groundPoints\n  },\n  uniforms: {\n    view: gl.regl.prop('view'),\n    proj: mat4.perspective([], Math.PI / 4, width / height, 0.001, 10000)\n  },\n  count: groundPoints.length,\n  primitive: 'lines'\n})","pinned":true,"mode":"js","data":null,"name":null},{"id":745,"value":"function lerp3D([x1, y1, z1], [x2, y2, z2], t) {\n  const v = [x2 - x1, y2 - y1, z2 - z1];\n  const d = Math.hypot(...v);\n  return [x1 + t * v[0], y1 + t * v[1], z1 + t * v[2]];\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":338,"value":"groundPoints = [\n  [-w, h, -d],\n  [-w, h, d],\n  [w, h, -d],\n  [w, h, d],\n  [w, h, d],\n  [-w, h, d],\n  [w, h, -d],\n  [-w, h, -d]\n]","pinned":true,"mode":"js","data":null,"name":null},{"id":638,"value":"camera = {\n  const camera = createCamera(gl.canvas);\n  camera.lookAt([0, 800, -900], [0, 0, 0], [0, 1, 0]);\n  return camera;\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":10,"value":"function createGL(opts) {\n  var canvas = DOM.canvas(width, height)\n  var regl = createREGL(Object.assign({ canvas: canvas }, opts || {}));\n  return { canvas, regl };\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":536,"value":"n = 5","pinned":true,"mode":"js","data":null,"name":null},{"id":374,"value":"d = 250","pinned":true,"mode":"js","data":null,"name":null},{"id":377,"value":"w = 250","pinned":true,"mode":"js","data":null,"name":null},{"id":380,"value":"h = -10","pinned":true,"mode":"js","data":null,"name":null},{"id":621,"value":"randh = 350","pinned":true,"mode":"js","data":null,"name":null},{"id":12,"value":"height = width","pinned":true,"mode":"js","data":null,"name":null},{"id":1276,"value":"import { bernstein_poly } from '@timhau/bernstein-poylnomial'","pinned":true,"mode":"js","data":null,"name":null},{"id":4,"value":"createREGL = require('https://unpkg.com/regl@1.3.1/dist/regl.min.js')","pinned":true,"mode":"js","data":null,"name":null},{"id":62,"value":"createCamera = require(\"https://bundle.run/canvas-orbit-camera@1.0.2\")","pinned":true,"mode":"js","data":null,"name":null},{"id":59,"value":"mat4 = require('https://bundle.run/gl-mat4@1.0.0')","pinned":true,"mode":"js","data":null,"name":null}],"resolutions":[],"schedule":null,"last_view_time":null}