Jakub Menšík
Jakub Menšík

Reputation: 174

Clear circle in webgl library

Hi i am using a javascript library for my website. Library use webgl to generate animation. So it creates a canvas inside element and shows animation. But i want that animation to look like a donut. I already set border-radius 50% on canvas element and it generates a circle, but i need cut another transparent circle inside canvas. Here is an image of what i want to achive. enter image description here

I already tried manipulate html elements but so far no success. Also i cant use overlaping element due to background image behind canvas.

Next what i tried was override library function which, but i never done anything in webgl so it went poorly. I add another method that is aplied after animation update. And i achived cut a square inside canvas, but no success with circle. Is there easy way, how to cut a circle in webgl, that i can fit inside my drawEmptyCircle method without any complicated edits in library?

    drawEmptyCircle: function(){
         gl.enable(gl.SCISSOR_TEST);
         gl.scissor(150, 150, 200, 200);
         gl.clearColor(0, 0, 0, 0);
         gl.clear(gl.COLOR_BUFFER_BIT);
         gl.disable(gl.SCISSOR_TEST);
     },

here is fiddle

Upvotes: 0

Views: 554

Answers (2)

Anton
Anton

Reputation: 2703

I think you can't make holes in the Canvas element.

Also i cant use overlaping element due to background image behind canvas.

If you can control the absolute positions of all the elements, this will not be a problem.

As an example I've changed your initial jsfiddle.

First the HTML markup. The outer class is used to control absolute positions of inner elements. The ontop class creates a circle above the canvas to imitate the transparency.

<div class="outer bg">
  <div class="circle"></div>
  <div class="ontop bg"></div>
</div>

In this CSS all the numbers are relative to the original canvas size, that you used in your example (500px x 500px). I used some margin for the canvas (20px) just to show that some more advanced position possible.

.bg {
  background-image: url(-your-image-here-);
}
.ontop {
  background-position: -145px -145px;
  position: absolute;
  left: 145px;
  top: 145px;
  width: 250px;
  height: 250px;
  background-color: white;
  border-radius:50%;
}
.outer {
  position:relative;
  background-position: 0 0;
  width: 540px;
  height: 540px;
  padding: 20px;
}

Here is the working jsfiddle: click.

To make all other elements (text) visible, I have to use a trick with doubling its HTML elements. Here is the example with the text, which is on a background level (so below the canvas): click.

Upvotes: 1

user128511
user128511

Reputation:

You say you've never done any WebGL. That suggests you need some tutorials because how to use WebGL not something that can be explained in a single answer on stack overflow.

The most common way to draw a circle with a hole in it in WebGL is to generate triangles that make a circle with a hole in it. This is what all the major GPU accelerated 2D graphics libraries do.

function makeCircleTriangles(innerRadius, outerRadius, divisions, start = 0, end = Math.PI * 2) {
  const positions = [];
  for (let d = 0; d <= divisions; ++d) {
    const u = d / divisions;
    const angle = start + (end - start) * u;
    const s = Math.sin(angle);
    const c = Math.cos(angle);
    positions.push(c * innerRadius, s * innerRadius);
    positions.push(c * outerRadius, s * outerRadius);
  }
  const indices = [];
  for (let d = 0; d < divisions; ++d) {
    const offset = d * 2;
    indices.push(offset + 0, offset + 1, offset + 2);
    indices.push(offset + 2, offset + 1, offset + 3);
  }
  return {position, indices};
}

const gl = document.querySelector('canvas').getContext('webgl');

const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
  gl_Position = matrix * position;
}
`;

const fs = `
precision highp float;
void main() {
  gl_FragColor = vec4(0, .6, 0, 1);
}
`;

// compile shaders, link progrma, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

const {positions, indices} = makeCircleTriangles(30, 70, 64);

// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
  position: { numComponents: 2, data: positions },
  indices,
});

gl.useProgram(programInfo.program);

// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

// calls gl.uniform
twgl.setUniforms(programInfo, {
  matrix: [
    2 / gl.canvas.clientWidth, 0, 0, 0,
    0, 2 / gl.canvas.clientHeight, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1,
  ],
});

gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

function makeCircleTriangles(innerRadius, outerRadius, divisions, start = 0, end = Math.PI * 2) {
  const positions = [];
  for (let d = 0; d <= divisions; ++d) {
    const u = d / divisions;
    const angle = start + (end - start) * u;
    const s = Math.sin(angle);
    const c = Math.cos(angle);
    positions.push(c * innerRadius, s * innerRadius);
    positions.push(c * outerRadius, s * outerRadius);
  }
  const indices = [];
  for (let d = 0; d < divisions; ++d) {
    const offset = d * 2;
    indices.push(offset + 0, offset + 1, offset + 2);
    indices.push(offset + 2, offset + 1, offset + 3);
  }
  return {positions, indices};
}
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

The second most common way is to draw a quad (2 triangles) with a texture of a circle with a hole it in.

Upvotes: 1

Related Questions