diff --git a/src/drawables/drawable-descriptor.ts b/src/drawables/drawable-descriptor.ts index 72a8098..8c529a5 100644 --- a/src/drawables/drawable-descriptor.ts +++ b/src/drawables/drawable-descriptor.ts @@ -5,6 +5,7 @@ export interface DrawableDescriptor { uniformCountMacroName: string; sdf?: { shader: string; + isInverted?: boolean; distanceFunctionName: string; }; shaderCombinationSteps: Array; diff --git a/src/drawables/shapes/inverted-tunnel.ts b/src/drawables/shapes/inverted-tunnel.ts index 9f65925..bded6ba 100644 --- a/src/drawables/shapes/inverted-tunnel.ts +++ b/src/drawables/shapes/inverted-tunnel.ts @@ -14,8 +14,9 @@ export class InvertedTunnel extends Drawable { uniform float toRadii[INVERTED_TUNNEL_COUNT]; float invertedTunnelMinDistance(vec2 target, out float colorIndex) { - float minDistance = 1000.0; + colorIndex = 3.0; + float minDistance = 1000.0; for (int i = 0; i < INVERTED_TUNNEL_COUNT; i++) { vec2 targetFromDelta = target - froms[i]; @@ -37,6 +38,7 @@ export class InvertedTunnel extends Drawable { return -minDistance; } `, + isInverted: true, distanceFunctionName: 'invertedTunnelMinDistance', }, propertyUniformMapping: { diff --git a/src/graphics/graphics-library/frame-buffer/frame-buffer.ts b/src/graphics/graphics-library/frame-buffer/frame-buffer.ts index eeafb13..f6dbb3d 100644 --- a/src/graphics/graphics-library/frame-buffer/frame-buffer.ts +++ b/src/graphics/graphics-library/frame-buffer/frame-buffer.ts @@ -21,8 +21,6 @@ export abstract class FrameBuffer { }); this.gl.viewport(0, 0, this.size.x, this.size.y); - this.gl.clearColor(0, 0, 0, 0); - this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); } public destroy(): void { diff --git a/src/graphics/graphics-library/helper/load-uniform.ts b/src/graphics/graphics-library/helper/load-uniform.ts index fa29eb1..83425a7 100644 --- a/src/graphics/graphics-library/helper/load-uniform.ts +++ b/src/graphics/graphics-library/helper/load-uniform.ts @@ -1,4 +1,4 @@ -import { mat3, ReadonlyVec3, vec2, vec3 } from 'gl-matrix'; +import { mat3, ReadonlyVec3, ReadonlyVec4, vec2, vec3, vec4 } from 'gl-matrix'; import { UniversalRenderingContext } from '../universal-rendering-context'; const loaderMat3 = mat3.create(); @@ -71,6 +71,30 @@ export const loadUniform = ( } ); + converters.set( + WebGLRenderingContext.FLOAT_VEC4, + (gl, v: ReadonlyVec4 | Array, l) => { + if (v.length == 0) { + return; + } + + if (v[0] instanceof Array || v[0] instanceof Float32Array) { + const result = new Float32Array(v.length * 4); + + for (let i = 0; i < v.length; i++) { + result[3 * i] = (v[i] as Array)[0]; + result[3 * i + 1] = (v[i] as Array)[1]; + result[3 * i + 2] = (v[i] as Array)[2]; + result[3 * i + 3] = (v[i] as Array)[3]; + } + + gl.uniform4fv(l, result); + } else { + gl.uniform4fv(l, v as vec4); + } + } + ); + converters.set(WebGLRenderingContext.FLOAT_MAT3, (gl, v, l) => { if (gl.isWebGL2) { gl.uniformMatrix3fv(l, true, mat3.fromMat2d(loaderMat3, v)); diff --git a/src/graphics/graphics-library/palette-texture.ts b/src/graphics/graphics-library/palette-texture.ts index 23eec21..00f540a 100644 --- a/src/graphics/graphics-library/palette-texture.ts +++ b/src/graphics/graphics-library/palette-texture.ts @@ -38,12 +38,10 @@ export class PaletteTexture { imageData.data[4 * i + 0] = c[0] * 255; imageData.data[4 * i + 1] = c[1] * 255; imageData.data[4 * i + 2] = c[2] * 255; - imageData.data[4 * i + 3] = c.length == 4 ? c[3] : 255; + imageData.data[4 * i + 3] = c.length == 4 ? c[3] * 255 : 255; }); ctx.putImageData(imageData, 0, 0); - document.body.appendChild(canvas); - this.setImage(canvas); } diff --git a/src/graphics/graphics-library/program/uniform-array-autoscaling-program.ts b/src/graphics/graphics-library/program/uniform-array-autoscaling-program.ts index f7e6e93..28b28d0 100644 --- a/src/graphics/graphics-library/program/uniform-array-autoscaling-program.ts +++ b/src/graphics/graphics-library/program/uniform-array-autoscaling-program.ts @@ -144,10 +144,15 @@ export class UniformArrayAutoScalingProgram implements IProgram { descriptors[i].sdf!.distanceFunctionName }(position, objectColor); - color = mix(objectColor / {paletteSize}, color, step( - distanceNdcPixelSize + SURFACE_OFFSET, - objectMinDistance - )); + color = mix( + objectColor / {paletteSize}, + color, + ${ + descriptors[i].sdf?.isInverted + ? `step(-distanceNdcPixelSize, -objectMinDistance)` + : `step(distanceNdcPixelSize, objectMinDistance)` + } + ); minDistance = min(minDistance, objectMinDistance); `; diff --git a/src/graphics/rendering/settings/default-startup-settings.ts b/src/graphics/rendering/settings/default-startup-settings.ts new file mode 100644 index 0000000..1a176c5 --- /dev/null +++ b/src/graphics/rendering/settings/default-startup-settings.ts @@ -0,0 +1,7 @@ +import { StartupSettings } from './startup-settings'; + +export const defaultStartupSettings: StartupSettings = { + shadowTraceCount: 16, + paletteSize: 256, + ignoreWebGL2: false, +}; diff --git a/src/graphics/rendering/settings/runtime-settings.ts b/src/graphics/rendering/settings/runtime-settings.ts index 68d1dfc..83ed3c9 100644 --- a/src/graphics/rendering/settings/runtime-settings.ts +++ b/src/graphics/rendering/settings/runtime-settings.ts @@ -1,11 +1,11 @@ -import { vec3 } from 'gl-matrix'; +import { vec3, vec4 } from 'gl-matrix'; export interface RuntimeSettings { enableHighDpiRendering: boolean; tileMultiplier: number; isWorldInverted: boolean; - shadowLength: number; lightCutoffDistance: number; - colorPalette: Array; + backgroundColor: vec3 | vec4; + colorPalette: Array; ambientLight: vec3; } diff --git a/src/graphics/rendering/settings/startup-settings.ts b/src/graphics/rendering/settings/startup-settings.ts index 03cd6ce..2a81f14 100644 --- a/src/graphics/rendering/settings/startup-settings.ts +++ b/src/graphics/rendering/settings/startup-settings.ts @@ -1,4 +1,5 @@ export interface StartupSettings { shadowTraceCount: number; paletteSize: number; + ignoreWebGL2: boolean; } diff --git a/src/graphics/rendering/shaders/distance-fs-100.glsl b/src/graphics/rendering/shaders/distance-fs-100.glsl index ae51453..eba9f1e 100644 --- a/src/graphics/rendering/shaders/distance-fs-100.glsl +++ b/src/graphics/rendering/shaders/distance-fs-100.glsl @@ -2,8 +2,6 @@ precision lowp float; -#define SURFACE_OFFSET 0.001 - uniform float maxMinDistance; uniform float distanceNdcPixelSize; varying vec2 position; @@ -24,6 +22,5 @@ void main() { minDistance = maxMinDistance; #endif - // minDistance / 2.0: NDC to UV scale - gl_FragColor = vec4(minDistance / 2.0, color, 0.0, 0.0); + gl_FragColor = vec4(minDistance * 8.0, color, 0.0, 1.0); } diff --git a/src/graphics/rendering/shaders/distance-fs.glsl b/src/graphics/rendering/shaders/distance-fs.glsl index 59234e7..14e122e 100644 --- a/src/graphics/rendering/shaders/distance-fs.glsl +++ b/src/graphics/rendering/shaders/distance-fs.glsl @@ -2,8 +2,6 @@ precision lowp float; -#define SURFACE_OFFSET 0.001 - uniform float maxMinDistance; uniform float distanceNdcPixelSize; in vec2 position; @@ -26,6 +24,5 @@ void main() { minDistance = maxMinDistance; #endif - // minDistance / 2.0: NDC to UV scale - fragmentColor = vec2(minDistance / 2.0, color); + fragmentColor = vec2(minDistance, color); } diff --git a/src/graphics/rendering/shaders/shading-fs-100.glsl b/src/graphics/rendering/shaders/shading-fs-100.glsl index 4552c0b..a715668 100644 --- a/src/graphics/rendering/shaders/shading-fs-100.glsl +++ b/src/graphics/rendering/shaders/shading-fs-100.glsl @@ -2,47 +2,38 @@ precision lowp float; -#define INFINITY 1000.0 #define INTENSITY_INSIDE_RATIO 0.5 #define SHADOW_TRACE_COUNT {shadowTraceCount} {macroDefinitions} -uniform vec2 squareToAspectRatioTimes2; uniform float shadingNdcPixelSize; -uniform float shadowLength; +uniform vec2 squareToAspectRatioTimes2; +uniform vec3 ambientLight; +uniform vec4 backgroundColor; + uniform sampler2D distanceTexture; uniform sampler2D palette; varying vec2 position; varying vec2 uvCoordinates; -uniform vec3 ambientLight; - -float getDistance(in vec2 target, out vec3 color) { +float getDistance(in vec2 target, out vec4 color) { vec4 values = texture2D(distanceTexture, target); - color = texture2D(palette, vec2(values[1], 0.0)).rgb; - return values[0]; + color = texture2D(palette, vec2(values[1], 0.0)); + return values[0] / 8.0; } float getDistance(in vec2 target) { - return texture2D(distanceTexture, target)[0]; + return texture2D(distanceTexture, target)[0] / 8.0; } float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 direction) { float rayLength = startingDistance; - for (int j = 0; j < SHADOW_TRACE_COUNT; j++) { - rayLength += getDistance(uvCoordinates + direction * rayLength); + rayLength += max(0.0, getDistance(uvCoordinates + direction * rayLength)); } - - return min( - 1.0, - ( - step(lightCenterDistance, rayLength) + - rayLength / (shadowLength * shadingNdcPixelSize) - ) * 2.0 - ); + return min(1.0, pow(rayLength / lightCenterDistance, 0.3)); } #ifdef CIRCLE_LIGHT_COUNT @@ -50,7 +41,6 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 uniform vec2 circleLightCenters[CIRCLE_LIGHT_COUNT]; uniform float circleLightIntensities[CIRCLE_LIGHT_COUNT]; uniform vec3 circleLightColors[CIRCLE_LIGHT_COUNT]; - varying vec2 circleLightDirections[CIRCLE_LIGHT_COUNT]; #endif #endif @@ -61,7 +51,6 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 uniform float flashlightIntensities[FLASHLIGHT_COUNT]; uniform vec3 flashlightColors[FLASHLIGHT_COUNT]; uniform vec2 flashlightDirections[FLASHLIGHT_COUNT]; - varying vec2 flashlightActualDirections[FLASHLIGHT_COUNT]; float intensityInDirection(vec2 lightDirection, vec2 targetDirection) { @@ -71,73 +60,70 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 #endif void main() { + vec4 rgbaColorAtPosition; + float startingDistance = getDistance(uvCoordinates, rgbaColorAtPosition); + + vec3 colorAtPosition = rgbaColorAtPosition.rgb; + vec3 lighting = ambientLight; - - vec3 colorAtPosition; - float startingDistance = getDistance(uvCoordinates, colorAtPosition); - - if (startingDistance <= 0.0) { - #ifdef CIRCLE_LIGHT_COUNT - #if CIRCLE_LIGHT_COUNT > 0 - for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { - float lightCenterDistance = distance(circleLightCenters[i], position); - lighting += circleLightColors[i] / pow( - lightCenterDistance / (circleLightIntensities[i] * INTENSITY_INSIDE_RATIO) + 1.0, 2.0 - ); - } - #endif - #endif + vec3 lightingInside = lighting * INTENSITY_INSIDE_RATIO; - #ifdef FLASHLIGHT_COUNT - #if FLASHLIGHT_COUNT > 0 - for (int i = 0; i < FLASHLIGHT_COUNT; i++) { - float lightCenterDistance = distance(flashlightCenters[i], position); - lighting += intensityInDirection( - flashlightDirections[i], - normalize(flashlightActualDirections[i]) - ) * flashlightColors[i] / pow( - lightCenterDistance / (flashlightIntensities[i] * INTENSITY_INSIDE_RATIO) + 1.0, 2.0 - ); - } - #endif - #endif - } else { - colorAtPosition = vec3(1.0); + #ifdef CIRCLE_LIGHT_COUNT + #if CIRCLE_LIGHT_COUNT > 0 + for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { + float lightCenterDistance = distance(circleLightCenters[i], position); - #ifdef CIRCLE_LIGHT_COUNT - #if CIRCLE_LIGHT_COUNT > 0 - for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { - vec2 direction = normalize(circleLightDirections[i]) / squareToAspectRatioTimes2; + vec3 lightColorAtPosition = circleLightColors[i] / pow( + lightCenterDistance / circleLightIntensities[i] + 1.0, 2.0 + ); - float lightCenterDistance = distance(circleLightCenters[i], position); - lighting += circleLightColors[i] / pow( - lightCenterDistance / circleLightIntensities[i] + 1.0, 2.0 - ) * shadowTransparency(startingDistance, lightCenterDistance, direction); - } - #endif - #endif + vec2 direction = normalize(circleLightDirections[i]) / squareToAspectRatioTimes2; + lighting += lightColorAtPosition * shadowTransparency( + startingDistance, + lightCenterDistance, + direction + ); - #ifdef FLASHLIGHT_COUNT - #if FLASHLIGHT_COUNT > 0 - for (int i = 0; i < FLASHLIGHT_COUNT; i++) { - vec2 originalDirection = normalize(flashlightDirections[i]); - vec2 direction = originalDirection / squareToAspectRatioTimes2; + lightingInside += lightColorAtPosition; + } + #endif + #endif - float lightCenterDistance = distance(flashlightCenters[i], position); - vec3 lightColorAtPosition = intensityInDirection(flashlightDirections[i], positionDirection) * - flashlightColors[i] / pow( + #ifdef FLASHLIGHT_COUNT + #if FLASHLIGHT_COUNT > 0 + for (int i = 0; i < FLASHLIGHT_COUNT; i++) { + vec2 originalDirection = normalize(flashlightDirections[i]); + + vec3 lightColorAtPosition = colorInPosition(i, originalDirection, lightCenterDistance); + float lightCenterDistance = intensityInDirection(flashlightDirections[i], positionDirection) + * flashlightColors[lightIndex] / pow( lightCenterDistance / flashlightIntensities[i] + 1.0, 2.0 ); - - if (length(lightColorAtPosition) < 0.01) { - continue; - } - lighting += lightColorAtPosition * shadowTransparency(startingDistance, lightCenterDistance, direction); + vec2 direction = originalDirection / squareToAspectRatioTimes2 + + if (length(lightColorAtPosition) < 0.0) { + continue; } - #endif - #endif + + lighting += lightColorAtPosition * shadowTransparency( + startingDistance, + lightCenterDistance, + direction + ); + + lightingInside += lightColorAtPosition; } - - gl_FragColor = vec4(colorAtPosition * lighting, 1.0); + #endif + #endif + + vec3 outsideColor = backgroundColor.rgb * lighting; + vec3 insideColor = colorAtPosition * lightingInside; + + float edge = clamp(startingDistance / shadingNdcPixelSize, 0.0, 1.0); + vec3 antialiasedColor = mix(insideColor, outsideColor, edge); + gl_FragColor = vec4( + antialiasedColor, + mix(rgbaColorAtPosition.a, backgroundColor.a, step(0.0, startingDistance)) + ); } diff --git a/src/graphics/rendering/shaders/shading-fs.glsl b/src/graphics/rendering/shaders/shading-fs.glsl index b90185a..9472300 100644 --- a/src/graphics/rendering/shaders/shading-fs.glsl +++ b/src/graphics/rendering/shaders/shading-fs.glsl @@ -2,26 +2,25 @@ precision lowp float; -#define INFINITY 1000.0 #define INTENSITY_INSIDE_RATIO 0.5 #define SHADOW_TRACE_COUNT {shadowTraceCount} {macroDefinitions} -uniform vec2 squareToAspectRatioTimes2; uniform float shadingNdcPixelSize; -uniform float shadowLength; +uniform vec2 squareToAspectRatioTimes2; +uniform vec3 ambientLight; +uniform vec4 backgroundColor; + uniform sampler2D distanceTexture; uniform sampler2D palette; in vec2 position; in vec2 uvCoordinates; -uniform vec3 ambientLight; - -float getDistance(in vec2 target, out vec3 color) { +float getDistance(in vec2 target, out vec4 color) { vec4 values = texture(distanceTexture, target); - color = texture(palette, vec2(values[1], 0.0)).rgb; + color = texture(palette, vec2(values[1], 0.0)); return values[0]; } @@ -31,18 +30,10 @@ float getDistance(in vec2 target) { float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 direction) { float rayLength = startingDistance; - for (int j = 0; j < SHADOW_TRACE_COUNT; j++) { rayLength += max(0.0, getDistance(uvCoordinates + direction * rayLength)); } - - return min( - 1.0, - ( - step(lightCenterDistance, rayLength) + - rayLength / (shadowLength * shadingNdcPixelSize) - ) * 2.0 - ); + return min(1.0, pow(rayLength / lightCenterDistance, 0.3)); } #ifdef CIRCLE_LIGHT_COUNT @@ -51,21 +42,14 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 uniform float circleLightIntensities[CIRCLE_LIGHT_COUNT]; uniform vec3 circleLightColors[CIRCLE_LIGHT_COUNT]; - in vec2[CIRCLE_LIGHT_COUNT] circleLightDirections; + in vec2 circleLightDirections[CIRCLE_LIGHT_COUNT]; vec3 colorInPosition(int lightIndex, out float lightCenterDistance) { - lightCenterDistance = max(0.0, distance(circleLightCenters[lightIndex], position)); + lightCenterDistance = distance(circleLightCenters[lightIndex], position); return circleLightColors[lightIndex] / pow( lightCenterDistance / circleLightIntensities[lightIndex] + 1.0, 2.0 ); } - - vec3 colorInPositionInside(int lightIndex) { - float lightCenterDistance = distance(circleLightCenters[lightIndex], position); - return circleLightColors[lightIndex] / pow( - lightCenterDistance / (circleLightIntensities[lightIndex] * INTENSITY_INSIDE_RATIO) + 1.0, 2.0 - ); - } #endif #endif @@ -76,7 +60,7 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 uniform vec3 flashlightColors[FLASHLIGHT_COUNT]; uniform vec2 flashlightDirections[FLASHLIGHT_COUNT]; - in vec2[FLASHLIGHT_COUNT] flashlightActualDirections; + in vec2 flashlightActualDirections[FLASHLIGHT_COUNT]; float intensityInDirection(vec2 lightDirection, vec2 targetDirection) { return smoothstep(0.0, 1.0, 10.0 * max(0.0, dot(targetDirection, lightDirection) - 0.9)); @@ -89,74 +73,69 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 lightCenterDistance / flashlightIntensities[lightIndex] + 1.0, 2.0 ); } - - vec3 colorInPositionInside(int lightIndex, vec2 positionDirection) { - float lightCenterDistance = distance(flashlightCenters[lightIndex], position); - return intensityInDirection(flashlightDirections[lightIndex], positionDirection) * - flashlightColors[lightIndex] / pow( - lightCenterDistance / (flashlightIntensities[lightIndex] * INTENSITY_INSIDE_RATIO) + 1.0, 2.0 - )); - } #endif #endif out vec4 fragmentColor; void main() { + vec4 rgbaColorAtPosition; + float startingDistance = getDistance(uvCoordinates, rgbaColorAtPosition); + + vec3 colorAtPosition = rgbaColorAtPosition.rgb; + vec3 lighting = ambientLight; + vec3 lightingInside = lighting * INTENSITY_INSIDE_RATIO; - vec3 colorAtPosition; - float startingDistance = getDistance(uvCoordinates, colorAtPosition); - - if (startingDistance < 0.0) { - #ifdef CIRCLE_LIGHT_COUNT - #if CIRCLE_LIGHT_COUNT > 0 - for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { - lighting += colorInPositionInside(i); - } - #endif - #endif + #ifdef CIRCLE_LIGHT_COUNT + #if CIRCLE_LIGHT_COUNT > 0 + for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { + float lightCenterDistance; + vec3 lightColorAtPosition = colorInPosition(i, lightCenterDistance); - #ifdef FLASHLIGHT_COUNT - #if FLASHLIGHT_COUNT > 0 - for (int i = 0; i < FLASHLIGHT_COUNT; i++) { - lighting += colorInPositionInside(i, normalize(flashlightActualDirections[i])); - } - #endif - #endif - } else { - colorAtPosition = vec3(1.0); + vec2 direction = normalize(circleLightDirections[i]) / squareToAspectRatioTimes2; + lighting += lightColorAtPosition * shadowTransparency( + startingDistance, + lightCenterDistance, + direction + ); - #ifdef CIRCLE_LIGHT_COUNT - #if CIRCLE_LIGHT_COUNT > 0 - for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { - vec2 direction = normalize(circleLightDirections[i]) / squareToAspectRatioTimes2; - - float lightCenterDistance; - vec3 lightColorAtPosition = colorInPosition(i, lightCenterDistance); - - lighting += lightColorAtPosition * shadowTransparency(startingDistance, lightCenterDistance, direction); - } - #endif - #endif - - #ifdef FLASHLIGHT_COUNT - #if FLASHLIGHT_COUNT > 0 - for (int i = 0; i < FLASHLIGHT_COUNT; i++) { - vec2 originalDirection = normalize(flashlightDirections[i]); - vec2 direction = originalDirection / squareToAspectRatioTimes2; - - float lightCenterDistance; - vec3 lightColorAtPosition = colorInPosition(i, originalDirection, lightCenterDistance); - - if (length(lightColorAtPosition) < 0.01) { - continue; - } - - lighting += lightColorAtPosition * shadowTransparency(startingDistance, lightCenterDistance, direction); - } - #endif - #endif + lightingInside += lightColorAtPosition; } - - fragmentColor = vec4(colorAtPosition * lighting, 1.0); + #endif + #endif + + #ifdef FLASHLIGHT_COUNT + #if FLASHLIGHT_COUNT > 0 + for (int i = 0; i < FLASHLIGHT_COUNT; i++) { + vec2 originalDirection = normalize(flashlightDirections[i]); + + float lightCenterDistance; + vec3 lightColorAtPosition = colorInPosition(i, originalDirection, lightCenterDistance); + + vec2 direction = originalDirection / squareToAspectRatioTimes2 + + if (length(lightColorAtPosition) < 0.0) { + continue; + } + + lighting += lightColorAtPosition * shadowTransparency( + startingDistance, + lightCenterDistance, + direction + ); + + lightingInside += lightColorAtPosition; + } + #endif + #endif + + vec3 outsideColor = backgroundColor.rgb * lighting; + vec3 insideColor = colorAtPosition * lightingInside; + + float edge = clamp(startingDistance / shadingNdcPixelSize, 0.0, 1.0); + vec3 antialiasedColor = mix(insideColor, outsideColor, edge); + fragmentColor = vec4( + antialiasedColor, + mix(rgbaColorAtPosition.a, backgroundColor.a, step(0.0, startingDistance)) + ); }