Not able to draw text on top or arc in Flutter - dart

I have drawn a custom arc using the draw arc method of the canvas library, now I want to write some text on top of this arc using TextPainter but when I paint the text on canvas I do not see anything. I just know the arc is drawn, and the text is invisible.
Is there some concept of Z-index or layering which I have to give to text paint to draw on top of the arc?
Below is my sample code
import 'dart:math' as math;
import 'package:flutter/material.dart';
class ChartPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
print(size.width);
print(size.height);
final Paint paint = new Paint()..color = Colors.blue;
// Draw a circle that circumscribes the arrow.
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 50.0;
paint.isAntiAlias = true;
//canvas.drawCircle(new Offset(centerX, centerY), r, paint);
canvas.drawArc(new Rect.fromLTWH(0.0, 0.0, size.width, size.height), 0.0,
-math.pi / 4, false, paint);
paint.color = Colors.orange;
canvas.drawArc(new Rect.fromLTWH(0.0, 0.0, size.width, size.height),
-math.pi / 4, -math.pi / 4, false, paint);
paint.color = Colors.green;
canvas.drawArc(new Rect.fromLTWH(0.0, 0.0, size.width, size.height),
-math.pi / 2, -math.pi / 4, false, paint);
paint.color = Colors.purple;
canvas.drawArc(new Rect.fromLTWH(0.0, 0.0, size.width, size.height),
-math.pi * 3 / 4, -math.pi / 4, false, paint);
TextSpan span = new TextSpan(
style: new TextStyle(color: Colors.black, fontSize: 24.0,
fontFamily: 'Roboto'), text: "Ajay Singh");
TextPainter tp = new TextPainter(
text: span, textAlign: TextAlign.left);
tp.layout();
tp.paint(canvas, new Offset(100.0, 100.0));
}
#override
bool shouldRepaint(ChartPainter oldDelegate) {
return true;
}
}

Related

console.log draw counts of the polygon in WebGL

How to console.log the polygon count in WebGL, where I want the check the reference of the polygon count before culling and after culling. I'm new to WebGL where I'm analyzing the culling concepts where I want to check the count of the polygons before and culled.
Kindly, help me out! Thanks in advance.
Here's the sample code
(function(global) {
/*
* Constants, State, and Main
* www.programmingtil.com
* www.codenameparkerllc.com
*/
var KEYPRESS_SPEED = 0.2;
var IMAGES = [
{name:"stainglass", src:"/images/txStainglass.bmp"},
{name:"crate", src:"/images/txCrate.bmp"},
];
var state = {
gl: null,
mode: 'render',
ui: {
dragging: false,
mouse: {
lastX: -1,
lastY: -1,
},
pressedKeys: {},
},
animation: {},
app: {
animate: true,
eye: {
x:2.,
y:2.,
z:5.,
w:1.,
},
fog: {
color: new Float32Array([0.5,0.5,0.5]),
dist: new Float32Array([60, 80]),
on: false,
},
light: {
ambient: [0.2, 0.2, 0.2],
diffuse: [1.0, 1.0, 1.0],
position: [1.0, 2.0, 1.7],
},
objects: [],
textures: {},
},
eyeInArray: function() {
return [this.app.eye.x, this.app.eye.y, this.app.eye.z, this.app.eye.w];
}
};
glUtils.SL.init({ callback:function() { main(); } });
function main() {
state.canvas = document.getElementById("glcanvas");
state.overlay = document.getElementById("overlay");
state.ctx = state.overlay.getContext("2d");
state.gl = glUtils.checkWebGL(state.canvas, {
preserveDrawingBuffer: true,
});
initCallbacks();
initShaders();
initGL();
initCanvasTexture();
initState();
glUtils.initTextures(IMAGES, state.app.textures, function() {
draw();
if (state.app.animate) {
animate();
}
});
}
/*
* Primitives and Objects
* www.programmingtil.com
* www.codenameparkerllc.com
*/
// Create a cube
function Cube(opts) {
var opts = opts || {};
this.id = saKnife.uuid();
this.opts = opts;
this.gl = opts.gl;
this.programs = opts.programs;
// Vextex positions
// v0-v1-v2-v3 front
// v0-v3-v4-v5 right
// v0-v5-v6-v1 up
// v1-v6-v7-v2 left
// v7-v4-v3-v2 down
// v4-v7-v6-v5 back
this.attributes = {
aColor: {
size:4,
offset:0,
bufferData: new Float32Array([
1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1,
1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1
]),
},
aNormal: {
size:3,
offset:0,
bufferData: new Float32Array([
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0,
0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0
]),
},
aPosition: {
size:3,
offset:0,
bufferData: new Float32Array([
1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0,-1.0, 1.0, 1.0,-1.0, 1.0,
1.0, 1.0, 1.0, 1.0,-1.0, 1.0, 1.0,-1.0,-1.0, 1.0, 1.0,-1.0,
1.0, 1.0, 1.0, 1.0, 1.0,-1.0, -1.0, 1.0,-1.0, -1.0, 1.0, 1.0,
-1.0, 1.0, 1.0, -1.0, 1.0,-1.0, -1.0,-1.0,-1.0, -1.0,-1.0, 1.0,
-1.0,-1.0,-1.0, 1.0,-1.0,-1.0, 1.0,-1.0, 1.0, -1.0,-1.0, 1.0,
1.0,-1.0,-1.0, -1.0,-1.0,-1.0, -1.0, 1.0,-1.0, 1.0, 1.0,-1.0
]),
},
aSelColor: {
size:4,
offset:0,
bufferData: undefined,
},
aTexCoord: {
size:2,
offset:0,
bufferData: new Float32Array([
1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0,
1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0,
1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0
]),
},
};
this.indices = new Uint8Array([
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9,10, 8,10,11,
12,13,14, 12,14,15,
16,17,18, 16,18,19,
20,21,22, 20,22,23
]);
// Functionality
this.readState = function() {
this.attributes.aColorBackup = this.attributes.aColor;
this.attributes.aColor = this.attributes.aSelColor;
this.state.renderMode = 'read';
};
this.drawState = function() {
this.attributes.aColor = this.attributes.aColorBackup;
this.state.renderMode = 'render';
};
this.draw = function() {
this.gl.useProgram(this.programs[this.state.renderMode]);
var uMVPMatrix = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uMVPMatrix');
var uModelMatrix = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uModelMatrix');
var uNormalMatrix = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uNormalMatrix');
var uAmbientLight = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uAmbientLight');
var uDiffuseLight = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uDiffuseLight');
var uLightPosition = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uLightPosition');
var uFogColor = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uFogColor');
var uFogDist = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uFogDist');
var uSampler0 = this.gl.getUniformLocation(this.programs[this.state.renderMode], 'uSampler0');
var mvp = state.mvp;
this.programs[this.state.renderMode].renderBuffers(this);
var n = this.indices.length;
// Model matrix
var mm = mat4.create();
if (this.opts.translate) {
mat4.translate(mm, mm, this.opts.translate);
}
if (this.opts.scale) {
mat4.scale(mm, mm, this.opts.scale);
}
if (this.state.angle[0] || this.state.angle[1] || this.state.angle[2]) {
mat4.rotateX(mm, mm, this.state.angle[0]);
mat4.rotateY(mm, mm, this.state.angle[1]);
mat4.rotateZ(mm, mm, this.state.angle[2]);
}
this.gl.uniformMatrix4fv(uModelMatrix, false, mm);
// MVP matrix
mat4.copy(mvp, state.pm);
mat4.multiply(mvp, mvp, state.vm);
mat4.multiply(mvp, mvp, mm);
this.gl.uniformMatrix4fv(uMVPMatrix, false, mvp);
// Fog
if (state.app.fog.on) {
this.gl.uniform3fv(uFogColor, state.app.fog.color);
this.gl.uniform2fv(uFogDist, state.app.fog.dist);
}
// Lighting
if (this.state.renderMode === 'render') {
this.gl.uniform3f(uDiffuseLight, state.app.light.diffuse[0], state.app.light.diffuse[1], state.app.light.diffuse[2]);
this.gl.uniform3f(uAmbientLight, state.app.light.ambient[0], state.app.light.ambient[1], state.app.light.ambient[2]);
this.gl.uniform3f(uLightPosition, state.app.light.position[0], state.app.light.position[1], state.app.light.position[2]);
var nm = mat3.normalFromMat4(mat3.create(), mm);
this.gl.uniformMatrix3fv(uNormalMatrix, false, nm);
}
// Textures
if (this.state.hasTexture) {
this.gl.activeTexture(this.gl.TEXTURE0);
this.gl.bindTexture(this.gl.TEXTURE_2D, state.app.textures[this.opts.texture]);
this.gl.uniform1i(uSampler0, 0);
}
// Blending
if (this.state.hasBlend && this.state.renderMode === 'render') {
this.gl.blendFunc(this.state.blendSrc, this.state.blendDst);
this.gl.blendEquation(this.state.blendEquation);
this.gl.disable(this.gl.CULL_FACE);
}
else {
this.gl.enable(this.gl.DEPTH_TEST);
this.gl.disable(this.gl.BLEND);
this.gl.depthMask(true);
// Culling
this.gl.enable(this.gl.CULL_FACE);
this.gl.cullFace(this.gl.BACK);
}
// Culling
// this.gl.enable(this.gl.CULL_FACE);
// this.gl.cullFace(this.gl.FRONT_AND_BACK);
// this.gl.cullFace(this.gl.FRONT);
// this.gl.cullFace(this.gl.BACK);
// Draw!
this.gl.drawElements(this.gl.TRIANGLES, n, this.gl.UNSIGNED_BYTE, 0);
};
// Cube Initialization
this.init = function(_this) {
var selColor = opts.selColor ? opts.selColor : [0,0,0,0];
_this.selColor = selColor.map(function(n) { return n/255; });
var arrays = [
_this.selColor, _this.selColor, _this.selColor, _this.selColor,
_this.selColor, _this.selColor, _this.selColor, _this.selColor,
_this.selColor, _this.selColor, _this.selColor, _this.selColor,
_this.selColor, _this.selColor, _this.selColor, _this.selColor,
_this.selColor, _this.selColor, _this.selColor, _this.selColor,
_this.selColor, _this.selColor, _this.selColor, _this.selColor,
];
_this.attributes.aSelColor.bufferData = new Float32Array([].concat.apply([], arrays));
_this.state = {
angle: opts.angle ? opts.angle : [0,0,0],
blendEquation: opts.blendEquation ? opts.blendEquation : _this.gl.FUNC_ADD,
blendSrc: opts.blendSrc ? opts.blendSrc : _this.gl.SRC_ALPHA,
blendDst: opts.blendDst ? opts.blendDst : _this.gl.ONE_MINUS_SRC_ALPHA,
hasBlend: opts.blend ? true : false,
hasTexture: opts.texture ? true : false,
mm: mat4.create(),
nm: null,
renderMode: 'render',
};
}(this);
}
/*
* Initialization
* www.programmingtil.com
* www.codenameparkerllc.com
*/
function initCallbacks() {
document.onkeydown = keydown;
document.onkeyup = keyup;
state.canvas.onmousedown = mousedown;
state.canvas.onmouseup = mouseup;
state.canvas.onmousemove = mousemove;
}
function initShaders() {
var vertexRead = glUtils.getShader(state.gl.VERTEX_SHADER, glUtils.SL.Shaders.read.vertex),
fragmentRead = glUtils.getShader(state.gl.FRAGMENT_SHADER, glUtils.SL.Shaders.read.fragment),
vertexRender = glUtils.getShader(state.gl.VERTEX_SHADER, glUtils.SL.Shaders.render.vertex),
fragmentRender = glUtils.getShader(state.gl.FRAGMENT_SHADER, glUtils.SL.Shaders.render.fragment),
vertexTex = glUtils.getShader(state.gl.VERTEX_SHADER, glUtils.SL.Shaders.tex.vertex),
fragmentTex = glUtils.getShader(state.gl.FRAGMENT_SHADER, glUtils.SL.Shaders.tex.fragment);
state.programs = {
read: glUtils.createProgram(vertexRead, fragmentRead),
render: glUtils.createProgram(vertexRender, fragmentRender),
texture: glUtils.createProgram(vertexTex, fragmentTex),
};
}
function initGL() {
state.gl.clearColor(0,0,0,1);
}
function initState() {
state.vm = mat4.create();
state.pm = mat4.create();
state.mvp = mat4.create();
state.app.objects = [
new Cube({
blend: true,
blendDst: state.gl.ONE,
gl: state.gl,
programs: {
render: state.programs.texture,
read: state.programs.read,
},
selColor:[255,254,0,0],
scale:[0.5,0.5,0.5],
texture:'stainglass',
}),
new Cube({
gl: state.gl,
programs: {
render: state.programs.render,
read: state.programs.read,
},
selColor:[255,255,0,0],
translate:[3,0,0],
}),
new Cube({
gl: state.gl,
programs: {
render: state.programs.render,
read: state.programs.read,
},
selColor:[255,253,0,0],
scale:[0.5,0.5,0.5],
translate:[-2, 2, 0],
}),
new Cube({
blend: true,
blendDst: state.gl.ONE,
gl: state.gl,
programs: {
render: state.programs.texture,
read: state.programs.read,
},
selColor:[255,252,0,0],
scale:[0.6,0.6,0.6],
texture:'crate',
translate:[-2, -2, 2],
angle: [0,35,0],
}),
new Cube({
gl: state.gl,
programs: {
render: state.programs.texture,
read: state.programs.read,
},
selColor:[255,251,0,0],
scale:[0.2,0.2,0.2],
texture:'crate',
translate:[2, 2, 2],
angle: [75,0,0],
}),
];
}
function initCanvasTexture() {
var texture = state.gl.createTexture();
var textCanvas = document.createElement('canvas');
textCanvas.width = 256;
textCanvas.height = 256;
var ctx = textCanvas.getContext('2d');
// Setup background
ctx.fillStyle = 'rgba(53, 60, 145, 1.0)';
ctx.fillRect(0, 0, textCanvas.width, textCanvas.height);
// Setup font
ctx.font = '36px bold sans-serif';
ctx.fillStyle = 'rgba(0, 60, 145, 1.0)';
ctx.textBaseline = 'middle';
ctx.shadowColor = 'rgba(10, 160, 190, 1.0)';
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 5;
// Draw out some text
var text = 'ProgrammingTIL';
var textWidth = ctx.measureText(text).width;
ctx.fillText(text, (textCanvas.width-textWidth)/2, textCanvas.height/2);
// Change the font and draw out more text
ctx.font = '26px bold sans-serif';
ctx.fillStyle = 'white';
ctx.shadowColor = 'rgba(0, 0, 0, 0)';
text = 'David';
textWidth = ctx.measureText(text).width;
ctx.fillText(text, (textCanvas.width-textWidth)/2, textCanvas.height/2 - 60);
text = 'Parker';
textWidth = ctx.measureText(text).width;
ctx.fillText(text, (textCanvas.width-textWidth)/2, textCanvas.height/2 + 60);
// Put the canvas onto the texture object
state.gl.pixelStorei(state.gl.UNPACK_FLIP_Y_WEBGL, 1);
state.gl.bindTexture(state.gl.TEXTURE_2D, texture);
state.gl.texImage2D(state.gl.TEXTURE_2D, 0, state.gl.RGBA, state.gl.RGBA, state.gl.UNSIGNED_BYTE, textCanvas);
state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_MAG_FILTER, state.gl.LINEAR);
state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_MIN_FILTER, state.gl.LINEAR);
state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_WRAP_S, state.gl.CLAMP_TO_EDGE);
state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_WRAP_T, state.gl.CLAMP_TO_EDGE);
state.app.textures['canvastext'] = texture;
}
/*
* State Management
* www.programmingtil.com
* www.codenameparkerllc.com
*/
function updateOverlay() {
var msg = "Eye position: ("+state.app.eye.x.toFixed(2)+","+state.app.eye.y.toFixed(2)+","+state.app.eye.z.toFixed(2)+")";
state.ctx.clearRect(0,0,state.ctx.canvas.width,state.ctx.canvas.height);
state.ctx.save();
state.ctx.font = "20px Helvetica";
state.ctx.fillStyle = "white";
state.ctx.fillText(msg, 10, 25);
state.ctx.restore();
}
function updateState() {
if (state.ui.pressedKeys[37]) {
// left
state.app.eye.x += KEYPRESS_SPEED;
} else if (state.ui.pressedKeys[39]) {
// right
state.app.eye.x -= KEYPRESS_SPEED;
} else if (state.ui.pressedKeys[40]) {
// down
state.app.eye.y += KEYPRESS_SPEED;
} else if (state.ui.pressedKeys[38]) {
// up
state.app.eye.y -= KEYPRESS_SPEED;
} else if (state.ui.pressedKeys[90] && !state.ui.pressedKeys[16]) {
// z
state.app.eye.z += KEYPRESS_SPEED;
} else if (state.ui.pressedKeys[90] && state.ui.pressedKeys[16]) {
// Shift+z
state.app.eye.z -= KEYPRESS_SPEED;
}
}
/*
* Rendering / Drawing / Animation
* www.programmingtil.com
* www.codenameparkerllc.com
*/
function animate() {
state.animation.tick = function() {
updateOverlay();
updateState();
draw();
requestAnimationFrame(state.animation.tick);
};
state.animation.tick();
}
function draw() {
state.gl.clear(state.gl.COLOR_BUFFER_BIT | state.gl.DEPTH_BUFFER_BIT);
mat4.perspective(state.pm,
20, state.canvas.width/state.canvas.height, 1, 100
);
mat4.lookAt(state.vm,
vec3.fromValues(state.app.eye.x,state.app.eye.y,state.app.eye.z),
vec3.fromValues(0,0,0),
vec3.fromValues(0,1,0)
);
// Note: First you should sort (transparent) objects based on distance -> furthest away first
// For our purposes, we'll loop through everything twice. Once to draw opaque objects
// and another for transparent objects.
state.gl.enable(state.gl.DEPTH_TEST);
state.gl.disable(state.gl.BLEND);
state.gl.depthMask(true);
state.app.objects.forEach(function(obj) {
if (!obj.state.hasBlend) {
obj.draw();
}
});
// Leave on the depth test!
// state.gl.disable(state.gl.DEPTH_TEST);
state.gl.enable(state.gl.BLEND);
state.gl.depthMask(false);
state.app.objects.forEach(function(obj) {
if (obj.state.hasBlend) {
obj.draw();
}
});
state.gl.depthMask(true);
}
/*
* UI Events
* www.programmingtil.com
* www.codenameparkerllc.com
*/
function keydown(event) {
state.ui.pressedKeys[event.keyCode] = true;
}
function keyup(event) {
state.ui.pressedKeys[event.keyCode] = false;
}
function mousedown(event) {
if (uiUtils.inCanvas(event)) {
state.gl.disable(state.gl.BLEND);
state.gl.enable(state.gl.DEPTH_TEST);
state.gl.depthMask(true);
state.app.objects.forEach(function(obj) {
obj.readState();
draw();
});
var pixels = Array.from(uiUtils.pixelsFromMouseClick(event, state.canvas, state.gl));
var obj2 = uiUtils.pickObject(pixels, state.app.objects, 'selColor');
if (obj2) {
state.app.objSel = obj2;
state.ui.mouse.lastX = event.clientX;
state.ui.mouse.lastY = event.clientY;
state.ui.dragging = true;
}
state.gl.enable(state.gl.BLEND);
state.gl.disable(state.gl.DEPTH_TEST);
state.gl.depthMask(false);
state.app.objects.forEach(function(obj) {
obj.drawState();
draw();
});
}
}
function mouseup(event) {
state.ui.dragging = false;
}
function mousemove(event) {
var x = event.clientX;
var y = event.clientY;
if (state.ui.dragging) {
// The rotation speed factor
// dx and dy here are how for in the x or y direction the mouse moved
var factor = 10/state.canvas.height;
var dx = factor * (x - state.ui.mouse.lastX);
var dy = factor * (y - state.ui.mouse.lastY);
// update the latest angle
state.app.objSel.state.angle[0] = state.app.objSel.state.angle[0] + dy;
state.app.objSel.state.angle[1] = state.app.objSel.state.angle[1] + dx;
}
// update the last mouse position
state.ui.mouse.lastX = x;
state.ui.mouse.lastY = y;
}
})(window || this);
WebGL has no way to give you the info you seek.
WebGL just looks at clipspace triangles in 2D (the values your shader writes to gl_Position). In that 2D space it labels them clockwise or counter-clockwise and then discards triangles that you requested to be culled based on the culling settings.
In order to know how many triangles are drawn (or cullled) you'd need to take the math happening in your shaders related to gl_Position and reproduce that math in JavaScript. Then run your data through that math and generate triangle vertices. For any triangle you compute the area
--pseudo code--
area = 0;
for (let i = 0; i < 3; ++i) {
p0 = threeScreenSpaceTriangleVertices[i];
p1 = threeScreenSpaceTriangleVertices[(i + 1) % 3];
area += p0.x * p1.y - p1.x * p0.y;
}
area *= 0.5; // not really important
If area is positive it's clockwise, if it's negative it's counter clockwise
I guess it's not possible out of the box with WebGL.
Face culling (in your case backface culling) works by comparing the orientation of the face with the looking direction of the camera.
Thus you can easily write some code on CPU/JS side to compute the number of faces displayed when culling is enabled.
To do so, create at render time a loop over all your cubes faces. Get the normal vectors of the face, transform it with the normal matrix (world inverse transposed) and the view matrix and finally normalize it. At this point you have the normal vector transformed in eye space.
Then compute the dot product with the eye looking vector (0,0,-1) (already normalized). The result is the cosine of the angle between normal and camera.
If the sign is negative, the camera is looking at the front of the face and you can increase the draw count. It it's positive, the camera sees the back of the face and you can discard this one.
// In pseudo GLSL code (write it on JS side)
vec3 normalEyeSpace = normalize(viewMatrix * worldInverseTranspose * aNormal)
float dir = dot(normalEyeSpace, vec3(0.0, 0.0, -1.0)
if (dir < 0) drawCount++

how to enable ui interaction with visible components behind a canvas?

I'm writing a flutter application and I'm trying to create a tutorial overlay, that will fully show only components that I want the user to be able to interact with.
so I extended CustomPainter class, while getting the Page's context (to get his dimensions) and a list of GlobalKeys (for elements that I want to display and to interact with)
Color colorBlack = Colors.black.withOpacity(0.4);
class CurvePainter extends CustomPainter{
BuildContext context;
List<GlobalKey> globalKeys;
double padding;
#override
void paint(Canvas canvas, Size size) {
final double screenWidth = MediaQuery.of(context).size.width;
final double screenHeight = MediaQuery.of(context).size.height;
Path path = Path()..addRect(Rect.fromLTWH(0, 0, screenWidth, screenHeight));
Set<GlobalKey> keysSet = Set.from(globalKeys);
keysSet.forEach((element){
final List<double> vals = global_key_util.getArea(element);
path = Path.combine(PathOperation.difference,
path,
Path()
..addOval(Rect.fromLTWH(vals[0]-(padding/2),vals[1]-padding/2,vals[2]+padding,vals[3]+padding)));
});
canvas.drawPath(path,
Paint()..color = colorBlack);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return oldDelegate != this;
}
CurvePainter({this.context,this.globalKeys,this.padding=4});
}
so far so good... the button that I want the user to interact with is fully visible, in the initState() of my page I create the overlay and show it.
the problem is that I can't interact with that button!
how can I resolve this ?
thanks
so.. the answer is simply not to use CustomPainter, to use ClipPath with CustomClipper.
in my case I wanted to detect when the user is clicking on the overflow, but to also allow him to interact with the visible widgets behind it.
so I created an InvertedClipper class that extends CustomClipper:
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'HoleArea.dart';
import 'WidgetData.dart';
class InvertedClipper extends CustomClipper<Path> {
final Animation<double> animation;
final List<WidgetData> widgetsData;
double padding;
Function deepEq = const DeepCollectionEquality().equals;
List<HoleArea> areas = [];
InvertedClipper({#required this.padding, this.animation, Listenable reclip,
this.widgetsData}) : super(reclip: reclip) {
if (widgetsData.isNotEmpty) {
widgetsData.forEach((WidgetData widgetData) {
if (widgetData.isEnabled) {
final GlobalKey key = widgetData.key;
if (key == null) {
// throw new Exception("GlobalKey is null!");
} else if (key.currentWidget == null) {
// throw new Exception("GlobalKey is not assigned to a Widget!");
} else {
areas.add(getHoleArea(key: key,shape: widgetData.shape,padding: widgetData.padding));
}
}
});
}
}
#override
Path getClip(Size size) {
Path path = Path();
double animationValue = animation != null ? animation.value : 0;
areas.forEach((HoleArea area) {
switch (area.shape) {
case WidgetShape.Oval: {
path.addOval(Rect.fromLTWH(area.x - (((area.padding + padding) + animationValue*15) / 2), area.y - ((area.padding + padding) + animationValue*15) / 2,
area.width + ((area.padding + padding) + animationValue*15), area.height + ((area.padding + padding) + animationValue*15)));
}
break;
case WidgetShape.Rect: {
path.addRect(Rect.fromLTWH(area.x - (((area.padding + padding) + animationValue*15) / 2), area.y - ((area.padding + padding) + animationValue*15) / 2,
area.width + ((area.padding + padding) + animationValue*15), area.height + ((area.padding + padding) + animationValue*15)));
}
break;
case WidgetShape.RRect: {
path.addRRect(RRect.fromRectAndCorners(Rect.fromLTWH(area.x - (((area.padding + padding) + animationValue*15) / 2), area.y - ((area.padding + padding) + animationValue*15) / 2,
area.width + ((area.padding + padding) + animationValue*15), area.height + ((area.padding + padding) + animationValue*15)),
topLeft: Radius.circular(5.0),
topRight: Radius.circular(5.0),
bottomLeft: Radius.circular(5.0),
bottomRight: Radius.circular(5.0)));
}
break;
}
});
return path
..addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height))
..fillType = PathFillType.evenOdd;
}
#override
bool shouldReclip(InvertedClipper oldClipper) {
return !deepEq(oldClipper.areas, areas);
}
}
and I configure a GestureDetector before that to detect when user is clicking on overlay:
return GestureDetector(
onTap: onTap,
child: ClipPath(
clipper: InvertedClipper(
padding: defaultPadding,
animation: animation,
reclip: animationController,
widgetsData: widgetsData),
...

How to shape the container?

I want to make different shape of a container in flutter.
For example, shaping the container to have a shape of octagonal, etc.
Thank you in advance.
You can extend CustomClipper and define a custom path to be used with ClipPath. There are other pre-made clip widgets like ClipOval and ClipRRect (a rectangle with rounded corners). The below is an example of a star-shaped Container.
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ClipPath(
child: Container(
color: Colors.amber,
),
clipper: _MyClipper(),
),
);
}
}
class _MyClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
final path = Path();
path.lineTo(size.width * 0.5, size.height * 0.15);
path.lineTo(size.width * 0.35, size.height * 0.4);
path.lineTo(0.0, size.height * 0.4);
path.lineTo(size.width * 0.25, size.height * 0.55);
path.lineTo(size.width * 0.1, size.height * 0.8);
path.lineTo(size.width * 0.5, size.height * 0.65);
path.lineTo(size.width * 0.9, size.height * 0.8);
path.lineTo(size.width * 0.75, size.height * 0.55);
path.lineTo(size.width, size.height * 0.4);
path.lineTo(size.width * 0.65, size.height * 0.4);
path.lineTo(size.width * 0.5, size.height * 0.15);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}

Arc gradients in Flutter?

I have a Paint object and I'm trying to use it to paint an Arc Gradient using canvas.drawArc, but the only way to do this (at least according to my research) is to use a Shader, but to get a Shader from a Gradient object, you have to use Gradient.createShader(Rect rect), which takes a rectangle. My question is, is there any way to create a shader for an Arc and not a Rectangle? Here's what I have so far for reference:
Paint paint = new Paint()
..color = bgColor
..strokeCap = StrokeCap.round
..strokeWidth = 3.0
..style = PaintingStyle.stroke
..shader = new Gradient.radial(size.width / 2.0, size.height / 2.0, size.height / 3.0, Colors.transparent, timerColor, TileMode.mirror).createShader(/* I don't have a rect object */);
canvas.drawArc(..., paint);
The Rectangle that you need is actually a square into which the circle that you are drawing would fit. The arc is just a slice of pie from that circle swept through so many radians. Create this square using Rect.fromCircle, using the centre and radius. You then use this square when creating the gradient and drawing the arc.
Here's an example
import 'dart:math';
import 'package:flutter/material.dart';
class X1Painter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
// create a bounding square, based on the centre and radius of the arc
Rect rect = new Rect.fromCircle(
center: new Offset(165.0, 55.0),
radius: 180.0,
);
// a fancy rainbow gradient
final Gradient gradient = new RadialGradient(
colors: <Color>[
Colors.green.withOpacity(1.0),
Colors.green.withOpacity(0.3),
Colors.yellow.withOpacity(0.2),
Colors.red.withOpacity(0.1),
Colors.red.withOpacity(0.0),
],
stops: [
0.0,
0.5,
0.7,
0.9,
1.0,
],
);
// create the Shader from the gradient and the bounding square
final Paint paint = new Paint()..shader = gradient.createShader(rect);
// and draw an arc
canvas.drawArc(rect, pi / 4, pi * 3 / 4, true, paint);
}
#override
bool shouldRepaint(X1Painter oldDelegate) {
return true;
}
}
class X1Demo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: const Text('Arcs etc')),
body: new CustomPaint(
painter: new X1Painter(),
),
);
}
}
void main() {
runApp(
new MaterialApp(
theme: new ThemeData.dark(),
home: new X1Demo(),
),
);
}
My SweepGradient version.
Complete example:
import 'dart:math' as math;
import 'package:flutter/material.dart';
class GradientArcPainterDemo extends StatefulWidget {
const GradientArcPainterDemo({
Key key,
}) : super(key: key);
#override
GradientArcPainterDemoState createState() => GradientArcPainterDemoState();
}
class GradientArcPainterDemoState extends State<GradientArcPainterDemo> {
double _progress = 0.9;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(title: const Text('GradientArcPainter Demo')),
body: GestureDetector(
onTap: () {
setState(() {
_progress += 0.1;
});
},
child: Center(
child: SizedBox(
width: 200.0,
height: 200.0,
child: CustomPaint(
painter: GradientArcPainter(
progress: _progress,
startColor: Colors.blue,
endColor: Colors.red,
width: 8.0,
),
child: Center(child: Text('$_progress')),
),
),
),
),
);
}
}
class GradientArcPainter extends CustomPainter {
const GradientArcPainter({
#required this.progress,
#required this.startColor,
#required this.endColor,
#required this.width,
}) : assert(progress != null),
assert(startColor != null),
assert(endColor != null),
assert(width != null),
super();
final double progress;
final Color startColor;
final Color endColor;
final double width;
#override
void paint(Canvas canvas, Size size) {
final rect = new Rect.fromLTWH(0.0, 0.0, size.width, size.height);
final gradient = new SweepGradient(
startAngle: 3 * math.pi / 2,
endAngle: 7 * math.pi / 2,
tileMode: TileMode.repeated,
colors: [startColor, endColor],
);
final paint = new Paint()
..shader = gradient.createShader(rect)
..strokeCap = StrokeCap.butt // StrokeCap.round is not recommended.
..style = PaintingStyle.stroke
..strokeWidth = width;
final center = new Offset(size.width / 2, size.height / 2);
final radius = math.min(size.width / 2, size.height / 2) - (width / 2);
final startAngle = -math.pi / 2;
final sweepAngle = 2 * math.pi * progress;
canvas.drawArc(new Rect.fromCircle(center: center, radius: radius),
startAngle, sweepAngle, false, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
Result:

KineticJS simple animation not working on mobile devices

I was wondering if someone could help me find the solution to this.
I've made a very simple animation using KineticJS.
All works perfect on desktop, unfortunately not on mobile devices (iPhone, iPad, Android).
Result is a slowish performance but most importantly distorted shapes.
I suspect it has something to do with resolution or viewport but am not sure.
Preview is on www.bartvanhelsdingen.com
Any suggestions are highly appreciated.
Below is the code:
var shapes = {
sizes: [30, 40, 50, 55, 60, 80],
gradients: [
[0, '#fdfaee', 1, '#524f43'],
[0, '#a39175', 1, '#dbae5e'],
[0, '#b4c188', 1, '#f3de7c'],
[0, '#eaf2ef', 1, '#587c71'],
[0, '#a39175', 1, '#dbae5e'],
[0, '#61845c', 1, '#b4b092']
],
},
dims = {
width: 300,
height: 500
},
stage = new Kinetic.Stage({
container: 'animation',
width: dims.width,
height: dims.height,
x: 0,
y: 0,
draggable: false
});
function getRandomColor() {
return colors[getRandomFromInterval(0, colors.length - 1)];
}
function getRandomGradient() {
return gradients[getRandomFromInterval(0, gradients.length - 1)];
}
function getRandomFromInterval(from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
}
function getRandomSpeed() {
var speed = getRandomFromInterval(1, 1);
return getRandomFromInterval(0, 1) ? speed : speed * -1;
}
function createGroup(x, y, size, strokeWidth) {
return new Kinetic.Group({
x: x,
y: y,
width: size,
height: size,
opacity: 0,
draggable: false,
clipFunc: function (canvas) {
var context = canvas.getContext();
context.beginPath();
context.moveTo(0, 0);
context.lineTo(0, size);
context.lineTo(size, size);
context.lineTo(size, 0);
context.rect(strokeWidth, strokeWidth, size - strokeWidth * 2, size - strokeWidth * 2);
}
});
}
function createShape(size, gradient, strokeWidth, cornerRadius) {
return new Kinetic.Rect({
x: 0,
y: 0,
width: size,
height: size,
fillLinearGradientStartPoint: [size, 0],
fillLinearGradientEndPoint: [size, size],
fillLinearGradientColorStops: gradient,
opacity: 1,
lineJoin: 'bevel',
strokeWidth: 0,
cornerRadius: cornerRadius
});
}
var layer = new Kinetic.Layer(),
animAttribs = [];
for (var n = 0; n < 6; n++) {
var size = shapes.sizes[n],
strokeWidth = Math.ceil(size * 0.12),
cornerRadius = Math.ceil(size * 0.04),
gradient = shapes.gradients[n],
x = getRandomFromInterval(size, dims.width) - size,
y = getRandomFromInterval(size, dims.height) - size;
var group = createGroup(x, y, size, strokeWidth);
var shape = createShape(size, gradient, strokeWidth, cornerRadius);
animAttribs.push({
nextChange: getRandomFromInterval(1, 3) * 1000,
startTime: 1000,
duration: 0,
x: getRandomSpeed(),
y: getRandomSpeed()
});
group.add(shape);
layer.add(group);
}
stage.add(layer);
anim = new Kinetic.Animation(function (frame) {
var time = frame.time,
timeDiff = frame.timeDiff,
frameRate = frame.frameRate;
for (var n = 0; n < layer.getChildren().length; n++) {
var shape = layer.getChildren()[n],
opacity = shape.getOpacity() + 0.01 > 1 ? 1 : shape.getOpacity() + 0.01,
attribs = animAttribs[n],
x, y;
if (attribs.duration >= attribs.nextChange) {
attribs.x = getRandomSpeed();
attribs.y = getRandomSpeed();
attribs.nextChange = getRandomFromInterval(3, 5) * 1000;
attribs.duration = 0;
}
if (time >= attribs.startTime) {
if (shape.getX() + attribs.x + shape.getWidth() >= stage.getWidth() || shape.getX() + attribs.x - shape.getWidth() / 2 <= 0) {
attribs.x *= -1;
}
if (shape.getY() + attribs.y + shape.getHeight() >= stage.getHeight() || shape.getY() + attribs.y - shape.getHeight() / 2 <= 0) {
attribs.y *= -1;
}
x = shape.getX() + attribs.x;
y = shape.getY() + attribs.y;
attribs.duration += timeDiff;
shape.setOpacity(opacity);
shape.setX(x);
shape.setY(y);
}
}
}, layer);
anim.start();
the problem you are facing is, that clipFunc isn't currently working on devices with pixelratio != 1.
This problem came up in this post as well. Eric Rowell, the creator of KineticJS added this issue to his release scedule for late September.
So there is nothing wrong with your animations, they're working as expected, but you can't see them because of the distorted clipping region
To resolve this issue "unofficially" you can simply replace the last line of the _clip function in your kinetic.js with the following: context.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0); (credits for that go to Mark Smits)

Resources