com.dlsc.gemsfx.util.Utils Maven / Gradle / Ivy
Show all versions of gemsfx Show documentation
/*
* Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.dlsc.gemsfx.util;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.HPos;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.paint.Stop;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.util.List;
/**
* Some basic utilities which need to be in java (for shifting operations or
* other reasons), which are not toolkit dependent.
*/
public class Utils {
/***************************************************************************
* *
* Math-related utilities *
* *
**************************************************************************/
/**
* Simple utility function which clamps the given value to be strictly
* between the min and max values.
*/
public static float clamp(float min, float value, float max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
/**
* Simple utility function which clamps the given value to be strictly
* between the min and max values.
*/
public static int clamp(int min, int value, int max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
/**
* Simple utility function which clamps the given value to be strictly
* between the min and max values.
*/
public static double clamp(double min, double value, double max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
/**
* Simple utility function which clamps the given value to be strictly
* between the min and max values.
*/
public static long clamp(long min, long value, long max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
/**
* Simple utility function which clamps the given value to be strictly
* above the min value.
*/
public static double clampMin(double value, double min) {
if (value < min) return min;
return value;
}
/**
* Simple utility function which clamps the given value to be strictly
* under the max value.
*/
public static int clampMax(int value, int max) {
if (value > max) return max;
return value;
}
/**
* Utility function which returns either {@code less} or {@code more}
* depending on which {@code value} is closer to. If {@code value}
* is perfectly between them, then either may be returned.
*/
public static double nearest(double less, double value, double more) {
double lessDiff = value - less;
double moreDiff = more - value;
if (lessDiff < moreDiff) return less;
return more;
}
/***************************************************************************
* *
* String-related utilities *
* *
**************************************************************************/
/**
* Helper to remove leading and trailing quotes from a string.
* Works with single or double quotes.
*/
public static String stripQuotes(String str) {
if (str == null) return str;
if (str.isEmpty()) return str;
int beginIndex = 0;
char openQuote = str.charAt(beginIndex);
if (openQuote == '\"' || openQuote == '\'') beginIndex += 1;
int endIndex = str.length();
char closeQuote = str.charAt(endIndex - 1);
if (closeQuote == '\"' || closeQuote == '\'') endIndex -= 1;
if ((endIndex - beginIndex) < 0) return str;
// note that String.substring returns "this" if beginIndex == 0 && endIndex == count
// or a new string that shares the character buffer with the original string.
return str.substring(beginIndex, endIndex);
}
/**
* Because mobile doesn't have string.split(s) function, this function
* was written.
*/
public static String[] split(String str, String separator) {
if (str == null || str.length() == 0) return new String[]{};
if (separator == null || separator.length() == 0) return new String[]{};
if (separator.length() > str.length()) return new String[]{};
List result = new java.util.ArrayList();
int index = str.indexOf(separator);
while (index >= 0) {
String newStr = str.substring(0, index);
if (!newStr.isEmpty()) {
result.add(newStr);
}
str = str.substring(index + separator.length());
index = str.indexOf(separator);
}
if (!str.isEmpty()) {
result.add(str);
}
return result.toArray(new String[]{});
}
/**
* Because mobile doesn't have string.contains(s) function, this function
* was written.
*/
public static boolean contains(String src, String s) {
if (src == null || src.length() == 0) return false;
if (s == null || s.length() == 0) return false;
if (s.length() > src.length()) return false;
return src.indexOf(s) > -1;
}
/***************************************************************************
* *
* Color-related utilities *
* *
**************************************************************************/
/**
* Calculates a perceptual brightness for a color between 0.0 black and 1.0 while
*/
public static double calculateBrightness(Color color) {
return (0.3 * color.getRed()) + (0.59 * color.getGreen()) + (0.11 * color.getBlue());
}
/**
* Derives a lighter or darker of a given color.
*
* @param c The color to derive from
* @param brightness The brightness difference for the new color -1.0 being 100% dark which is always black, 0.0 being
* no change and 1.0 being 100% lighter which is always white
*/
public static Color deriveColor(Color c, double brightness) {
double baseBrightness = calculateBrightness(c);
double calcBrightness = brightness;
// Fine adjustments to colors in ranges of brightness to adjust the contrast for them
if (brightness > 0) {
if (baseBrightness > 0.85) {
calcBrightness = calcBrightness * 1.6;
} else if (baseBrightness > 0.6) {
// no change
} else if (baseBrightness > 0.5) {
calcBrightness = calcBrightness * 0.9;
} else if (baseBrightness > 0.4) {
calcBrightness = calcBrightness * 0.8;
} else if (baseBrightness > 0.3) {
calcBrightness = calcBrightness * 0.7;
} else {
calcBrightness = calcBrightness * 0.6;
}
} else {
if (baseBrightness < 0.2) {
calcBrightness = calcBrightness * 0.6;
}
}
// clamp brightness
if (calcBrightness < -1) {
calcBrightness = -1;
} else if (calcBrightness > 1) {
calcBrightness = 1;
}
// window two take the calculated brightness multiplyer and derive color based on source color
double[] hsb = RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue());
// change brightness
if (calcBrightness > 0) { // brighter
hsb[1] *= 1 - calcBrightness;
hsb[2] += (1 - hsb[2]) * calcBrightness;
} else { // darker
hsb[2] *= calcBrightness + 1;
}
// clip saturation and brightness
if (hsb[1] < 0) {
hsb[1] = 0;
} else if (hsb[1] > 1) {
hsb[1] = 1;
}
if (hsb[2] < 0) {
hsb[2] = 0;
} else if (hsb[2] > 1) {
hsb[2] = 1;
}
// convert back to color
Color c2 = Color.hsb((int) hsb[0], hsb[1], hsb[2], c.getOpacity());
return Color.hsb((int) hsb[0], hsb[1], hsb[2], c.getOpacity());
/* var hsb:Number[] = RGBtoHSB(c.red,c.green,c.blue);
// change brightness
if (brightness > 0) {
//var bright:Number = brightness * (1-calculateBrightness(c));
var bright:Number = if (calculateBrightness(c)<0.65 and brightness > 0.5) {
if (calculateBrightness(c)<0.2) then brightness * 0.55 else brightness * 0.7
} else brightness;
// brighter
hsb[1] *= 1 - bright;
hsb[2] += (1 - hsb[2]) * bright;
} else {
// darker
hsb[2] *= brightness+1;
}
// clip saturation and brightness
if (hsb[1] < 0) { hsb[1] = 0;} else if (hsb[1] > 1) {hsb[1] = 1}
if (hsb[2] < 0) { hsb[2] = 0;} else if (hsb[2] > 1) {hsb[2] = 1}
// convert back to color
return Color.hsb(hsb[0],hsb[1],hsb[2]) */
}
/**
* interpolate at a set {@code position} between two colors {@code color1} and {@code color2}.
* The interpolation is done is linear RGB color space not the default sRGB color space.
*/
private static Color interpolateLinear(double position, Color color1, Color color2) {
Color c1Linear = convertSRGBtoLinearRGB(color1);
Color c2Linear = convertSRGBtoLinearRGB(color2);
return convertLinearRGBtoSRGB(Color.color(
c1Linear.getRed() + (c2Linear.getRed() - c1Linear.getRed()) * position,
c1Linear.getGreen() + (c2Linear.getGreen() - c1Linear.getGreen()) * position,
c1Linear.getBlue() + (c2Linear.getBlue() - c1Linear.getBlue()) * position,
c1Linear.getOpacity() + (c2Linear.getOpacity() - c1Linear.getOpacity()) * position
));
}
/**
* Get the color at the give {@code position} in the ladder of color stops
*/
private static Color ladder(double position, Stop[] stops) {
Stop prevStop = null;
for (int i = 0; i < stops.length; i++) {
Stop stop = stops[i];
if (position <= stop.getOffset()) {
if (prevStop == null) {
return stop.getColor();
} else {
return interpolateLinear((position - prevStop.getOffset()) / (stop.getOffset() - prevStop.getOffset()), prevStop.getColor(), stop.getColor());
}
}
prevStop = stop;
}
// position is greater than biggest stop, so will we biggest stop's color
if (prevStop != null) {
return prevStop.getColor();
}
return null;
}
/**
* Get the color at the give {@code position} in the ladder of color stops
*/
public static Color ladder(Color color, Stop[] stops) {
return ladder(calculateBrightness(color), stops);
}
public static double[] HSBtoRGB(double hue, double saturation, double brightness) {
// normalize the hue
double normalizedHue = ((hue % 360) + 360) % 360;
hue = normalizedHue / 360;
double r = 0, g = 0, b = 0;
if (saturation == 0) {
r = g = b = brightness;
} else {
double h = (hue - Math.floor(hue)) * 6.0;
double f = h - Math.floor(h);
double p = brightness * (1.0 - saturation);
double q = brightness * (1.0 - saturation * f);
double t = brightness * (1.0 - (saturation * (1.0 - f)));
switch ((int) h) {
case 0:
r = brightness;
g = t;
b = p;
break;
case 1:
r = q;
g = brightness;
b = p;
break;
case 2:
r = p;
g = brightness;
b = t;
break;
case 3:
r = p;
g = q;
b = brightness;
break;
case 4:
r = t;
g = p;
b = brightness;
break;
case 5:
r = brightness;
g = p;
b = q;
break;
}
}
double[] f = new double[3];
f[0] = r;
f[1] = g;
f[2] = b;
return f;
}
public static double[] RGBtoHSB(double r, double g, double b) {
double hue, saturation, brightness;
double[] hsbvals = new double[3];
double cmax = (r > g) ? r : g;
if (b > cmax) cmax = b;
double cmin = (r < g) ? r : g;
if (b < cmin) cmin = b;
brightness = cmax;
if (cmax != 0)
saturation = (cmax - cmin) / cmax;
else
saturation = 0;
if (saturation == 0) {
hue = 0;
} else {
double redc = (cmax - r) / (cmax - cmin);
double greenc = (cmax - g) / (cmax - cmin);
double bluec = (cmax - b) / (cmax - cmin);
if (r == cmax)
hue = bluec - greenc;
else if (g == cmax)
hue = 2.0 + redc - bluec;
else
hue = 4.0 + greenc - redc;
hue = hue / 6.0;
if (hue < 0)
hue = hue + 1.0;
}
hsbvals[0] = hue * 360;
hsbvals[1] = saturation;
hsbvals[2] = brightness;
return hsbvals;
}
/**
* Helper function to convert a color in sRGB space to linear RGB space.
*/
public static Color convertSRGBtoLinearRGB(Color color) {
double[] colors = new double[]{color.getRed(), color.getGreen(), color.getBlue()};
for (int i = 0; i < colors.length; i++) {
if (colors[i] <= 0.04045) {
colors[i] = colors[i] / 12.92;
} else {
colors[i] = Math.pow((colors[i] + 0.055) / 1.055, 2.4);
}
}
return Color.color(colors[0], colors[1], colors[2], color.getOpacity());
}
/**
* Helper function to convert a color in linear RGB space to SRGB space.
*/
public static Color convertLinearRGBtoSRGB(Color color) {
double[] colors = new double[]{color.getRed(), color.getGreen(), color.getBlue()};
for (int i = 0; i < colors.length; i++) {
if (colors[i] <= 0.0031308) {
colors[i] = colors[i] * 12.92;
} else {
colors[i] = (1.055 * Math.pow(colors[i], (1.0 / 2.4))) - 0.055;
}
}
return Color.color(colors[0], colors[1], colors[2], color.getOpacity());
}
/**
* helper function for calculating the sum of a series of numbers
*/
public static double sum(double[] values) {
double sum = 0;
for (double v : values) sum = sum + v;
return sum / values.length;
}
public static Point2D pointRelativeTo(Node parent, Node node, HPos hpos,
VPos vpos, double dx, double dy, boolean reposition) {
double nodeWidth = node.getLayoutBounds().getWidth();
double nodeHeight = node.getLayoutBounds().getHeight();
return pointRelativeTo(parent, nodeWidth, nodeHeight, hpos, vpos, dx, dy, reposition);
}
public static Point2D pointRelativeTo(Node parent, double anchorWidth,
double anchorHeight, HPos hpos, VPos vpos, double dx, double dy,
boolean reposition) {
Bounds parentBounds = getBounds(parent);
Scene scene = parent.getScene();
NodeOrientation orientation = parent.getEffectiveNodeOrientation();
if (orientation == NodeOrientation.RIGHT_TO_LEFT) {
if (hpos == HPos.LEFT) {
hpos = HPos.RIGHT;
} else if (hpos == HPos.RIGHT) {
hpos = HPos.LEFT;
}
dx *= -1;
}
double layoutX = positionX(parentBounds, anchorWidth, hpos) + dx;
double layoutY = positionY(parentBounds, anchorHeight, vpos) + dy;
if (orientation == NodeOrientation.RIGHT_TO_LEFT && hpos == HPos.CENTER) {
//TODO - testing for an instance of Stage seems wrong but works for menus
if (scene.getWindow() instanceof Stage) {
layoutX = layoutX + parentBounds.getWidth() - anchorWidth;
} else {
layoutX = layoutX - parentBounds.getWidth() - anchorWidth;
}
}
if (reposition) {
return pointRelativeTo(parent, anchorWidth, anchorHeight, layoutX, layoutY, hpos, vpos);
} else {
return new Point2D(layoutX, layoutY);
}
}
/**
* This is the fallthrough function that most other functions fall into. It takes
* care specifically of the repositioning of the item such that it remains onscreen
* as best it can, given it's unique qualities.
*
* As will all other functions, this one returns a Point2D that represents an x,y
* location that should safely position the item onscreen as best as possible.
*
* Note that width
and refer to the width and height of the
* node/popup that is needing to be repositioned, not of the parent.
*
* Don't use the BASELINE vpos, it doesn't make sense and would produce wrong result.
*/
public static Point2D pointRelativeTo(Object parent, double width,
double height, double screenX, double screenY, HPos hpos, VPos vpos) {
double finalScreenX = screenX;
double finalScreenY = screenY;
Bounds parentBounds = getBounds(parent);
// ...and then we get the bounds of this screen
Screen currentScreen = getScreen(parent);
Rectangle2D screenBounds =
hasFullScreenStage(currentScreen)
? currentScreen.getBounds()
: currentScreen.getVisualBounds();
// test if this layout will force the node to appear outside
// of the screens bounds. If so, we must reposition the item to a better position.
// We firstly try to do this intelligently, so as to not overlap the parent if
// at all possible.
if (hpos != null) {
// Firstly we consider going off the right hand side
if ((finalScreenX + width) > screenBounds.getMaxX()) {
finalScreenX = positionX(parentBounds, width, getHPosOpposite(hpos, vpos));
}
// don't let the node go off to the left of the current screen
if (finalScreenX < screenBounds.getMinX()) {
finalScreenX = positionX(parentBounds, width, getHPosOpposite(hpos, vpos));
}
}
if (vpos != null) {
// don't let the node go off the bottom of the current screen
if ((finalScreenY + height) > screenBounds.getMaxY()) {
finalScreenY = positionY(parentBounds, height, getVPosOpposite(hpos, vpos));
}
// don't let the node out of the top of the current screen
if (finalScreenY < screenBounds.getMinY()) {
finalScreenY = positionY(parentBounds, height, getVPosOpposite(hpos, vpos));
}
}
// --- after all the moving around, we do one last check / rearrange.
// Unlike the check above, this time we are just fully committed to keeping
// the item on screen at all costs, regardless of whether or not that results
/// in overlapping the parent object.
if ((finalScreenX + width) > screenBounds.getMaxX()) {
finalScreenX -= (finalScreenX + width - screenBounds.getMaxX());
}
if (finalScreenX < screenBounds.getMinX()) {
finalScreenX = screenBounds.getMinX();
}
if ((finalScreenY + height) > screenBounds.getMaxY()) {
finalScreenY -= (finalScreenY + height - screenBounds.getMaxY());
}
if (finalScreenY < screenBounds.getMinY()) {
finalScreenY = screenBounds.getMinY();
}
return new Point2D(finalScreenX, finalScreenY);
}
/**
* Utility function that returns the x-axis position that an object should be positioned at,
* given the parents screen bounds, the width of the object, and
* the required HPos.
*/
private static double positionX(Bounds parentBounds, double width, HPos hpos) {
if (hpos == HPos.CENTER) {
// this isn't right, but it is needed for root menus to show properly
return parentBounds.getMinX();
} else if (hpos == HPos.RIGHT) {
return parentBounds.getMaxX();
} else if (hpos == HPos.LEFT) {
return parentBounds.getMinX() - width;
} else {
return 0;
}
}
/**
* Utility function that returns the y-axis position that an object should be positioned at,
* given the parents screen bounds, the height of the object, and
* the required VPos.
*
* The BASELINE vpos doesn't make sense here, 0 is returned for it.
*/
private static double positionY(Bounds parentBounds, double height, VPos vpos) {
if (vpos == VPos.BOTTOM) {
return parentBounds.getMaxY();
} else if (vpos == VPos.CENTER) {
return parentBounds.getMinY();
} else if (vpos == VPos.TOP) {
return parentBounds.getMinY() - height;
} else {
return 0;
}
}
/**
* To facilitate multiple types of parent object, we unfortunately must allow for
* Objects to be passed in. This method handles determining the bounds of the
* given Object. If the Object type is not supported, a default Bounds will be returned.
*/
private static Bounds getBounds(Object obj) {
if (obj instanceof Node) {
Node n = (Node) obj;
Bounds b = n.localToScreen(n.getLayoutBounds());
return b != null ? b : new BoundingBox(0, 0, 0, 0);
} else if (obj instanceof Window) {
Window window = (Window) obj;
return new BoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight());
} else {
return new BoundingBox(0, 0, 0, 0);
}
}
/*
* Simple utitilty function to return the 'opposite' value of a given HPos, taking
* into account the current VPos value. This is used to try and avoid overlapping.
*/
private static HPos getHPosOpposite(HPos hpos, VPos vpos) {
if (vpos == VPos.CENTER) {
if (hpos == HPos.LEFT) {
return HPos.RIGHT;
} else if (hpos == HPos.RIGHT) {
return HPos.LEFT;
} else if (hpos == HPos.CENTER) {
return HPos.CENTER;
} else {
// by default center for now
return HPos.CENTER;
}
} else {
return HPos.CENTER;
}
}
/*
* Simple utitilty function to return the 'opposite' value of a given VPos, taking
* into account the current HPos value. This is used to try and avoid overlapping.
*/
private static VPos getVPosOpposite(HPos hpos, VPos vpos) {
if (hpos == HPos.CENTER) {
if (vpos == VPos.BASELINE) {
return VPos.BASELINE;
} else if (vpos == VPos.BOTTOM) {
return VPos.TOP;
} else if (vpos == VPos.CENTER) {
return VPos.CENTER;
} else if (vpos == VPos.TOP) {
return VPos.BOTTOM;
} else {
// by default center for now
return VPos.CENTER;
}
} else {
return VPos.CENTER;
}
}
public static boolean hasFullScreenStage(Screen screen) {
List allWindows = Window.getWindows();
for (Window window : allWindows) {
if (window instanceof Stage) {
Stage stage = (Stage) window;
if (stage.isFullScreen() && (getScreen(stage) == screen)) {
return true;
}
}
}
return false;
}
/*
* Returns true if the primary Screen has QVGA dimensions, in landscape or portrait mode.
*/
public static boolean isQVGAScreen() {
Rectangle2D bounds = Screen.getPrimary().getBounds();
return ((bounds.getWidth() == 320 && bounds.getHeight() == 240) ||
(bounds.getWidth() == 240 && bounds.getHeight() == 320));
}
/**
* This function attempts to determine the best screen given the parent object
* from which we are wanting to position another item relative to. This is particularly
* important when we want to keep items from going off screen, and for handling
* multiple monitor support.
*/
public static Screen getScreen(Object obj) {
Bounds parentBounds = getBounds(obj);
Rectangle2D rect = new Rectangle2D(
parentBounds.getMinX(),
parentBounds.getMinY(),
parentBounds.getWidth(),
parentBounds.getHeight());
return getScreenForRectangle(rect);
}
public static Screen getScreenForRectangle(Rectangle2D rect) {
List screens = Screen.getScreens();
double rectX0 = rect.getMinX();
double rectX1 = rect.getMaxX();
double rectY0 = rect.getMinY();
double rectY1 = rect.getMaxY();
Screen selectedScreen;
selectedScreen = null;
double maxIntersection = 0;
for (Screen screen : screens) {
Rectangle2D screenBounds = screen.getBounds();
double intersection =
getIntersectionLength(rectX0, rectX1,
screenBounds.getMinX(),
screenBounds.getMaxX())
* getIntersectionLength(rectY0, rectY1,
screenBounds.getMinY(),
screenBounds.getMaxY());
if (maxIntersection < intersection) {
maxIntersection = intersection;
selectedScreen = screen;
}
}
if (selectedScreen != null) {
return selectedScreen;
}
selectedScreen = Screen.getPrimary();
double minDistance = Double.MAX_VALUE;
for (Screen screen : screens) {
Rectangle2D screenBounds = screen.getBounds();
double dx = getOuterDistance(rectX0, rectX1,
screenBounds.getMinX(),
screenBounds.getMaxX());
double dy = getOuterDistance(rectY0, rectY1,
screenBounds.getMinY(),
screenBounds.getMaxY());
double distance = dx * dx + dy * dy;
if (minDistance > distance) {
minDistance = distance;
selectedScreen = screen;
}
}
return selectedScreen;
}
public static Screen getScreenForPoint(double x, double y) {
List screens = Screen.getScreens();
// first check whether the point is inside some screen
for (Screen screen : screens) {
// can't use screen.bounds.contains, because it returns true for
// the min + width point
Rectangle2D screenBounds = screen.getBounds();
if ((x >= screenBounds.getMinX())
&& (x < screenBounds.getMaxX())
&& (y >= screenBounds.getMinY())
&& (y < screenBounds.getMaxY())) {
return screen;
}
}
// the point is not inside any screen, find the closest screen now
Screen selectedScreen = Screen.getPrimary();
double minDistance = Double.MAX_VALUE;
for (Screen screen : screens) {
Rectangle2D screenBounds = screen.getBounds();
double dx = getOuterDistance(screenBounds.getMinX(),
screenBounds.getMaxX(),
x);
double dy = getOuterDistance(screenBounds.getMinY(),
screenBounds.getMaxY(),
y);
double distance = dx * dx + dy * dy;
if (minDistance >= distance) {
minDistance = distance;
selectedScreen = screen;
}
}
return selectedScreen;
}
private static double getIntersectionLength(
double a0, double a1,
double b0, double b1) {
// (a0 <= a1) && (b0 <= b1)
return (a0 <= b0) ? getIntersectionLengthImpl(b0, b1, a1)
: getIntersectionLengthImpl(a0, a1, b1);
}
private static double getIntersectionLengthImpl(
double v0, double v1, double v) {
// (v0 <= v1)
if (v <= v0) {
return 0;
}
return (v <= v1) ? v - v0 : v1 - v0;
}
private static double getOuterDistance(
double a0, double a1,
double b0, double b1) {
// (a0 <= a1) && (b0 <= b1)
if (a1 <= b0) {
return b0 - a1;
}
if (b1 <= a0) {
return b1 - a0;
}
return 0;
}
private static double getOuterDistance(double v0,
double v1,
double v) {
// (v0 <= v1)
if (v <= v0) {
return v0 - v;
}
if (v >= v1) {
return v - v1;
}
return 0;
}
}