class dt_GM_GlobalMaths { /// Returns the sign of s. static int sign(double s) { if (s > 0) return 1; if (s < 0) return -1; return 0; } /// Copies the sign from signSource to source. static int copySignInt(int source, int signSource) { return abs(source) * sign(signSource); } /// Copies the sign from signSource to source. static double copySignDouble(double source, double signSource) { return abs(source) * sign(signSource); } /// Remaps a value in a range to another range. static double remapRange(double value, double range1L, double range1H, double range2L, double range2H) { return range2L + (value - range1L) * (range2H - range1H) / (range1H - range1L); } /// Remaps a value in a range to another range. static int remapRangeInt(int value, int range1L, int range1H, int range2L, int range2H) { return int(range2L + (value - range1L) * (range2H - range1H) / double(range1H - range1L)); } /// Returns if two values are close enough to be considered equal. static bool closeEnough(double a, double b, double epsilon = double.epsilon) { if (a == b) return true; return abs(a - b) <= epsilon; } // Creates a smoothed transition between edge0 and edge1. static double smoothStep(double x, double edge0 = 0, double edge1 = 1) { x = clamp((x - edge0) / (edge1 - edge0), 0, 1); return x * x * (3 - 2 * x); } // Creates a smoother transition between edge0 and edge1. static double smootherStep(double x, double edge0 = 0, double edge1 = 1) { x = clamp((x - edge0) / (edge1 - edge0), 0, 1); return x * x * x * (x * (x * 6 - 15) + 10); } /// Converts from horizontal FOV to vertical FOV, according to how GZDoom handles it. static double fovHToY(double fovH) { // this is how gzdoom does it internally, so i'm using it here double aspect = Screen.getAspectRatio(); double fovratio = (aspect >= 1.3) ? 1.333333 : aspect; return 2 * atan(tan(clamp(fovH, 5, 170) / 2.0) / fovratio); } /// Linearly interpolates between two doubles, clamping the parameters. static double lerpDouble(double from, double to, double time) { time = clamp(time, 0, 1); return lerpUnclampedDouble(from, to, time); } /// Linearly interpolates between two doubles. static double lerpUnclampedDouble(double from, double to, double time) { double ret; double reverseTime = 1 - time; ret = reverseTime * from + time * to; return ret; } // Converts from Normalised Device Coordinates to Viewport coordinates. // This is `ui` scope to safely access `screenblocks`. static ui Vector2 ndcToViewport(Vector3 ndcCoords, bool useScreenblocks = true) { if (useScreenblocks) { int viewwindowx, viewwindowy, viewwidth, viewheight; [viewwindowx, viewwindowy, viewwidth, viewheight] = Screen.getViewWindow(); int screenHeight = Screen.getHeight(); int height = screenHeight; if (screenblocks < 10) { height = (screenblocks * screenHeight / 10) & ~7; } int bottom = screenHeight - (height + viewwindowy - ((height - viewheight) / 2)); return (viewwindowx, screenHeight - bottom - height) + (((ndcCoords.x + 1) * viewwidth) / 2, ((-ndcCoords.y + 1) * height) / 2); } else { return (((ndcCoords.x + 1) * Screen.getWidth()) / 2, ((-ndcCoords.y + 1) * Screen.getHeight()) / 2); } } enum OutCode { OUT_Inside = 0, OUT_Left = 1 << 0, OUT_Right = 1 << 1, OUT_Bottom = 1 << 2, OUT_Top = 1 << 3 } /// Computes an outcode for a point in a rectangle. static OutCode computeOutcode(Vector2 point, Vector2 min, Vector2 max) { OutCode code = OUT_Inside; if (point.x < min.x) { code |= OUT_Left; } else if (point.x > max.x) { code |= OUT_Right; } if (point.y < min.y) { code |= OUT_Top; } else if (point.y > max.y) { code |= OUT_Bottom; } return code; } /// Clips a line to a rectangle. static bool, Vector2, Vector2 cohenSutherlandClip(Vector2 point0, Vector2 point1, Vector2 min, Vector2 max) { OutCode outcode0 = computeOutCode(point0, min, max); OutCode outcode1 = computeOutCode(point1, min, max); while (true) { // trivial accept - points are both on screen if ((outcode0 | outcode1) == 0) { return true, point0, point1; } // trivial reject - points are in the same region offscreen else if ((outcode0 & outcode1) != 0) { return false, point0, point1; } else { Vector2 new; OutCode outcodeOut = (outcode0 != 0) ? outcode0 : outcode1; if ((outcodeOut & OUT_Bottom) != 0) { new.x = point0.x + (point1.x - point0.x) * (max.y - point0.y) / (point1.y - point0.y); new.y = max.y; } else if ((outcodeOut & OUT_Top) != 0) { new.x = point0.x + (point1.x - point0.x) * (min.y - point0.y) / (point1.y - point0.y); new.y = min.y; } else if ((outcodeOut & OUT_Right) != 0) { new.y = point0.y + (point1.y - point0.y) * (max.x - point0.x) / (point1.x - point0.x); new.x = max.x; } else if ((outcodeOut & OUT_Left) != 0) { new.y = point0.y + (point1.y - point0.y) * (min.x - point0.x) / (point1.x - point0.x); new.x = min.x; } if (outcodeOut == outCode0) { point0.x = new.x; point0.y = new.y; outCode0 = computeOutCode(point0, min, max); } else { point1.x = new.x; point1.y = new.y; outCode1 = computeOutCode(point1, min, max); } } } return false, (0, 0), (0, 0); } // Normalizes an angle to (-180, 180]. Like Actor.normalize180, but callable in data scope. static double normalize180(double ang) { ang = ang % 360; ang = (ang + 360) % 360; if (ang > 180) ang -= 360; return ang; } }