-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP: write customer shaders for circular gauges
Animating the Shape-based circular gauges is too expensive. So reimplement it as a single-pass shader.
- Loading branch information
Showing
4 changed files
with
236 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import QtQuick | ||
import Victron.VenusOS | ||
|
||
Item { | ||
id: gauge | ||
|
||
property real value // from 0.0 to 1.0 | ||
property color remainderColor: "gray" | ||
property color progressColor: "blue" | ||
property color shineColor: Qt.rgba(1.0, 1.0, 1.0, 0.85) | ||
property real startAngle: 0 | ||
property real endAngle: 270 | ||
property real progressAngle: startAngle + ((endAngle - startAngle) * Math.min(Math.max(gauge.value, 0.0), 100.0) / 100.0) | ||
property real strokeWidth: width/25 | ||
property real radius: width/2 - 2*strokeWidth | ||
property real smoothing: 1 // how many pixels of antialiasing to apply. | ||
property bool clockwise: true | ||
property bool shineAnimationEnabled: true | ||
property bool animationEnabled: true | ||
|
||
onProgressAngleChanged: { | ||
if (!progressAnimator.running) { | ||
progressAnimator.from = shader.progressAngle | ||
progressAnimator.to = (gauge.progressAngle * (Math.PI/180)) / (2*Math.PI) | ||
progressAnimator.start() | ||
} | ||
} | ||
|
||
Timer { | ||
running: gauge.shineAnimationEnabled | ||
interval: 4040 | ||
repeat: true | ||
onTriggered: { | ||
shineAnimator.from = 0.0 | ||
shineAnimator.to = (gauge.endAngle * (Math.PI/180)) / (2*Math.PI) | ||
shineAnimator.start() | ||
} | ||
} | ||
|
||
ShaderEffect { | ||
id: shader | ||
anchors.fill: parent | ||
fragmentShader: "shaders/circulargauge.frag.qsb" | ||
|
||
property color remainderColor: gauge.remainderColor | ||
property color progressColor: gauge.progressColor | ||
property color shineColor: gauge.shineColor | ||
// transform angles to radians and then normalize | ||
property real startAngle: (gauge.startAngle * (Math.PI/180)) / (2*Math.PI) | ||
property real endAngle: (gauge.endAngle * (Math.PI/180)) / (2*Math.PI) | ||
property real progressAngle: -1.0 | ||
property real shineAngle: -1.0 | ||
// transform radii to uv coords | ||
property real innerRadius: (gauge.radius - (gauge.strokeWidth/2)) / (gauge.height/2) | ||
property real radius: gauge.radius / (gauge.height/2) | ||
property real outerRadius: (gauge.radius + (gauge.strokeWidth/2)) / (gauge.height/2) | ||
// transform smoothing pixels to uv distance | ||
property real smoothing: gauge.smoothing / height | ||
property real clockwise: gauge.clockwise ? 1.0 : 0.0 | ||
|
||
UniformAnimator { | ||
id: progressAnimator | ||
target: shader | ||
uniform: "progressAngle" | ||
duration: 400 | ||
easing.type: Easing.InOutQuad | ||
} | ||
|
||
UniformAnimator { | ||
id: shineAnimator | ||
target: shader | ||
uniform: "shineAngle" | ||
duration: 1200 | ||
easing.type: Easing.InOutQuad | ||
onRunningChanged: if (!running) shader.shineAngle = -1.0 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
#version 440 | ||
#define CONSTANT_PI 3.141592653589793 | ||
#define CONSTANT_TAU 6.283185307179586 | ||
layout(location = 0) in vec2 coord; | ||
layout(location = 0) out vec4 fragColor; | ||
|
||
layout(std140, binding = 0) uniform buf { | ||
mat4 qt_Matrix; | ||
float qt_Opacity; | ||
|
||
vec4 remainderColor; | ||
vec4 progressColor; | ||
vec4 shineColor; | ||
float startAngle; | ||
float endAngle; | ||
float progressAngle; | ||
float shineAngle; | ||
float innerRadius; | ||
float radius; | ||
float outerRadius; | ||
float smoothing; | ||
float clockwise; | ||
} ubuf; | ||
|
||
// atan2 which isn't undefined at x == 0 | ||
float my_atan2(float y, float x) { | ||
return x == 0.0 ? sign(y) * CONSTANT_PI : atan(y, x); | ||
} | ||
|
||
// angle expressed in normal radians, but with orientation. | ||
float denormalizedAngle(float a, float clockwise) { | ||
float angle = clockwise*(1.0 - a) + (1.0 - clockwise)*a; | ||
return (angle * CONSTANT_TAU) - CONSTANT_PI; | ||
} | ||
|
||
// angle must be in radians [-PI, PI]. | ||
// return value is normalized to [0.0, 1.0], | ||
// inverted if necessary to adjust for clockwise vs anticlockwise. | ||
float normalizedAngle(float angle, float clockwise) { | ||
float a = (angle + CONSTANT_PI) / CONSTANT_TAU; | ||
a = clockwise*(1.0 - a) + (1.0 - clockwise)*a; | ||
return a; | ||
} | ||
|
||
// converts from cartesian to polar coordinates (angle, radius). | ||
// angle is normalized to [0.0, 1.0]. | ||
vec2 toPolar(vec2 point, float clockwise) { | ||
// note that we have flipped y/x to x/y to get angle from y axis (i.e. vertical). | ||
return vec2(normalizedAngle(atan(point.x, point.y), clockwise), length(point)); | ||
} | ||
|
||
// converts from polar to cartesian coordinates (x, y). | ||
vec2 toCartesian(vec2 polar, float clockwise) { | ||
float angle = denormalizedAngle(polar.x, clockwise); | ||
// note that we have flipped cos/sin to sin/cos since angle is from y axis (i.e. vertical). | ||
return vec2(polar.y * sin(angle), polar.y * cos(angle)); | ||
} | ||
|
||
// if you move the specified distance along the arc, how much angle has been traversed? | ||
// the error grows larger and larger the bigger distanceMoved is (since we assume straight-line movement)... | ||
float angleDelta(float startAngle, float endAngle, float radius, float distanceMoved) { | ||
float arcLength = (endAngle - startAngle) * CONSTANT_TAU * radius; | ||
return distanceMoved / arcLength; | ||
} | ||
|
||
void main() { | ||
vec2 uv = coord * 2.0 - 1.0; | ||
float uvDistance = length(uv); // distance from the center | ||
|
||
// note: we want angle from y axis rather than x axis, so flip args of atan2. | ||
float uvAngle = normalizedAngle(my_atan2(uv.x, uv.y), ubuf.clockwise); | ||
float withinGaugeAngle = (uvAngle < ubuf.startAngle || uvAngle > ubuf.endAngle) ? 0.0 : 1.0; | ||
|
||
// calculate the rounded caps. | ||
float capRadius = ubuf.outerRadius - ubuf.radius; | ||
float capAngleDelta = angleDelta(ubuf.startAngle, ubuf.endAngle, ubuf.radius, capRadius); | ||
|
||
// the startAngle cap. | ||
vec2 startCapCenter = toCartesian(vec2(ubuf.startAngle, ubuf.radius), ubuf.clockwise); | ||
float startCapAlpha = 1.0 - smoothstep(capRadius, capRadius + ubuf.smoothing, distance(uv, startCapCenter)); | ||
|
||
// the endAngle cap. | ||
vec2 endCapCenter = toCartesian(vec2(ubuf.endAngle, ubuf.radius), ubuf.clockwise); | ||
float endCapAlpha = 1.0 - smoothstep(capRadius, capRadius + ubuf.smoothing, distance(uv, endCapCenter)); | ||
|
||
// the progress cap. | ||
vec2 progressCapCenter = toCartesian(vec2(ubuf.progressAngle, ubuf.radius), ubuf.clockwise); | ||
float progressCapMix = 1.0 - smoothstep(capRadius, capRadius + ubuf.smoothing, distance(uv, progressCapCenter)); | ||
|
||
// calculate shine animation. we have to adjust the angles because of the startCap. | ||
float adjustedAngle = (uvAngle + 0.08); | ||
adjustedAngle = adjustedAngle > 1.0 ? adjustedAngle - 1.0 : adjustedAngle; | ||
float adjustedShineAngle = ubuf.shineAngle + 0.08; | ||
float shineMix = smoothstep(adjustedShineAngle - 0.08, adjustedShineAngle, adjustedAngle) | ||
* (1.0 - smoothstep(adjustedShineAngle, adjustedShineAngle + 0.02, adjustedAngle)); | ||
|
||
// antialiasing. | ||
float gaugeAngleAlpha = max(endCapAlpha, max(startCapAlpha, withinGaugeAngle)); | ||
float gaugeStrokeAlpha = smoothstep(ubuf.innerRadius - ubuf.smoothing, ubuf.innerRadius, uvDistance) | ||
* (1.0 - smoothstep(ubuf.outerRadius, ubuf.outerRadius + ubuf.smoothing, uvDistance)); | ||
|
||
float isProgressBar = ((uvAngle > (ubuf.endAngle + capAngleDelta)) || uvAngle <= ubuf.progressAngle) ? 1.0 : 0.0; | ||
float progressColorMix = max(isProgressBar, progressCapMix); | ||
|
||
// Qt expects pre-multiplied output, so don't just set w-channel. | ||
fragColor = mix(ubuf.remainderColor, mix(ubuf.progressColor, ubuf.shineColor, shineMix), progressColorMix) * gaugeAngleAlpha * gaugeStrokeAlpha * ubuf.qt_Opacity; | ||
} |