mirror of https://github.com/mosra/magnum.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
7.6 KiB
206 lines
7.6 KiB
/* |
|
This file is part of Magnum. |
|
|
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
|
2020, 2021 Vladimír Vondruš <mosra@centrum.cz> |
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a |
|
copy of this software and associated documentation files (the "Software"), |
|
to deal in the Software without restriction, including without limitation |
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|
and/or sell copies of the Software, and to permit persons to whom the |
|
Software is furnished to do so, subject to the following conditions: |
|
|
|
The above copyright notice and this permission notice shall be included |
|
in all copies or substantial portions of the Software. |
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
DEALINGS IN THE SOFTWARE. |
|
*/ |
|
|
|
#ifndef NEW_GLSL |
|
#define in varying |
|
#define value gl_FragColor.x |
|
#define texture texture2D |
|
#endif |
|
|
|
#ifndef RUNTIME_CONST |
|
#define const |
|
#endif |
|
|
|
#if (defined(GL_ES) && __VERSION__ >= 300) || (!defined(GL_ES) && __VERSION__ >= 150) |
|
#define TEXELFETCH_USABLE |
|
#endif |
|
|
|
#ifdef EXPLICIT_UNIFORM_LOCATION |
|
layout(location = 0) |
|
#endif |
|
uniform mediump vec2 scaling; |
|
|
|
#ifdef EXPLICIT_BINDING |
|
layout(binding = 7) |
|
#endif |
|
uniform lowp sampler2D textureData; |
|
|
|
#ifdef TEXELFETCH_USABLE |
|
#ifndef GL_ES |
|
layout(pixel_center_integer) in mediump vec4 gl_FragCoord; |
|
#endif |
|
#else |
|
#ifdef EXPLICIT_UNIFORM_LOCATION |
|
layout(location = 1) |
|
#endif |
|
uniform mediump vec2 imageSizeInverted; |
|
#endif |
|
|
|
#ifdef NEW_GLSL |
|
out lowp float value; |
|
#endif |
|
|
|
#ifdef TEXELFETCH_USABLE |
|
bool hasValue(const mediump ivec2 position, const mediump ivec2 offset) { |
|
return texelFetch(textureData, position + offset, 0).r > 0.5; |
|
} |
|
#else |
|
bool hasValue(const mediump vec2 position, const mediump ivec2 offset) { |
|
return texture(textureData, position + (vec2(offset) + vec2(0.5))*imageSizeInverted).r > 0.5; |
|
} |
|
#endif |
|
|
|
mediump int findMinDistanceSquared(const mediump |
|
#ifdef TEXELFETCH_USABLE |
|
ivec2 |
|
#else |
|
vec2 |
|
#endif |
|
position, const bool isInside) |
|
{ |
|
/* Initialize minimal distance to a value just outside the radius */ |
|
mediump int minDistanceSquared = (RADIUS+1)*(RADIUS+1); |
|
|
|
/* Go in cocentric squares around the point */ |
|
for(int i = 1; i <= RADIUS; ++i) { |
|
/* First check the nearest points, since that's only four combinations. |
|
If any of the four values is opposite of what is on `position`, we |
|
found the nearest value. If the distance is not less than what's |
|
found already, we don't even check the texture. |
|
|
|
i = 1 i = 2 i = 3 |
|
|
|
0 |
|
0 |
|
0 |
|
1o3 1 o 3 1 o 3 |
|
2 |
|
2 |
|
2 |
|
|
|
Since everything else in the cocentric square and all others will be |
|
further away, we can stop if we found something. */ |
|
const mediump int centerDistanceSquared = i*i; |
|
if(centerDistanceSquared >= minDistanceSquared) |
|
return minDistanceSquared; |
|
if(hasValue(position, ivec2(0, i)) != isInside || |
|
hasValue(position, ivec2(-i, 0)) != isInside || |
|
hasValue(position, ivec2(0, -i)) != isInside || |
|
hasValue(position, ivec2(i, 0)) != isInside) { |
|
return centerDistanceSquared; |
|
} |
|
|
|
/* Now check for points further away, except for the corner points. |
|
Every iteration checks all eight rotations/reflections at the same |
|
distance. Again, if the distance is not less than what's found |
|
already, we don't even check the texture -- but can't return, since |
|
next iteration can still have closer values. |
|
|
|
i = 1 i = 2 i = 3 |
|
(none) |
|
91 08 |
|
1 0 a f |
|
2 7 2 7 |
|
o o o |
|
3 6 3 6 |
|
4 5 b e |
|
c4 5d |
|
|
|
Once we find something, it's the closest value possible in this |
|
cycle, so we stop the cycle. But next iterations can still have |
|
values that are closer, so can't return. */ |
|
for(int j = 1; j < RADIUS; ++j) { |
|
/* Don't go further than current radius - 1 (i.e., excluding the |
|
corner). The loop needs to be compile-time bound otherwise some |
|
drivers crash on it, so we're breaking inside instead of having |
|
this directly in the loop condition. */ |
|
if(j >= i) break; |
|
|
|
const mediump int sideDistanceSquared = i*i + j*j; |
|
if(sideDistanceSquared >= minDistanceSquared) |
|
break; |
|
if(hasValue(position, ivec2( j, i)) != isInside || |
|
hasValue(position, ivec2(-j, i)) != isInside || |
|
hasValue(position, ivec2(-i, j)) != isInside || |
|
hasValue(position, ivec2(-i, -j)) != isInside || |
|
hasValue(position, ivec2(-j, -i)) != isInside || |
|
hasValue(position, ivec2( j, -i)) != isInside || |
|
hasValue(position, ivec2( i, -j)) != isInside || |
|
hasValue(position, ivec2( i, j)) != isInside) { |
|
minDistanceSquared = sideDistanceSquared; |
|
break; |
|
} |
|
} |
|
|
|
/* Finally, check for the corners, which is again just four cases: |
|
|
|
i = 1 i = 2 i = 3 |
|
|
|
1 0 |
|
1 0 |
|
1 0 |
|
o o o |
|
2 3 |
|
2 3 |
|
2 3 |
|
|
|
If we find something, it's most probably not the nearest distance, |
|
since the following iterations can be much closer. */ |
|
const mediump int cornerDistanceSquared = 2*i*i; |
|
if(cornerDistanceSquared >= minDistanceSquared) |
|
continue; |
|
if(hasValue(position, ivec2( i, i)) != isInside || |
|
hasValue(position, ivec2(-i, i)) != isInside || |
|
hasValue(position, ivec2(-i, -i)) != isInside || |
|
hasValue(position, ivec2( i, -i)) != isInside) { |
|
minDistanceSquared = cornerDistanceSquared; |
|
} |
|
} |
|
|
|
return minDistanceSquared; |
|
} |
|
|
|
void main() { |
|
#ifdef TEXELFETCH_USABLE |
|
#ifndef GL_ES |
|
const mediump ivec2 position = ivec2(gl_FragCoord.xy*scaling); |
|
#else |
|
const mediump ivec2 position = ivec2((gl_FragCoord.xy - vec2(0.5))*scaling); |
|
#endif |
|
#else |
|
const mediump vec2 position = (gl_FragCoord.xy - vec2(0.5))*scaling*imageSizeInverted; |
|
#endif |
|
|
|
/* If pixel at the position is inside (its value > 0.5), we are looking for |
|
nearest pixel outside and the value will be positive (or > 0.5 after |
|
normalization). If it is outside (its value < 0), we are looking for |
|
nearest pixel inside and the value will be negative (or < 0.5). */ |
|
const bool isInside = hasValue(position, ivec2(0, 0)); |
|
const mediump float minDistance = sqrt(float(findMinDistanceSquared(position, isInside))); |
|
|
|
/* Final signed distance, normalized from [-radius-1, radius+1] to [0, 1] */ |
|
const highp float halfSign = isInside ? 0.5 : -0.5; |
|
value = halfSign*minDistance/float(RADIUS + 1) + 0.5; |
|
}
|
|
|