313 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			313 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.util;
 | |
| 
 | |
| import com.google.common.annotations.VisibleForTesting;
 | |
| import com.google.common.collect.ImmutableList;
 | |
| import com.google.common.collect.Lists;
 | |
| import com.mojang.datafixers.util.Either;
 | |
| import com.mojang.serialization.Codec;
 | |
| import com.mojang.serialization.codecs.RecordCodecBuilder;
 | |
| import it.unimi.dsi.fastutil.floats.FloatArrayList;
 | |
| import it.unimi.dsi.fastutil.floats.FloatList;
 | |
| import java.util.List;
 | |
| import java.util.Locale;
 | |
| import java.util.stream.Collectors;
 | |
| import java.util.stream.IntStream;
 | |
| import org.apache.commons.lang3.mutable.MutableObject;
 | |
| 
 | |
| public interface CubicSpline<C, I extends ToFloatFunction<C>> extends ToFloatFunction<C> {
 | |
| 	@VisibleForDebug
 | |
| 	String parityString();
 | |
| 
 | |
| 	CubicSpline<C, I> mapAll(CubicSpline.CoordinateVisitor<I> visitor);
 | |
| 
 | |
| 	static <C, I extends ToFloatFunction<C>> Codec<CubicSpline<C, I>> codec(Codec<I> codec) {
 | |
| 		MutableObject<Codec<CubicSpline<C, I>>> mutableObject = new MutableObject<>();
 | |
| 
 | |
| 		record Point<C, I extends ToFloatFunction<C>>(float location, CubicSpline<C, I> value, float derivative) {
 | |
| 		}
 | |
| 
 | |
| 		Codec<Point<C, I>> codec2 = RecordCodecBuilder.create(
 | |
| 			instance -> instance.group(
 | |
| 					Codec.FLOAT.fieldOf("location").forGetter(Point::location),
 | |
| 					Codec.lazyInitialized(mutableObject::getValue).fieldOf("value").forGetter(Point::value),
 | |
| 					Codec.FLOAT.fieldOf("derivative").forGetter(Point::derivative)
 | |
| 				)
 | |
| 				.apply(instance, (f, cubicSpline, g) -> new Point(f, cubicSpline, g))
 | |
| 		);
 | |
| 		Codec<CubicSpline.Multipoint<C, I>> codec3 = RecordCodecBuilder.create(
 | |
| 			instance -> instance.group(
 | |
| 					codec.fieldOf("coordinate").forGetter(CubicSpline.Multipoint::coordinate),
 | |
| 					ExtraCodecs.nonEmptyList(codec2.listOf())
 | |
| 						.fieldOf("points")
 | |
| 						.forGetter(
 | |
| 							multipoint -> IntStream.range(0, multipoint.locations.length)
 | |
| 								.mapToObj(i -> new Point(multipoint.locations()[i], (CubicSpline<C, I>)multipoint.values().get(i), multipoint.derivatives()[i]))
 | |
| 								.toList()
 | |
| 						)
 | |
| 				)
 | |
| 				.apply(instance, (toFloatFunction, list) -> {
 | |
| 					float[] fs = new float[list.size()];
 | |
| 					ImmutableList.Builder<CubicSpline<C, I>> builder = ImmutableList.builder();
 | |
| 					float[] gs = new float[list.size()];
 | |
| 
 | |
| 					for (int i = 0; i < list.size(); i++) {
 | |
| 						Point<C, I> lv = (Point<C, I>)list.get(i);
 | |
| 						fs[i] = lv.location();
 | |
| 						builder.add(lv.value());
 | |
| 						gs[i] = lv.derivative();
 | |
| 					}
 | |
| 
 | |
| 					return CubicSpline.Multipoint.create((I)toFloatFunction, fs, builder.build(), gs);
 | |
| 				})
 | |
| 		);
 | |
| 		mutableObject.setValue(
 | |
| 			Codec.either(Codec.FLOAT, codec3)
 | |
| 				.xmap(
 | |
| 					either -> either.map(CubicSpline.Constant::new, multipoint -> multipoint),
 | |
| 					cubicSpline -> cubicSpline instanceof CubicSpline.Constant<C, I> constant
 | |
| 						? Either.left(constant.value())
 | |
| 						: Either.right((CubicSpline.Multipoint)cubicSpline)
 | |
| 				)
 | |
| 		);
 | |
| 		return mutableObject.getValue();
 | |
| 	}
 | |
| 
 | |
| 	static <C, I extends ToFloatFunction<C>> CubicSpline<C, I> constant(float value) {
 | |
| 		return new CubicSpline.Constant<>(value);
 | |
| 	}
 | |
| 
 | |
| 	static <C, I extends ToFloatFunction<C>> CubicSpline.Builder<C, I> builder(I coordinate) {
 | |
| 		return new CubicSpline.Builder<>(coordinate);
 | |
| 	}
 | |
| 
 | |
| 	static <C, I extends ToFloatFunction<C>> CubicSpline.Builder<C, I> builder(I coordinate, ToFloatFunction<Float> valueTransformer) {
 | |
| 		return new CubicSpline.Builder<>(coordinate, valueTransformer);
 | |
| 	}
 | |
| 
 | |
| 	public static final class Builder<C, I extends ToFloatFunction<C>> {
 | |
| 		private final I coordinate;
 | |
| 		private final ToFloatFunction<Float> valueTransformer;
 | |
| 		private final FloatList locations = new FloatArrayList();
 | |
| 		private final List<CubicSpline<C, I>> values = Lists.<CubicSpline<C, I>>newArrayList();
 | |
| 		private final FloatList derivatives = new FloatArrayList();
 | |
| 
 | |
| 		protected Builder(I coordinate) {
 | |
| 			this(coordinate, ToFloatFunction.IDENTITY);
 | |
| 		}
 | |
| 
 | |
| 		protected Builder(I coordinate, ToFloatFunction<Float> valueTransformer) {
 | |
| 			this.coordinate = coordinate;
 | |
| 			this.valueTransformer = valueTransformer;
 | |
| 		}
 | |
| 
 | |
| 		public CubicSpline.Builder<C, I> addPoint(float location, float value) {
 | |
| 			return this.addPoint(location, new CubicSpline.Constant<>(this.valueTransformer.apply(value)), 0.0F);
 | |
| 		}
 | |
| 
 | |
| 		public CubicSpline.Builder<C, I> addPoint(float location, float value, float derivative) {
 | |
| 			return this.addPoint(location, new CubicSpline.Constant<>(this.valueTransformer.apply(value)), derivative);
 | |
| 		}
 | |
| 
 | |
| 		public CubicSpline.Builder<C, I> addPoint(float location, CubicSpline<C, I> value) {
 | |
| 			return this.addPoint(location, value, 0.0F);
 | |
| 		}
 | |
| 
 | |
| 		private CubicSpline.Builder<C, I> addPoint(float location, CubicSpline<C, I> value, float derivative) {
 | |
| 			if (!this.locations.isEmpty() && location <= this.locations.getFloat(this.locations.size() - 1)) {
 | |
| 				throw new IllegalArgumentException("Please register points in ascending order");
 | |
| 			} else {
 | |
| 				this.locations.add(location);
 | |
| 				this.values.add(value);
 | |
| 				this.derivatives.add(derivative);
 | |
| 				return this;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		public CubicSpline<C, I> build() {
 | |
| 			if (this.locations.isEmpty()) {
 | |
| 				throw new IllegalStateException("No elements added");
 | |
| 			} else {
 | |
| 				return CubicSpline.Multipoint.create(this.coordinate, this.locations.toFloatArray(), ImmutableList.copyOf(this.values), this.derivatives.toFloatArray());
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@VisibleForDebug
 | |
| 	public record Constant<C, I extends ToFloatFunction<C>>(float value) implements CubicSpline<C, I> {
 | |
| 		@Override
 | |
| 		public float apply(C object) {
 | |
| 			return this.value;
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public String parityString() {
 | |
| 			return String.format(Locale.ROOT, "k=%.3f", this.value);
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public float minValue() {
 | |
| 			return this.value;
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public float maxValue() {
 | |
| 			return this.value;
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public CubicSpline<C, I> mapAll(CubicSpline.CoordinateVisitor<I> visitor) {
 | |
| 			return this;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public interface CoordinateVisitor<I> {
 | |
| 		I visit(I object);
 | |
| 	}
 | |
| 
 | |
| 	@VisibleForDebug
 | |
| 	public record Multipoint<C, I extends ToFloatFunction<C>>(
 | |
| 		I coordinate, float[] locations, List<CubicSpline<C, I>> values, float[] derivatives, float minValue, float maxValue
 | |
| 	) implements CubicSpline<C, I> {
 | |
| 
 | |
| 		public Multipoint(I coordinate, float[] locations, List<CubicSpline<C, I>> values, float[] derivatives, float minValue, float maxValue) {
 | |
| 			validateSizes(locations, values, derivatives);
 | |
| 			this.coordinate = coordinate;
 | |
| 			this.locations = locations;
 | |
| 			this.values = values;
 | |
| 			this.derivatives = derivatives;
 | |
| 			this.minValue = minValue;
 | |
| 			this.maxValue = maxValue;
 | |
| 		}
 | |
| 
 | |
| 		static <C, I extends ToFloatFunction<C>> CubicSpline.Multipoint<C, I> create(
 | |
| 			I coordinate, float[] locations, List<CubicSpline<C, I>> values, float[] derivatives
 | |
| 		) {
 | |
| 			validateSizes(locations, values, derivatives);
 | |
| 			int i = locations.length - 1;
 | |
| 			float f = Float.POSITIVE_INFINITY;
 | |
| 			float g = Float.NEGATIVE_INFINITY;
 | |
| 			float h = coordinate.minValue();
 | |
| 			float j = coordinate.maxValue();
 | |
| 			if (h < locations[0]) {
 | |
| 				float k = linearExtend(h, locations, ((CubicSpline)values.get(0)).minValue(), derivatives, 0);
 | |
| 				float l = linearExtend(h, locations, ((CubicSpline)values.get(0)).maxValue(), derivatives, 0);
 | |
| 				f = Math.min(f, Math.min(k, l));
 | |
| 				g = Math.max(g, Math.max(k, l));
 | |
| 			}
 | |
| 
 | |
| 			if (j > locations[i]) {
 | |
| 				float k = linearExtend(j, locations, ((CubicSpline)values.get(i)).minValue(), derivatives, i);
 | |
| 				float l = linearExtend(j, locations, ((CubicSpline)values.get(i)).maxValue(), derivatives, i);
 | |
| 				f = Math.min(f, Math.min(k, l));
 | |
| 				g = Math.max(g, Math.max(k, l));
 | |
| 			}
 | |
| 
 | |
| 			for (CubicSpline<C, I> cubicSpline : values) {
 | |
| 				f = Math.min(f, cubicSpline.minValue());
 | |
| 				g = Math.max(g, cubicSpline.maxValue());
 | |
| 			}
 | |
| 
 | |
| 			for (int m = 0; m < i; m++) {
 | |
| 				float l = locations[m];
 | |
| 				float n = locations[m + 1];
 | |
| 				float o = n - l;
 | |
| 				CubicSpline<C, I> cubicSpline2 = (CubicSpline<C, I>)values.get(m);
 | |
| 				CubicSpline<C, I> cubicSpline3 = (CubicSpline<C, I>)values.get(m + 1);
 | |
| 				float p = cubicSpline2.minValue();
 | |
| 				float q = cubicSpline2.maxValue();
 | |
| 				float r = cubicSpline3.minValue();
 | |
| 				float s = cubicSpline3.maxValue();
 | |
| 				float t = derivatives[m];
 | |
| 				float u = derivatives[m + 1];
 | |
| 				if (t != 0.0F || u != 0.0F) {
 | |
| 					float v = t * o;
 | |
| 					float w = u * o;
 | |
| 					float x = Math.min(p, r);
 | |
| 					float y = Math.max(q, s);
 | |
| 					float z = v - s + p;
 | |
| 					float aa = v - r + q;
 | |
| 					float ab = -w + r - q;
 | |
| 					float ac = -w + s - p;
 | |
| 					float ad = Math.min(z, ab);
 | |
| 					float ae = Math.max(aa, ac);
 | |
| 					f = Math.min(f, x + 0.25F * ad);
 | |
| 					g = Math.max(g, y + 0.25F * ae);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return new CubicSpline.Multipoint<>(coordinate, locations, values, derivatives, f, g);
 | |
| 		}
 | |
| 
 | |
| 		private static float linearExtend(float coordinate, float[] locations, float value, float[] derivatives, int index) {
 | |
| 			float f = derivatives[index];
 | |
| 			return f == 0.0F ? value : value + f * (coordinate - locations[index]);
 | |
| 		}
 | |
| 
 | |
| 		private static <C, I extends ToFloatFunction<C>> void validateSizes(float[] locations, List<CubicSpline<C, I>> values, float[] derivatives) {
 | |
| 			if (locations.length != values.size() || locations.length != derivatives.length) {
 | |
| 				throw new IllegalArgumentException("All lengths must be equal, got: " + locations.length + " " + values.size() + " " + derivatives.length);
 | |
| 			} else if (locations.length == 0) {
 | |
| 				throw new IllegalArgumentException("Cannot create a multipoint spline with no points");
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public float apply(C object) {
 | |
| 			float f = this.coordinate.apply(object);
 | |
| 			int i = findIntervalStart(this.locations, f);
 | |
| 			int j = this.locations.length - 1;
 | |
| 			if (i < 0) {
 | |
| 				return linearExtend(f, this.locations, ((CubicSpline)this.values.get(0)).apply(object), this.derivatives, 0);
 | |
| 			} else if (i == j) {
 | |
| 				return linearExtend(f, this.locations, ((CubicSpline)this.values.get(j)).apply(object), this.derivatives, j);
 | |
| 			} else {
 | |
| 				float g = this.locations[i];
 | |
| 				float h = this.locations[i + 1];
 | |
| 				float k = (f - g) / (h - g);
 | |
| 				ToFloatFunction<C> toFloatFunction = (ToFloatFunction<C>)this.values.get(i);
 | |
| 				ToFloatFunction<C> toFloatFunction2 = (ToFloatFunction<C>)this.values.get(i + 1);
 | |
| 				float l = this.derivatives[i];
 | |
| 				float m = this.derivatives[i + 1];
 | |
| 				float n = toFloatFunction.apply(object);
 | |
| 				float o = toFloatFunction2.apply(object);
 | |
| 				float p = l * (h - g) - (o - n);
 | |
| 				float q = -m * (h - g) + (o - n);
 | |
| 				return Mth.lerp(k, n, o) + k * (1.0F - k) * Mth.lerp(k, p, q);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		private static int findIntervalStart(float[] locations, float start) {
 | |
| 			return Mth.binarySearch(0, locations.length, i -> start < locations[i]) - 1;
 | |
| 		}
 | |
| 
 | |
| 		@VisibleForTesting
 | |
| 		@Override
 | |
| 		public String parityString() {
 | |
| 			return "Spline{coordinate="
 | |
| 				+ this.coordinate
 | |
| 				+ ", locations="
 | |
| 				+ this.toString(this.locations)
 | |
| 				+ ", derivatives="
 | |
| 				+ this.toString(this.derivatives)
 | |
| 				+ ", values="
 | |
| 				+ (String)this.values.stream().map(CubicSpline::parityString).collect(Collectors.joining(", ", "[", "]"))
 | |
| 				+ "}";
 | |
| 		}
 | |
| 
 | |
| 		private String toString(float[] locations) {
 | |
| 			return "["
 | |
| 				+ (String)IntStream.range(0, locations.length)
 | |
| 					.mapToDouble(i -> locations[i])
 | |
| 					.mapToObj(d -> String.format(Locale.ROOT, "%.3f", d))
 | |
| 					.collect(Collectors.joining(", "))
 | |
| 				+ "]";
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public CubicSpline<C, I> mapAll(CubicSpline.CoordinateVisitor<I> visitor) {
 | |
| 			return create(
 | |
| 				visitor.visit(this.coordinate), this.locations, this.values().stream().map(cubicSpline -> cubicSpline.mapAll(visitor)).toList(), this.derivatives
 | |
| 			);
 | |
| 		}
 | |
| 	}
 | |
| }
 |