Update #48 - Added some features from OptiFine

This commit is contained in:
lax1dude
2025-01-24 18:39:36 -08:00
parent 1f0d593a8c
commit e83a912e38
1056 changed files with 17706 additions and 898 deletions

View File

@ -0,0 +1,496 @@
package dev.redstudio.alfheim.lighting;
import dev.redstudio.alfheim.utils.DeduplicatedLongQueue;
import dev.redstudio.redcore.math.ClampUtil;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.BlockPos;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.Vec3i;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
/**
* Modified by lax1dude not to abuse interfaces
*
* @author Luna Lage (Desoroxxx)
* @author kappa-maintainer
* @author embeddedt
* @author Angeline (@jellysquid)
* @since 1.0
*/
public final class LightingEngine {
private static final byte MAX_LIGHT_LEVEL = 15;
private final World world;
// Layout of longs: [padding(4)] [y(8)] [x(26)] [z(26)]
private final DeduplicatedLongQueue[] lightUpdateQueues = new DeduplicatedLongQueue[EnumSkyBlock.values().length];
// Layout of longs: see above
private final DeduplicatedLongQueue[] darkeningQueues = new DeduplicatedLongQueue[MAX_LIGHT_LEVEL + 1];
private final DeduplicatedLongQueue[] brighteningQueues = new DeduplicatedLongQueue[MAX_LIGHT_LEVEL + 1];
// Layout of longs: [newLight(4)] [pos(60)]
private final DeduplicatedLongQueue initialBrightenings;
// Layout of longs: [padding(4)] [pos(60)]
private final DeduplicatedLongQueue initialDarkenings;
private boolean updating = false;
// Layout parameters
// Length of bit segments
private static final int L_X = 26, L_Y = 8, L_Z = 26, L_L = 4;
// Bit segment shifts/positions
private static final int S_Z = 0, S_X = S_Z + L_Z, S_Y = S_X + L_X, S_L = S_Y + L_Y;
// Bit segment masks
private static final long M_X = (1L << L_X) - 1, M_Y = (1L << L_Y) - 1, M_Z = (1L << L_Z) - 1,
M_L = (1L << L_L) - 1, M_POS = (M_Y << S_Y) | (M_X << S_X) | (M_Z << S_Z);
// Bit to check whether y had overflow
private static final long Y_CHECK = 1L << (S_Y + L_Y);
private static final long[] neighborShifts = new long[6];
static {
for (byte i = 0; i < 6; ++i) {
final Vec3i offset = EnumFacing._VALUES[i].getDirectionVec();
neighborShifts[i] = ((long) offset.getY() << S_Y) | ((long) offset.getX() << S_X)
| ((long) offset.getZ() << S_Z);
}
}
// Mask to extract chunk identifier
private static final long M_CHUNK = ((M_X >> 4) << (4 + S_X)) | ((M_Z >> 4) << (4 + S_Z));
// Iteration state data
// Cache position to avoid allocation of new object each time
private final BlockPos currentPos = new BlockPos();
private Chunk currentChunk;
private long currentChunkIdentifier;
private long currentData;
// Cached data about neighboring blocks (of tempPos)
private boolean isNeighborDataValid = false;
private final NeighborInfo[] neighborInfos = new NeighborInfo[6];
private DeduplicatedLongQueue currentQueue;
public LightingEngine(final World world) {
this.world = world;
initialBrightenings = new DeduplicatedLongQueue(16384);
initialDarkenings = new DeduplicatedLongQueue(16384);
for (int i = 0; i < EnumSkyBlock.values().length; ++i)
lightUpdateQueues[i] = new DeduplicatedLongQueue(16384);
for (int i = 0; i < darkeningQueues.length; ++i)
darkeningQueues[i] = new DeduplicatedLongQueue(16384);
for (int i = 0; i < brighteningQueues.length; ++i)
brighteningQueues[i] = new DeduplicatedLongQueue(16384);
for (int i = 0; i < neighborInfos.length; ++i)
neighborInfos[i] = new NeighborInfo();
}
/**
* Schedules a light update for the specified light type and position to be
* processed later by
* {@link LightingEngine#processLightUpdatesForType(EnumSkyBlock)}
*/
public void scheduleLightUpdate(final EnumSkyBlock lightType, final BlockPos pos) {
scheduleLightUpdate(lightType, encodeWorldCoord(pos));
}
/**
* Schedules a light update for the specified light type and position to be
* processed later by {@link LightingEngine#processLightUpdates()}
*/
private void scheduleLightUpdate(final EnumSkyBlock lightType, final long blockPos) {
lightUpdateQueues[lightType.ordinal()].enqueue(blockPos);
}
/**
* Calls {@link LightingEngine#processLightUpdatesForType(EnumSkyBlock)} for
* both light types
*/
public void processLightUpdates() {
processLightUpdatesForType(EnumSkyBlock.SKY);
processLightUpdatesForType(EnumSkyBlock.BLOCK);
}
/**
* Processes light updates of the given light type
*/
public void processLightUpdatesForType(final EnumSkyBlock lightType) {
final DeduplicatedLongQueue queue = lightUpdateQueues[lightType.ordinal()];
// Quickly check if the queue is empty before we acquire a more expensive lock.
if (queue.isEmpty())
return;
processLightUpdatesForTypeInner(lightType, queue);
}
private void processLightUpdatesForTypeInner(final EnumSkyBlock lightType, final DeduplicatedLongQueue queue) {
// Avoid nested calls
if (updating)
throw new IllegalStateException("Already processing updates!");
updating = true;
currentChunkIdentifier = -1; // Reset chunk cache
currentQueue = queue;
if (currentQueue != null)
currentQueue.newDeduplicationSet();
// Process the queued updates and enqueue them for further processing
while (nextItem()) {
if (currentChunk == null)
continue;
final byte oldLight = getCursorCachedLight(lightType);
final byte newLight = calculateNewLightFromCursor(lightType);
if (oldLight < newLight)
initialBrightenings.enqueue(((long) newLight << S_L) | currentData); // Don't enqueue directly for
// brightening to avoid
// duplicate scheduling
else if (oldLight > newLight)
initialDarkenings.enqueue(currentData); // Don't enqueue directly for darkening to avoid duplicate
// scheduling
}
currentQueue = initialBrightenings;
if (currentQueue != null)
currentQueue.newDeduplicationSet();
while (nextItem()) {
final byte newLight = (byte) (currentData >> S_L & M_L);
if (newLight > getCursorCachedLight(lightType))
enqueueBrightening(currentPos, currentData & M_POS, newLight, currentChunk, lightType); // Sets the
// light to
// newLight to
// only schedule
// once. Clear
// leading bits
// of curData
// for later
}
currentQueue = initialDarkenings;
if (currentQueue != null)
currentQueue.newDeduplicationSet();
while (nextItem()) {
final byte oldLight = getCursorCachedLight(lightType);
if (oldLight != 0)
enqueueDarkening(currentPos, currentData, oldLight, currentChunk, lightType); // Sets the light to zero
// to only schedule once
}
// Iterate through enqueued updates (brightening and darkening in parallel) from
// brightest to darkest so that we only need to iterate once
for (byte currentLight = MAX_LIGHT_LEVEL; currentLight >= 0; --currentLight) {
currentQueue = darkeningQueues[currentLight];
if (currentQueue != null)
currentQueue.newDeduplicationSet();
while (nextItem()) {
// Don't darken if we got brighter due to some other change
if (getCursorCachedLight(lightType) >= currentLight)
continue;
final IBlockState blockState = currentChunk.getBlockState(currentPos);
final byte luminosity = getCursorLuminosity(blockState, lightType);
final byte opacity; // If luminosity is high enough, opacity is irrelevant
if (luminosity >= MAX_LIGHT_LEVEL - 1)
opacity = 1;
else
opacity = getPosOpacity(currentPos, blockState);
// Only darken neighbors if we indeed became darker
if (calculateNewLightFromCursor(luminosity, opacity, lightType) < currentLight) {
// Need to calculate new light value from neighbors IGNORING neighbors which are
// scheduled for darkening
byte newLight = luminosity;
fetchNeighborDataFromCursor(lightType);
for (final NeighborInfo neighborInfo : neighborInfos) {
final Chunk neighborChunk = neighborInfo.chunk;
if (neighborChunk == null)
continue;
final byte neighborLight = neighborInfo.light;
if (neighborLight == 0)
continue;
final BlockPos neighborPos = neighborInfo.mutableBlockPos;
if (currentLight - getPosOpacity(neighborPos, neighborChunk
.getBlockState(neighborPos)) >= neighborLight) /*
* Schedule neighbor for darkening if we
* possibly light it
*/ {
enqueueDarkening(neighborPos, neighborInfo.key, neighborLight, neighborChunk, lightType);
} else /* Only use for new light calculation if not */ {
// If we can't darken the neighbor, no one else can (because of processing
// order) -> safe to let us be illuminated by it
newLight = (byte) Math.max(newLight, neighborLight - opacity);
}
}
// Schedule brightening since light level was set to 0
enqueueBrighteningFromCursor(newLight, lightType);
} else /*
* We didn't become darker, so we need to re-set our initial light value (was
* set to zero) and notify neighbors
*/ {
enqueueBrighteningFromCursor(currentLight, lightType); // Do not spread to neighbors immediately to
// avoid scheduling multiple times
}
}
currentQueue = brighteningQueues[currentLight];
if (currentQueue != null)
currentQueue.newDeduplicationSet();
while (nextItem()) {
final byte oldLight = getCursorCachedLight(lightType);
// Only process this if nothing else has happened at this position since
// scheduling
if (oldLight == currentLight) {
world.notifyLightSet(currentPos);
if (currentLight > 1)
spreadLightFromCursor(currentLight, lightType);
}
}
}
updating = false;
}
/**
* Gets data for neighbors of {@link #currentPos} and saves the results into
* neighbor state data members. If a neighbor can't be accessed/doesn't exist,
* the corresponding entry in neighborChunks is null - others are not reset
*/
private void fetchNeighborDataFromCursor(final EnumSkyBlock lightType) {
// Only update if curPos was changed
if (isNeighborDataValid)
return;
isNeighborDataValid = true;
for (int i = 0; i < neighborInfos.length; ++i) {
final NeighborInfo neighborInfo = neighborInfos[i];
final long neighborLongPos = neighborInfo.key = currentData + neighborShifts[i];
if ((neighborLongPos & Y_CHECK) != 0) {
neighborInfo.chunk = null;
continue;
}
final BlockPos neighborPos = decodeWorldCoord(neighborInfo.mutableBlockPos, neighborLongPos);
final Chunk neighborChunk;
if ((neighborLongPos & M_CHUNK) == currentChunkIdentifier)
neighborChunk = neighborInfo.chunk = currentChunk;
else
neighborChunk = neighborInfo.chunk = getChunk(neighborPos);
if (neighborChunk != null) {
final ExtendedBlockStorage neighborSection = neighborChunk
.getBlockStorageArray()[neighborPos.getY() >> 4];
neighborInfo.light = getCachedLightFor(neighborChunk, neighborSection, neighborPos, lightType);
}
}
}
private static byte getCachedLightFor(final Chunk chunk, final ExtendedBlockStorage storage,
final BlockPos blockPos, final EnumSkyBlock type) {
final int x = blockPos.getX() & 15;
final int y = blockPos.getY();
final int z = blockPos.getZ() & 15;
if (storage == null)
return type == EnumSkyBlock.SKY && chunk.canSeeSky(blockPos) ? (byte) type.defaultLightValue : 0;
else if (type == EnumSkyBlock.SKY)
return chunk.getWorld().provider.getHasNoSky() ? 0 : (byte) storage.getExtSkylightValue(x, y & 15, z);
else
return type == EnumSkyBlock.BLOCK ? (byte) storage.getExtBlocklightValue(x, y & 15, z)
: (byte) type.defaultLightValue;
}
private byte calculateNewLightFromCursor(final EnumSkyBlock lightType) {
final IBlockState blockState = currentChunk.getBlockState(currentPos);
final byte luminosity = getCursorLuminosity(blockState, lightType);
final byte opacity;
if (luminosity >= MAX_LIGHT_LEVEL - 1)
opacity = 1;
else
opacity = getPosOpacity(currentPos, blockState);
return calculateNewLightFromCursor(luminosity, opacity, lightType);
}
private byte calculateNewLightFromCursor(final byte luminosity, final byte opacity, final EnumSkyBlock lightType) {
if (luminosity >= MAX_LIGHT_LEVEL - opacity)
return luminosity;
byte newLight = luminosity;
fetchNeighborDataFromCursor(lightType);
for (final NeighborInfo neighborInfo : neighborInfos) {
if (neighborInfo.chunk == null)
continue;
newLight = (byte) Math.max(neighborInfo.light - opacity, newLight);
}
return newLight;
}
private void spreadLightFromCursor(final byte currentLight, final EnumSkyBlock lightType) {
fetchNeighborDataFromCursor(lightType);
for (final NeighborInfo neighborInfo : neighborInfos) {
final Chunk neighborChunk = neighborInfo.chunk;
if (neighborChunk == null || currentLight < neighborInfo.light)
continue;
final BlockPos neighborBlockPos = neighborInfo.mutableBlockPos;
final byte newLight = (byte) (currentLight
- getPosOpacity(neighborBlockPos, neighborChunk.getBlockState(neighborBlockPos)));
if (newLight > neighborInfo.light)
enqueueBrightening(neighborBlockPos, neighborInfo.key, newLight, neighborChunk, lightType);
}
}
private void enqueueBrighteningFromCursor(final byte newLight, final EnumSkyBlock lightType) {
enqueueBrightening(currentPos, currentData, newLight, currentChunk, lightType);
}
/**
* Enqueues the blockPos for brightening and sets its light value to newLight
*/
private void enqueueBrightening(final BlockPos blockPos, final long longPos, final byte newLight, final Chunk chunk,
final EnumSkyBlock lightType) {
brighteningQueues[newLight].enqueue(longPos);
chunk.setLightFor(lightType, blockPos, newLight);
}
/**
* Enqueues the blockPos for darkening and sets its light value to 0
*/
private void enqueueDarkening(final BlockPos blockPos, final long longPos, final byte oldLight, final Chunk chunk,
final EnumSkyBlock lightType) {
darkeningQueues[oldLight].enqueue(longPos);
chunk.setLightFor(lightType, blockPos, 0);
}
private static BlockPos decodeWorldCoord(final BlockPos mutableBlockPos, final long longPos) {
return mutableBlockPos.func_181079_c((int) (longPos >> S_X & M_X) - (1 << L_X - 1), (int) (longPos >> S_Y & M_Y),
(int) (longPos >> S_Z & M_Z) - (1 << L_Z - 1));
}
private static long encodeWorldCoord(final BlockPos pos) {
return ((long) pos.getY() << S_Y) | ((long) pos.getX() + (1 << L_X - 1) << S_X)
| ((long) pos.getZ() + (1 << L_Z - 1) << S_Z);
}
/**
* Polls a new item from {@link #currentQueue} and fills in state data members
*
* @return If there was an item to poll
*/
private boolean nextItem() {
if (currentQueue.isEmpty()) {
currentQueue = null;
return false;
}
currentData = currentQueue.dequeue();
isNeighborDataValid = false;
decodeWorldCoord(currentPos, currentData);
final long chunkIdentifier = currentData & M_CHUNK;
if (currentChunkIdentifier != chunkIdentifier) {
currentChunk = getChunk(currentPos);
currentChunkIdentifier = chunkIdentifier;
}
return true;
}
private byte getCursorCachedLight(final EnumSkyBlock lightType) {
return currentChunk.alfheim$getCachedLightFor(lightType, currentPos);
}
/**
* Calculates the luminosity for {@link #currentPos}, taking into account the
* light type
*/
private byte getCursorLuminosity(final IBlockState state, final EnumSkyBlock lightType) {
if (lightType == EnumSkyBlock.SKY) {
if (currentChunk.canSeeSky(currentPos))
return (byte) EnumSkyBlock.SKY.defaultLightValue;
else
return 0;
}
return (byte) ClampUtil.clampMinFirst(state.getBlock().getLightValue(), 0, MAX_LIGHT_LEVEL);
}
private byte getPosOpacity(final BlockPos blockPos, final IBlockState blockState) {
return (byte) ClampUtil.clampMinFirst(blockState.getBlock().getLightOpacity(), 1, MAX_LIGHT_LEVEL);
}
private Chunk getChunk(final BlockPos blockPos) {
return world.getChunkProvider().getLoadedChunk(blockPos.getX() >> 4, blockPos.getZ() >> 4);
}
private static final class NeighborInfo {
public final BlockPos mutableBlockPos = new BlockPos();
public Chunk chunk;
public byte light;
public long key;
}
}

View File

@ -0,0 +1,75 @@
package dev.redstudio.alfheim.utils;
import com.carrotsearch.hppc.LongArrayDeque;
import com.carrotsearch.hppc.LongHashSet;
/**
* A queue implementation for long values that are deduplicated on addition.
* <p>
* This is achieved by storing the values in a {@link LongOpenHashSet} and a
* {@link LongArrayFIFOQueue}.
*
* @author Luna Lage (Desoroxxx)
* @since 1.3
*/
public final class DeduplicatedLongQueue {
// TODO: Fully Implement my own implementation to get rid of the downsides of
// reduce etc...
private final LongArrayDeque queue;
private LongHashSet set;
/**
* Creates a new deduplicated queue with the given capacity.
*
* @param capacity The capacity of the deduplicated queue
*/
public DeduplicatedLongQueue(final int capacity) {
set = new LongHashSet(capacity);
queue = new LongArrayDeque(capacity);
}
/**
* Adds a value to the queue.
*
* @param value The value to add to the queue
*/
public void enqueue(final long value) {
if (set.add(value))
queue.addLast(value);
}
/**
* Removes and returns the first value in the queue.
*
* @return The first value in the queue
*/
public long dequeue() {
return queue.removeFirst();
}
/**
* Returns whether the queue is empty.
*
* @return {@code true} if the queue is empty, {@code false} otherwise
*/
public boolean isEmpty() {
return queue.isEmpty();
}
/**
* Creates a new deduplication set.
*/
public void newDeduplicationSet() {
int i = queue.size();
if(i < 4) {
i = 4;
}
if((set.keys.length * 3 / 2) > i) {
set = new LongHashSet(i);
}else {
set.clear();
}
}
}

View File

@ -0,0 +1,5 @@
package dev.redstudio.alfheim.utils;
public enum EnumBoundaryFacing {
IN, OUT
}

View File

@ -0,0 +1,88 @@
package dev.redstudio.alfheim.utils;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.IChunkProvider;
/**
* Represents a slice of a world containing a collection of chunks.
*
* @author Luna Lage (Desoroxxx)
* @author Angeline (@jellysquid)
* @since 1.0
*/
public class WorldChunkSlice {
private static final int DIAMETER = 5;
private static final int RADIUS = DIAMETER / 2;
private final int x, z;
private final Chunk[] chunks;
/**
* Initializes a {@link WorldChunkSlice} object using a given chunk provider and
* coordinates.
*
* @param chunkProvider The chunk provider to get chunks from
* @param x The X-coordinate of the center chunk
* @param z The Z-coordinate of the center chunk
*/
public WorldChunkSlice(final IChunkProvider chunkProvider, final int x, final int z) {
chunks = new Chunk[DIAMETER * DIAMETER];
for (int xDiff = -RADIUS; xDiff <= RADIUS; xDiff++)
for (int zDiff = -RADIUS; zDiff <= RADIUS; zDiff++)
chunks[((xDiff + RADIUS) * DIAMETER) + (zDiff + RADIUS)] = chunkProvider.getLoadedChunk(x + xDiff,
z + zDiff);
this.x = x - RADIUS;
this.z = z - RADIUS;
}
/**
* Checks if all chunks within a radius around a coordinate are loaded.
*
* @param x The X-coordinate to check around
* @param z The Z-coordinate to check around
* @param radius The radius around the coordinates to check
*
* @return true if all chunks are loaded, false otherwise
*/
public boolean isLoaded(final int x, final int z, final int radius) {
final int xStart = ((x - radius) >> 4) - this.x;
final int zStart = ((z - radius) >> 4) - this.z;
final int xEnd = ((x + radius) >> 4) - this.x;
final int zEnd = ((z + radius) >> 4) - this.z;
for (int currentX = xStart; currentX <= xEnd; ++currentX)
for (int currentZ = zStart; currentZ <= zEnd; ++currentZ)
if (getChunk(currentX, currentZ) == null)
return false;
return true;
}
/**
* Retrieves the chunk that includes the provided world coordinates.
*
* @param x The X-coordinate in the world
* @param z The Z-coordinate in the world
*
* @return The Chunk object that includes these coordinates
*/
public Chunk getChunkFromWorldCoords(final int x, final int z) {
return getChunk((x >> 4) - this.x, (z >> 4) - this.z);
}
/**
* Retrieves the chunk located at the given coordinates within this chunk slice.
*
* @param x The X-coordinate within the slice
* @param z The Z-coordinate within the slice
*
* @return The Chunk object at these coordinates
*/
private Chunk getChunk(final int x, final int z) {
return chunks[(x * DIAMETER) + z];
}
}

View File

@ -0,0 +1,201 @@
package dev.redstudio.redcore.math;
public class ClampUtil {
/**
* Clamps a value within a specified range [min, max], checking for the minimum
* value first.
* <p>
* If the input is less than min, it returns min. If the input is greater than
* max, it returns max. Otherwise, it returns the input.
*
* @param input The input value to clamp
* @param min The minimum value to clamp to
* @param max The maximum value to clamp to
*
* @return The clamped value
*/
public static byte clampMinFirst(final byte input, final byte min, final byte max) {
return input < min ? min : input > max ? max : input;
}
/**
* Clamps a value within a specified range [min, max], checking for the minimum
* value first.
* <p>
* If the input is less than min, it returns min. If the input is greater than
* max, it returns max. Otherwise, it returns the input.
*
* @param input The input value to clamp
* @param min The minimum value to clamp to
* @param max The maximum value to clamp to
*
* @return The clamped value
*/
public static short clampMinFirst(final short input, final short min, final short max) {
return input < min ? min : input > max ? max : input;
}
/**
* Clamps a value within a specified range [min, max], checking for the minimum
* value first.
* <p>
* If the input is less than min, it returns min. If the input is greater than
* max, it returns max. Otherwise, it returns the input.
*
* @param input The input value to clamp
* @param min The minimum value to clamp to
* @param max The maximum value to clamp to
*
* @return The clamped value
*/
public static int clampMinFirst(final int input, final int min, final int max) {
return input < min ? min : input > max ? max : input;
}
/**
* Clamps a value within a specified range [min, max], checking for the minimum
* value first.
* <p>
* If the input is less than min, it returns min. If the input is greater than
* max, it returns max. Otherwise, it returns the input.
*
* @param input The input value to clamp
* @param min The minimum value to clamp to
* @param max The maximum value to clamp to
*
* @return The clamped value
*/
public static long clampMinFirst(final long input, final long min, final long max) {
return input < min ? min : input > max ? max : input;
}
/**
* Clamps a value within a specified range [min, max], checking for the minimum
* value first.
* <p>
* If the input is less than min, it returns min. If the input is greater than
* max, it returns max. Otherwise, it returns the input.
*
* @param input The input value to clamp
* @param min The minimum value to clamp to
* @param max The maximum value to clamp to
*
* @return The clamped value
*/
public static float clampMinFirst(final float input, final float min, final float max) {
return input < min ? min : input > max ? max : input;
}
/**
* Clamps a value within a specified range [min, max], checking for the minimum
* value first.
* <p>
* If the input is less than min, it returns min. If the input is greater than
* max, it returns max. Otherwise, it returns the input.
*
* @param input The input value to clamp
* @param min The minimum value to clamp to
* @param max The maximum value to clamp to
*
* @return The clamped value
*/
public static double clampMinFirst(final double input, final double min, final double max) {
return input < min ? min : input > max ? max : input;
}
/**
* Clamps a value within a specified range [min, max], checking for the maximum
* value first.
* <p>
* If the input is greater than max, it returns max. If the input is less than
* min, it returns min. Otherwise, it returns the input.
*
* @param input The input value to clamp
* @param min The minimum value to clamp to
* @param max The maximum value to clamp to
* @return The clamped value
*/
public static byte clampMaxFirst(final byte input, final byte min, final byte max) {
return input > max ? max : input < min ? min : input;
}
/**
* Clamps a value within a specified range [min, max], checking for the maximum
* value first.
* <p>
* If the input is greater than max, it returns max. If the input is less than
* min, it returns min. Otherwise, it returns the input.
*
* @param input The input value to clamp
* @param min The minimum value to clamp to
* @param max The maximum value to clamp to
* @return The clamped value
*/
public static short clampMaxFirst(final short input, final short min, final short max) {
return input > max ? max : input < min ? min : input;
}
/**
* Clamps a value within a specified range [min, max], checking for the maximum
* value first.
* <p>
* If the input is greater than max, it returns max. If the input is less than
* min, it returns min. Otherwise, it returns the input.
*
* @param input The input value to clamp
* @param min The minimum value to clamp to
* @param max The maximum value to clamp to
* @return The clamped value
*/
public static int clampMaxFirst(final int input, final int min, final int max) {
return input > max ? max : input < min ? min : input;
}
/**
* Clamps a value within a specified range [min, max], checking for the maximum
* value first.
* <p>
* If the input is greater than max, it returns max. If the input is less than
* min, it returns min. Otherwise, it returns the input.
*
* @param input The input value to clamp
* @param min The minimum value to clamp to
* @param max The maximum value to clamp to
* @return The clamped value
*/
public static long clampMaxFirst(final long input, final long min, final long max) {
return input > max ? max : input < min ? min : input;
}
/**
* Clamps a value within a specified range [min, max], checking for the maximum
* value first.
* <p>
* If the input is greater than max, it returns max. If the input is less than
* min, it returns min. Otherwise, it returns the input.
*
* @param input The input value to clamp
* @param min The minimum value to clamp to
* @param max The maximum value to clamp to
* @return The clamped value
*/
public static float clampMaxFirst(final float input, final float min, final float max) {
return input > max ? max : input < min ? min : input;
}
/**
* Clamps a value within a specified range [min, max], checking for the maximum
* value first.
* <p>
* If the input is greater than max, it returns max. If the input is less than
* min, it returns min. Otherwise, it returns the input.
*
* @param input The input value to clamp
* @param min The minimum value to clamp to
* @param max The maximum value to clamp to
* @return The clamped value
*/
public static double clampMaxFirst(final double input, final double min, final double max) {
return input > max ? max : input < min ? min : input;
}
}