4343import org .spongepowered .asm .mixin .injection .callback .CallbackInfo ;
4444import org .spongepowered .asm .mixin .injection .callback .CallbackInfoReturnable ;
4545
46- import java .util .concurrent .ExecutorService ;
47- import java .util .concurrent .Executors ;
46+ import java .util .function .BooleanSupplier ;
4847
4948import javax .annotation .ParametersAreNonnullByDefault ;
5049
5756@ Mixin (ViewFrustum .class )
5857public class MixinViewFrustum_RenderHeightFix {
5958
60- @ Unique private static final ExecutorService BACKGROUND_EXECUTOR = Executors .newSingleThreadExecutor ((runnable ) -> {
61- Thread t = new Thread (runnable );
62- t .setDaemon (true );
63- t .setName ("ViewFrustum RenderChunk position updater (CubicChunks)" );
64- return t ;
65- });
66-
6759 @ Shadow @ Final protected World world ;
6860 @ SuppressWarnings ("MismatchedReadAndWriteOfArray" ) @ Shadow public RenderChunk [] renderChunks ;
6961 @ Shadow protected int countChunksX ;
7062 @ Shadow protected int countChunksY ;
7163 @ Shadow protected int countChunksZ ;
7264
65+ @ Unique private int cubicchunks_oldViewX = Integer .MAX_VALUE ; //sufficiently large default value that it can never intersect with real values
66+ @ Unique private int cubicchunks_oldViewY = Integer .MAX_VALUE ;
67+ @ Unique private int cubicchunks_oldViewZ = Integer .MAX_VALUE ;
68+
7369 @ Inject (method = "updateChunkPositions" , at = @ At (value = "HEAD" ), cancellable = true , require = 1 )
7470 private void updateChunkPositionsInject (double viewEntityX , double viewEntityZ , CallbackInfo cbi ) {
7571 if (!((ICubicWorld ) world ).isCubicWorld ()) {
@@ -85,44 +81,148 @@ private void updateChunkPositionsInject(double viewEntityX, double viewEntityZ,
8581 int dz = countChunksZ ;
8682 RenderChunk [] chunks = this .renderChunks ;
8783
88- BACKGROUND_EXECUTOR .submit (() -> {
89- int minX = viewX - (dx >> 1 );
90- int minY = viewY - (dy >> 1 );
91- int minZ = viewZ - (dz >> 1 );
92- int px = MathHelper .intFloorDiv (minX , dx ) * dx ;
93- int py = MathHelper .intFloorDiv (minY , dy ) * dy ;
94- int pz = MathHelper .intFloorDiv (minZ , dz ) * dz ;
95-
96- for (int zIndex = 0 ; zIndex < this .countChunksZ ; zIndex ++) {
97- int blockZ = pz + zIndex ;
98- if (blockZ < minZ ) {
99- blockZ += dz ;
84+ //the coordinate of the RenderChunk in the lowest corner
85+ int minX = viewX - (dx >> 1 );
86+ int minY = viewY - (dy >> 1 );
87+ int minZ = viewZ - (dz >> 1 );
88+
89+ //the coordinate of a RenderChunk which sits at the origin. Wraps around within the min/max range
90+ int px = MathHelper .intFloorDiv (minX , dx ) * dx ;
91+ int py = MathHelper .intFloorDiv (minY , dy ) * dy ;
92+ int pz = MathHelper .intFloorDiv (minZ , dz ) * dz ;
93+
94+ //use longs here just in case the int values overflow (they shouldn't ever, but i want to play it safe)
95+ long changeX = (long ) viewX - this .cubicchunks_oldViewX ;
96+ long changeY = (long ) viewY - this .cubicchunks_oldViewY ;
97+ long changeZ = (long ) viewZ - this .cubicchunks_oldViewZ ;
98+ this .cubicchunks_oldViewX = viewX ;
99+ this .cubicchunks_oldViewY = viewY ;
100+ this .cubicchunks_oldViewZ = viewZ ;
101+
102+ if (Math .abs (changeX ) <= 1 && Math .abs (changeY ) <= 1 && Math .abs (changeZ ) <= 1 ) {
103+ //fast-path: the camera has moved by at most one cube so we only need to perform updates along a 2d plane
104+
105+ /*
106+ * d: 4
107+ *
108+ * 0123456789 0123456789 0123456789 0123456789 0123456789 .
109+ * min: # min: # min: # min: # min: # .
110+ * p: # p: # p: # p: # p: # .
111+ * 0+p: # 0+p: * # 0+p: * # 0+p: * # 0+p: # .
112+ * 1+p: # 1+p: # 1+p: * # 1+p: * # 1+p: # .
113+ * 2+p: # 2+p: # 2+p: # 2+p: * # 2+p: # .
114+ * 3+p: # 3+p: # 3+p: # 3+p: # 3+p: # .
115+ */
116+
117+ if (changeX != 0 ) { //we'll need to update one layer of RenderChunks perpendicular to the YZ plane
118+ int xIndex = Math .floorMod (changeX < 0 ? minX - px : minX - px - 1 , dx );
119+ int blockX = cubicchunks_getBlockCoord (xIndex , dx , px , minX );
120+
121+ for (int zIndex = 0 ; zIndex < dz ; zIndex ++) {
122+ int blockZ = cubicchunks_getBlockCoord (zIndex , dz , pz , minZ );
123+ int idxZ = zIndex * dy * dx ;
124+
125+ for (int yIndex = 0 ; yIndex < dy ; yIndex ++) {
126+ int blockY = cubicchunks_getBlockCoord (yIndex , dy , py , minY );
127+ int idxYZ = idxZ + yIndex * dx ;
128+
129+ chunks [idxYZ + xIndex ].setPosition (blockX , blockY , blockZ );
130+ }
131+ }
132+ }
133+
134+ if (changeY != 0 ) { //we'll need to update one layer of RenderChunks perpendicular to the XZ plane
135+ int yIndex = Math .floorMod (changeY < 0 ? minY - py : minY - py - 1 , dy );
136+ int blockY = cubicchunks_getBlockCoord (yIndex , dy , py , minY );
137+
138+ for (int zIndex = 0 ; zIndex < dz ; zIndex ++) {
139+ int blockZ = cubicchunks_getBlockCoord (zIndex , dz , pz , minZ );
140+ int idxZ = zIndex * dy * dx ;
141+
142+ int idxYZ = idxZ + yIndex * dx ;
143+
144+ for (int xIndex = 0 ; xIndex < dx ; xIndex ++) {
145+ int blockX = cubicchunks_getBlockCoord (xIndex , dx , px , minX );
146+
147+ chunks [idxYZ + xIndex ].setPosition (blockX , blockY , blockZ );
148+ }
100149 }
101- blockZ <<= 4 ;
102- int idxZ = zIndex * this .countChunksY * this .countChunksX ;
150+ }
103151
104- for (int yIndex = 0 ; yIndex < this .countChunksY ; yIndex ++) {
105- int blockY = py + yIndex ;
106- if (blockY < minY ) {
107- blockY += dy ;
152+ if (changeZ != 0 ) { //we'll need to update one layer of RenderChunks perpendicular to the XY plane
153+ int zIndex = Math .floorMod (changeZ < 0 ? minZ - pz : minZ - pz - 1 , dz );
154+ int blockZ = cubicchunks_getBlockCoord (zIndex , dz , pz , minZ );
155+ int idxZ = zIndex * dy * dx ;
156+
157+ for (int yIndex = 0 ; yIndex < dy ; yIndex ++) {
158+ int blockY = cubicchunks_getBlockCoord (yIndex , dy , py , minY );
159+ int idxYZ = idxZ + yIndex * dx ;
160+
161+ for (int xIndex = 0 ; xIndex < dx ; xIndex ++) {
162+ int blockX = cubicchunks_getBlockCoord (xIndex , dx , px , minX );
163+
164+ chunks [idxYZ + xIndex ].setPosition (blockX , blockY , blockZ );
108165 }
109- blockY <<= 4 ;
110- int idxYZ = idxZ + yIndex * this .countChunksX ;
111- for (int xIndex = 0 ; xIndex < this .countChunksX ; xIndex ++) {
112- int blockX = px + xIndex ;
113- if (blockX < minX ) {
114- blockX += dx ;
166+ }
167+ }
168+
169+ //run the original loop to double-check that all RenderChunks are in the correct position
170+ // (doing this cancels out any benefits from skipping unchanged RenderChunks, but only runs with assertions enabled)
171+ assert ((BooleanSupplier ) () -> {
172+ for (int zIndex = 0 ; zIndex < dz ; zIndex ++) {
173+ int blockZ = cubicchunks_getBlockCoord (zIndex , dz , pz , minZ );
174+ int idxZ = zIndex * dy * dx ;
175+
176+ for (int yIndex = 0 ; yIndex < dy ; yIndex ++) {
177+ int blockY = cubicchunks_getBlockCoord (yIndex , dy , py , minY );
178+ int idxYZ = idxZ + yIndex * dx ;
179+
180+ for (int xIndex = 0 ; xIndex < dx ; xIndex ++) {
181+ int blockX = cubicchunks_getBlockCoord (xIndex , dx , px , minX );
182+ BlockPos pos = chunks [idxYZ + xIndex ].getPosition ();
183+
184+ if (pos .getX () != blockX || pos .getY () != blockY || pos .getZ () != blockZ ) {
185+ return false ;
186+ }
115187 }
116- blockX <<= 4 ;
117- RenderChunk renderer = chunks [idxYZ + xIndex ];
118- renderer .setPosition (blockX , blockY , blockZ );
188+ }
189+ }
190+ return true ;
191+ }).getAsBoolean () : "Not all RenderChunks are in the correct position!" ;
192+ } else {
193+ //slow path, this behaves like the original vanilla code.
194+ //loop over all RenderChunks and set their position.
195+
196+ //original loop, cleaned up:
197+ for (int zIndex = 0 ; zIndex < dz ; zIndex ++) {
198+ int blockZ = cubicchunks_getBlockCoord (zIndex , dz , pz , minZ );
199+ int idxZ = zIndex * dy * dx ;
200+
201+ for (int yIndex = 0 ; yIndex < dy ; yIndex ++) {
202+ int blockY = cubicchunks_getBlockCoord (yIndex , dy , py , minY );
203+ int idxYZ = idxZ + yIndex * dx ;
204+
205+ for (int xIndex = 0 ; xIndex < dx ; xIndex ++) {
206+ int blockX = cubicchunks_getBlockCoord (xIndex , dx , px , minX );
207+
208+ chunks [idxYZ + xIndex ].setPosition (blockX , blockY , blockZ );
119209 }
120210 }
121211 }
122- });
212+ }
213+
123214 cbi .cancel ();
124215 }
125216
217+ @ Unique
218+ private static int cubicchunks_getBlockCoord (int index , int d , int p , int min ) {
219+ int coord = p + index ;
220+ if (coord < min ) {
221+ coord += d ;
222+ }
223+ return coord << 4 ;
224+ }
225+
126226 @ Inject (method = "getRenderChunk" , at = @ At (value = "HEAD" ), cancellable = true , require = 1 )
127227 private void getRenderChunkInject (BlockPos pos , CallbackInfoReturnable <RenderChunk > cbi ) {
128228 if (!((ICubicWorld ) world ).isCubicWorld ()) {
0 commit comments