re-add some core rendering handlers
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
package com.seibel.distanthorizons.core.render;
|
||||
|
||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
||||
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||
import com.seibel.distanthorizons.core.api.internal.SharedApi;
|
||||
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.jar.EPlatform;
|
||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||
import com.seibel.distanthorizons.core.util.RenderUtil;
|
||||
import com.seibel.distanthorizons.core.util.math.Mat4f;
|
||||
import com.seibel.distanthorizons.core.util.math.Vec3d;
|
||||
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
import com.seibel.distanthorizons.core.world.IDhClientWorld;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.IMcGenericRenderer;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
|
||||
/**
|
||||
* An extension of {@link DhApiRenderParam}
|
||||
* that allows additional validation and putting all
|
||||
* rendering variables in a single place.
|
||||
*/
|
||||
public class RenderParams extends DhApiRenderParam
|
||||
{
|
||||
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
||||
|
||||
private static final long TIME_FOR_MAC_TO_FINISH_COMPILING_IN_MS = 10_000;
|
||||
private static boolean initialLoadingComplete = false;
|
||||
|
||||
|
||||
public IDhClientWorld dhClientWorld;
|
||||
public IDhClientLevel dhClientLevel;
|
||||
/** more specific override of the API value {@link DhApiRenderParam#clientLevelWrapper} */
|
||||
public IClientLevelWrapper clientLevelWrapper;
|
||||
public ILightMapWrapper lightmap;
|
||||
public RenderBufferHandler renderBufferHandler;
|
||||
public IMcGenericRenderer genericRenderer;
|
||||
public Vec3d exactCameraPosition;
|
||||
/** @see DhRenderState#vanillaFogEnabled */
|
||||
public boolean vanillaFogEnabled;
|
||||
|
||||
public boolean validationRun = false;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
//region
|
||||
|
||||
public RenderParams(EDhApiRenderPass renderPass, DhRenderState renderState)
|
||||
{
|
||||
this(renderPass,
|
||||
renderState.partialTickTime,
|
||||
renderState.mcProjectionMatrix, renderState.mcModelViewMatrix,
|
||||
renderState.clientLevelWrapper,
|
||||
renderState.vanillaFogEnabled
|
||||
);
|
||||
}
|
||||
private RenderParams(
|
||||
EDhApiRenderPass renderPass,
|
||||
float newPartialTicks,
|
||||
Mat4f newMcProjectionMatrix, Mat4f newMcModelViewMatrix,
|
||||
IClientLevelWrapper clientLevelWrapper,
|
||||
boolean vanillaFogEnabled
|
||||
)
|
||||
{
|
||||
super(renderPass,
|
||||
newPartialTicks,
|
||||
RenderUtil.getNearClipPlaneInBlocks(), RenderUtil.getFarClipPlaneDistanceInBlocks(),
|
||||
newMcProjectionMatrix, newMcModelViewMatrix,
|
||||
RenderUtil.createLodProjectionMatrix(newMcProjectionMatrix), RenderUtil.createLodModelViewMatrix(newMcModelViewMatrix),
|
||||
clientLevelWrapper.getMinHeight(),
|
||||
clientLevelWrapper);
|
||||
|
||||
|
||||
this.dhClientWorld = SharedApi.tryGetDhClientWorld();
|
||||
if (this.dhClientWorld != null)
|
||||
{
|
||||
this.dhClientLevel = (IDhClientLevel) this.dhClientWorld.getLevel(clientLevelWrapper);
|
||||
if (this.dhClientLevel != null)
|
||||
{
|
||||
this.renderBufferHandler = this.dhClientLevel.getRenderBufferHandler();
|
||||
this.genericRenderer = this.dhClientLevel.getGenericRenderer();
|
||||
}
|
||||
}
|
||||
|
||||
this.clientLevelWrapper = clientLevelWrapper;
|
||||
this.lightmap = MC_RENDER.getLightmapWrapper(this.clientLevelWrapper);
|
||||
|
||||
if (MC_CLIENT.playerExists())
|
||||
{
|
||||
this.exactCameraPosition = MC_RENDER.getCameraExactPosition();
|
||||
}
|
||||
|
||||
this.vanillaFogEnabled = vanillaFogEnabled;
|
||||
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//======================//
|
||||
// parameter validation //
|
||||
//======================//
|
||||
//region
|
||||
|
||||
/**
|
||||
* Should be called before rendering is done.
|
||||
* @return a message if LODs shouldn't be rendered, null if the LODs can render
|
||||
*/
|
||||
public String getValidationErrorMessage(long firstRenderTimeMs)
|
||||
{
|
||||
// Note: all strings here should be constants to prevent String allocations
|
||||
|
||||
this.validationRun = true;
|
||||
|
||||
|
||||
if (!MC_CLIENT.playerExists())
|
||||
{
|
||||
return "No Player Exists";
|
||||
}
|
||||
|
||||
if (this.dhClientWorld == null)
|
||||
{
|
||||
return "No DH Client World Loaded";
|
||||
}
|
||||
|
||||
if (this.dhClientLevel == null)
|
||||
{
|
||||
return "No DH Client Level Loaded";
|
||||
}
|
||||
|
||||
if (this.clientLevelWrapper == null)
|
||||
{
|
||||
return "No Client Level Wrapper Loaded";
|
||||
}
|
||||
|
||||
if (this.lightmap == null)
|
||||
{
|
||||
return "No Lightmap Loaded";
|
||||
}
|
||||
|
||||
if (this.renderBufferHandler == null)
|
||||
{
|
||||
return "No RenderBufferHandler Present";
|
||||
}
|
||||
|
||||
if (this.genericRenderer == null)
|
||||
{
|
||||
return "No Generic Renderer Present";
|
||||
}
|
||||
|
||||
if (this.dhModelViewMatrix == null
|
||||
|| this.mcModelViewMatrix == null)
|
||||
{
|
||||
return "No MVM or Proj Matrix Given";
|
||||
}
|
||||
|
||||
if (AbstractOptifineAccessor.optifinePresent()
|
||||
&& MC_RENDER.getTargetFramebuffer() == -1)
|
||||
{
|
||||
// wait for MC to finish setting up their renderer
|
||||
return "Optifine Target Frame Buffer not set";
|
||||
}
|
||||
|
||||
|
||||
// potential fix for a segfault when
|
||||
// Sodium and DH are running together
|
||||
if (EPlatform.get() == EPlatform.MACOS
|
||||
&& !initialLoadingComplete)
|
||||
{
|
||||
// Once MC starts rendering, wait a few seconds so
|
||||
// MC/Sodium can finish their shader compiling before DH does its own.
|
||||
// This will allow DH to compile its own shaders after Sodium finishes
|
||||
// compiling its own.
|
||||
long nowMs = System.currentTimeMillis();
|
||||
long firstAllowedRenderTimeMs = firstRenderTimeMs + TIME_FOR_MAC_TO_FINISH_COMPILING_IN_MS;
|
||||
if (nowMs < firstAllowedRenderTimeMs)
|
||||
{
|
||||
return "Waiting for initial MC compile...";
|
||||
}
|
||||
|
||||
|
||||
// null shouldn't happen, but just in case
|
||||
PriorityTaskPicker.Executor renderLoadExecutor = ThreadPoolUtil.getRenderLoadingExecutor();
|
||||
if (renderLoadExecutor == null)
|
||||
{
|
||||
return "Waiting for DH Threadpool...";
|
||||
}
|
||||
|
||||
// wait for DH to finish loading, by the time that's done
|
||||
// java should have finished all of DH's JIT compiling,
|
||||
// which will hopefully mean less concurrency and thus a lower
|
||||
// chance of breaking
|
||||
// (plus this gives Sodium/vanill a bit longer to finish their setup)
|
||||
int taskCount = renderLoadExecutor.getQueueSize();
|
||||
if (taskCount > 0)
|
||||
{
|
||||
return "Waiting for DH JIT compiling...";
|
||||
}
|
||||
|
||||
initialLoadingComplete = true;
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
}
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.distanthorizons.core.render.renderer;
|
||||
|
||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
|
||||
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderObjectFactory;
|
||||
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
|
||||
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
|
||||
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
|
||||
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.RenderUtil;
|
||||
import com.seibel.distanthorizons.core.util.math.Vec3d;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.IMcGenericRenderer;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class BeaconRenderHandler
|
||||
{
|
||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
||||
private static final IDhApiCustomRenderObjectFactory GENERIC_OBJECT_FACTORY = SingletonInjector.INSTANCE.get(IDhApiCustomRenderObjectFactory.class);
|
||||
|
||||
/** how often should we check if a beacon should be culled? */
|
||||
private static final int MAX_CULLING_FREQUENCY_IN_MS = 1_000;
|
||||
|
||||
private static final Comparator<BeaconBeamDTO> NEGATIVE_BLOCKPOS_COMPARATOR = new NegativeInfiniteBlockPosComparator();
|
||||
|
||||
|
||||
|
||||
private final ReentrantLock updateLock = new ReentrantLock();
|
||||
|
||||
/** only contains the beacons currently being rendered (culled beacons will be missing) */
|
||||
private final IDhApiRenderableBoxGroup activeBeaconBoxRenderGroup;
|
||||
/** contains all beacons that could be rendered (including those that are being culled) */
|
||||
private final ArrayList<DhApiRenderableBox> fullBeaconBoxList = new ArrayList<>();
|
||||
/** contains all beacons that could be rendered */
|
||||
private final HashSet<DhBlockPos> fullBeaconBlockPosSet = new HashSet<>();
|
||||
|
||||
private boolean cullingThreadRunning = false;
|
||||
private boolean updateRenderDataNextFrame = false;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
//region
|
||||
|
||||
public BeaconRenderHandler(@NotNull IMcGenericRenderer renderer)
|
||||
{
|
||||
this.activeBeaconBoxRenderGroup = GENERIC_OBJECT_FACTORY.createAbsolutePositionedGroup(ModInfo.NAME+":Beacons", new ArrayList<>(0));
|
||||
this.activeBeaconBoxRenderGroup.setBlockLight(LodUtil.MAX_MC_LIGHT);
|
||||
this.activeBeaconBoxRenderGroup.setSkyLight(LodUtil.MAX_MC_LIGHT);
|
||||
this.activeBeaconBoxRenderGroup.setSsaoEnabled(false);
|
||||
this.activeBeaconBoxRenderGroup.setShading(DhApiRenderableBoxGroupShading.getUnshaded());
|
||||
this.activeBeaconBoxRenderGroup.setPreRenderFunc(this::beforeRender);
|
||||
|
||||
renderer.add(this.activeBeaconBoxRenderGroup);
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// render handling //
|
||||
//=================//
|
||||
//region
|
||||
|
||||
public void startRenderingBeacons(ArrayList<BeaconBeamDTO> beaconList, byte detailLevel)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.updateLock.lock();
|
||||
|
||||
|
||||
// how wide should each beacon be?
|
||||
int beaconBlockWidth = 1;
|
||||
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
|
||||
{
|
||||
beaconBlockWidth = DhSectionPos.getBlockWidth(detailLevel);
|
||||
}
|
||||
|
||||
|
||||
ArrayList<BeaconBeamDTO> sortedBeaconList = new ArrayList<>(beaconList);
|
||||
|
||||
// merge distant beams if requested
|
||||
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
|
||||
{
|
||||
// sort beacons from neg inf -> pos inf
|
||||
// so we can consistently merge adjacent beacons
|
||||
sortedBeaconList.sort(NEGATIVE_BLOCKPOS_COMPARATOR);
|
||||
|
||||
// go through each beacon...
|
||||
for (int outerIndex = 0; outerIndex < sortedBeaconList.size(); outerIndex++)
|
||||
{
|
||||
BeaconBeamDTO outerBeacon = sortedBeaconList.get(outerIndex);
|
||||
DhBlockPos outerBlockPos = outerBeacon.blockPos;
|
||||
|
||||
// ...and remove any beacons that are within the block width to prevent overlaps
|
||||
for (int mergeIndex = outerIndex + 1; mergeIndex < sortedBeaconList.size(); mergeIndex++)
|
||||
{
|
||||
BeaconBeamDTO beaconToMerge = sortedBeaconList.get(mergeIndex);
|
||||
DhBlockPos mergeBlockPos = beaconToMerge.blockPos;
|
||||
|
||||
int xDiff = mergeBlockPos.getX() - outerBlockPos.getX();
|
||||
int zDiff = mergeBlockPos.getZ() - outerBlockPos.getZ();
|
||||
|
||||
// merge (remove) this beacon if
|
||||
// it's close to the outer beacon
|
||||
if (xDiff < beaconBlockWidth
|
||||
&& zDiff < beaconBlockWidth)
|
||||
{
|
||||
sortedBeaconList.remove(mergeIndex);
|
||||
mergeIndex--; // minus 1 so we don't go past the end of the array when incrementing in the for loop up top
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//LOGGER.info("startRenderingBeacons ["+sortedBeaconList+"]");
|
||||
|
||||
// add each beacon to the renderer
|
||||
for (int i = 0; i < sortedBeaconList.size(); i++)
|
||||
{
|
||||
BeaconBeamDTO beacon = sortedBeaconList.get(i);
|
||||
if (!this.fullBeaconBlockPosSet.add(beacon.blockPos))
|
||||
{
|
||||
// skip already present beacons
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
int maxBeaconBeamHeight = Config.Client.Advanced.Graphics.GenericRendering.beaconRenderHeight.get();
|
||||
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
|
||||
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
|
||||
new DhApiVec3d(beacon.blockPos.getX() + beaconBlockWidth, maxBeaconBeamHeight, beacon.blockPos.getZ() + beaconBlockWidth),
|
||||
beacon.color,
|
||||
EDhApiBlockMaterial.ILLUMINATED
|
||||
);
|
||||
|
||||
this.activeBeaconBoxRenderGroup.add(beaconBox);
|
||||
this.fullBeaconBoxList.add(beaconBox);
|
||||
this.activeBeaconBoxRenderGroup.triggerBoxChange();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.updateLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void stopRenderingBeaconsInRange(long pos)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.updateLock.lock();
|
||||
|
||||
Predicate<DhApiRenderableBox> removeBoxPredicate = (DhApiRenderableBox box) ->
|
||||
{
|
||||
DhBlockPos blockPos = new DhBlockPos((int)box.minPos.x, (int)box.minPos.y, (int)box.minPos.z);
|
||||
boolean contains = DhSectionPos.contains(pos, blockPos);
|
||||
//if (contains)
|
||||
//{
|
||||
// LOGGER.info("stopRenderingBeaconsInRange ["+DhSectionPos.toString(pos)+"] ["+blockPos+"]");
|
||||
//}
|
||||
return contains;
|
||||
};
|
||||
this.activeBeaconBoxRenderGroup.removeIf(removeBoxPredicate);
|
||||
this.fullBeaconBoxList.removeIf(removeBoxPredicate);
|
||||
|
||||
this.fullBeaconBlockPosSet.removeIf((DhBlockPos blockPos) -> DhSectionPos.contains(pos, blockPos));
|
||||
|
||||
this.activeBeaconBoxRenderGroup.triggerBoxChange();
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.updateLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void beforeRender(DhApiRenderParam renderEventParam)
|
||||
{
|
||||
if (Config.Client.Advanced.Graphics.Culling.disableBeaconDistanceCulling.get())
|
||||
{
|
||||
// this could be called only when the player moves, but it's an extremely cheap check,
|
||||
// so there isn't much of a reason to bother
|
||||
this.tryUpdateBeaconCullingAsync();
|
||||
}
|
||||
|
||||
|
||||
// this must be called on the render thread to prevent concurrency issues
|
||||
if (this.updateRenderDataNextFrame)
|
||||
{
|
||||
this.activeBeaconBoxRenderGroup.triggerBoxChange();
|
||||
this.updateRenderDataNextFrame = false;
|
||||
}
|
||||
this.activeBeaconBoxRenderGroup.setActive(Config.Client.Advanced.Graphics.GenericRendering.enableBeaconRendering.get());
|
||||
}
|
||||
/** does nothing if the culling thread is already running */
|
||||
private void tryUpdateBeaconCullingAsync()
|
||||
{
|
||||
ThreadPoolExecutor executor = ThreadPoolUtil.getBeaconCullingExecutor();
|
||||
if (executor != null
|
||||
&& !this.cullingThreadRunning)
|
||||
{
|
||||
this.cullingThreadRunning = true;
|
||||
|
||||
try
|
||||
{
|
||||
executor.execute(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(MAX_CULLING_FREQUENCY_IN_MS);
|
||||
}
|
||||
catch (InterruptedException ignore) { }
|
||||
|
||||
try
|
||||
{
|
||||
// lock to make sure we don't try adding beacons to the arrays while processing them
|
||||
this.updateLock.lock();
|
||||
|
||||
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
|
||||
|
||||
// fading by the overdraw prevention amount helps reduce beacons from rendering strangely
|
||||
// on the border of DH's render distance
|
||||
float dhFadeDistance = RenderUtil.getNearClipPlaneInBlocks();
|
||||
|
||||
|
||||
// Clear the existing box group so we can re-populate it.
|
||||
// Since the box group is only used when we trigger an update, clearing it here
|
||||
// and repopulating it is fine.
|
||||
this.activeBeaconBoxRenderGroup.clear();
|
||||
|
||||
// While iterating over every beacon isn't a great way of doing this,
|
||||
// when 940 beacons were tested this only took ~0.9 Milliseconds, so as long as
|
||||
// we aren't freezing the render thread this method of culling works just fine.
|
||||
for (DhApiRenderableBox box : this.fullBeaconBoxList)
|
||||
{
|
||||
// if a beacon is outside the vanilla render distance render it
|
||||
double distance = Vec3d.getHorizontalDistance(cameraPos, box.minPos);
|
||||
if (distance > dhFadeDistance)
|
||||
{
|
||||
this.activeBeaconBoxRenderGroup.add(box);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateRenderDataNextFrame = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unexpected issue while updating beacon culling. Error: " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.updateLock.unlock();
|
||||
this.cullingThreadRunning = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (RejectedExecutionException ignore)
|
||||
{ /* If this happens that means everything is already shut down and no culling is necessary */ }
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
//================//
|
||||
//region
|
||||
|
||||
private static class NegativeInfiniteBlockPosComparator implements Comparator<BeaconBeamDTO>
|
||||
{
|
||||
@Override
|
||||
public int compare(BeaconBeamDTO beacon1, BeaconBeamDTO beacon2)
|
||||
{
|
||||
DhBlockPos blockPos1 = beacon1.blockPos;
|
||||
DhBlockPos blockPos2 = beacon2.blockPos;
|
||||
|
||||
// sort by X, then by Z
|
||||
if (blockPos1.getX() != blockPos2.getX())
|
||||
{
|
||||
return Integer.compare(blockPos1.getX(), blockPos2.getX());
|
||||
}
|
||||
return Integer.compare(blockPos1.getZ(), blockPos2.getZ());
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
}
|
||||
+619
@@ -0,0 +1,619 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.distanthorizons.core.render.renderer;
|
||||
|
||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
|
||||
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderObjectFactory;
|
||||
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
|
||||
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
|
||||
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
|
||||
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.math.Vec3d;
|
||||
import com.seibel.distanthorizons.core.util.math.Vec3f;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.IMcGenericRenderer;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
|
||||
public class CloudRenderHandler
|
||||
{
|
||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
||||
private static final IDhApiCustomRenderObjectFactory GENERIC_OBJECT_FACTORY = SingletonInjector.INSTANCE.get(IDhApiCustomRenderObjectFactory.class);
|
||||
|
||||
private static final String CLOUD_RESOURCE_TEXTURE_PATH = "assets/distanthorizons/textures/clouds.png";
|
||||
|
||||
private static final boolean DEBUG_BORDER_COLORS = false;
|
||||
|
||||
/**
|
||||
* How wide an individual box is. <br>
|
||||
* Measured in blocks.
|
||||
*/
|
||||
private static final int CLOUD_BOX_WIDTH = 128;
|
||||
/** measured in blocks */
|
||||
private static final int CLOUD_BOX_THICKNESS = 32;
|
||||
|
||||
/**
|
||||
* How many cloud groups wide can we render at maximum? <br>
|
||||
* 1 = 3x3 or 9 total <br>
|
||||
* 2 = 5x5 or 25 total <br> <br>
|
||||
*
|
||||
* 5 seems like a good count since it can cover up to around 2048 render distance.
|
||||
*/
|
||||
private static final int CLOUD_INSTANCE_RADIUS_COUNT = 5;
|
||||
|
||||
private static final float MOVE_SPEED_IN_BLOCKS_PER_SECOND = 6.0f;
|
||||
|
||||
|
||||
private final IDhApiRenderableBoxGroup[][] boxGroupByOffset
|
||||
// radius * 2 to get the diameter
|
||||
// + 1 so we get an odd number wide (needed so we can have a center position)
|
||||
= new IDhApiRenderableBoxGroup[(CLOUD_INSTANCE_RADIUS_COUNT * 2) + 1][(CLOUD_INSTANCE_RADIUS_COUNT * 2) + 1];
|
||||
|
||||
private final IDhClientLevel level;
|
||||
private final IMcGenericRenderer renderer;
|
||||
|
||||
/** cached array so we don't need to re-create it each frame for each cloud group */
|
||||
private final Vec3d[] cullingCorners = new Vec3d[]
|
||||
{
|
||||
// the values of each will be overwritten during the culling pass
|
||||
new Vec3d(),
|
||||
new Vec3d(),
|
||||
new Vec3d(),
|
||||
new Vec3d(),
|
||||
};
|
||||
|
||||
|
||||
private boolean disabledWarningLogged = false;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
//region
|
||||
|
||||
public CloudRenderHandler(IDhClientLevel level, IMcGenericRenderer renderer)
|
||||
{
|
||||
this.level = level;
|
||||
this.renderer = renderer;
|
||||
|
||||
|
||||
|
||||
//=======================//
|
||||
// get the cloud texture //
|
||||
//=======================//
|
||||
//region
|
||||
|
||||
// default to a single empty slot in case the texture is broken
|
||||
boolean[][] cloudLocations = new boolean[1][1];
|
||||
try
|
||||
{
|
||||
cloudLocations = getCloudsFromTexture();
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOGGER.error("Unexpected issue getting cloud texture, error: ["+e.getMessage()+"].", e);
|
||||
}
|
||||
|
||||
if (cloudLocations.length != 0 &&
|
||||
cloudLocations.length != cloudLocations[0].length)
|
||||
{
|
||||
LOGGER.warn("Non-square cloud texture found, some parts of the texture will be clipped off.");
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//===================//
|
||||
// parse the texture //
|
||||
//===================//
|
||||
//region
|
||||
|
||||
int textureWidth = cloudLocations.length;
|
||||
ArrayList<DhApiRenderableBox> boxList = new ArrayList<>(512);
|
||||
for (int x = 0; x < textureWidth; x ++)
|
||||
{
|
||||
for (int z = 0; z < textureWidth; z ++)
|
||||
{
|
||||
if (cloudLocations[x][z])
|
||||
{
|
||||
// start a new box in Z direction
|
||||
int startZ = z;
|
||||
int startX = x;
|
||||
int endZ = startZ;
|
||||
int endX = x+1;
|
||||
|
||||
|
||||
|
||||
//==========================//
|
||||
// merge in the Z direction //
|
||||
//==========================//
|
||||
|
||||
// Find the cloud's length in the Z direction
|
||||
while (endZ < textureWidth
|
||||
&& cloudLocations[x][endZ])
|
||||
{
|
||||
endZ++;
|
||||
}
|
||||
// update the z iterator so we can skip over everything included in this cloud
|
||||
z = endZ - 1;
|
||||
|
||||
|
||||
|
||||
//==========================//
|
||||
// merge in the X direction //
|
||||
//==========================//
|
||||
|
||||
for (int currentX = startX + 1; currentX < textureWidth; currentX++)
|
||||
{
|
||||
boolean canMergeInXDir = true;
|
||||
|
||||
// check if all locations in this column are true
|
||||
for (int adjacentZ = startZ; adjacentZ < endZ; adjacentZ++)
|
||||
{
|
||||
if (!cloudLocations[currentX][adjacentZ])
|
||||
{
|
||||
// at least one pixel in the texture is false,
|
||||
// so we can't merge in this direction
|
||||
canMergeInXDir = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (canMergeInXDir)
|
||||
{
|
||||
// mark the adjacent column as processed
|
||||
for (int currentZ = startZ; currentZ < endZ; currentZ++)
|
||||
{
|
||||
// by flipping all the pixels in the adjacent column to false,
|
||||
// we don't have to worry about adding another cloud
|
||||
cloudLocations[currentX][currentZ] = false;
|
||||
}
|
||||
|
||||
endX = (currentX + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//============================//
|
||||
// Create the renderable box //
|
||||
//============================//
|
||||
|
||||
// endZ contains the last cloud index
|
||||
// so the cloud now goes from startZ to endZ (inclusive)
|
||||
int minXBlockPos = startX * CLOUD_BOX_WIDTH;
|
||||
int minZBlockPos = startZ * CLOUD_BOX_WIDTH;
|
||||
int maxXBlockPos = endX * CLOUD_BOX_WIDTH;
|
||||
int maxZBlockPos = endZ * CLOUD_BOX_WIDTH;
|
||||
|
||||
// this color is changed at render time based on the level time
|
||||
Color color = new Color(255,255,255,255);
|
||||
if (DEBUG_BORDER_COLORS)
|
||||
{
|
||||
// equals is included so the boarder is 2 blocks wide, making it easier to see
|
||||
if (x <= 1) { color = Color.RED; }
|
||||
else if (x >= textureWidth - 2) { color = Color.GREEN; }
|
||||
if (z <= 1) { color = Color.BLUE; }
|
||||
else if (z >= textureWidth - 2) { color = Color.BLACK; }
|
||||
}
|
||||
|
||||
DhApiRenderableBox box = new DhApiRenderableBox(
|
||||
new DhApiVec3d(minXBlockPos, 0, minZBlockPos),
|
||||
new DhApiVec3d(maxXBlockPos, CLOUD_BOX_THICKNESS, maxZBlockPos),
|
||||
color,
|
||||
EDhApiBlockMaterial.UNKNOWN
|
||||
);
|
||||
boxList.add(box);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//========================//
|
||||
// create the renderables //
|
||||
//========================//
|
||||
//region
|
||||
|
||||
// slightly lighter shading than the default
|
||||
DhApiRenderableBoxGroupShading cloudShading = DhApiRenderableBoxGroupShading.getUnshaded();
|
||||
cloudShading.north = cloudShading.south = 0.9f;
|
||||
cloudShading.east = cloudShading.west = 0.8f;
|
||||
cloudShading.top = 1.0f;
|
||||
cloudShading.bottom = 0.7f;
|
||||
|
||||
|
||||
for (int x = -CLOUD_INSTANCE_RADIUS_COUNT; x <= CLOUD_INSTANCE_RADIUS_COUNT; x++)
|
||||
{
|
||||
for (int z = -CLOUD_INSTANCE_RADIUS_COUNT; z <= CLOUD_INSTANCE_RADIUS_COUNT; z++)
|
||||
{
|
||||
IDhApiRenderableBoxGroup boxGroup = GENERIC_OBJECT_FACTORY.createRelativePositionedGroup(
|
||||
ModInfo.NAME + ":Clouds",
|
||||
new DhApiVec3d(0, 0, 0), // the offset will be set during rendering
|
||||
boxList);
|
||||
|
||||
// since cloud colors are set by the level based on the time of day lighting should affect it
|
||||
boxGroup.setBlockLight(LodUtil.MAX_MC_LIGHT);
|
||||
boxGroup.setSkyLight(LodUtil.MAX_MC_LIGHT);
|
||||
boxGroup.setSsaoEnabled(false);
|
||||
boxGroup.setShading(cloudShading);
|
||||
|
||||
CloudParams cloudParams = new CloudParams(textureWidth, x, z);
|
||||
boxGroup.setPreRenderFunc((renderParam) -> this.preRender(renderParam, cloudParams));
|
||||
|
||||
renderer.add(boxGroup);
|
||||
this.boxGroupByOffset[x+CLOUD_INSTANCE_RADIUS_COUNT][z+CLOUD_INSTANCE_RADIUS_COUNT] = boxGroup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// rendering //
|
||||
//===========//
|
||||
//region
|
||||
|
||||
private void preRender(DhApiRenderParam renderParam, CloudParams cloudParams)
|
||||
{
|
||||
IDhApiRenderableBoxGroup boxGroup = this.boxGroupByOffset[cloudParams.instanceOffsetX+CLOUD_INSTANCE_RADIUS_COUNT][cloudParams.instanceOffsetZ+CLOUD_INSTANCE_RADIUS_COUNT];
|
||||
|
||||
|
||||
|
||||
//===================//
|
||||
// should we render? //
|
||||
//===================//
|
||||
|
||||
boolean renderClouds = Config.Client.Advanced.Graphics.GenericRendering.enableCloudRendering.get();
|
||||
boxGroup.setActive(renderClouds);
|
||||
if(!renderClouds)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//if (!this.renderer.getInstancedRenderingAvailable())
|
||||
//{
|
||||
// if (!this.disabledWarningLogged)
|
||||
// {
|
||||
// this.disabledWarningLogged = true;
|
||||
// LOGGER.warn("Instanced rendering unavailable, cloud rendering disabled.");
|
||||
// }
|
||||
// boxGroup.setActive(false);
|
||||
// return;
|
||||
//}
|
||||
|
||||
IClientLevelWrapper clientLevelWrapper = this.level.getClientLevelWrapper();
|
||||
if (clientLevelWrapper == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// cloud movement //
|
||||
//================//
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
float deltaTime = (currentTime - cloudParams.lastFrameTime) / 1000.0f; // Delta time in seconds
|
||||
cloudParams.lastFrameTime = currentTime;
|
||||
|
||||
float deltaX = MOVE_SPEED_IN_BLOCKS_PER_SECOND * deltaTime;
|
||||
// negative delta is to match vanilla's cloud movement
|
||||
cloudParams.deltaOffsetX -= deltaX;
|
||||
// wrap the cloud around after reaching the edge
|
||||
cloudParams.deltaOffsetX %= cloudParams.widthInBlocks;
|
||||
|
||||
|
||||
|
||||
//============================//
|
||||
// camera movement and offset //
|
||||
//============================//
|
||||
|
||||
// camera position
|
||||
int cameraPosX = (int)MC_RENDER.getCameraExactPosition().x;
|
||||
int cameraPosZ = (int)MC_RENDER.getCameraExactPosition().z;
|
||||
// offset the camera position by negative 1 width when below zero to fix off-by-one errors in the negative direction
|
||||
if (cameraPosX < 0) { cameraPosX -= cloudParams.widthInBlocks; }
|
||||
if (cameraPosZ < 0) { cameraPosZ -= cloudParams.widthInBlocks; }
|
||||
|
||||
// determine how many cloud instances away from the origin we are
|
||||
int cloudInstanceOffsetCountX = (cameraPosX / cloudParams.widthInBlocks);
|
||||
int cloudInstanceOffsetCountZ = (cameraPosZ / cloudParams.widthInBlocks);
|
||||
// calculate the new offset
|
||||
float instanceOffsetX = (cloudInstanceOffsetCountX * cloudParams.widthInBlocks);
|
||||
float instanceOffsetZ = (cloudInstanceOffsetCountZ * cloudParams.widthInBlocks);
|
||||
|
||||
|
||||
float newMinPosX =
|
||||
cloudParams.deltaOffsetX
|
||||
+ (cloudParams.instanceOffsetX * cloudParams.widthInBlocks)
|
||||
+ instanceOffsetX + cloudParams.halfWidthInBlocks;
|
||||
float newMinPosY = this.level.getLevelWrapper().getMaxHeight() + 200;
|
||||
float newMinPosZ = cloudParams.deltaOffsetZ
|
||||
+ (cloudParams.instanceOffsetZ * cloudParams.widthInBlocks)
|
||||
+ instanceOffsetZ + cloudParams.halfWidthInBlocks;
|
||||
|
||||
boolean cullCloud = this.shouldCloudBeCulled(
|
||||
newMinPosX, newMinPosY, newMinPosZ,
|
||||
cloudParams
|
||||
);
|
||||
if(cullCloud)
|
||||
{
|
||||
boxGroup.setActive(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===========================//
|
||||
// update color and position //
|
||||
//===========================//
|
||||
|
||||
// if debug colors are enabled don't change them
|
||||
if (!DEBUG_BORDER_COLORS
|
||||
// don't modify cloud groups that aren't active
|
||||
&& boxGroup.isActive())
|
||||
{
|
||||
// cloud color changes based on the time of day and weather so we need to get it from the level
|
||||
Color newCloudColor = clientLevelWrapper.getCloudColor(renderParam.partialTicks);
|
||||
|
||||
|
||||
// all boxes should have the same color, so we can get their current color
|
||||
// via the first box
|
||||
DhApiRenderableBox firstBox = boxGroup.get(0);
|
||||
Color currentBoxColor = firstBox.color;
|
||||
|
||||
// update the boxes if their color should be changed
|
||||
if (!newCloudColor.equals(currentBoxColor))
|
||||
{
|
||||
// Note: cloud instances may share boxes
|
||||
// because of that this method may only need to be called once per all clouds
|
||||
for (DhApiRenderableBox box : boxGroup)
|
||||
{
|
||||
box.color = newCloudColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// trigger an update if this cloud section has a different color
|
||||
if (!cloudParams.previousColor.equals(newCloudColor))
|
||||
{
|
||||
cloudParams.previousColor = newCloudColor;
|
||||
|
||||
boxGroup.triggerBoxChange();
|
||||
}
|
||||
}
|
||||
|
||||
boxGroup.setOriginBlockPos(new DhApiVec3d(newMinPosX, newMinPosY, newMinPosZ));
|
||||
}
|
||||
private boolean shouldCloudBeCulled(
|
||||
float minPosX, float minPosY, float minPosZ,
|
||||
CloudParams cloudParams)
|
||||
{
|
||||
//========================//
|
||||
// skip center 3x3 clouds //
|
||||
//========================//
|
||||
|
||||
// always render the center 3x3 clouds, otherwise we may see
|
||||
// an un-rendered border
|
||||
if (cloudParams.instanceOffsetX >= -1 && cloudParams.instanceOffsetX <= 1
|
||||
&& cloudParams.instanceOffsetZ >= -1 && cloudParams.instanceOffsetZ <= 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// culling prep //
|
||||
//==============//
|
||||
|
||||
// we need all 4 corners since we want to draw any clouds that
|
||||
// could potentially be within render distance
|
||||
this.cullingCorners[0].x = minPosX;
|
||||
this.cullingCorners[0].y = minPosY;
|
||||
this.cullingCorners[0].z = minPosZ;
|
||||
|
||||
this.cullingCorners[1].x = minPosX;
|
||||
this.cullingCorners[1].y = minPosY;
|
||||
this.cullingCorners[1].z = minPosZ + cloudParams.widthInBlocks;
|
||||
|
||||
this.cullingCorners[2].x = minPosX + cloudParams.widthInBlocks;
|
||||
this.cullingCorners[2].y = minPosY;
|
||||
this.cullingCorners[2].z = minPosZ;
|
||||
|
||||
this.cullingCorners[3].x = minPosX + cloudParams.widthInBlocks;
|
||||
this.cullingCorners[3].y = minPosY;
|
||||
this.cullingCorners[3].z = minPosZ + cloudParams.widthInBlocks;
|
||||
|
||||
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
|
||||
Vec3f cameraLookAtVector = MC_RENDER.getLookAtVector();
|
||||
cameraLookAtVector.normalize();
|
||||
|
||||
double renderDistance = Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get()
|
||||
// * 1.5 is so we have a little extra buffer where clouds will render further than
|
||||
// necessary to prevent seeing the cloud border
|
||||
* LodUtil.CHUNK_WIDTH * 1.5;
|
||||
|
||||
|
||||
|
||||
//===================//
|
||||
// check each corner //
|
||||
//===================//
|
||||
|
||||
boolean allOutsideRenderDistance = true;
|
||||
boolean allBehindCamera = true;
|
||||
|
||||
for (Vec3d corner : this.cullingCorners)
|
||||
{
|
||||
// Check if the corner is within the render distance
|
||||
// (ignoring height, since LODs also ignore height)
|
||||
|
||||
Vec3d cornerNoHeight = new Vec3d(corner);
|
||||
cornerNoHeight.y = 0;
|
||||
Vec3d cameraPosNoHeight = new Vec3d(cameraPos);
|
||||
cameraPosNoHeight.y = 0;
|
||||
|
||||
double cornerDistance = cornerNoHeight.getDistance(cameraPosNoHeight);
|
||||
if (cornerDistance <= renderDistance)
|
||||
{
|
||||
allOutsideRenderDistance = false;
|
||||
}
|
||||
|
||||
|
||||
// Check if the corner is in front of the camera (dot product > 0 means in front)
|
||||
Vec3f toCorner = new Vec3f(
|
||||
(float) (corner.x - cameraPos.x),
|
||||
(float) (corner.y - cameraPos.y),
|
||||
(float) (corner.z - cameraPos.z));
|
||||
toCorner.normalize();
|
||||
|
||||
if (cameraLookAtVector.dotProduct(toCorner) > 0)
|
||||
{
|
||||
allBehindCamera = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Cull if all corners are either behind the camera or outside the render distance
|
||||
return allOutsideRenderDistance || allBehindCamera;
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//==================//
|
||||
// texture handling //
|
||||
//==================//
|
||||
//region
|
||||
|
||||
private static boolean[][] getCloudsFromTexture() throws FileNotFoundException, IOException
|
||||
{
|
||||
final ClassLoader loader = CloudRenderHandler.class.getClassLoader();
|
||||
|
||||
boolean[][] whitePixels = null;
|
||||
try(InputStream imageInputStream = loader.getResourceAsStream(CLOUD_RESOURCE_TEXTURE_PATH))
|
||||
{
|
||||
if (imageInputStream == null)
|
||||
{
|
||||
throw new FileNotFoundException("Unable to find cloud texture at resource path: ["+CLOUD_RESOURCE_TEXTURE_PATH+"].");
|
||||
}
|
||||
|
||||
BufferedImage image = ImageIO.read(imageInputStream);
|
||||
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
whitePixels = new boolean[width][height];
|
||||
|
||||
for (int x = 0; x < width; x ++)
|
||||
{
|
||||
for (int z = 0; z < width; z ++)
|
||||
{
|
||||
Color color = new Color(image.getRGB(x,z));
|
||||
whitePixels[x][z] = color.equals(Color.WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return whitePixels;
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
//================//
|
||||
//region
|
||||
|
||||
private static class CloudParams
|
||||
{
|
||||
public final int textureWidth;
|
||||
public final int widthInBlocks;
|
||||
public final int halfWidthInBlocks;
|
||||
|
||||
public final int instanceOffsetX;
|
||||
public final int instanceOffsetZ;
|
||||
|
||||
|
||||
/** how far this cloud group has moved in the X direction based on time */
|
||||
public float deltaOffsetX = 0;
|
||||
/** how far this cloud group has moved in the Z direction based on time */
|
||||
public float deltaOffsetZ = 0;
|
||||
|
||||
public long lastFrameTime = System.currentTimeMillis();
|
||||
|
||||
/** used so we can trigger a VBO update when necessary */
|
||||
public Color previousColor = Color.WHITE;
|
||||
|
||||
|
||||
|
||||
// constructor //
|
||||
|
||||
public CloudParams(int textureWidth, int instanceOffsetX, int instanceOffsetZ)
|
||||
{
|
||||
this.textureWidth = textureWidth;
|
||||
this.widthInBlocks = (this.textureWidth * CLOUD_BOX_WIDTH);
|
||||
this.halfWidthInBlocks = this.widthInBlocks / 2;
|
||||
|
||||
this.instanceOffsetX = instanceOffsetX;
|
||||
this.instanceOffsetZ = instanceOffsetZ;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.seibel.distanthorizons.core.render.renderer.cullingFrustum;
|
||||
|
||||
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiCullingFrustum;
|
||||
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
|
||||
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector;
|
||||
import com.seibel.distanthorizons.core.util.math.Mat4f;
|
||||
import org.joml.FrustumIntersection;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
|
||||
public class DhFrustumBounds implements IDhApiCullingFrustum
|
||||
{
|
||||
private final FrustumIntersection frustum;
|
||||
private final Vector3f boundsMin = new Vector3f();
|
||||
private final Vector3f boundsMax = new Vector3f();
|
||||
public float worldMinY;
|
||||
public float worldMaxY;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public DhFrustumBounds()
|
||||
{
|
||||
this.frustum = new FrustumIntersection();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// methods //
|
||||
//=========//
|
||||
|
||||
@Override
|
||||
public void update(int worldMinBlockY, int worldMaxBlockY, DhApiMat4f dhWorldViewProjection)
|
||||
{
|
||||
this.worldMinY = worldMinBlockY;
|
||||
this.worldMaxY = worldMaxBlockY;
|
||||
|
||||
Matrix4f worldViewProjection = new Matrix4f(Mat4f.createJomlMatrix(dhWorldViewProjection));
|
||||
this.frustum.set(worldViewProjection);
|
||||
|
||||
Matrix4fc matWorldViewProjectionInv = new Matrix4f(worldViewProjection).invert();
|
||||
matWorldViewProjectionInv.frustumAabb(this.boundsMin, this.boundsMax);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersects(int lodBlockPosMinX, int lodBlockPosMinZ, int lodBlockWidth, int lodDetailLevel)
|
||||
{
|
||||
Vector3f lodMin = new Vector3f(lodBlockPosMinX, this.worldMinY, lodBlockPosMinZ);
|
||||
Vector3f lodMax = new Vector3f(lodBlockPosMinX + lodBlockWidth, this.worldMaxY, lodBlockPosMinZ + lodBlockWidth);
|
||||
|
||||
return this.frustum.testAab(lodMin, lodMax);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=====================//
|
||||
// overridable methods //
|
||||
//=====================//
|
||||
|
||||
@Override
|
||||
public int getPriority() { return IOverrideInjector.CORE_PRIORITY; }
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.seibel.distanthorizons.core.render.renderer.cullingFrustum;
|
||||
|
||||
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiCullingFrustum;
|
||||
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShadowCullingFrustum;
|
||||
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
|
||||
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector;
|
||||
|
||||
/**
|
||||
* Dummy {@link IDhApiCullingFrustum} that allows everything through. <br>
|
||||
* Useful when a frustum is required, but culling shouldn't be done.
|
||||
*/
|
||||
public class NeverCullFrustum implements IDhApiCullingFrustum, IDhApiShadowCullingFrustum
|
||||
{
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public NeverCullFrustum() { }
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// methods //
|
||||
//=========//
|
||||
|
||||
@Override
|
||||
public void update(int worldMinBlockY, int worldMaxBlockY, DhApiMat4f dhWorldViewProjection) { /* update isn't needed */ }
|
||||
|
||||
@Override
|
||||
public boolean intersects(int lodBlockPosMinX, int lodBlockPosMinZ, int lodBlockWidth, int lodDetailLevel) { return true; }
|
||||
|
||||
|
||||
|
||||
//=====================//
|
||||
// overridable methods //
|
||||
//=====================//
|
||||
|
||||
@Override
|
||||
public int getPriority() { return IOverrideInjector.CORE_PRIORITY; }
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user