3838import net .minecraft .world .level .block .state .BlockState ;
3939import net .minecraft .world .phys .Vec3 ;
4040
41+ import org .joml .Vector3f ;
42+
4143public class ChainConveyorRenderer extends KineticBlockEntityRenderer <ChainConveyorBlockEntity > {
4244
4345 public static final ResourceLocation CHAIN_LOCATION = ResourceLocation .withDefaultNamespace ("textures/block/chain.png" );
@@ -55,10 +57,9 @@ protected void renderSafe(ChainConveyorBlockEntity be, float partialTicks, PoseS
5557
5658 FrustumIntersection frustum = null ;
5759 Vec3 camPos = null ;
58- if (Minecraft .getInstance ().level == be .getLevel ())
59- {
60+ if (Minecraft .getInstance ().level == be .getLevel ()) {
6061 frustum = getFrustumIntersection ();
61- camPos = Minecraft .getInstance ().gameRenderer . getMainCamera () .getPosition ();
62+ camPos = Minecraft .getInstance ().getBlockEntityRenderDispatcher (). camera .getPosition ();
6263 }
6364 boolean renderCentre = frustum == null || frustum .testAab (pos .getX () - 2 - (float ) camPos .x , pos .getY () - (float ) camPos .y , pos .getZ () - 2 - (float ) camPos .z , pos .getX () + 2 - (float ) camPos .x , pos .getY () + 1 - (float ) camPos .y , pos .getZ () + 2 - (float ) camPos .z );
6465 renderChains (be , ms , buffer , light , overlay , frustum , camPos , renderCentre );
@@ -144,13 +145,79 @@ private void renderBox(ChainConveyorBlockEntity be, PoseStack ms, MultiBufferSou
144145 }
145146 }
146147
147- private static Vec3 getClosestPointOnChain (Vec3 cam , Vec3 start , Vec3 end ) {
148- Vec3 seg = end .subtract (start );
149- Vec3 start2cam = cam .subtract (start );
148+ /**
149+ * Calculate the intersection points between a line segment and a circle centered at cameraPos with radius LODDistance.
150+ * The intersections array is used to store up to 2 intersection points.
151+ * Returns the number of intersection points (0, 1, or 2).
152+ */
153+ private static int calculateLineCircleIntersection (Vec3 start , Vec3 end , Vec3 cameraPos , Vec3 [] intersections ) {
154+ Vec3 ab = end .subtract (start );
155+ Vec3 ac = start .subtract (cameraPos );
156+ float a = (float ) ab .lengthSqr ();
157+ float b = 2 * (float ) ac .dot (ab );
158+ float c = (float ) ac .lengthSqr () - MIP_DISTANCE_SQR ;
159+ float discriminant = b * b - 4 * a * c ;
160+
161+ if (discriminant < 0 ) {
162+ return 0 ; // No intersection
163+ }
150164
151- double t = Mth .clamp (start2cam .dot (seg ) / seg .lengthSqr (), 0.0 , 1.0 );
165+ float sqrtDisc = Mth .sqrt (discriminant );
166+ float t1 = (-b - sqrtDisc ) / (2 * a );
167+ float t2 = (-b + sqrtDisc ) / (2 * a );
168+ int count = 0 ;
169+ if (t1 >= 0 && t1 <= 1 ) {
170+ intersections [count ++] = start .add (ab .scale (t1 ));
171+ }
172+ // Avoid duplicate calculations (when t1 and t2 are almost equal)
173+ if (t2 >= 0 && t2 <= 1 && Math .abs (t2 - t1 ) > 1e-6f ) {
174+ intersections [count ++] = start .add (ab .scale (t2 ));
175+ }
176+ return count ;
177+ }
152178
153- return start .add (seg .scale (t ));
179+ /**
180+ * Cut the line segment based on the intersection points with the circle centered at the camera position.
181+ * The output Vector3f contains:
182+ * x: The distance from the start of the line segment to the intersection point (outside the LOD);
183+ * y: The length of the part of the line segment LOD0;
184+ * z: The distance from the intersection point to the end of the line segment (outside the LOD).
185+ */
186+ public static Vector3f calculateLODCut (Vec3 start , Vec3 end , Vec3 cameraPos ) {
187+ Vec3 [] intersections = new Vec3 [2 ];
188+ int intersectionCount = calculateLineCircleIntersection (start , end , cameraPos , intersections );
189+ float totalLength = (float ) start .distanceTo (end );
190+ float x = 0 , y = 0 , z = 0 ;
191+
192+ if (intersectionCount == 0 ) {
193+ // No intersection: Determine if the line segment is entirely inside or outside the circle
194+ if (start .distanceToSqr (cameraPos ) < MIP_DISTANCE_SQR && end .distanceToSqr (cameraPos ) < MIP_DISTANCE_SQR ) {
195+ // Both ends are inside the circle
196+ y = totalLength ;
197+ } else {
198+ // The line segment is entirely outside the circle
199+ x = totalLength ;
200+ }
201+ } else if (intersectionCount == 1 ) {
202+ // Only one intersection point, determine which end is inside the circle
203+ // one end must be inside and the other outside
204+ boolean endInside = end .distanceToSqr (cameraPos ) < MIP_DISTANCE_SQR ;
205+ if (endInside ) {
206+ x = (float ) start .distanceTo (intersections [0 ]);
207+ y = (float ) intersections [0 ].distanceTo (end );
208+ z = 0 ;
209+ } else {
210+ x = 0 ;
211+ y = (float ) start .distanceTo (intersections [0 ]);
212+ z = (float ) intersections [0 ].distanceTo (end );
213+ }
214+ } else if (intersectionCount == 2 ) {
215+ x = (float ) start .distanceTo (intersections [0 ]);
216+ y = (float ) intersections [0 ].distanceTo (intersections [1 ]);
217+ z = (float ) end .distanceTo (intersections [1 ]);
218+ }
219+
220+ return new Vector3f (x , y , z );
154221 }
155222
156223 private void renderChains (ChainConveyorBlockEntity be , PoseStack ms , MultiBufferSource buffer , int light ,
@@ -191,8 +258,13 @@ private void renderChains(ChainConveyorBlockEntity be, PoseStack ms, MultiBuffer
191258
192259 Level level = be .getLevel ();
193260 BlockPos tilePos = be .getBlockPos ();
194- Vec3 startOffset = stats .start ()
195- .subtract (Vec3 .atCenterOf (tilePos ));
261+
262+ int light1 = LightTexture .pack (level .getBrightness (LightLayer .BLOCK , tilePos ),
263+ level .getBrightness (LightLayer .SKY , tilePos ));
264+ int light2 = LightTexture .pack (level .getBrightness (LightLayer .BLOCK , tilePos .offset (blockPos )),
265+ level .getBrightness (LightLayer .SKY , tilePos .offset (blockPos )));
266+
267+ Vec3 startOffset = stats .start ().subtract (Vec3 .atCenterOf (tilePos ));
196268
197269 ms .pushPose ();
198270 var chain = TransformStack .of (ms );
@@ -204,29 +276,34 @@ private void renderChains(ChainConveyorBlockEntity be, PoseStack ms, MultiBuffer
204276 chain .translate (0 , 8 / 16f , 0 );
205277 chain .uncenter ();
206278
207- int light1 = LightTexture .pack (level .getBrightness (LightLayer .BLOCK , tilePos ),
208- level .getBrightness (LightLayer .SKY , tilePos ));
209- int light2 = LightTexture .pack (level .getBrightness (LightLayer .BLOCK , tilePos .offset (blockPos )),
210- level .getBrightness (LightLayer .SKY , tilePos .offset (blockPos )));
211-
212- boolean far = false ;
213279 if (frustum != null ) {
214- Vec3 closest = getClosestPointOnChain (camPos , stats .start (), stats .end ());
215- if (closest .distanceToSqr (camPos ) > MIP_DISTANCE_SQR )
216- far = true ;
280+ Vector3f length = calculateLODCut (stats .start (), stats .end (), camPos );
281+ if (length .x > 1e-6f ) {
282+ renderChain (ms , buffer , animation , 0 , length .x , light1 , light2 , true );
283+ }
284+
285+ if (length .y > 1e-6f ) {
286+ chain .translate (0 , length .x , 0 );
287+ renderChain (ms , buffer , animation , length .x , length .y , light1 , light2 , false );
288+ }
289+
290+ if (length .z > 1e-6f ) {
291+ chain .translate (0 , length .y , 0 );
292+ renderChain (ms , buffer , animation , 0 , length .z , light1 , light2 , true );
293+ }
294+ } else {
295+ renderChain (ms , buffer , animation , 0 , stats .chainLength (), light1 , light2 , false );
217296 }
218297
219- renderChain (ms , buffer , animation , stats .chainLength (), light1 , light2 , far );
220-
221298 ms .popPose ();
222299 }
223300 }
224301
225- public static void renderChain (PoseStack ms , MultiBufferSource buffer , float animation , float length , int light1 ,
302+ public static void renderChain (PoseStack ms , MultiBufferSource buffer , float animation , float start , float length , int light1 ,
226303 int light2 , boolean far ) {
227304 float radius = far ? 1f / 16f : 1.5f / 16f ;
228- float minV = far ? 0 : animation ;
229- float maxV = far ? 1 / 16f : length + minV ;
305+ float maxV = far ? 0 : animation - start ;
306+ float minV = far ? 1 / 16f : maxV - length ;
230307 float minU = far ? 3 / 16f : 0 ;
231308 float maxU = far ? 4 / 16f : 3 / 16f ;
232309
@@ -236,7 +313,6 @@ public static void renderChain(PoseStack ms, MultiBufferSource buffer, float ani
236313 VertexConsumer vc = buffer .getBuffer (RenderTypes .chain (CHAIN_LOCATION ));
237314 renderPart (ms , vc , length , 0.0F , radius , radius , 0.0F , -radius , 0.0F , 0.0F , -radius , minU , maxU , minV , maxV ,
238315 light1 , light2 , far );
239-
240316 ms .popPose ();
241317 }
242318
0 commit comments