Context for Tickable/Dynamic visuals

- Add VisualFrameContext for DynamicVisual#beginFrame
- Add VisualTickContext for TickableVisual#tick
- Move checks for update limiting to within the update calls themselves
- Provide update limiting/culling primitives within (B)E Visuals
- Remove methods from *Visual interfaces related to update limiting
- Add thenMap and andMap to Plan
- Add Plan primitive to transform context
  - Used in Visual update dispatch
This commit is contained in:
Jozufozu 2023-05-21 18:10:46 -07:00
parent e64976df5d
commit d783617a73
27 changed files with 231 additions and 230 deletions

View file

@ -1,5 +1,7 @@
package com.jozufozu.flywheel.api.task;
import java.util.function.Function;
public interface Plan<C> {
/**
* Submit this plan for execution.
@ -31,6 +33,16 @@ public interface Plan<C> {
*/
Plan<C> then(Plan<C> plan);
/**
* Create a new plan that executes this plan, then transforms the context to execute the given plan.
*
* @param map A function that transforms the plan context.
* @param plan The plan to execute after this plan with the transformed context.
* @param <D> The type of the transformed context.
* @return The composed plan.
*/
<D> Plan<C> thenMap(Function<C, D> map, Plan<D> plan);
/**
* Create a new plan that executes this plan and the given plan in parallel.
*
@ -39,6 +51,16 @@ public interface Plan<C> {
*/
Plan<C> and(Plan<C> plan);
/**
* Create a new plan that executes this plan and the given plan in parallel.
*
* @param map A function that transforms the plan context.
* @param plan The plan to execute in parallel with this plan than accepts the transformed context.
* @param <D> The type of the transformed context.
* @return The composed plan.
*/
<D> Plan<C> andMap(Function<C, D> map, Plan<D> plan);
/**
* If possible, create a new plan that accomplishes everything
* this plan does but with a simpler execution schedule.

View file

@ -1,18 +1,13 @@
package com.jozufozu.flywheel.impl.visualization.ratelimit;
package com.jozufozu.flywheel.api.visual;
/**
* Interface for rate-limiting updates based on an object's distance from the camera.
*/
public interface DistanceUpdateLimiter {
/**
* Call this before every update.
*/
void tick();
/**
* Check to see if an object at the given position relative to the camera should be updated.
*
* @param distanceSquared
* @param distanceSquared The distance squared from the camera to the object.
* @return {@code true} if the object should be updated, {@code false} otherwise.
*/
boolean shouldUpdate(double distanceSquared);

View file

@ -1,7 +1,5 @@
package com.jozufozu.flywheel.api.visual;
import org.joml.FrustumIntersection;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.Instancer;
@ -10,39 +8,17 @@ import com.jozufozu.flywheel.api.instance.Instancer;
* the start of a frame. By implementing {@link DynamicVisual}, an {@link Visual}
* can animate its models in ways that could not be easily achieved by shader attribute
* parameterization.
*
* <br><br> If your goal is offloading work to shaders, but you're unsure exactly how you need
* <p>
* If your goal is offloading work to shaders, but you're unsure exactly how you need
* to parameterize the instances, you're encouraged to implement this for prototyping.
*/
public interface DynamicVisual extends Visual {
/**
* Called every frame, and after initialization.
* <br>
* <em>DISPATCHED IN PARALLEL</em>, don't attempt to mutate anything outside this visual.
* <br>
* Called every frame.
* <p>
* <b>DISPATCHED IN PARALLEL</b>. Ensure proper synchronization if you need to mutate anything outside this visual.
* <p>
* {@link Instancer}/{@link Instance} creation/acquisition is safe here.
*/
void beginFrame();
/**
* As a further optimization, dynamic visuals that are far away are updated less often.
* This behavior can be disabled by returning false.
*
* <br> You might want to opt out of this if you want your animations to remain smooth
* even when far away from the camera. It is recommended to keep this as is, however.
*
* @return {@code true} if your visual should be slow updated.
*/
default boolean decreaseFramerateWithDistance() {
return true;
}
/**
* Check this visual against a frustum.<p>
* An implementor may choose to return a constant to skip the frustum check.
*
* @param frustum A frustum intersection tester for the current frame.
* @return {@code true} if this visual should be considered for updates.
*/
boolean isVisible(FrustumIntersection frustum);
void beginFrame(VisualFrameContext context);
}

View file

@ -21,23 +21,11 @@ import com.jozufozu.flywheel.api.instance.Instancer;
*/
public interface TickableVisual extends Visual {
/**
* Called every tick, and after initialization.<p>
* <em>DISPATCHED IN PARALLEL</em>, don't attempt to mutate anything outside of this visual
* without proper synchronization.<p>
* Called every tick.
* <p>
* <b>DISPATCHED IN PARALLEL</b>. Ensure proper synchronization if you need to mutate anything outside this visual.
* <p>
* {@link Instancer}/{@link Instance} creation/acquisition is safe here.
*/
void tick();
/**
* As a further optimization, tickable visuals that are far away are ticked less often.
* This behavior can be disabled by returning false.
*
* <br> You might want to opt out of this if you want your animations to remain smooth
* even when far away from the camera. It is recommended to keep this as is, however.
*
* @return {@code true} if your visual should be slow ticked.
*/
default boolean decreaseTickRateWithDistance() {
return true;
}
void tick(VisualTickContext c);
}

View file

@ -2,6 +2,9 @@ package com.jozufozu.flywheel.api.visual;
/**
* A general interface providing information about any type of thing that could use Flywheel's visualized rendering.
*
* @see DynamicVisual
* @see TickableVisual
*/
public interface Visual {
/**
@ -30,16 +33,6 @@ public interface Visual {
*/
boolean shouldReset();
/**
* Calculate the distance squared between this visual and the given <em>world</em> position.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param z The z coordinate.
* @return The distance squared between this visual and the given position.
*/
double distanceSquared(double x, double y, double z);
/**
* Free any acquired resources.
*/

View file

@ -0,0 +1,7 @@
package com.jozufozu.flywheel.api.visual;
import org.joml.FrustumIntersection;
public record VisualFrameContext(double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum,
DistanceUpdateLimiter limiter) {
}

View file

@ -0,0 +1,4 @@
package com.jozufozu.flywheel.api.visual;
public record VisualTickContext(double cameraX, double cameraY, double cameraZ, DistanceUpdateLimiter limiter) {
}

View file

@ -12,7 +12,7 @@ import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.material.MaterialVertexTransformer;
import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.vertex.MutableVertexList;
import com.jozufozu.flywheel.lib.task.RunOnAllWithContextPlan;
import com.jozufozu.flywheel.lib.task.RunOnAllPlan;
import com.jozufozu.flywheel.lib.vertex.VertexTransformations;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Matrix3f;
@ -38,7 +38,7 @@ public class TransformCall<I extends Instance> {
meshVertexCount = mesh.getVertexCount();
boundingSphere = mesh.mesh.getBoundingSphere();
drawPlan = RunOnAllWithContextPlan.of(instancer::getAll, (instance, ctx) -> {
drawPlan = RunOnAllPlan.of(instancer::getAll, (instance, ctx) -> {
var boundingSphere = new Vector4f(this.boundingSphere);
boundingSphereTransformer.transform(boundingSphere, instance);

View file

@ -82,7 +82,7 @@ public class VisualWorld implements AutoCloseable {
/**
* Tick the visuals after the game has ticked:
* <p>
* Call {@link TickableVisual#tick()} on all visuals in this world.
* Call {@link TickableVisual#tick} on all visuals in this world.
* </p>
*/
public void tick(double cameraX, double cameraY, double cameraZ) {
@ -96,7 +96,7 @@ public class VisualWorld implements AutoCloseable {
* <p>
* Check and update the render origin.
* <br>
* Call {@link DynamicVisual#beginFrame()} on all visuals in this world.
* Call {@link DynamicVisual#beginFrame} on all visuals in this world.
* </p>
*/
public void beginFrame(RenderContext context) {

View file

@ -6,23 +6,25 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.TickableVisual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visual.VisualTickContext;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.impl.TickContext;
import com.jozufozu.flywheel.impl.visualization.FrameContext;
import com.jozufozu.flywheel.impl.visualization.ratelimit.BandedPrimeLimiter;
import com.jozufozu.flywheel.impl.visualization.ratelimit.DistanceUpdateLimiter;
import com.jozufozu.flywheel.impl.visualization.ratelimit.DistanceUpdateLimiterImpl;
import com.jozufozu.flywheel.impl.visualization.ratelimit.NonLimiter;
import com.jozufozu.flywheel.impl.visualization.storage.Storage;
import com.jozufozu.flywheel.impl.visualization.storage.Transaction;
import com.jozufozu.flywheel.lib.task.RunOnAllWithContextPlan;
import com.jozufozu.flywheel.lib.task.RunOnAllPlan;
import com.jozufozu.flywheel.lib.task.SimplePlan;
import com.jozufozu.flywheel.util.Unit;
public abstract class VisualManager<T> {
private final Queue<Transaction<T>> queue = new ConcurrentLinkedQueue<>();
protected DistanceUpdateLimiter tickLimiter;
protected DistanceUpdateLimiter frameLimiter;
protected DistanceUpdateLimiterImpl tickLimiter;
protected DistanceUpdateLimiterImpl frameLimiter;
public VisualManager() {
tickLimiter = createUpdateLimiter();
@ -31,8 +33,9 @@ public abstract class VisualManager<T> {
protected abstract Storage<T> getStorage();
protected DistanceUpdateLimiter createUpdateLimiter() {
if (FlwConfig.get().limitUpdates()) {
protected DistanceUpdateLimiterImpl createUpdateLimiter() {
if (FlwConfig.get()
.limitUpdates()) {
return new BandedPrimeLimiter();
} else {
return new NonLimiter();
@ -90,13 +93,7 @@ public abstract class VisualManager<T> {
tickLimiter.tick();
processQueue();
})
.then(RunOnAllWithContextPlan.of(getStorage()::getTickableVisuals, this::tickInstance));
}
protected void tickInstance(TickableVisual instance, TickContext c) {
if (!instance.decreaseTickRateWithDistance() || tickLimiter.shouldUpdate(instance.distanceSquared(c.cameraX(), c.cameraY(), c.cameraZ()))) {
instance.tick();
}
.thenMap(this::createVisualTickContext, RunOnAllPlan.of(getStorage()::getTickableVisuals, TickableVisual::tick));
}
public Plan<FrameContext> createFramePlan() {
@ -104,14 +101,14 @@ public abstract class VisualManager<T> {
frameLimiter.tick();
processQueue();
})
.then(RunOnAllWithContextPlan.of(getStorage()::getDynamicVisuals, this::updateInstance));
.thenMap(this::createVisualContext, RunOnAllPlan.of(getStorage()::getDynamicVisuals, DynamicVisual::beginFrame));
}
protected void updateInstance(DynamicVisual instance, FrameContext c) {
if (!instance.decreaseFramerateWithDistance() || frameLimiter.shouldUpdate(instance.distanceSquared(c.cameraX(), c.cameraY(), c.cameraZ()))) {
if (instance.isVisible(c.frustum())) {
instance.beginFrame();
}
}
private VisualFrameContext createVisualContext(FrameContext ctx) {
return new VisualFrameContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), ctx.frustum(), frameLimiter);
}
private VisualTickContext createVisualTickContext(TickContext ctx) {
return new VisualTickContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), frameLimiter);
}
}

View file

@ -2,9 +2,9 @@ package com.jozufozu.flywheel.impl.visualization.ratelimit;
import net.minecraft.util.Mth;
public class BandedPrimeLimiter implements DistanceUpdateLimiter {
public class BandedPrimeLimiter implements DistanceUpdateLimiterImpl {
// 1 followed by the prime numbers
private static final int[] DIVISOR_SEQUENCE = new int[] { 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 };
private static final int[] DIVISOR_SEQUENCE = new int[]{1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};
private int tickCount = 0;

View file

@ -0,0 +1,10 @@
package com.jozufozu.flywheel.impl.visualization.ratelimit;
import com.jozufozu.flywheel.api.visual.DistanceUpdateLimiter;
public interface DistanceUpdateLimiterImpl extends DistanceUpdateLimiter {
/**
* Call this before every update.
*/
void tick();
}

View file

@ -1,6 +1,6 @@
package com.jozufozu.flywheel.impl.visualization.ratelimit;
public class NonLimiter implements DistanceUpdateLimiter {
public class NonLimiter implements DistanceUpdateLimiterImpl {
@Override
public void tick() {
}

View file

@ -32,12 +32,10 @@ public abstract class AbstractStorage<T> implements Storage<T> {
if (visual instanceof TickableVisual tickable) {
tickableVisuals.add(tickable);
tickable.tick();
}
if (visual instanceof DynamicVisual dynamic) {
dynamicVisuals.add(dynamic);
dynamic.beginFrame();
}
}
}

View file

@ -0,0 +1,25 @@
package com.jozufozu.flywheel.lib.task;
import java.util.function.Function;
import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.task.TaskExecutor;
public record MapContextPlan<C, D>(Function<C, D> map, Plan<D> plan) implements SimplyComposedPlan<C> {
@Override
public void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion) {
D newContext = map.apply(context);
plan.execute(taskExecutor, newContext, onCompletion);
}
@Override
public Plan<C> maybeSimplify() {
var maybeSimplified = plan.maybeSimplify();
if (maybeSimplified instanceof UnitPlan) {
return UnitPlan.of();
}
return new MapContextPlan<>(map, maybeSimplified);
}
}

View file

@ -1,35 +1,36 @@
package com.jozufozu.flywheel.lib.task;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.lib.math.MoreMath;
public record RunOnAllPlan<T>(Supplier<List<T>> listSupplier, Consumer<T> action) implements ContextAgnosticPlan {
public static <T, C> Plan<C> of(Supplier<List<T>> iterable, Consumer<T> forEach) {
return new RunOnAllPlan<>(iterable, forEach).cast();
public record RunOnAllPlan<T, C>(Supplier<List<T>> listSupplier,
BiConsumer<T, C> action) implements SimplyComposedPlan<C> {
public static <T, C> Plan<C> of(Supplier<List<T>> iterable, BiConsumer<T, C> forEach) {
return new RunOnAllPlan<>(iterable, forEach);
}
@Override
public void execute(TaskExecutor taskExecutor, Runnable onCompletion) {
public void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion) {
taskExecutor.execute(() -> {
var list = listSupplier.get();
final int size = list.size();
if (size == 0) {
onCompletion.run();
} else if (size <= getChunkingThreshold()) {
processList(list, onCompletion);
} else if (size <= getChunkSize(taskExecutor, size)) {
processList(list, context, onCompletion);
} else {
dispatchChunks(list, taskExecutor, onCompletion);
dispatchChunks(list, taskExecutor, context, onCompletion);
}
});
}
private void dispatchChunks(List<T> suppliedList, TaskExecutor taskExecutor, Runnable onCompletion) {
private void dispatchChunks(List<T> suppliedList, TaskExecutor taskExecutor, C context, Runnable onCompletion) {
final int size = suppliedList.size();
final int chunkSize = getChunkSize(taskExecutor, size);
@ -42,7 +43,7 @@ public record RunOnAllPlan<T>(Supplier<List<T>> listSupplier, Consumer<T> action
int start = Math.max(remaining, 0);
var subList = suppliedList.subList(start, end);
taskExecutor.execute(() -> processList(subList, synchronizer));
taskExecutor.execute(() -> processList(subList, context, synchronizer));
}
}
@ -50,14 +51,10 @@ public record RunOnAllPlan<T>(Supplier<List<T>> listSupplier, Consumer<T> action
return MoreMath.ceilingDiv(totalSize, taskExecutor.getThreadCount() * 32);
}
private void processList(List<T> suppliedList, Runnable onCompletion) {
private void processList(List<T> suppliedList, C context, Runnable onCompletion) {
for (T t : suppliedList) {
action.accept(t);
action.accept(t, context);
}
onCompletion.run();
}
private static int getChunkingThreshold() {
return 256;
}
}

View file

@ -1,64 +0,0 @@
package com.jozufozu.flywheel.lib.task;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.lib.math.MoreMath;
public record RunOnAllWithContextPlan<T, C>(Supplier<List<T>> listSupplier,
BiConsumer<T, C> action) implements SimplyComposedPlan<C> {
public static <T, C> Plan<C> of(Supplier<List<T>> iterable, BiConsumer<T, C> forEach) {
return new RunOnAllWithContextPlan<>(iterable, forEach);
}
@Override
public void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion) {
taskExecutor.execute(() -> {
var list = listSupplier.get();
final int size = list.size();
if (size == 0) {
onCompletion.run();
} else if (size <= getChunkingThreshold()) {
processList(list, context, onCompletion);
} else {
dispatchChunks(list, taskExecutor, context, onCompletion);
}
});
}
private void dispatchChunks(List<T> suppliedList, TaskExecutor taskExecutor, C context, Runnable onCompletion) {
final int size = suppliedList.size();
final int chunkSize = getChunkSize(taskExecutor, size);
var synchronizer = new Synchronizer(MoreMath.ceilingDiv(size, chunkSize), onCompletion);
int remaining = size;
while (remaining > 0) {
int end = remaining;
remaining -= chunkSize;
int start = Math.max(remaining, 0);
var subList = suppliedList.subList(start, end);
taskExecutor.execute(() -> processList(subList, context, synchronizer));
}
}
private static int getChunkSize(TaskExecutor taskExecutor, int totalSize) {
return MoreMath.ceilingDiv(totalSize, taskExecutor.getThreadCount() * 32);
}
private void processList(List<T> suppliedList, C context, Runnable onCompletion) {
for (T t : suppliedList) {
action.accept(t, context);
}
onCompletion.run();
}
private static int getChunkingThreshold() {
return 256;
}
}

View file

@ -1,5 +1,7 @@
package com.jozufozu.flywheel.lib.task;
import java.util.function.Function;
import com.jozufozu.flywheel.api.task.Plan;
public interface SimplyComposedPlan<C> extends Plan<C> {
@ -8,11 +10,21 @@ public interface SimplyComposedPlan<C> extends Plan<C> {
return new BarrierPlan<>(this, plan);
}
@Override
default <D> Plan<C> thenMap(Function<C, D> map, Plan<D> plan) {
return then(new MapContextPlan<>(map, plan));
}
@Override
default Plan<C> and(Plan<C> plan) {
return NestedPlan.of(this, plan);
}
@Override
default <D> Plan<C> andMap(Function<C, D> map, Plan<D> plan) {
return and(new MapContextPlan<>(map, plan));
}
@Override
default Plan<C> maybeSimplify() {
return this;

View file

@ -1,5 +1,7 @@
package com.jozufozu.flywheel.lib.task;
import java.util.function.Function;
import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.task.TaskExecutor;
@ -24,11 +26,21 @@ public class UnitPlan<C> implements Plan<C> {
return plan;
}
@Override
public <D> Plan<C> thenMap(Function<C, D> map, Plan<D> plan) {
return new MapContextPlan<>(map, plan);
}
@Override
public Plan<C> and(Plan<C> plan) {
return plan;
}
@Override
public <D> Plan<C> andMap(Function<C, D> map, Plan<D> plan) {
return new MapContextPlan<>(map, plan);
}
@Override
public Plan<C> maybeSimplify() {
return this;

View file

@ -5,6 +5,7 @@ import org.joml.FrustumIntersection;
import com.jozufozu.flywheel.api.visual.BlockEntityVisual;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.TickableVisual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.impl.visualization.manager.BlockEntityVisualManager;
import com.jozufozu.flywheel.lib.box.ImmutableBox;
@ -50,11 +51,6 @@ public abstract class AbstractBlockEntityVisual<T extends BlockEntity> extends A
return blockEntity.getBlockState() != blockState;
}
@Override
public double distanceSquared(double x, double y, double z) {
return pos.distToCenterSqr(x, y, z);
}
@Override
public ImmutableBox getVolume() {
return MutableBox.from(pos);
@ -72,8 +68,24 @@ public abstract class AbstractBlockEntityVisual<T extends BlockEntity> extends A
return visualPos;
}
public boolean isVisible(FrustumIntersection frustum) {
return frustum.testAab(visualPos.getX(), visualPos.getY(), visualPos.getZ(),
visualPos.getX() + 1, visualPos.getY() + 1, visualPos.getZ() + 1);
/**
* @param frustum The current frustum.
* @return {@code true} if this visual within the given frustum.
*/
public boolean visible(FrustumIntersection frustum) {
return frustum.testAab(visualPos.getX(), visualPos.getY(), visualPos.getZ(), visualPos.getX() + 1, visualPos.getY() + 1, visualPos.getZ() + 1);
}
/**
* Limits which frames this visual is updated on based on its distance from the camera.
* <p>
* You may optionally do this check to avoid updating your visual every frame when it is far away.
*
* @param context The current frame context.
* @return {@code true} if this visual shouldn't be updated this frame based on its distance from the camera.
*/
public boolean doDistanceLimitThisFrame(VisualFrameContext context) {
return !context.limiter()
.shouldUpdate(pos.distToCenterSqr(context.cameraX(), context.cameraY(), context.cameraZ()));
}
}

View file

@ -35,16 +35,23 @@ import net.minecraft.world.phys.Vec3;
public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVisual implements EntityVisual<T>, TickingLightListener {
protected final T entity;
protected final MutableBox bounds;
protected final EntityVisibilityTester boxTracker;
protected final EntityVisibilityTester visibilityTester;
public AbstractEntityVisual(VisualizationContext ctx, T entity) {
super(ctx, entity.level);
this.entity = entity;
bounds = MutableBox.from(entity.getBoundingBox());
boxTracker = new EntityVisibilityTester(entity, ctx.renderOrigin());
visibilityTester = new EntityVisibilityTester(entity, ctx.renderOrigin());
}
@Override
/**
* Calculate the distance squared between this visual and the given <em>world</em> position.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param z The z coordinate.
* @return The distance squared between this visual and the given position.
*/
public double distanceSquared(double x, double y, double z) {
return entity.distanceToSqr(x, y, z);
}
@ -92,15 +99,10 @@ public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVis
*/
public Vector3f getVisualPosition(float partialTicks) {
Vec3 pos = entity.position();
return new Vector3f((float) (Mth.lerp(partialTicks, entity.xOld, pos.x) - renderOrigin.getX()),
(float) (Mth.lerp(partialTicks, entity.yOld, pos.y) - renderOrigin.getY()),
(float) (Mth.lerp(partialTicks, entity.zOld, pos.z) - renderOrigin.getZ()));
return new Vector3f((float) (Mth.lerp(partialTicks, entity.xOld, pos.x) - renderOrigin.getX()), (float) (Mth.lerp(partialTicks, entity.yOld, pos.y) - renderOrigin.getY()), (float) (Mth.lerp(partialTicks, entity.zOld, pos.z) - renderOrigin.getZ()));
}
public boolean isVisible(FrustumIntersection frustum) {
if (entity.noCulling) {
return true;
}
return boxTracker.isVisible(frustum);
public boolean visible(FrustumIntersection frustum) {
return entity.noCulling || visibilityTester.check(frustum);
}
}

View file

@ -24,7 +24,13 @@ public class EntityVisibilityTester {
this.renderOrigin = renderOrigin;
}
public boolean isVisible(FrustumIntersection frustum) {
/**
* Check whether the Entity is visible.
*
* @param frustum The frustum to test against.
* @return {@code true} if the Entity is visible, {@code false} otherwise.
*/
public boolean check(FrustumIntersection frustum) {
AABB aabb = entity.getBoundingBoxForCulling();
boolean visible = adjustAndTestAABB(frustum, aabb);

View file

@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.lib.instance.InstanceTypes;
import com.jozufozu.flywheel.lib.instance.OrientedInstance;
@ -38,11 +39,21 @@ public class BellVisual extends AbstractBlockEntityVisual<BellBlockEntity> imple
bell = createBellInstance().setPivot(0.5f, 0.75f, 0.5f)
.setPosition(getVisualPosition());
updateRotation();
super.init();
}
@Override
public void beginFrame() {
public void beginFrame(VisualFrameContext context) {
if (doDistanceLimitThisFrame(context) || !visible(context.frustum())) {
return;
}
updateRotation();
}
private void updateRotation() {
float ringTime = (float) blockEntity.ticks + AnimationTickHolder.getPartialTicks();
if (ringTime == lastRingTime) {

View file

@ -7,6 +7,7 @@ import java.util.function.BiFunction;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.lib.instance.InstanceTypes;
import com.jozufozu.flywheel.lib.instance.OrientedInstance;
@ -80,7 +81,11 @@ public class ChestVisual<T extends BlockEntity & LidBlockEntity> extends Abstrac
}
@Override
public void beginFrame() {
public void beginFrame(VisualFrameContext context) {
if (doDistanceLimitThisFrame(context) || !visible(context.frustum())) {
return;
}
float progress = lidProgress.get(AnimationTickHolder.getPartialTicks());
if (lastProgress == progress) {
@ -96,7 +101,7 @@ public class ChestVisual<T extends BlockEntity & LidBlockEntity> extends Abstrac
lid.loadIdentity()
.translate(getVisualPosition())
.translate(0, 9f/16f, 0)
.translate(0, 9f / 16f, 0)
.centre()
.multiply(baseRotation)
.unCentre()

View file

@ -5,6 +5,8 @@ import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.TickableVisual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visual.VisualTickContext;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.lib.instance.InstanceTypes;
import com.jozufozu.flywheel.lib.instance.TransformedInstance;
@ -44,11 +46,13 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
blockState = entity.getDisplayBlockState();
contents = createContentsInstance();
updatePosition();
super.init();
}
@Override
public void tick() {
public void tick(VisualTickContext c) {
BlockState displayBlockState = entity.getDisplayBlockState();
if (displayBlockState != blockState) {
@ -62,12 +66,19 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
}
@Override
public void beginFrame() {
public void beginFrame(VisualFrameContext context) {
if (visible(context.frustum())) {
return;
}
// TODO: add proper way to temporarily disable rendering a specific instance
if (!active) {
return;
}
updatePosition();
}
private void updatePosition() {
TransformStack tstack = TransformStack.cast(stack);
stack.setIdentity();
float pt = AnimationTickHolder.getPartialTicks();
@ -76,7 +87,7 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
float yaw = Mth.lerp(pt, entity.yRotO, entity.getYRot());
long i = (long)entity.getId() * 493286711L;
long i = (long) entity.getId() * 493286711L;
i = i * i * 4392167121L + i * 98761L;
float f = (((float)(i >> 16 & 7L) + 0.5F) / 8 - 0.5F) * 0.004F;
float f1 = (((float)(i >> 20 & 7L) + 0.5F) / 8 - 0.5F) * 0.004F;
@ -134,11 +145,6 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
body.setTransform(stack);
}
@Override
public boolean decreaseFramerateWithDistance() {
return false;
}
@Override
public void updateLight() {
if (contents == null) {

View file

@ -6,6 +6,7 @@ import java.util.function.Function;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.lib.instance.InstanceTypes;
import com.jozufozu.flywheel.lib.instance.TransformedInstance;
@ -81,10 +82,15 @@ public class ShulkerBoxVisual extends AbstractBlockEntityVisual<ShulkerBoxBlockE
}
@Override
public void beginFrame() {
public void beginFrame(VisualFrameContext context) {
if (doDistanceLimitThisFrame(context) || !visible(context.frustum())) {
return;
}
float progress = blockEntity.getProgress(AnimationTickHolder.getPartialTicks());
if (progress == lastProgress) return;
if (progress == lastProgress) {
return;
}
lastProgress = progress;
Quaternion spin = Vector3f.YP.rotationDegrees(270.0F * progress);

View file

@ -5,7 +5,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.joml.FrustumIntersection;
import org.joml.Vector3f;
import com.jozufozu.flywheel.api.event.ReloadRenderersEvent;
@ -14,6 +13,8 @@ import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.Effect;
import com.jozufozu.flywheel.api.visual.EffectVisual;
import com.jozufozu.flywheel.api.visual.TickableVisual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visual.VisualTickContext;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.impl.visualization.VisualizedRenderDispatcher;
import com.jozufozu.flywheel.lib.box.ImmutableBox;
@ -268,12 +269,12 @@ public class ExampleEffect implements Effect {
}
@Override
public void tick() {
public void tick(VisualTickContext c) {
self.tick();
}
@Override
public void beginFrame() {
public void beginFrame(VisualFrameContext context) {
float partialTicks = AnimationTickHolder.getPartialTicks();
var x = Mth.lerp(partialTicks, self.lastPosition.x, self.position.x);
@ -285,25 +286,5 @@ public class ExampleEffect implements Effect {
.translate(x, y, z)
.scale(RENDER_SCALE);
}
@Override
public boolean decreaseTickRateWithDistance() {
return false;
}
@Override
public boolean decreaseFramerateWithDistance() {
return false;
}
@Override
public boolean isVisible(FrustumIntersection frustum) {
return true;
}
@Override
public double distanceSquared(double x, double y, double z) {
return 0;
}
}
}