Compare commits

..

7 Commits

Author SHA1 Message Date
Morippi d5072ed475 added new classes for the quadTree 2022-03-18 17:01:16 +01:00
Morippi ffee2141d4 added some new methods 2022-03-18 16:41:45 +01:00
Morippi 9f4b6b8709 added some comments 2022-03-18 16:39:43 +01:00
Morippi b05f074f4c added some comments 2022-03-18 16:24:19 +01:00
Morippi 06a549983b added methods names 2022-03-18 16:20:19 +01:00
Morippi 0b96ca8509 changed names of variables 2022-03-18 16:04:31 +01:00
Morippi edd50096d6 Added First concept of LodSection. 2022-03-16 23:03:54 +01:00
174 changed files with 8324 additions and 15384 deletions
-3
View File
@@ -16,6 +16,3 @@ It should be automatically included when pulling the full mod.
XZ for Java (data compression)\
https://tukaani.org/xz/java.html
Toml for Java (config handeling)\
https://github.com/TheElectronWill/night-config
+1
View File
@@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2021-12-22T02:14:44.485Z" agent="5.0 (Windows)" etag="8Lz4CpREcKLpQpROSPVl" version="16.0.3" type="gitlab"><diagram id="xLs7mM1S-vncSruOQYJG" name="Page-1">xZVNj5swEIZ/DcetAJdkc2yTbXvZVaQcuunNtSfgrsGRcRbor6+Jx4BDom3VSr1Enmc+7HnHOBFZl+1nTY/Fo+IgozTmbUQ2UZou71P724POAZIlDuRacIcmYCd+AsIY6UlwqINAo5Q04hhCpqoKmAkY1Vo1YdhByXDXI81hBnaMyjn9KrgpHL3P4pF/AZEXfuckRk9JfTCCuqBcNRNEHiKy1koZtyrbNcheO6+Ly/t0wzscTENlfichf/r2ohqemP2Pp+3dtn3s9tkdVnml8oQNCwOlJeioTeeV0OpUceiLxRH52BQ2cHekrPc2dvSWFaaU1krsEsuCNtDePG8yqGBvD6gSjO5sCCasUDe8OMl7tJtxDInXtpiMYIGM4uTzofIojl2gPn+gVXpLq/TvtDoIKddKKn3OJYcDLBizvDZavcDEw5er73H8b9QlSSjv4n+rS2bqzlSFin/oP2lrMUnrWrBQyFB1aIV5Rk+/3vf8XYbWpp2EbTpvVLaV56nhspaZt8e8s+UT3VmBzx6Ti3nYftRJM3j7kzRU52Deuo7z+U4GmF2Zn2caJDXiNTzutaHiDlslbCM3r89Q15dwbWLW9FW6LLS4+MxXF4WcDrNC5zs2tH3t2llzfFxd+PgPRR5+AQ==</diagram></mxfile>
@@ -1,221 +0,0 @@
<mxfile host="app.diagrams.net" modified="2022-02-06T23:07:56.707Z" agent="5.0 (Windows)" etag="McdhDnDn_b3JSW_6sAfD" version="16.5.3" type="gitlab">
<diagram id="xLs7mM1S-vncSruOQYJG" name="Page-1">
<mxGraphModel dx="2854" dy="971" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" background="none" math="0" shadow="0">
<root>
<mxCell id="0" />
<object label="Background" id="1">
<mxCell parent="0" />
</object>
<mxCell id="bW8zysbPbXfA33rAzGtO-46" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFE9CC;strokeColor=#d79b00;shadow=0;" vertex="1" parent="1">
<mxGeometry x="400" y="300" width="720" height="320" as="geometry" />
</mxCell>
<mxCell id="ZgqzLkNpqH_WLmXpxmUH-3" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFE9CC;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="30" y="10" width="1090" height="290" as="geometry" />
</mxCell>
<mxCell id="lUieYn43trCVNQSoQYE8-16" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#CBF2CB;strokeColor=#06962D;gradientColor=none;" parent="1" vertex="1">
<mxGeometry x="-680" y="70" width="200" height="220" as="geometry" />
</mxCell>
<mxCell id="lUieYn43trCVNQSoQYE8-18" value="Minecraft &lt;br&gt;Version Specific" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#CBF2CB;strokeColor=#06962D;gradientColor=none;" parent="1" vertex="1">
<mxGeometry x="-680" y="10" width="200" height="60" as="geometry" />
</mxCell>
<mxCell id="gNZkowd1tYjNP-PxMyY5-1" value="Minecraft" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#A5E8A5;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="-650" y="205" width="140" height="50" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-27" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=3;" parent="1" source="lUieYn43trCVNQSoQYE8-14" target="XNAtI1EKQKx7pIlif8ke-22" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="lUieYn43trCVNQSoQYE8-14" value="Mod API&lt;br&gt;(Forge or Fabric)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#A5E8A5;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="-650" y="90" width="140" height="60" as="geometry" />
</mxCell>
<mxCell id="ZgqzLkNpqH_WLmXpxmUH-2" value="Distant Horizons Core&lt;br&gt;(Version Independent)" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFE9CC;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="450" y="10" width="220" height="60" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-1" target="XNAtI1EKQKx7pIlif8ke-11" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="10" y="120" />
<mxPoint x="10" y="176" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-72" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;fontColor=#FFFFFF;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-11" target="bW8zysbPbXfA33rAzGtO-39" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="134" y="90" />
<mxPoint x="1000" y="90" />
</Array>
<mxPoint x="997.5" y="165.0000000000001" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-1" value="Wrappers&lt;br&gt;Or&lt;br&gt;&lt;div&gt;DH-Object&lt;/div&gt;" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#FFD7B0;strokeColor=#d79b00;rounded=0;labelBackgroundColor=none;" parent="1" vertex="1">
<mxGeometry x="-170" y="80" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-3" target="XNAtI1EKQKx7pIlif8ke-21" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-3" value="Mixins" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#A5E8A5;strokeColor=#82b366;rounded=0;labelBackgroundColor=none;" parent="1" vertex="1">
<mxGeometry x="-470" y="190" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-4" value="" style="endArrow=classic;html=1;rounded=0;strokeWidth=3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="gNZkowd1tYjNP-PxMyY5-1" target="XNAtI1EKQKx7pIlif8ke-3" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="-420" y="130" as="sourcePoint" />
<mxPoint x="-220" y="-25" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-5" target="XNAtI1EKQKx7pIlif8ke-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-5" value="Wrappers&lt;br&gt;Or&lt;br&gt;&lt;div&gt;DH-Object&lt;/div&gt;" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#FFD7B0;strokeColor=#d79b00;rounded=0;labelBackgroundColor=none;" parent="1" vertex="1">
<mxGeometry x="-170" y="190" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-60" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;fontColor=#FFFFFF;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-11" target="XNAtI1EKQKx7pIlif8ke-33" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-63" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fontColor=#FFFFFF;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-11" target="lUieYn43trCVNQSoQYE8-19" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-11" value="core.api" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="60" y="165" width="147.5" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-21" target="XNAtI1EKQKx7pIlif8ke-5" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-21" value="" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#C7C7C7;strokeColor=#666666;fontColor=#333333;rounded=0;labelBackgroundColor=none;" parent="1" vertex="1">
<mxGeometry x="-320" y="175" width="120" height="85" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-26" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-22" target="XNAtI1EKQKx7pIlif8ke-1" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-22" value="" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#C7C7C7;strokeColor=#666666;fontColor=#333333;rounded=0;labelBackgroundColor=none;" parent="1" vertex="1">
<mxGeometry x="-320" y="90" width="120" height="85" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-28" value="&lt;div&gt;Wrapper&lt;/div&gt;&lt;div&gt;Factory&lt;br&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;fontColor=default;" parent="1" vertex="1">
<mxGeometry x="-290" y="110" width="60" height="130" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-44" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;" edge="1" parent="1" source="XNAtI1EKQKx7pIlif8ke-31" target="bW8zysbPbXfA33rAzGtO-39">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-31" value="GPU Buffer building" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="710" y="165" width="130" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-67" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;fontColor=#FFFFFF;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-33" target="XNAtI1EKQKx7pIlif8ke-54" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-33" value="Lod Building" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="290" y="165" width="120" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-52" value="" style="group" parent="1" vertex="1" connectable="0">
<mxGeometry x="460" y="110" width="200" height="175" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-25" value="" style="group" vertex="1" connectable="0" parent="XNAtI1EKQKx7pIlif8ke-52">
<mxGeometry width="200" height="175" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-53" value="LOD Storage" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="bW8zysbPbXfA33rAzGtO-25" vertex="1">
<mxGeometry width="200" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-54" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFF4E8;strokeColor=#d79b00;" parent="bW8zysbPbXfA33rAzGtO-25" vertex="1">
<mxGeometry y="45" width="200" height="130" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-55" value="Memory Storage" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" parent="bW8zysbPbXfA33rAzGtO-25" vertex="1">
<mxGeometry x="10" y="115" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-56" value="&lt;div&gt;File Storage&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" parent="bW8zysbPbXfA33rAzGtO-25" vertex="1">
<mxGeometry x="10" y="60" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-61" value="" style="group" parent="1" vertex="1" connectable="0">
<mxGeometry x="130" y="310" width="220" height="200" as="geometry" />
</mxCell>
<mxCell id="lUieYn43trCVNQSoQYE8-13" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#C7C7C7;strokeColor=#666666;fontColor=#333333;" parent="XNAtI1EKQKx7pIlif8ke-61" vertex="1">
<mxGeometry y="70" width="220" height="130" as="geometry" />
</mxCell>
<mxCell id="lUieYn43trCVNQSoQYE8-19" value="Distant Horizons&lt;br&gt;(Version dependent)" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#C7C7C7;strokeColor=#666666;fontColor=#333333;" parent="XNAtI1EKQKx7pIlif8ke-61" vertex="1">
<mxGeometry width="220" height="70" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-19" value="Chunk Generation" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#EBEBEB;strokeColor=#666666;fontColor=#333333;" parent="XNAtI1EKQKx7pIlif8ke-61" vertex="1">
<mxGeometry x="12.5" y="80" width="195" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-29" value="&lt;div&gt;Chunk loading from file&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#EBEBEB;strokeColor=#666666;fontColor=#333333;" parent="XNAtI1EKQKx7pIlif8ke-61" vertex="1">
<mxGeometry x="12.5" y="140" width="195" height="45" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-64" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;fontColor=#FFFFFF;strokeWidth=3;" parent="1" source="lUieYn43trCVNQSoQYE8-19" target="XNAtI1EKQKx7pIlif8ke-33" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="XNAtI1EKQKx7pIlif8ke-66" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fontColor=#FFFFFF;strokeWidth=3;" parent="1" source="XNAtI1EKQKx7pIlif8ke-54" target="XNAtI1EKQKx7pIlif8ke-31" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-23" value="" style="group" vertex="1" connectable="0" parent="1">
<mxGeometry x="455" y="324" width="210" height="270" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-16" value="Memory Storage" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-23">
<mxGeometry width="210" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-17" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFF4E8;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-23">
<mxGeometry y="45" width="210" height="225" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-18" value="dimension" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-23">
<mxGeometry x="10.5" y="110" width="189" height="40" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-19" value="World" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-23">
<mxGeometry x="10.5" y="60" width="189" height="40" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-21" value="Region" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-23">
<mxGeometry x="10.5" y="160" width="189" height="40" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-22" value="Level Container&lt;br&gt;(Long primitive)" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-23">
<mxGeometry x="10.5" y="210" width="189" height="40" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-26" value="" style="group" vertex="1" connectable="0" parent="1">
<mxGeometry x="680" y="419" width="200" height="175" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-27" value="Config System" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-26">
<mxGeometry width="200" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-28" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFF4E8;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-26">
<mxGeometry y="45" width="200" height="130" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-29" value="Config GUI" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-26">
<mxGeometry x="10" y="115" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-30" value="Config File" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-26">
<mxGeometry x="10" y="60" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-38" value="" style="group" vertex="1" connectable="0" parent="1">
<mxGeometry x="900" y="165" width="200" height="175" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-39" value="Rendering" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-38">
<mxGeometry width="200" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-40" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFF4E8;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-38">
<mxGeometry y="45" width="200" height="130" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-41" value="Cloud Rendering" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-38">
<mxGeometry x="10" y="115" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-42" value="LOD Rendering" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-38">
<mxGeometry x="10" y="60" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-47" value="" style="group" vertex="1" connectable="0" parent="1">
<mxGeometry x="900" y="419" width="200" height="175" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-32" value="Mod Support" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-47">
<mxGeometry width="200" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-33" value="" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFF4E8;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-47">
<mxGeometry y="45" width="200" height="130" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-34" value="Mod API" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-47">
<mxGeometry x="10" y="115" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-35" value="&lt;div&gt;Mod Accessors&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#FFD7B0;strokeColor=#d79b00;" vertex="1" parent="bW8zysbPbXfA33rAzGtO-47">
<mxGeometry x="10" y="60" width="180" height="45" as="geometry" />
</mxCell>
<mxCell id="bW8zysbPbXfA33rAzGtO-20" value="" style="shape=flexArrow;endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="XNAtI1EKQKx7pIlif8ke-55" target="bW8zysbPbXfA33rAzGtO-16">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="410" y="-36" as="sourcePoint" />
<mxPoint x="360" y="-86" as="targetPoint" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
-1
View File
@@ -1 +0,0 @@
<mxfile host="app.diagrams.net" modified="2022-01-04T16:15:44.618Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62" etag="-Vdo5wDmcYQvA_9MRiBE" version="16.0.0" type="google"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">7V1db5s6GP41kbaLTHxDLpt063aWHfWs2zrtzgtOwhnBOcZpm/36Y2M7CRg60mGoNEuVGt44Bvw876eNGbmzzcMVBtv1BxTDdORY8cPIvRw5jmPZDv3HJHsuCQIhWOEk5iL7KLhJfkIhtIR0l8QwLzUkCKUk2ZaFC5RlcEFKMoAxui83W6K0fNYtWEFFcLMAqSq9TWKy5tLICY/ytzBZreWZ7WDCv9kA2VjcSb4GMbo/EbmvR+4MI0T4p83DDKZs8OS43L7b36bzH8HVX//k/4HP0/ef/v4y5p29Oecnh1vAMCNP7prcfA4vxsv3wY+3YTb+mb27Dq/GchhyspcDBmM6fuIQYbJGK5SB9PVROsVol8WQdWvRo2ObOUJbKrSp8F9IyF6QAewIoqI12aTi25xg9APOUIowlcRwCXYpvbEpzOILBjkVZiiDXPImSVNxKn6t7AIr2P5iYES7HO3wAj4yGq4vCArwCpLHGk4O+FPFgWgDCd7TH2KYApLclS8PCAavDu0OP71GCb1wxxLa5kqqCV1z/KjcBb8D8atTrNWOXlmu51Ga+1HgWGGp28NpZLf8fpVu6YeTuzuKCmKdQTJpCvol2eBkmrQkk+f2QiYvsp5MplJHftAzffw/kT6cFS3oE9i90Cd0PB22KLLakYliAfYnzbasQa6DbtIJ3IF0JwZvjuJbhGngUeVhfp9sUlCwY4kyIinJ+APSZJXRzwtKB0jd3PQOYpLQuORCfEEYEaeLdZLGc7BHO4ZxTsDihzyarhFOftJuwdFvAkwEY52g1OKG/VJQE8OctrmWTLQrog/godRwDnIiBAuUpmCbJ98Pt7GhcCTZFBGCNiXms9uBD0/gvspUGWt6Fe8nqXF/jNtsaXvWJzFbFDSTu8SOs6kQKFQYOVMWhSaQhaEXTD/3W6jwgo4MUeIcYUaW1IZURJIrKVySRqbkW7BIstW8aHPpHSUfxUgwEaK/XaaF1VoncQyzwhQSQACHlOEnVIdeqD+lf3RAZ9Yrf+TTC5/RY/t4TP9Yc0xmKKP3ApICXUj5cg8ZZ9pR4REtU7kgsQ/aQe9ogz6sgb6CcpoU6HGUZXJhPwniDQWr8FsC00+FlxrbCu6uirtbg3EKvsP0GuUJSRDrH/O2FewHg9d3Wmq2LnSjBsWm3a9R/IIp9Uuj3/oIELQ07fr0e1Ln5S+TDcxypjLG0/fo6d22dLBlNapzPniqRRjzqtQyWeXcFMzhHUzzWSEyJuF8k9BQNHkuLt92nRoOzJzRhZUy5GdM/ajLxydsOMoMHzrmQ9sYQRsfPNuEgPrgDaNhQ0BPVXYTAvZJgGhoex+5CqjPoK7YNDcyei71Rr+TcnVzPa++iuhblXKkVZnt+kX7wPcqhOFX0GnpUI7haVLxeJRg0ooO04qD+ThEEGpacShrl0yMdAbdexmvNq0o4skbuGCuWSQXH9AdM+BXOInnCR1PJyjUfn7SkspWhSngjtt4pHM9UtNMW3MGUk8XbRGnOvVgIs7O4K1JKGrh1RZxNs0m7LYxIHBOg84YUj+RFjpPFWKL8q/83zcRid4hegaj9x0To6b61K/eR6qX4MygkZjhxCCcqMlO+uWEbzVyIvuEoUlIO4f8UFweDvOmmsRiRwzmWjB31TJUv5jbVlNYkEPyEWYxxHTs36Rglb8wFl8nFQJvaCrYdYsOBBWuYAYxYJG14YJ+LkyGDgn9usmHggrgDl4mmOwPRQEDfsdlAHvw2K+mjohYkRi+BRlN2fGIndq1j8U0U1LUU1L0K6tgA9tuxw1taxL9unDB1Ija6n7T7MWZJUBdNSJfXZbEzX6KQGwsvibU3aEjv6Ap2Wfe3sCuCfbB636BGuSV53uMa+9vtjDyWtLBtxxdfFDrP2y28GTYCzBY3icfjzEG4RyD0PSs1HOZAAxVgzAWt8qgMfBrhb/tBKE+f6DmfeOi+lO4g2s29WPQ14V+zYLEntFXY/8y+t8M+trQt62hc4Cw3vkL+C9PZoELFnzfEzMf1D0N3KFzglBNBRkNtqJkckmHlBMgp2EhMUvBNPHAHzwUVNeCnYaCRx6wQTEs0MOCaOiIMKxfP7pgyBkK9JESWkOHhWF9UpCyUxsK9EGB4cvDdesDzZxPZ1WfljGfrkmfqGmuny8MrqwA5cHg18Onb/wTVTSIT6vHL40l6J4pQzuDoO4JAbFS+PokRXhh0O8e/cngfqBpqSBF/8tJYmDQ7x59t/XDZNrQb1odSNGfyYTAQK8Bem/wYkDTfkUU+rlMBAz0GqCXVdnhng1qXBpiPL529CdDx3th03qw3Hh83eh7ztDxXvjI8wDG4+uE3h882Gvy+Lnx+HqhD4cO9iaBguofvC+NXEjf05785+5L41U3LRY5etO+NNX2bmhVCKNhX5pIrRs8upehWWd6PpVbP0JyeDPKrwyMvs0uIzWuYFNKG/AgY8rySkMxsdT8zJHxOr/yOlwFn+/K00n9cgO+8WWCFyn8COIE7XLDiv5YMfiC1Kgu/TQTjx3B23bFqa55x0lTesnnHc1Gx3pgH3xrGds2GcapGkQtMwxNL2ryJuUA0ZfHT34lTv15qlti+uIdF2230JTvfdKaqkzUusccxdMdTSfM9pn9Jiqtt890K2+G6nBvHLMF9+84Iq5Mv5106Io/bEsNQKiy802PjLbr1faxUzHvrRc46FN3WRkx6v4kdRf69Hz1XTLHxJ18OKy+As/fhE1Vy+luuYTYhGVDGOogGtxQuyYu+y1DzTVqAENND48vLOc52/G17+7r/wE=</diagram></mxfile>
@@ -1,26 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core;
public class JarMain {
public static void main(String[] args){
System.out.println("Why are you running the jar, this isn't done yet >:(");
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,6 +18,7 @@
*/
package com.seibel.lod.core;
/**
* This file is similar to mcmod.info
* <br>
@@ -27,18 +28,15 @@ package com.seibel.lod.core;
* Pretty much all of the mod stems from there.
*
* @author James Seibel
* @author Ran
* @version 11-29-2021
*/
public final class ModInfo
{
public static final String ID = "lod";
/** The internal protocol version used for networking */
public static final int PROTOCOL_VERSION = 1;
/** The internal mod name */
public static final String NAME = "DistantHorizons";
/** Human readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons";
public static final String API = "LodAPI";
public static final String VERSION = "1.6.7a-dev";
}
public static final String VERSION = "a1.5.4";
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,12 +19,9 @@
package com.seibel.lod.core.api;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.objects.lod.LodWorld;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* This stores objects and variables that
@@ -35,9 +32,9 @@ import org.apache.logging.log4j.Logger;
*/
public class ApiShared
{
public static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME);
public ApiShared INSTANCE = new ApiShared();
public static final LodBufferBuilderFactory lodBufferBuilderFactory = new LodBufferBuilderFactory();
public static final LodWorld lodWorld = new LodWorld();
public static final LodBuilder lodBuilder = new LodBuilder();
@@ -45,14 +42,12 @@ public class ApiShared
public static int previousChunkRenderDistance = 0;
/** Used to determine if the LODs should be regenerated */
public static int previousLodRenderDistance = 0;
public static VerticalQuality previousVertQual = null;
/** Signal whether a world is shutting down */
public static volatile boolean isShuttingDown = false;
private ApiShared()
{
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,37 +19,20 @@
package com.seibel.lod.core.api;
import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.rendering.RendererType;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.logging.ConfigBasedSpamLogger;
import com.seibel.lod.core.render.RenderSystemTest;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import org.apache.logging.log4j.Level;
import com.seibel.lod.core.handlers.LodDimensionFinder;
import org.lwjgl.glfw.GLFW;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.logging.SpamReducedLogger;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
/**
* This holds the methods that should be called
@@ -57,43 +40,19 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
* Specifically for the client.
*
* @author James Seibel
* @version 2022-3-26
* @version 12-8-2021
*/
public class ClientApi
{
public static boolean prefLoggerEnabled = false;
{
public static final ClientApi INSTANCE = new ClientApi();
public static final LodBufferBuilderFactory lodBufferBuilderFactory = new LodBufferBuilderFactory();
public static LodRenderer renderer = new LodRenderer(lodBufferBuilderFactory);
public static RenderSystemTest testRenderer = new RenderSystemTest();
public static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME);
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
public static LodRenderer renderer = new LodRenderer(ApiShared.lodBufferBuilderFactory);
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final EventApi EVENT_API = EventApi.INSTANCE;
public static final boolean ENABLE_LAG_SPIKE_LOGGING = false;
public static final long LAG_SPIKE_THRESOLD_NS = TimeUnit.NANOSECONDS.convert(16, TimeUnit.MILLISECONDS);
public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
public static LodDimensionFinder DIMENSION_FINDER = new LodDimensionFinder();;
public static class LagSpikeCatcher {
long timer = System.nanoTime();
public LagSpikeCatcher() {}
public void end(String source) {
if (!ENABLE_LAG_SPIKE_LOGGING) return;
timer = System.nanoTime() - timer;
if (timer > LAG_SPIKE_THRESOLD_NS) {
ApiShared.LOGGER.info("LagSpikeCatcher: "+source+" took "+Duration.ofNanos(timer)+"!");
}
}
}
/**
* there is some setup that should only happen once,
@@ -103,161 +62,48 @@ public class ClientApi
private boolean configOverrideReminderPrinted = false;
public boolean rendererDisabledBecauseOfExceptions = false;
private ClientApi()
{
}
public static void logToChat(Level logLevel, String str) {
String prefix = "["+ModInfo.READABLE_NAME+"] ";
if (logLevel == Level.ERROR) {
prefix += "\u00A74";
} else if (logLevel == Level.WARN) {
prefix += "\u00A76";
} else if (logLevel == Level.INFO) {
prefix += "\u00A7f";
} else if (logLevel == Level.DEBUG) {
prefix += "\u00A77";
} else if (logLevel == Level.TRACE) {
prefix += "\u00A78";
} else {
prefix += "\u00A7f";
}
prefix += "\u00A7l\u00A7u";
prefix += logLevel.name();
prefix += ":\u00A7r ";
if (MC != null) MC.sendChatMessage(prefix + str);
}
private final ConcurrentHashMap.KeySetView<Long,Boolean> generating = ConcurrentHashMap.newKeySet();
public final ConcurrentHashMap.KeySetView<Long,Boolean> toBeLoaded = ConcurrentHashMap.newKeySet();
public void clientChunkLoadEvent(IChunkWrapper chunk, IWorldWrapper world)
{
LagSpikeCatcher clientChunkLoad = new LagSpikeCatcher();
//ApiShared.LOGGER.info("Lod Generating add: "+chunk.getLongChunkPos());
toBeLoaded.add(chunk.getLongChunkPos());
clientChunkLoad.end("clientChunkLoad");
}
private long lastFlush = 0;
public void renderLods(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
{
// comment out when creating a release
//applyConfigOverrides();
applyConfigOverrides();
// clear any out of date objects
MC.clearFrameObjectCache();
try
{
boolean doFlush = System.nanoTime() - lastFlush >= SPAM_LOGGER_FLUSH_NS;
if (doFlush) {
lastFlush = System.nanoTime();
SpamReducedLogger.flushAll();
}
ConfigBasedLogger.updateAll();
ConfigBasedSpamLogger.updateAll(doFlush);
if (ApiShared.previousVertQual != CONFIG.client().graphics().quality().getVerticalQuality()) {
ApiShared.previousVertQual = CONFIG.client().graphics().quality().getVerticalQuality();
EventApi.INSTANCE.worldUnloadEvent(MC.getWrappedServerWorld());
EventApi.INSTANCE.worldLoadEvent(MC.getWrappedClientWorld());
return;
}
// only run the first time setup once
if (!firstTimeSetupComplete)
firstFrameSetup();
if (!MC.playerExists() || ApiShared.lodWorld.getIsWorldNotLoaded())
return;
IWorldWrapper world = MC.getWrappedClientWorld();
if (world == null)
return;
LodDimension lodDim = ApiShared.lodWorld.getLodDimension(world.getDimensionType());
// Make sure the player's data is up-to-date
DIMENSION_FINDER.updatePlayerData();
// Make the LodDim if it does not exist
LodDimension lodDim = ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension());
if (lodDim == null)
{
if (DIMENSION_FINDER.isDone())
{
lodDim = DIMENSION_FINDER.getAndClearFoundLodDimension();
ApiShared.lodWorld.addLodDimension(lodDim);
}
else
{
DIMENSION_FINDER.AttemptToDetermineSubDimensionAsync(MC.getCurrentDimension());
return;
}
}
return;
if (prefLoggerEnabled) {
lodDim.dumpRamUsage();
lodBufferBuilderFactory.dumpBufferMemoryUsage();
}
LagSpikeCatcher updateToBeLoadedChunk = new LagSpikeCatcher();
for (long pos : toBeLoaded) {
if (generating.size() >= 1) {
//ApiShared.LOGGER.info("Lod Generating Full! Remining: "+toBeLoaded.size());
break;
}
IChunkWrapper chunk = world.tryGetChunk(FACTORY.createChunkPos(pos));
if (chunk == null) {
toBeLoaded.remove(pos);
LodBuilder.EVENT_LOGGER.debug("Manual Chunk: {} not ready. Remaining queue: {}", FACTORY.createChunkPos(pos), toBeLoaded.size());
continue;
}
if (!chunk.isLightCorrect()) continue;
if (!chunk.doesNearbyChunksExist()) continue;
toBeLoaded.remove(pos);
generating.add(pos);
//ApiShared.LOGGER.info("Lod Generation trying "+pos+". Remining: " +toBeLoaded.size());
ApiShared.lodBuilder.generateLodNodeAsync(chunk, ApiShared.lodWorld,
world.getDimensionType(), DistanceGenerationMode.FULL, true, true, () -> {
generating.remove(pos);
LodBuilder.EVENT_LOGGER.debug("Manual Chunk: {} done. Remaining queue: {}", FACTORY.createChunkPos(pos), toBeLoaded.size());
}, () -> {
generating.remove(pos);
toBeLoaded.add(pos);
LodBuilder.EVENT_LOGGER.debug("Manual Chunk: {} not ready. Remaining queue: {}", FACTORY.createChunkPos(pos), toBeLoaded.size());
});
}
updateToBeLoadedChunk.end("updateToBeLoadedChunk");
LagSpikeCatcher updateSettings = new LagSpikeCatcher();
DetailDistanceUtil.updateSettings();
EVENT_API.viewDistanceChangedEvent();
updateSettings.end("updateSettings");
LagSpikeCatcher updatePlayerMove = new LagSpikeCatcher();
EVENT_API.playerMoveEvent(lodDim);
updatePlayerMove.end("updatePlayerMove");
LagSpikeCatcher cutAndExpendAsync = new LagSpikeCatcher();
lodDim.cutRegionNodesAsync(MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getZ());
lodDim.expandOrLoadRegionsAsync(MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getZ());
cutAndExpendAsync.end("cutAndExpendAsync");
if (CONFIG.client().advanced().debugging().getRendererType() == RendererType.DEFAULT)
if (CONFIG.client().advanced().debugging().getDrawLods())
{
// Note to self:
// if "unspecified" shows up in the pie chart, it is
@@ -272,26 +118,18 @@ public class ClientApi
ClientApi.renderer.drawLODs(lodDim, mcModelViewMatrix, mcProjectionMatrix, partialTicks, MC.getProfiler());
} catch (RuntimeException e) {
rendererDisabledBecauseOfExceptions = true;
ApiShared.LOGGER.error("Renderer thrown an uncaught exception: ",e);
try {
MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons"
+ " renderer has encountered an exception!");
MC.sendChatMessage("\u00A74Renderer is now disabled to prevent futher issues.");
MC.sendChatMessage("\u00A74Exception detail: "+e.toString());
} catch (RuntimeException ignored) {}
//ClientApi.renderer.ma ();
} catch (RuntimeException welpLookLikeWeWillLeakResource) {}
throw e;
}
}
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
} else if (CONFIG.client().advanced().debugging().getRendererType() == RendererType.DEBUG) {
IProfilerWrapper profiler = MC.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("LODTestRendering");
ClientApi.testRenderer.render();
profiler.pop(); // end LODTestRendering
profiler.push("terrain"); // go back into "terrain"
}
// these can't be set until after the buffers are built (in renderer.drawLODs)
// otherwise the buffers may be set to the wrong size, or not changed at all
ApiShared.previousChunkRenderDistance = MC_RENDER.getRenderDistance();
@@ -299,7 +137,8 @@ public class ClientApi
}
catch (Exception e)
{
ApiShared.LOGGER.error("client proxy uncaught exception: ", e);
ClientApi.LOGGER.error("client proxy: " + e.getMessage());
e.printStackTrace();
}
}
@@ -310,7 +149,10 @@ public class ClientApi
if (!configOverrideReminderPrinted)
{
MC.sendChatMessage(ModInfo.READABLE_NAME + " experimental build " + ModInfo.VERSION);
MC.sendChatMessage("You are running an unsupported version of the mod!");
MC.sendChatMessage("You are running a unsupported version of the mod!");
MC.sendChatMessage("==========================================");
MC.sendChatMessage("SEIZURE WARNING: Flashing lights expected!"); // remove this line when the lighting shaders are fixed
MC.sendChatMessage("==========================================");
MC.sendChatMessage("Here be dragons!");
configOverrideReminderPrinted = true;
@@ -329,38 +171,13 @@ public class ClientApi
// CONFIG.client().advanced().buffers().setRebuildTimes(BufferRebuildTimes.FREQUENT);
// CONFIG.client().advanced().debugging().setDebugKeybindingsEnabled(true);
}
//=================//
// DUBUG USE //
//=================//
// Trigger once on key press, with CLIENT PLAYER.
public void keyPressedEvent(int glfwKey) {
if (!CONFIG.client().advanced().debugging().getDebugKeybindingsEnabled()) return;
if (glfwKey == GLFW.GLFW_KEY_F8) {
CONFIG.client().advanced().debugging()
.setDebugMode(CONFIG.client().advanced().debugging().getDebugMode().getNext());
MC.sendChatMessage("F8: Set debug mode to " + CONFIG.client().advanced().debugging().getDebugMode());
}
if (glfwKey == GLFW.GLFW_KEY_F6) {
CONFIG.client().advanced().debugging()
.setRendererType(RendererType.next(CONFIG.client().advanced().debugging().getRendererType()));
MC.sendChatMessage("F6: Set rendering to " + CONFIG.client().advanced().debugging().getRendererType());
}
if (glfwKey == GLFW.GLFW_KEY_P) {
prefLoggerEnabled = !prefLoggerEnabled;
MC.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
}
CONFIG.client().advanced().debugging().setDebugKeybindingsEnabled(true);
}
//=================//
// Lod maintenance //
//=================//
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,206 +19,203 @@
package com.seibel.lod.core.api;
import com.seibel.lod.core.api.ClientApi.LagSpikeCatcher;
import org.lwjgl.glfw.GLFW;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.builders.worldGeneration.BatchGenerator;
import com.seibel.lod.core.enums.WorldType;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.builders.worldGeneration.LodWorldGenerator;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.util.ThreadMapUtil;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
/**
* This holds the methods that should be called by the host mod loader (Fabric,
* Forge, etc.). Specifically server and client events.
* This holds the methods that should be called
* by the host mod loader (Fabric, Forge, etc.).
* Specifically server and client events.
*
* @author James Seibel
* @version 11-12-2021
*/
public class EventApi
{
public static final boolean ENABLE_STACK_DUMP_LOGGING = false;
public static final EventApi INSTANCE = new EventApi();
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IVersionConstants VERSION_CONSTANTS = SingletonHandler.get(IVersionConstants.class);
/**
* can be set if we want to recalculate variables related to the LOD view
* distance
* can be set if we want to recalculate variables related
* to the LOD view distance
*/
private boolean recalculateWidths = false;
private boolean isCurrentlyOnSinglePlayerServer = false;
private EventApi()
{
}
// =============//
//=============//
// tick events //
// =============//
public BatchGenerator batchGenerator = null;
private int lastWorldGenTickDelta = 0;
//=============//
public void serverTickEvent()
{
lastWorldGenTickDelta--;
if (!MC.playerExists() || ApiShared.lodWorld.getIsWorldNotLoaded())
return;
LodDimension lodDim = ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension());
if (lodDim == null)
return;
if (ApiShared.isShuttingDown)
return;
if (CONFIG.client().worldGenerator().getEnableDistantGeneration())
{
if (lastWorldGenTickDelta <= 0) {
lastWorldGenTickDelta = 20; // 20 ticks is 1 second. We don't need to refresh world gen status every tick.
try {
if (batchGenerator == null)
batchGenerator = new BatchGenerator(ApiShared.lodBuilder, lodDim);
batchGenerator.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
} catch (Exception e) {
// Exception may happen if world got unloaded unorderly
e.printStackTrace();
}
}
}
else
{
if (batchGenerator != null)
{
batchGenerator.stop(false);
batchGenerator = null;
}
}
// FIXME: This is in server thread. We shouldn't be accessing the client's renderer!
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
}
// ==============//
//==============//
// world events //
// ==============//
//==============//
public void chunkLoadEvent(IChunkWrapper chunk, IDimensionTypeWrapper dimType)
{
ApiShared.lodBuilder.generateLodNodeAsync(chunk, ApiShared.lodWorld, dimType, DistanceGenerationMode.FULL);
}
public void worldSaveEvent()
{
ApiShared.lodWorld.saveAllDimensions(false); // Do an async save.
ApiShared.lodWorld.saveAllDimensions();
}
/** This is also called when a new dimension loads */
public void worldLoadEvent(IWorldWrapper world)
{
if (ENABLE_STACK_DUMP_LOGGING)
ApiShared.LOGGER.info(
"WorldLoadEvent called here for "
+ (world.getWorldType() == WorldType.ClientWorld ? "clientLevel" : "serverLevel"),
new RuntimeException());
// Always ignore ServerWorld event
if (world.getWorldType() == WorldType.ServerWorld)
return;
isCurrentlyOnSinglePlayerServer = MC.hasSinglePlayerServer();
if (!ApiShared.isShuttingDown) ApiShared.LOGGER.warn("WorldLoadEvent called on {} while another world is loaded!",
(world.getWorldType() == WorldType.ClientWorld ? "clientLevel" : "serverLevel"));
ApiShared.isShuttingDown = false;
//DataPointUtil.WORLD_HEIGHT = world.getHeight();
DataPointUtil.WORLD_HEIGHT = world.getHeight();
LodBuilder.MIN_WORLD_HEIGHT = world.getMinHeight(); // This updates the World height
// LodNodeGenWorker.restartExecutorService();
// ThreadMapUtil.clearMaps();
//LodNodeGenWorker.restartExecutorService();
//ThreadMapUtil.clearMaps();
// the player just loaded a new world/dimension
String worldID = LodUtil.getWorldID(world);
ApiShared.LOGGER.info("Loading new world/dimension: {}",worldID);
ApiShared.lodWorld.selectWorld(worldID);
ApiShared.LOGGER.info("World/dimension loaded: {}",worldID);
ApiShared.lodWorld.selectWorld(LodUtil.getWorldID(world));
// make sure the correct LODs are being rendered
// (if this isn't done the previous world's LODs may be drawn)
ClientApi.renderer.regenerateLODsNextFrame();
ApiShared.previousVertQual = CONFIG.client().graphics().quality().getVerticalQuality();
}
/** This is also called when the user disconnects from a server+ */
public void worldUnloadEvent(IWorldWrapper world)
public void worldUnloadEvent()
{
if (ENABLE_STACK_DUMP_LOGGING)
ApiShared.LOGGER.info(
"WorldUnloadEvent called here for "
+ (world.getWorldType() == WorldType.ClientWorld ? "clientLevel" : "serverLevel"),
new RuntimeException());
// the player just unloaded a world/dimension
ThreadMapUtil.clearMaps();
// ClientApi.renderer.markForCleanup();
// ClientApi.renderer.destroyBuffers();
// If it's single player, ignore the client side world unload event
// Note: using isCurrentlyOnSinglePlayerServer as often API call unload event
// AFTER setting MC to not be in a singlePlayerServer
if (isCurrentlyOnSinglePlayerServer && world.getWorldType() == WorldType.ClientWorld)
return;
// if this isn't done unfinished tasks may be left in the queue
// preventing new LodChunks form being generated
if (ApiShared.isShuttingDown) return; // Don't do this if we're already shutting down
ApiShared.isShuttingDown = true;
new Thread(() -> checkIfDisconnectedFromServer()).start();
}
private void checkIfDisconnectedFromServer()
{
try
{
// world unloading events are called before disconnecting from the server,
// so we need to wait a second for MC to disconnect
Thread.sleep(1000);
}
catch (InterruptedException e)
{
// this should never happen, but just in case
e.printStackTrace();
}
// TODO Better report on when world gen is stuck and timeout
if (batchGenerator != null)
batchGenerator.stop(true);
batchGenerator = null;
ApiShared.lodWorld.deselectWorld(); // This force a save and shutdown lodDim properly
// prevent issues related to the buffer builder
// breaking or retaining previous data when changing worlds.
ClientApi.renderer.destroyBuffers();
ClientApi.renderer.requestCleanup();
GLProxy.ensureAllGLJobCompleted();
recalculateWidths = true;
ApiShared.previousVertQual = null;
// TODO: Check if after the refactoring, is this still needed
ClientApi.renderer = new LodRenderer(ClientApi.lodBufferBuilderFactory);
ClientApi.INSTANCE.rendererDisabledBecauseOfExceptions = false;
ApiShared.LOGGER.info("Distant Horizon unloaded");
if (MC.getWrappedClientWorld() == null || (!MC.connectedToServer() && !MC.hasSinglePlayerServer()))
{
// the player just left the server
// TODO should "resetMod()" be called here? -James
// if this isn't done unfinished tasks may be left in the queue
// preventing new LodChunks form being generated
LodWorldGenerator.INSTANCE.restartExecutorService();
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0);
ApiShared.lodWorld.deselectWorld();
// prevent issues related to the buffer builder
// breaking or retaining previous data when changing worlds.
ClientApi.renderer.destroyBuffers();
ClientApi.renderer.requestCleanup();
recalculateWidths = true;
// TODO: Check if after the refactoring, is this still needed
ClientApi.renderer = new LodRenderer(ApiShared.lodBufferBuilderFactory);
ClientApi.INSTANCE.rendererDisabledBecauseOfExceptions = false;
// make sure the nulled objects are freed.
// (this prevents an out of memory error when
// changing worlds)
System.gc();
}
}
public void blockChangeEvent(IChunkWrapper chunk, IDimensionTypeWrapper dimType)
{
if (dimType != MC.getCurrentDimension())
return;
// recreate the LOD where the blocks were changed
LagSpikeCatcher blockChangeUpdate = new LagSpikeCatcher();
ClientApi.INSTANCE.toBeLoaded.add(chunk.getLongChunkPos());
blockChangeUpdate.end("clientChunkLoad");
ApiShared.lodBuilder.generateLodNodeAsync(chunk, ApiShared.lodWorld, dimType);
}
// =============//
// Misc Events //
// =============//
// NOTE: This is being called from Render Thread.
//=============//
// Misc Events //
//=============//
public void onKeyInput(int key, int keyAction)
{
if (CONFIG.client().advanced().debugging().getDebugKeybindingsEnabled())
{
if (key == GLFW.GLFW_KEY_F8 && keyAction == GLFW.GLFW_PRESS)
{
CONFIG.client().advanced().debugging().setDebugMode(CONFIG.client().advanced().debugging().getDebugMode().getNext());
MC.sendChatMessage("F8: Set debug mode " + CONFIG.client().advanced().debugging().getDebugMode());
}
if (key == GLFW.GLFW_KEY_F6 && keyAction == GLFW.GLFW_PRESS)
{
CONFIG.client().advanced().debugging().setDrawLods(!CONFIG.client().advanced().debugging().getDrawLods());
MC.sendChatMessage("F6: Set rendering " + CONFIG.client().advanced().debugging().getDrawLods());
}
}
}
/** Re-centers the given LodDimension if it needs to be. */
public void playerMoveEvent(LodDimension lodDim)
{
// make sure the dimension is centered
RegionPos playerRegionPos = new RegionPos(MC.getPlayerBlockPos());
RegionPos center = lodDim.getCenterRegionPos();
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - center.x, playerRegionPos.z - center.z);
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterRegionPosX(), playerRegionPos.z - lodDim.getCenterRegionPosZ());
if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0)
{
ApiShared.lodWorld.saveAllDimensions();
lodDim.move(worldRegionOffset);
// LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z +
// "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ());
//LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z + "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ());
}
}
@@ -228,8 +225,7 @@ public class EventApi
// calculate how wide the dimension(s) should be in regions
int chunksWide;
if (MC.getWrappedClientWorld().getDimensionType().hasCeiling())
chunksWide = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(),
LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * 2 + 1;
chunksWide = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * 2 + 1;
else
chunksWide = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * 2 + 1;
@@ -240,16 +236,18 @@ public class EventApi
// do the dimensions need to change in size?
if (ApiShared.lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths)
{
ApiShared.lodWorld.saveAllDimensions();
// update the dimensions to fit the new width
ApiShared.lodWorld.resizeDimensionRegionWidth(newWidth);
ApiShared.lodBuilder.defaultDimensionWidthInRegions = newWidth;
ClientApi.renderer.setupBuffers();
ClientApi.renderer.setupBuffers(ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension()));
recalculateWidths = false;
// LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: "
// + newWidth );
//LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth );
}
DetailDistanceUtil.updateSettings();
}
}
@@ -0,0 +1,160 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.bufferBuilding;
import java.util.Map;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.objects.VertexOptimizer;
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.LodUtil;
import static com.seibel.lod.core.builders.lodBuilding.LodBuilder.MIN_WORLD_HEIGHT;
/**
* Builds LODs as rectangular prisms.
* @author James Seibel
* @version 12-8-2021
*/
public class CubicLodTemplate
{
//TODO make it a config
static int cullingRange = 128;
public static void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerZ, long data, Map<LodDirection, long[]> adjData,
byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled)
{
if (vertexOptimizer == null)
return;
// equivalent to 2^detailLevel
int blockWidth = 1 << detailLevel;
int color;
if (debugging != DebugMode.OFF)
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB();
else
color = DataPointUtil.getColor(data);
generateBoundingBox(
vertexOptimizer,
DataPointUtil.getHeight(data),
DataPointUtil.getDepth(data),
blockWidth,
posX * blockWidth, 0, posZ * blockWidth, // x, y, z offset
playerX,
playerZ,
adjData,
color,
DataPointUtil.getLightSkyAlt(data),
DataPointUtil.getLightBlock(data),
adjShadeDisabled);
addBoundingBoxToBuffer(buffer, vertexOptimizer);
}
/** add the given position and color to the buffer */
public static void addPosAndColor(LodBufferBuilder buffer,
float x, float y, float z,
int color, byte skyLightValue, byte blockLightValue)
{
// TODO transparency re-add by replacing the color 255 with "ColorUtil.getAlpha(color)"
buffer.position(x, y, z)
.color(ColorUtil.getRed(color), ColorUtil.getGreen(color), ColorUtil.getBlue(color), 255)
.minecraftLightValue(skyLightValue).minecraftLightValue(blockLightValue)
.endVertex();
}
private static void generateBoundingBox(VertexOptimizer vertexOptimizer,
int height, int depth, int width,
double xOffset, double yOffset, double zOffset,
int playerX, int playerZ,
Map<LodDirection, long[]> adjData,
int color, byte skyLight, byte blockLight,
boolean[] adjShadeDisabled)
{
// don't add an LOD if it is empty
if (height == -1 && depth == -1)
return;
if (depth == height)
// if the top and bottom points are at the same height
// render this LOD as 1 block thick
height++;
// offset the AABB by its x/z position in the world since
// it uses doubles to specify its location, unlike the model view matrix
// which only uses floats
double x = -playerX;
double z = -playerZ;
vertexOptimizer.reset();
vertexOptimizer.setColor(color, adjShadeDisabled);
vertexOptimizer.setLights(skyLight, blockLight);
vertexOptimizer.setWidth(width, height - depth, width);
vertexOptimizer.setOffset((int) (xOffset + x), (int) (depth + yOffset), (int) (zOffset + z));
vertexOptimizer.setAdjData(adjData);
}
private static void addBoundingBoxToBuffer(LodBufferBuilder buffer, VertexOptimizer vertexOptimizer)
{
int color;
byte skyLight;
byte blockLight;
for (LodDirection lodDirection : VertexOptimizer.DIRECTIONS)
{
//if(vertexOptimizer.isCulled(lodDirection))
// continue;
// culling
if (lodDirection == LodDirection.NORTH && vertexOptimizer.getZ(lodDirection, 0) < -cullingRange
|| lodDirection == LodDirection.EAST && vertexOptimizer.getX(lodDirection, 0) > cullingRange
|| lodDirection == LodDirection.SOUTH && vertexOptimizer.getZ(lodDirection, 0) > cullingRange
|| lodDirection == LodDirection.WEST && vertexOptimizer.getX(lodDirection, 0) < -cullingRange)
continue;
int verticalFaceIndex = 0;
while (vertexOptimizer.shouldRenderFace(lodDirection, verticalFaceIndex))
{
for (int vertexIndex = 0; vertexIndex < 6; vertexIndex++)
{
skyLight = vertexOptimizer.getSkyLight(lodDirection, verticalFaceIndex);
blockLight = (byte) vertexOptimizer.getBlockLight();
color = vertexOptimizer.getColor(lodDirection);
addPosAndColor(buffer,
vertexOptimizer.getX(lodDirection, vertexIndex),
vertexOptimizer.getY(lodDirection, vertexIndex, verticalFaceIndex) + MIN_WORLD_HEIGHT,
vertexOptimizer.getZ(lodDirection, vertexIndex),
color, skyLight, blockLight );
}
verticalFaceIndex++;
}
}
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,31 +19,31 @@
package com.seibel.lod.core.builders.lodBuilding;
import java.util.ConcurrentModificationException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.config.BlocksToAvoid;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.enums.config.HorizontalResolution;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.LodRegion;
import com.seibel.lod.core.objects.lod.LodWorld;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockDetailWrapper;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.util.ThreadMapUtil;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorSingletonWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockShapeWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import org.apache.logging.log4j.LogManager;
/**
* This object is in charge of creating Lod related objects.
@@ -56,12 +56,9 @@ import org.apache.logging.log4j.LogManager;
@SuppressWarnings("GrazieInspection")
public class LodBuilder
{
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final ILodConfigWrapperSingleton config = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public static final ConfigBasedLogger EVENT_LOGGER = new ConfigBasedLogger(LogManager.getLogger(LodBuilder.class),
() -> config.client().advanced().debugging().debugSwitch().getLogLodBuilderEvent());
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final IBlockColorSingletonWrapper BLOCK_COLOR = SingletonHandler.get(IBlockColorSingletonWrapper.class);
/** This cannot be final! Different world have different height, and in menu, this causes Null Exceptions*/
//public static final short MIN_WORLD_HEIGHT = MC.getWrappedClientWorld().getMinHeight();
@@ -69,10 +66,9 @@ public class LodBuilder
/** Minecraft's max light value */
public static final short DEFAULT_MAX_LIGHT = 15;
//public static final ExecutorService lodGenThreadPool = Executors.newFixedThreadPool(8, new ThreadFactoryBuilder().setNameFormat("Lod-Builder-%d").build());
private final ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(
new LodThreadFactory(this.getClass().getSimpleName(), Thread.NORM_PRIORITY-1));
private final ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
private final ILodConfigWrapperSingleton config = SingletonHandler.get(ILodConfigWrapperSingleton.class);
@@ -80,10 +76,11 @@ public class LodBuilder
* How wide LodDimensions should be in regions <br>
* Is automatically set before the first frame in ClientProxy.
*/
public int defaultDimensionWidthInRegions = 1;
public int defaultDimensionWidthInRegions = 0;
//public static final boolean useExperimentalLighting = true;
private static int timesToEdgeDetect = 1;
@@ -92,33 +89,27 @@ public class LodBuilder
}
public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim, boolean genAll)
public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim)
{
// Block change event
generateLodNodeAsync(chunk, lodWorld, dim, DistanceGenerationMode.FULL, true, genAll, ()->{},
()->{generateLodNodeAsync(chunk,lodWorld,dim, genAll);});
generateLodNodeAsync(chunk, lodWorld, dim, DistanceGenerationMode.FULL);
}
public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim,
DistanceGenerationMode generationMode, boolean override, boolean genAll, Runnable endCallback, Runnable retryCallback)
public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim, DistanceGenerationMode generationMode)
{
if (lodWorld == null || lodWorld.getIsWorldNotLoaded()) {
endCallback.run();
if (lodWorld == null || lodWorld.getIsWorldNotLoaded())
return;
}
// don't try to create an LOD object
// if for some reason we aren't
// given a valid chunk object
if (chunk == null) {
endCallback.run();
if (chunk == null)
return;
}
Runnable thread = () ->
Thread thread = new Thread(() ->
{
boolean retryNeeded = false;
try
{
//noinspection GrazieInspection
//try
//{
// we need a loaded client world in order to
// get the textures for blocks
if (MC.getWrappedClientWorld() == null)
@@ -130,25 +121,26 @@ public class LodBuilder
return;
// make sure the dimension exists
// if not, it prob means that player left
LodDimension lodDim = lodWorld.getLodDimension(dim);
if (lodDim == null) return;
retryNeeded = !generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode), override, genAll);
}
catch (RuntimeException e)
{
EVENT_LOGGER.error("LodBuilder Thread Uncaught Exception: ", e);
// if the world changes while LODs are being generated
// they will throw errors as they try to access things that no longer
// exist.
} finally {
if (!retryNeeded)
endCallback.run();
LodDimension lodDim;
if (lodWorld.getLodDimension(dim) == null)
{
lodDim = new LodDimension(dim, lodWorld, defaultDimensionWidthInRegions);
lodWorld.addLodDimension(lodDim);
}
else
retryCallback.run();
}
};
{
lodDim = lodWorld.getLodDimension(dim);
}
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode));
//}
//catch (IllegalArgumentException | NullPointerException e)
//{
// e.printStackTrace();
// // if the world changes while LODs are being generated
// // they will throw errors as they try to access things that no longer
// // exist.
//}
});
lodGenThreadPool.execute(thread);
}
@@ -156,275 +148,202 @@ public class LodBuilder
* Creates a LodNode for a chunk in the given world.
* @throws IllegalArgumentException thrown if either the chunk or world is null.
*/
public boolean generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk, LodBuilderConfig config, boolean override, boolean genAll)
public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk) throws IllegalArgumentException
{
try {
if (chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
LodRegion region = lodDim.getRegion(chunk.getRegionPosX(), chunk.getRegionPosZ());
if (region == null)
return false;
// this happens if a LOD is generated after the user leaves the world.
if (MC.getWrappedClientWorld() == null)
return false;
if (!canGenerateLodFromChunk(chunk))
return false;
// generate the LODs
int maxVerticalData = DetailDistanceUtil.getMaxVerticalData((byte)0);
long[] data = new long[maxVerticalData*16*16];
boolean isAllVoid = true;
if (!config.quickFillWithVoid) {
for (int i = 0; i < 16*16; i++)
{
int subX = i/16;
int subZ = i%16;
writeVerticalData(data, i*maxVerticalData, maxVerticalData, chunk, config, subX, subZ);
isAllVoid &= DataPointUtil.isVoid(data[i*maxVerticalData]);
if (!DataPointUtil.doesItExist(data[i*maxVerticalData]))
throw new RuntimeException("writeVerticalData result: Datapoint does not exist at "+ chunk.getMinX()+subX +", "+ chunk.getMinZ()+subZ);
if (DataPointUtil.getGenerationMode(data[i*maxVerticalData]) != config.distanceGenerationMode.complexity)
throw new RuntimeException("writeVerticalData result: Datapoint invalid at "+ chunk.getMinX()+subX +", "+ chunk.getMinZ()+subZ);
}
} else {
for (int i = 0; i < 16*16; i++)
{
data[i*maxVerticalData] = DataPointUtil.createVoidDataPoint(config.distanceGenerationMode.complexity);
}
}
if (isAllVoid) EVENT_LOGGER.debug("The chunk {} is completely void.", chunk);
// This MUST be done after the data is generated, to ensure that during the generation, the data is valid.
if (!canGenerateLodFromChunk(chunk)) // TODO Why are we calling this again? - James
return false; // Answer: Because concurrency change may cause the chunk to have invalid data, like light.
if (genAll) {
return writeAllLodNodeData(lodDim, region, chunk.getChunkPosX(), chunk.getChunkPosZ(), data, config, override);
} else {
return writePartialLodNodeData(lodDim, region, chunk.getChunkPosX(), chunk.getChunkPosZ(), data, config, override);
}
} catch (RuntimeException e) {
EVENT_LOGGER.error("LodBuilder encountered an error on building lod: ", e);
return false;
}
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig());
}
public static boolean canGenerateLodFromChunk(IChunkWrapper chunk)
/**
* Creates a LodNode for a chunk in the given world.
* @throws IllegalArgumentException thrown if either the chunk or world is null.
*/
public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk, LodBuilderConfig config)
throws IllegalArgumentException
{
return chunk != null && chunk.isLightCorrect() && chunk.doesNearbyChunksExist();
}
private boolean writeAllLodNodeData(LodDimension lodDim, LodRegion region, int chunkX, int chunkZ,
long[] data, LodBuilderConfig config, boolean override)
{
region.isWriting.incrementAndGet();
try {
if (region.getMinDetailLevel()!= 0) {
if (!LodUtil.checkRamUsage(0.05, 16)) {
EVENT_LOGGER.debug("LodBuilder: Not enough RAM avalible for loading files to build lods! Returning...");
return false;
}
LodRegion newRegion = lodDim.getRegionFromFile(region, (byte)0, region.getVerticalQuality());
if (region!=newRegion)
throw new RuntimeException();
}
//ApiShared.LOGGER.info("Generate chunk: {}, {} ({}, {}) at genMode {}",
// chunk.getChunkPosX(), chunk.getChunkPosZ(), chunk.getMinX(), chunk.getMinZ(), config.distanceGenerationMode);
region.addChunkOfData((byte)0, chunkX*16, chunkZ*16, 16, 16, data, data.length/16/16, override);
region.regenerateLodFromArea((byte)0, chunkX*16, chunkZ*16, 16, 16);
//long executeTime = System.currentTimeMillis();
if (chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
int startX;
int startZ;
LodRegion region = lodDim.getRegion(chunk.getRegionPosX(), chunk.getRegionPosZ());
if (region == null)
return;
// this happens if a LOD is generated after the user leaves the world.
if (MC.getWrappedClientWorld() == null)
return;
// determine how many LODs to generate horizontally
byte minDetailLevel = region.getMinDetailLevel();
HorizontalResolution detail = DetailDistanceUtil.getLodGenDetail(minDetailLevel);
// determine how many LODs to generate vertically
//VerticalQuality verticalQuality = LodConfig.CLIENT.graphics.qualityOption.verticalQuality.get();
byte detailLevel = detail.detailLevel;
// generate the LODs
int posX;
int posZ;
for (int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++)
{
startX = detail.startX[i];
startZ = detail.startZ[i];
if (!region.doesDataExist((byte)0, chunkX*16, chunkZ*16, config.distanceGenerationMode))
throw new RuntimeException("data at detail 0 is still null after writes to it!");
if (!region.doesDataExist(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, chunkZ, config.distanceGenerationMode))
throw new RuntimeException("data at chunk detail level is still null after writes to it!");
} finally {
region.isWriting.decrementAndGet();
long[] data;
long[] dataToMergeVertical = createVerticalDataToMerge(detail, chunk, config, startX, startZ);
data = DataPointUtil.mergeMultiData(dataToMergeVertical, DataPointUtil.WORLD_HEIGHT / 2 + 1, DetailDistanceUtil.getMaxVerticalData(detailLevel));
//lodDim.clear(detailLevel, posX, posZ);
if (data != null && data.length != 0)
{
posX = LevelPosUtil.convert((byte) 0, chunk.getChunkPosX() * 16 + startX, detail.detailLevel);
posZ = LevelPosUtil.convert((byte) 0, chunk.getChunkPosZ() * 16 + startZ, detail.detailLevel);
lodDim.addVerticalData(detailLevel, posX, posZ, data, false);
}
}
return true;
lodDim.updateData(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getChunkPosX(), chunk.getChunkPosZ());
//executeTime = System.currentTimeMillis() - executeTime;
//if (executeTime > 0) ClientApi.LOGGER.info("generateLodNodeFromChunk level: " + detailLevel + " time ms: " + executeTime);
}
private boolean writePartialLodNodeData(LodDimension lodDim, LodRegion region, int chunkX, int chunkZ,
long[] data, LodBuilderConfig config, boolean override)
{
region.isWriting.incrementAndGet();
try {
byte targetLevel = region.getMinDetailLevel();
int vertQual = DetailDistanceUtil.getMaxVerticalData(targetLevel);
int lodCount = (targetLevel >= LodUtil.CHUNK_DETAIL_LEVEL) ?
1 : 1 << (LodUtil.CHUNK_DETAIL_LEVEL - targetLevel);
if (targetLevel != 0) {
int lodWidth = 16/lodCount;
int inputVertQual = data.length/16/16;
long[] mergedData = new long[vertQual*lodCount*lodCount];
for (int subX=0; subX<lodCount; subX++) {
for (int subZ=0; subZ<lodCount; subZ++) {
long[] toBeMerged = DataPointUtil.extractDataArray(
data, 16, 16, subX*lodWidth, subZ*lodWidth, lodWidth, lodWidth);
if(toBeMerged.length != lodWidth*lodWidth*inputVertQual) throw new RuntimeException();
long[] merged = DataPointUtil.mergeMultiData(toBeMerged, inputVertQual, vertQual);
if (merged.length != vertQual) throw new RuntimeException();
if (!DataPointUtil.doesItExist(merged[0]) ||
DataPointUtil.getGenerationMode(merged[0]) != config.distanceGenerationMode.complexity)
throw new RuntimeException();
System.arraycopy(merged, 0, mergedData, (subZ+subX*lodCount)*vertQual, vertQual);
}
}
data = mergedData;
}
if (lodCount*lodCount*vertQual != data.length) throw new RuntimeException();
for (int i=0; i<data.length; i+=vertQual) {
if (!DataPointUtil.doesItExist(data[i]) ||
DataPointUtil.getGenerationMode(data[i]) != config.distanceGenerationMode.complexity) {
EVENT_LOGGER.error("NULL data at {}, detail {}, vertQual {}, lodCount {}, chunkPos [{},{}]\n"
+ "Data: {}",
i, targetLevel, vertQual, lodCount, chunkX, chunkZ, DataPointUtil.toString(data[i]));
throw new RuntimeException("Null data!");
}
}
//ApiShared.LOGGER.info("Generate chunk: {}, {} ({}, {}) at genMode {}",
// chunk.getChunkPosX(), chunk.getChunkPosZ(), chunk.getMinX(), chunk.getMinZ(), config.distanceGenerationMode);
if (targetLevel != region.getMinDetailLevel()) {
//Concurrency issues happened.
throw new ConcurrentModificationException("Min detail level changed while writing data");
}
region.addChunkOfData(targetLevel,
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, targetLevel),
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkZ, targetLevel),
lodCount, lodCount, data, vertQual, override);
region.regenerateLodFromArea(targetLevel,
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, targetLevel),
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkZ, targetLevel),
lodCount, lodCount);
if (!region.doesDataExist(targetLevel,
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, targetLevel),
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkZ, targetLevel),
config.distanceGenerationMode))
throw new RuntimeException("data at detail "+ targetLevel+" is still null after writes to it!");
} catch (Exception e) {
EVENT_LOGGER.error("LodBuilder encountered an error on writePartialLodNodeData: ", e);
} finally {
region.isWriting.decrementAndGet();
}
return true;
}
/** creates a vertical DataPoint */
private void writeVerticalData(long[] data, int dataOffset, int maxVerticalData,
IChunkWrapper chunk, LodBuilderConfig config, int chunkSubPosX, int chunkSubPosZ)
private long[] createVerticalDataToMerge(HorizontalResolution detail, IChunkWrapper chunk, LodBuilderConfig config, int startX, int startZ)
{
int totalVerticalData = (chunk.getHeight());
long[] dataToMerge = new long[totalVerticalData];
// equivalent to 2^detailLevel
int size = 1 << detail.detailLevel;
long[] dataToMerge = ThreadMapUtil.getBuilderVerticalArray(detail.detailLevel);
int verticalData = DataPointUtil.WORLD_HEIGHT / 2 + 1;
int height;
int depth;
int color;
int light;
int lightSky;
int lightBlock;
int generation = config.distanceGenerationMode.complexity;
int xRel;
int zRel;
int xAbs;
int yAbs;
int zAbs;
boolean hasCeiling = MC.getWrappedClientWorld().getDimensionType().hasCeiling();
boolean hasSkyLight = MC.getWrappedClientWorld().getDimensionType().hasSkyLight();
byte generation = config.distanceGenerationMode.complexity;
int count = 0;
// FIXME: This yAbs is just messy!
int x = chunk.getMinX() + chunkSubPosX;
int z = chunk.getMinZ() + chunkSubPosZ;
int y = chunk.getMaxY(x, z);
boolean isDefault;
int index;
boolean topBlock = true;
if (y < chunk.getMinBuildHeight())
dataToMerge[0] = DataPointUtil.createVoidDataPoint(generation);
int maxConnectedLods = LodBuilder.config.client().graphics().quality().getVerticalQuality().maxVerticalData[0];
while (y >= chunk.getMinBuildHeight()) {
int height = determineHeightPointFrom(chunk, config, x, y, z);
// If the lod is at the default height, it must be void data
if (height < chunk.getMinBuildHeight()) {
if (topBlock) dataToMerge[0] = DataPointUtil.createVoidDataPoint(generation);
break;
for (index = 0; index < size * size; index++)
{
xRel = startX + index % size;
zRel = startZ + index / size;
xAbs = chunk.getMinX() + xRel;
zAbs = chunk.getMinZ() + zRel;
//Calculate the height of the lod
yAbs = chunk.getMaxY(xRel,zRel) - MIN_WORLD_HEIGHT;
int count = 0;
boolean topBlock = true;
if (yAbs <= 0);
dataToMerge[index * verticalData] = DataPointUtil.createVoidDataPoint(generation);
while (yAbs > 0)
{
height = determineHeightPointFrom(chunk, config, xAbs, yAbs, zAbs);
// If the lod is at the default height, it must be void data
if (height == 0)
break;
yAbs = height - 1;
// We search light on above air block
depth = determineBottomPointFrom(chunk, config, xAbs, yAbs, zAbs, count < timesToEdgeDetect && !hasCeiling);
if (hasCeiling && topBlock)
{
yAbs = depth;
light = getLightValue(chunk, xAbs,yAbs + MIN_WORLD_HEIGHT, zAbs, true, hasSkyLight, true);
color = generateLodColor(chunk, config, xAbs, yAbs, zAbs);
}
else
{
light = getLightValue(chunk, xAbs, yAbs + MIN_WORLD_HEIGHT, zAbs, hasCeiling, hasSkyLight, topBlock);
color = generateLodColor(chunk, config, xAbs, yAbs, zAbs);
}
lightBlock = light & 0b1111;
lightSky = (light >> 4) & 0b1111;
isDefault = ((light >> 8)) == 1;
dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height, depth, color, lightSky, lightBlock, generation, isDefault);
topBlock = false;
yAbs = depth - 1;
count++;
}
y = height - 1;
// We search light on above air block
int depth = determineBottomPointFrom(chunk, config, x, y, z,
count < maxConnectedLods && (!hasCeiling || !topBlock));
if (hasCeiling && topBlock)
y = depth;
int light = getLightValue(chunk, x, y, z, hasCeiling, hasSkyLight, topBlock);
int color = generateLodColor(chunk, config, x, y, z);
int lightBlock = light & 0b1111;
int lightSky = (light >> 4) & 0b1111;
dataToMerge[count] = DataPointUtil.createDataPoint(height-chunk.getMinBuildHeight(), depth-chunk.getMinBuildHeight(),
color, lightSky, lightBlock, generation);
topBlock = false;
y = depth - 1;
count++;
}
long[] result = DataPointUtil.mergeMultiData(dataToMerge, totalVerticalData, maxVerticalData);
if (result.length != maxVerticalData) throw new ArrayIndexOutOfBoundsException();
System.arraycopy(result, 0, data, dataOffset, maxVerticalData);
}
public static final LodDirection[] DIRECTIONS = new LodDirection[] {
LodDirection.UP,
LodDirection.DOWN,
LodDirection.WEST,
LodDirection.EAST,
LodDirection.NORTH,
LodDirection.SOUTH };
private boolean hasCliffFace(IChunkWrapper chunk, int x, int y, int z) {
for (LodDirection dir : DIRECTIONS) {
IBlockDetailWrapper block = chunk.getBlockDetailAtFace(x, y, z, dir);
if (block == null || !block.hasFaceCullingFor(LodDirection.OPPOSITE_DIRECTIONS[dir.ordinal()]))
return true;
}
return false;
return dataToMerge;
}
/**
* Find the lowest valid point from the bottom.
* Used when creating a vertical LOD.
*/
private int determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig builderConfig, int xAbs, int yAbs, int zAbs, boolean strictEdge)
private short determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, boolean strictEdge)
{
int depth = chunk.getMinBuildHeight();
IBlockDetailWrapper currentBlockDetail = null;
short depth = 0;
int colorOfBlock = 0;
if (strictEdge)
{
IBlockDetailWrapper blockAbove = chunk.getBlockDetail(xAbs, yAbs + 1, zAbs);
if (blockAbove != null && !blockAbove.shouldRender(config.client().worldGenerator().getBlocksToAvoid()))
{ // The above block is skipped. Lets use its skipped color for currrent block
currentBlockDetail = blockAbove;
colorOfBlock = chunk.getBlockColorWrapper(xAbs, yAbs, zAbs).getColor();
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(xAbs, yAbs + 1, zAbs);
if (block != null && ((this.config.client().worldGenerator().getBlocksToAvoid().nonFull && block.isNonFull())
|| (this.config.client().worldGenerator().getBlocksToAvoid().noCollision && block.hasNoCollision())))
{
int aboveColorInt = chunk.getBlockColorWrapper(xAbs, yAbs + 1, zAbs).getColor();
if (aboveColorInt != 0)
colorOfBlock = aboveColorInt;
}
if (currentBlockDetail == null) currentBlockDetail = chunk.getBlockDetail(xAbs, yAbs, zAbs);
}
for (int y = yAbs - 1; y >= chunk.getMinBuildHeight(); y--)
for (int y = yAbs - 1; y >= 0; y--)
{
IBlockDetailWrapper nextBlock = chunk.getBlockDetail(xAbs, y, zAbs);
if (isLayerValidLodPoint(nextBlock)) {
if (!strictEdge) continue;
if (currentBlockDetail.equals(nextBlock)) continue;
if (!hasCliffFace(chunk, xAbs, y, zAbs)) continue;
if (!isLayerValidLodPoint(chunk, xAbs, y, zAbs))
{
depth = (short) (y + 1);
break;
}
if (strictEdge)
{
if (colorOfBlock != chunk.getBlockColorWrapper(xAbs, y, zAbs).getColor())
{
depth = (short) (y + 1);
break;
}
}
depth = (y + 1);
break;
}
return depth;
}
/** Find the highest valid point from the Top */
private int determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs)
private short determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs)
{
//TODO find a way to skip bottom of the world
int height = chunk.getMinBuildHeight()-1;
for (int y = yAbs; y >= chunk.getMinBuildHeight(); y--)
short height = 0;
if (config.useHeightmap)
height = (short) chunk.getHeightMapValue(xAbs, zAbs);
else
{
if (isLayerValidLodPoint(chunk, xAbs, y, zAbs))
for (int y = yAbs; y >= 0; y--)
{
height = (y + 1);
break;
if (isLayerValidLodPoint(chunk, xAbs, y, zAbs))
{
height = (short) (y + 1);
break;
}
}
}
return height;
@@ -451,24 +370,25 @@ public class LodBuilder
}
else
{
colorInt = getColorForBlock(chunk, x, y, z);
// if we are skipping non-full and non-solid blocks that means we ignore
// snow, flowers, etc. Get the above block so we can still get the color
// of the snow, flower, etc. that may be above this block
colorInt = 0;
if (chunk.blockPosInsideChunk(x, y+1, z)) {
IBlockDetailWrapper blockAbove = chunk.getBlockDetail(x, y+1, z);
if (blockAbove != null && !blockAbove.shouldRender(config.client().worldGenerator().getBlocksToAvoid()))
{ // The above block is skipped. Lets use its skipped color for currrent block
colorInt = blockAbove.getAndResolveFaceColor(null, chunk, FACTORY.createBlockPos(x, y+1, z));
}
}
int aboveColorInt = 0;
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(x, y + 1, z);
if (block != null && ((config.client().worldGenerator().getBlocksToAvoid().nonFull && block.isNonFull())
|| (config.client().worldGenerator().getBlocksToAvoid().noCollision && block.hasNoCollision())))
aboveColorInt = getColorForBlock(chunk, x, y + 1, z);
//if (colorInt == 0 && yAbs > 0)
// if this block is invisible, check the block below it
// colorInt = generateLodColor(chunk, config, xRel, yAbs - 1, zRel, blockPos);
// override this block's color if there was a block above this
// and we were avoiding non-full/non-solid blocks
if (colorInt == 0) {
IBlockDetailWrapper detail = chunk.getBlockDetail(x, y, z);
colorInt = detail.getAndResolveFaceColor(null, chunk, FACTORY.createBlockPos(x, y, z));
}
if (aboveColorInt != 0)
colorInt = aboveColorInt;
}
return colorInt;
@@ -477,8 +397,12 @@ public class LodBuilder
/** Gets the light value for the given block position */
private int getLightValue(IChunkWrapper chunk, int x, int y, int z, boolean hasCeiling, boolean hasSkyLight, boolean topBlock)
{
int skyLight;
int skyLight = 0;
int blockLight;
// 1 means the lighting is a guess
int isDefault = 0;
IWorldWrapper world = MC.getWrappedServerWorld();
int blockBrightness = chunk.getEmittedBrightness(x, y, z);
// get the air block above or below this block
@@ -487,60 +411,69 @@ public class LodBuilder
else
y++;
blockLight = chunk.getBlockLight(x, y, z);
skyLight = hasSkyLight ? chunk.getSkyLight(x, y, z) : 0;
if (blockLight == -1 || skyLight == -1)
if (world != null)
{
IWorldWrapper world = MC.getWrappedServerWorld();
if (world != null)
// server world sky light (always accurate)
blockLight = world.getBlockLight(x,y,z);
if (topBlock && !hasCeiling && hasSkyLight)
skyLight = DEFAULT_MAX_LIGHT;
else
{
// server world sky light (always accurate)
blockLight = world.getBlockLight(x, y, z);
if (topBlock && !hasCeiling && hasSkyLight)
skyLight = DEFAULT_MAX_LIGHT;
else
skyLight = hasSkyLight ? world.getSkyLight(x, y, z) : 0;
if (!topBlock && skyLight == 15)
if (hasSkyLight)
skyLight = world.getSkyLight(x,y,z);
//else
// skyLight = 0;
}
if (!topBlock && skyLight == 15)
{
// we are on predicted terrain, and we don't know what the light here is,
// lets just take a guess
if (y >= MC.getWrappedClientWorld().getSeaLevel() - 5)
{
// we are on predicted terrain, and we don't know what the light here is,
// lets just take a guess
skyLight = 12;
isDefault = 1;
}
else
skyLight = 0;
}
}
else
{
world = MC.getWrappedClientWorld();
if (world==null)
{
blockLight = 0;
skyLight = 12;
isDefault = 1;
}
else
{
world = MC.getWrappedClientWorld();
if (world == null)
// client world sky light (almost never accurate)
blockLight = world.getBlockLight(x,y,z);
// estimate what the lighting should be
if (hasSkyLight || !hasCeiling)
{
blockLight = 0;
skyLight = 12;
}
else
{
// client world sky light (almost never accurate)
blockLight = world.getBlockLight(x, y, z);
// estimate what the lighting should be
if (hasSkyLight || !hasCeiling)
if (topBlock)
skyLight = DEFAULT_MAX_LIGHT;
else
{
if (topBlock)
skyLight = DEFAULT_MAX_LIGHT;
else
if (hasSkyLight)
skyLight = world.getSkyLight(x,y,z);
//else
// skyLight = 0;
if (!chunk.isLightCorrect() && (skyLight == 0 || skyLight == 15))
{
if (hasSkyLight)
skyLight = world.getSkyLight(x, y, z);
//else
// skyLight = 0;
if (!chunk.isLightCorrect() && (skyLight == 0 || skyLight == 15))
// we don't know what the light here is,
// lets just take a guess
if (y >= MC.getWrappedClientWorld().getSeaLevel() - 5)
{
// we don't know what the light here is,
// lets just take a guess
skyLight = 12;
isDefault = 1;
}
else
skyLight = 0;
}
}
}
@@ -548,21 +481,68 @@ public class LodBuilder
}
blockLight = LodUtil.clamp(0, Math.max(blockLight, blockBrightness), DEFAULT_MAX_LIGHT);
return blockLight + (skyLight << 4);
return blockLight + (skyLight << 4) + (isDefault << 8);
}
/** Is the block at the given blockPos a valid LOD point? */
private boolean isLayerValidLodPoint(IBlockDetailWrapper blockDetail)
/** Returns a color int for the given block. */
private int getColorForBlock(IChunkWrapper chunk, int x, int y, int z)
{
BlocksToAvoid avoid = config.client().worldGenerator().getBlocksToAvoid();
return blockDetail != null && blockDetail.shouldRender(avoid);
int colorOfBlock;
int colorInt;
IBlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(x, y, z);
if (blockShapeWrapper == null || blockShapeWrapper.isToAvoid())
return 0;
IBlockColorWrapper blockColorWrapper;
if (chunk.isWaterLogged(x, y, z))
blockColorWrapper = BLOCK_COLOR.getWaterColor();
else
blockColorWrapper = chunk.getBlockColorWrapper(x, y, z);
colorOfBlock = blockColorWrapper.getColor();
if (blockColorWrapper.hasTint())
{
IBiomeWrapper biome = chunk.getBiome(x, y, z);
int tintValue;
if (blockColorWrapper.hasGrassTint())
// grass and green plants
tintValue = biome.getGrassTint(0,0);
else if (blockColorWrapper.hasFolliageTint())
tintValue = biome.getFolliageTint();
else
//we can reintroduce this with the wrappers
tintValue = biome.getWaterTint();
colorInt = ColorUtil.multiplyRGBcolors(tintValue | 0xFF000000, colorOfBlock);
}
else
colorInt = colorOfBlock;
return colorInt;
}
/** Is the block at the given blockPos a valid LOD point? */
private boolean isLayerValidLodPoint(IChunkWrapper chunk, int x, int y, int z)
{
BlocksToAvoid avoid = config.client().worldGenerator().getBlocksToAvoid();
IBlockDetailWrapper block = chunk.getBlockDetail(x, y, z);
return block != null && block.shouldRender(avoid);
if (chunk.isWaterLogged(x, y, z))
return true;
boolean nonFullAvoidance = config.client().worldGenerator().getBlocksToAvoid().nonFull;
boolean noCollisionAvoidance = config.client().worldGenerator().getBlocksToAvoid().noCollision;
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(x, y, z);
if (block == null) return false;
return !block.isToAvoid()
&& !(nonFullAvoidance && block.isNonFull())
&& !(noCollisionAvoidance && block.hasNoCollision());
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -39,7 +39,6 @@ public class LodBuilderConfig
public boolean useSolidBlocksInColorGen;
/** default: server */
public DistanceGenerationMode distanceGenerationMode;
public boolean quickFillWithVoid;
/**
* default settings for a normal chunk <br>
@@ -48,18 +47,49 @@ public class LodBuilderConfig
* useSolidBlocksInColorGen = true <br>
* generationMode = Server <br>
*/
public LodBuilderConfig(DistanceGenerationMode newDistanceGenerationMode)
public LodBuilderConfig()
{
useHeightmap = false;
useBiomeColors = false;
useSolidBlocksInColorGen = true;
quickFillWithVoid = false;
distanceGenerationMode = DistanceGenerationMode.FULL;
}
/**
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseSolidBlocksInBiomeColor default = true
* @param newDistanceGenerationMode default = Server
*/
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors,
boolean newUseSolidBlocksInBiomeColor, DistanceGenerationMode newDistanceGenerationMode)
{
useHeightmap = newUseHeightmap;
useBiomeColors = newUseBiomeColors;
useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
distanceGenerationMode = newDistanceGenerationMode;
}
public static LodBuilderConfig getFillVoidConfig() {
LodBuilderConfig config = new LodBuilderConfig(DistanceGenerationMode.NONE);
config.quickFillWithVoid = true;
return config;
/**
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseSolidBlocksInBiomeColor default = true
*/
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors, boolean newUseSolidBlocksInBiomeColor)
{
this();
useHeightmap = newUseHeightmap;
useBiomeColors = newUseBiomeColors;
useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
distanceGenerationMode = newUseHeightmap ? DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT : DistanceGenerationMode.BIOME_ONLY;
}
/**
* @param newDistanceGenerationMode default = Server
*/
public LodBuilderConfig(DistanceGenerationMode newDistanceGenerationMode)
{
this();
distanceGenerationMode = newDistanceGenerationMode;
}
}
@@ -1,281 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.lodBuilding.bufferBuilding;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.util.ColorUtil;
import static com.seibel.lod.core.render.LodRenderer.EVENT_LOGGER;
/**
* Represents a renderable quad.
*
* @author James Seibel
* @author ?
* @version 4-9-2022
*/
public final class BufferQuad
{
final short x;
final short y;
final short z;
short widthEastWest;
/** This is both North/South and Up/Down since the merging logic is the same either way */
short widthNorthSouthOrUpDown;
final int color;
final byte skyLight;
final byte blockLight;
final LodDirection direction;
boolean hasError = false;
BufferQuad(short x, short y, short z, short widthEastWest, short widthNorthSouthOrUpDown,
int color, byte skylight, byte blocklight,
LodDirection direction)
{
if (widthEastWest == 0 || widthNorthSouthOrUpDown == 0)
throw new IllegalArgumentException("Size 0 quad!");
if (widthEastWest < 0 || widthNorthSouthOrUpDown < 0)
throw new IllegalArgumentException("Negative sized quad!");
this.x = x;
this.y = y;
this.z = z;
this.widthEastWest = widthEastWest;
this.widthNorthSouthOrUpDown = widthNorthSouthOrUpDown;
this.color = color;
this.skyLight = skylight;
this.blockLight = blocklight;
this.direction = direction;
}
/** a rough but fast calculation */
double calculateDistance(double relativeX, double relativeY, double relativeZ)
{
return Math.pow(relativeX - x, 2) + Math.pow(relativeY - y, 2) + Math.pow(relativeZ - z, 2);
}
/** compares this quad's position to the given quad */
public int compare(BufferQuad quad, BufferMergeDirectionEnum compareDirection)
{
if (direction != quad.direction)
throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + direction);
if (compareDirection == BufferMergeDirectionEnum.EastWest)
{
switch (direction.getAxis())
{
case X:
return threeDimensionalCompare(x, y, z, quad.x, quad.y, quad.z);
case Y:
return threeDimensionalCompare(y, z, x, quad.y, quad.z, quad.x);
case Z:
return threeDimensionalCompare(z, y, x, quad.z, quad.y, quad.x);
default:
throw new IllegalArgumentException("Invalid Axis enum: " + direction.getAxis());
}
}
else // if ()
{
switch (direction.getAxis())
{
case X:
return threeDimensionalCompare(x, z, y, quad.x, quad.z, quad.y);
case Y:
return threeDimensionalCompare(y, x, z, quad.y, quad.x, quad.z);
case Z:
return threeDimensionalCompare(z, x, y, quad.z, quad.x, quad.y);
default:
throw new IllegalArgumentException("Invalid Axis enum: " + direction.getAxis());
}
}
}
/**
* Compares two 3D points A and B. <br>
* The X, Y, and Z coordinates can be passed into parameters 0, 1, and 2 in any order
* provided they are in the same order for both A and B. <br>
* With the 0th parameter being the most significant when comparing.
*/
private static int threeDimensionalCompare(short a0, short a1, short a2, short b0, short b1, short b2)
{
long a = (long) a0 << 48 | (long) a1 << 32 | (long) a2 << 16;
long b = (long) b0 << 48 | (long) b1 << 32 | (long) b2 << 16;
return Long.compare(a, b);
}
/**
* Attempts to merge the given quad into this one.
* @returns true if the quads were merged, false otherwise.
*/
public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection)
{
if (quad.hasError || this.hasError) return false;
// only merge quads that are in the same direction
if (direction != quad.direction)
return false;
// make sure these quads share the same perpendicular axis
if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y) ||
(mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown && this.x != quad.x))
{
return false;
}
// get the position of each quad to compare against
short thisPerpendicularCompareStartPos; // edge perpendicular to the merge direction
short thisParallelCompareStartPos; // edge parallel to the merge direction
short otherPerpendicularCompareStartPos;
short otherParallelCompareStartPos;
switch (this.direction.getAxis())
{
default: // shouldn't normally happen, just here to make the compiler happy
case X:
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{
thisPerpendicularCompareStartPos = this.z;
thisParallelCompareStartPos = this.x;
otherPerpendicularCompareStartPos = quad.z;
otherParallelCompareStartPos = quad.x;
}
else //if (mergeDirection == MergeDirection.NorthSouthOrUpDown)
{
thisPerpendicularCompareStartPos = this.y;
thisParallelCompareStartPos = this.z;
otherPerpendicularCompareStartPos = quad.y;
otherParallelCompareStartPos = quad.z;
}
break;
case Y:
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{
thisPerpendicularCompareStartPos = this.x;
thisParallelCompareStartPos = this.z;
otherPerpendicularCompareStartPos = quad.x;
otherParallelCompareStartPos = quad.z;
}
else //if (mergeDirection == MergeDirection.NorthSouthOrUpDown)
{
thisPerpendicularCompareStartPos = this.z;
thisParallelCompareStartPos = this.y;
otherPerpendicularCompareStartPos = quad.z;
otherParallelCompareStartPos = quad.y;
}
break;
case Z:
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{
thisPerpendicularCompareStartPos = this.x;
thisParallelCompareStartPos = this.z;
otherPerpendicularCompareStartPos = quad.x;
otherParallelCompareStartPos = quad.z;
}
else //if (mergeDirection == MergeDirection.NorthSouthOrUpDown)
{
thisPerpendicularCompareStartPos = this.y;
thisParallelCompareStartPos = this.z;
otherPerpendicularCompareStartPos = quad.y;
otherParallelCompareStartPos = quad.z;
}
break;
}
// get the width of this quad in the relevant axis
short thisPerpendicularCompareWidth;
short thisParallelCompareWidth;
short otherParallelCompareWidth;
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{
thisPerpendicularCompareWidth = this.widthEastWest;
thisParallelCompareWidth = this.widthNorthSouthOrUpDown;
otherParallelCompareWidth = quad.widthNorthSouthOrUpDown;
}
else
{
thisPerpendicularCompareWidth = this.widthNorthSouthOrUpDown;
thisParallelCompareWidth = this.widthEastWest;
otherParallelCompareWidth = quad.widthEastWest;
}
// FIXME: TEMP: Hard limit for width
if (thisPerpendicularCompareWidth >= 16) return false;
if (Math.floorDiv(otherPerpendicularCompareStartPos, 16)
!= Math.floorDiv(thisPerpendicularCompareStartPos, 16)) return false;
// check if these quads are adjacent
if (thisPerpendicularCompareStartPos + thisPerpendicularCompareWidth < otherPerpendicularCompareStartPos ||
thisParallelCompareStartPos != otherParallelCompareStartPos)
{
// these quads aren't adjacent, they can't be merged
return false;
}
else if (thisPerpendicularCompareStartPos + thisPerpendicularCompareWidth > otherPerpendicularCompareStartPos)
{
// these quads are overlapping, they can't be merged
EVENT_LOGGER.warn("Overlapping quads detected!");
quad.hasError = true;
this.hasError = true;
return false;
}
// only merge quads that have the same width edges
if (thisParallelCompareWidth != otherParallelCompareWidth)
{
return false;
}
// do the quads' color, light, etc. match?
if (color != quad.color ||
skyLight != quad.skyLight ||
blockLight != quad.blockLight)
{
// we can only merge identically colored/lit quads
return false;
}
// merge the two quads
if (mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown)
{
widthNorthSouthOrUpDown += quad.widthNorthSouthOrUpDown;
}
else // if (mergeDirection == MergeDirection.EastWest)
{
widthEastWest += quad.widthEastWest;
}
// merge successful
return true;
}
}
@@ -1,106 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.lodBuilding.bufferBuilding;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.opengl.LodBox;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
/**
* Builds LODs as rectangular prisms.
* @author James Seibel
* @version 3-19-2022
*/
public class CubicLodTemplate
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public static void addLodToBuffer(long data, long topData, long botData, long[][][] adjData,
boolean[] adjFillBlack, byte detailLevel,int offsetPosX, int offsetOosZ, LodQuadBuilder quadBuilder, DebugMode debugging)
{
short width = (short) (1 << detailLevel);
short x = (short) LevelPosUtil.convert(detailLevel, offsetPosX, LodUtil.BLOCK_DETAIL_LEVEL);
short y = DataPointUtil.getDepth(data);
short z = (short) LevelPosUtil.convert(detailLevel, offsetOosZ, LodUtil.BLOCK_DETAIL_LEVEL);
short dy = (short) (DataPointUtil.getHeight(data) - y);
if (dy == 0)
return;
if (dy < 0)
{
throw new IllegalArgumentException("Negative y size for the data! Data: " + DataPointUtil.toString(data));
}
int color;
boolean fullBright = false;
switch (debugging) {
case OFF:
case SHOW_WIREFRAME:
{
float saturationMultiplier = (float)CONFIG.client().graphics().advancedGraphics().getSaturationMultiplier();
float brightnessMultiplier = (float)CONFIG.client().graphics().advancedGraphics().getBrightnessMultiplier();
if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0) {
color = DataPointUtil.getColor(data);
} else {
float[] ahsv = ColorUtil.argbToAhsv(DataPointUtil.getColor(data));
color = ColorUtil.ahsvToArgb(ahsv[0], ahsv[1], ahsv[2] * saturationMultiplier, ahsv[3] * brightnessMultiplier);
//ApiShared.LOGGER.info("Raw color:[{}], AHSV:{}, Out color:[{}]",
// ColorUtil.toString(DataPointUtil.getColor(data)),
// ahsv, ColorUtil.toString(color));
}
break;
}
case SHOW_DETAIL:
case SHOW_DETAIL_WIREFRAME:
{
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel];
fullBright = true;
break;
}
case SHOW_GENMODE:
case SHOW_GENMODE_WIREFRAME:
{
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[DataPointUtil.getGenerationMode(data)];
fullBright = true;
break;
}
case SHOW_OVERLAPPING_QUADS:
case SHOW_OVERLAPPING_QUADS_WIREFRAME:
{
color = ColorUtil.WHITE;
fullBright = true;
break;
}
default:
throw new IllegalArgumentException("Unknown debug mode: " + debugging);
}
LodBox.addBoxQuadsToBuilder(quadBuilder, // buffer
width, dy, width, // setWidth
x, y, z, // setOffset
color, // setColor
DataPointUtil.getLightSky(data), // setSkyLights
fullBright ? 15 : DataPointUtil.getLightBlock(data), // setBlockLights
topData, botData, adjData, adjFillBlack); // setAdjData
}
}
@@ -1,355 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.lodBuilding.bufferBuilding;
import java.time.Duration;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.logging.SpamReducedLogger;
import com.seibel.lod.core.objects.Pos2D;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.opengl.RenderRegion;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.render.objects.GLBuffer;
import com.seibel.lod.core.util.*;
import com.seibel.lod.core.util.gridList.MovableGridRingList;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
/**
* This object creates the buffers that are rendered by the LodRenderer.
*
* @author James Seibel
* @version 12-9-2021
*/
public class LodBufferBuilderFactory {
// TODO: Do some Perf logging of Buffer Building
public static final boolean ENABLE_BUFFER_PERF_LOGGING = false;
public static final boolean ENABLE_EVENT_LOGGING = false;
public static final boolean ENABLE_LAG_SPIKE_LOGGING = false;
public static final long LAG_SPIKE_THRESOLD_NS = TimeUnit.NANOSECONDS.convert(16, TimeUnit.MILLISECONDS);
public static class LagSpikeCatcher {
long timer = System.nanoTime();
public LagSpikeCatcher() {
}
public void end(String source) {
if (!ENABLE_LAG_SPIKE_LOGGING)
return;
timer = System.nanoTime() - timer;
if (timer > LAG_SPIKE_THRESOLD_NS) {
ApiShared.LOGGER.info("LagSpikeCatcher: " + source + " took " + Duration.ofNanos(timer) + "!");
}
}
}
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
/** The thread used to generate new LODs off the main thread. */
private static LodThreadFactory mainGenThreadFactory = new LodThreadFactory(
LodBufferBuilderFactory.class.getSimpleName() + " - main", Thread.NORM_PRIORITY - 2);
public static ExecutorService mainGenThread = Executors.newSingleThreadExecutor(mainGenThreadFactory);
/** The threads used to generate buffers. */
private static LodThreadFactory bufferBuilderThreadFactory = new LodThreadFactory("BufferBuilder",
Thread.NORM_PRIORITY - 2);
private static int previousBufferBuilderThreads = CONFIG.client().advanced().threading().getNumberOfBufferBuilderThreads();
public static ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(previousBufferBuilderThreads, bufferBuilderThreadFactory);
/** The thread used to upload buffers. */
private static LodThreadFactory bufferUploadThreadFactory = new LodThreadFactory(
LodBufferBuilderFactory.class.getSimpleName() + " - upload", Thread.NORM_PRIORITY - 1);
public static ExecutorService bufferUploadThread = Executors.newSingleThreadExecutor(bufferUploadThreadFactory);
/**
* When buffers are first created they are allocated to this size (in Bytes).
* This size will be too small, more than likely. The buffers will be expanded
* when need be to fit the larger sizes.
*/
public static final int DEFAULT_MEMORY_ALLOCATION = (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3) * 8;
public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * (LodRenderer.ENABLE_IBO ? 4 : 6);
public static final int MAX_TRIANGLES_PER_BUFFER = (1024 * 1024 * 1)
/ (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3);
public static final int MAX_QUADS_PER_BUFFER = (1024 * 1024 * 1) / QUADS_BYTE_SIZE;
public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
public static int skyLightPlayer = 15;
public MovableGridRingList<RenderRegion> renderRegions = null;
/** Size of the buffer builders in bytes last time we created them */
public int previousBufferSize = 0;
/** Width of the dimension in regions last time we created the buffers */
public int previousRegionWidth = 0;
private boolean builderThreadRunning = false;
public ReentrantLock regionsListLock = new ReentrantLock();
public LodBufferBuilderFactory() {
}
public void setRegionNeedRegen(int regionX, int regionZ) {
MovableGridRingList<RenderRegion> r = renderRegions;
if (r==null) return;
RenderRegion rr = r.get(regionX, regionZ);
if (rr==null) return;
rr.setNeedRegen();
}
/**
* Create a thread to asynchronously generate LOD buffers centered around the
* given camera X and Z. <br>
* This method will write to the drawable near and far buffers. <br>
* After the buildable buffers have been generated they must be swapped with the
* drawable buffers in the LodRenderer to be drawn.
*
* @return whether it has started a generation task or is blocked
*/
public boolean updateAndSwapLodBuffersAsync(LodRenderer renderer, LodDimension lodDim, int playerX, int playerY,
int playerZ, boolean fullRegen) {
// only allow one generation process to happen at a time
if (builderThreadRunning) return false;
builderThreadRunning = true;
Runnable thread = () -> generateLodBuffersThread(renderer, lodDim, playerX, playerY, playerZ, fullRegen);
mainGenThread.execute(thread);
return true;
}
private void updateRingList(int playerX, int playerZ, int regionWidth) throws InterruptedException {
if (renderRegions != null && regionWidth != renderRegions.getSize()) {
renderRegions.clear(RenderRegion::close);
renderRegions = null;
}
LodUtil.checkInterrupts();
if (renderRegions == null) {
renderRegions = new MovableGridRingList<RenderRegion>(regionWidth/2,
LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerX),
LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerZ));
ApiShared.LOGGER.info("============Render Regions rebuilt============");
} else {
renderRegions.move(LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerX),
LevelPosUtil.getRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerZ), RenderRegion::close);
}
}
private void resetThreadPools(boolean dumpThread) {
if (dumpThread) {
bufferBuilderThreadFactory.dumpAllThreadStacks();
bufferUploadThreadFactory.dumpAllThreadStacks();
}
bufferBuilderThreads.shutdownNow();
bufferUploadThread.shutdownNow();
previousBufferBuilderThreads = CONFIG.client().advanced().threading().getNumberOfBufferBuilderThreads();
bufferBuilderThreadFactory = new LodThreadFactory("BufferBuilder", Thread.NORM_PRIORITY - 2);
bufferBuilderThreads = Executors.newFixedThreadPool(previousBufferBuilderThreads, bufferBuilderThreadFactory);
bufferUploadThreadFactory = new LodThreadFactory(
LodBufferBuilderFactory.class.getSimpleName() + " - upload", Thread.NORM_PRIORITY - 1);
bufferUploadThread = Executors.newSingleThreadExecutor(bufferUploadThreadFactory);
}
private void generateLodBuffersThread(LodRenderer renderer, LodDimension lodDim, int playerX, int playerY,
int playerZ, boolean fullRegen) {
//ArrayList<RenderRegion> regionsToCleanup = new ArrayList<RenderRegion>();
try {
if (previousBufferBuilderThreads != CONFIG.client().advanced().threading().getNumberOfBufferBuilderThreads())
resetThreadPools(false);
regionsListLock.lockInterruptibly();
if (ENABLE_EVENT_LOGGING)
ApiShared.LOGGER.info("BufferBuilderStarter locked the region lock! LodDim: [{}], RenderRegion: [{}]",
lodDim, renderRegions==null ? "NULL" : renderRegions.toString());
long startTime = System.currentTimeMillis();
boolean doCaveCulling = CONFIG.client().graphics().advancedGraphics().getEnableCaveCulling();
doCaveCulling &= !lodDim.dimension.hasCeiling();
doCaveCulling &= lodDim.dimension.hasSkyLight();
doCaveCulling &= playerY > CONFIG.client().graphics().advancedGraphics().getCaveCullingHeight() + 5;
int playerSkylight = MC.getPlayerSkylight(); // if fail returns -1.
doCaveCulling &= playerSkylight > 7;
try {
updateRingList(playerX, playerZ, lodDim.getWidth());
// ================================//
// create the nodeToRenderThreads //
// ================================//
skyLightPlayer = MC.getWrappedClientWorld().getSkyLight(playerX, playerY, playerZ);
// int minCullingRange =
// SingletonHandler.get(ILodConfigWrapperSingleton.class).client().graphics().advancedGraphics().getBacksideCullingRange();
// int cullingRangeX = Math.max((int)(1.5 * Math.abs(lastX - playerX)),
// minCullingRange);
// int cullingRangeZ = Math.max((int)(1.5 * Math.abs(lastZ - playerZ)),
// minCullingRange);
Pos2D minPos = renderRegions.getMinInRange();
Pos2D maxPos = renderRegions.getMaxInRange();
CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
try {
int numOfJobs = 0;
for (int regX = minPos.x; regX < maxPos.x; regX++) {
for (int regZ = minPos.y; regZ < maxPos.y; regZ++) {
RenderRegion r = renderRegions.get(regX, regZ);
RegionPos regPos = new RegionPos(regX, regZ);
if (r != null && !r.canRender(lodDim, regPos)) {
renderRegions.set(regX, regZ, null);
r.close();
r = null;
}
if (r == null) {
r = new RenderRegion(regPos, lodDim);
renderRegions.set(regX, regZ, r);
}
CompletableFuture<Void> newFuture =
r.updateStatus(bufferUploadThread, bufferBuilderThreads, fullRegen,
playerX, playerZ, doCaveCulling).orElse(null);
if (newFuture != null) {
future = CompletableFuture.allOf(future, newFuture);
numOfJobs++;
}
}
}
// ================================//
// wait on completion //
// ================================//
long executeStart = System.currentTimeMillis();
try {
future.get(1, TimeUnit.MINUTES);
} catch (CancellationException ce) {
throw new InterruptedException("Future interrupted");
} catch (ExecutionException ee) {
ApiShared.LOGGER.error("LodBufferBuilder ran into trouble: ", ee.getCause());
}
long executeEnd = System.currentTimeMillis();
long endTime = System.currentTimeMillis();
long buildTime = endTime - startTime;
long executeTime = executeEnd - executeStart;
if (ENABLE_BUFFER_PERF_LOGGING)
ApiShared.LOGGER.info("Thread Build&Upload(" + numOfJobs + "/"
+ (lodDim.getWidth() * lodDim.getWidth()) + (fullRegen ? "FULL" : "") + ") time: " + buildTime
+ " ms" + '\n' + "thread execute time: " + executeTime + " ms");
} catch (InterruptedException ie) {
resetThreadPools(false);
try {
future.get();
} catch (Throwable ignored) {
}
} catch (TimeoutException te) {
ApiShared.LOGGER.error("LodBufferBuilder timed out: ", te);
resetThreadPools(true);
}
}
catch (RuntimeException e) {
ApiShared.LOGGER.error("\"LodNodeBufferBuilder.generateLodBuffersAsync\" ran into trouble: ", e);
}
}
catch (InterruptedException ignored) { }
finally {
regionsListLock.unlock();
if (ENABLE_EVENT_LOGGING) ApiShared.LOGGER.info("BufferBuilderStarter unlocked the region lock!");
builderThreadRunning = false;
}
}
private final SpamReducedLogger ramLogger = new SpamReducedLogger(1);
public void dumpBufferMemoryUsage() {
if (!ramLogger.canMaybeLog())
return;
ramLogger.info("Dumping Ram Usage for buffer usage...");
StatsMap statsMap = new StatsMap();
if (renderRegions == null) {
ramLogger.info("Buildable VBOs are null!");
} else {
for (RenderRegion buffers : renderRegions) {
if (buffers == null)
continue;
buffers.debugDumpStats(statsMap);
}
}
statsMap.incStat("Total Buffers", GLBuffer.count.get());
ramLogger.info("================================================");
ramLogger.info("Stats: {}", statsMap);
ramLogger.info("================================================");
ramLogger.incLogTries();
}
// ===============================//
// BufferBuilder related methods //
// ===============================//
/**
* Sets the buffers and Vbos to null, forcing them to be recreated <br>
* and destroys any bound OpenGL objects. <br>
* <br>
* <p>
* May have to wait for the bufferLock to open.
*/
public void destroyBuffers() {
ApiShared.LOGGER.info("Destroying LodBufferBuilder...");
mainGenThread.shutdownNow();
mainGenThread = Executors.newSingleThreadExecutor(mainGenThreadFactory);
boolean locked = false;
try {
locked = regionsListLock.tryLock(5, TimeUnit.SECONDS); // FIXME: For some reason we get a deadlock here sometimes
} catch (InterruptedException ignored) {}
try {
if (renderRegions != null) renderRegions.clear(RenderRegion::close);
renderRegions = null;
} finally {
if (locked) regionsListLock.unlock();
}
ApiShared.LOGGER.info("LodBufferBuilder destroyed.");
}
/** Get the newly created VBOs
* Note: SHOULD NEVER MODIFY THE LIST */
public MovableGridRingList<RenderRegion> getRenderRegions() {
return renderRegions;
}
@Deprecated
public void triggerReset() {
}
}
@@ -1,539 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.lodBuilding.bufferBuilding;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.LodDirection.Axis;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.render.objects.GLVertexBuffer;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import static com.seibel.lod.core.render.LodRenderer.EVENT_LOGGER;
/**
* Used to create the quads before they are converted to renderable buffers.
*
* @version 2022-4-9
*/
public class LodQuadBuilder
{
static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public final boolean skipQuadsWithZeroSkylight;
public final short skyLightCullingBelow;
final ArrayList<BufferQuad>[] quads = (ArrayList<BufferQuad>[]) new ArrayList[6];
public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][]
{
// X,Z //
{ // UP
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
{ // DOWN
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
// X,Y //
{ // NORTH
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
{ // SOUTH
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
// Z,Y //
{ // WEST
{ 0, 0 }, // 0
{ 1, 0 }, // 1
{ 1, 1 }, // 2
{ 0, 1 }, // 3
},
{ // EAST
{ 0, 1 }, // 0
{ 1, 1 }, // 1
{ 1, 0 }, // 2
{ 0, 0 }, // 3
},
};
public static final int[][][] DIRECTION_VERTEX_QUAD = new int[][][]
{
// X,Z //
{ // UP
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 1, 0 }, // 0
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
{ // DOWN
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
// X,Y //
{ // NORTH
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
{ // SOUTH
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 1, 0 }, // 0
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
// Z,Y //
{ // WEST
{ 0, 0 }, // 0
{ 1, 0 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 0, 1 }, // 3
},
{ // EAST
{ 0, 1 }, // 0
{ 1, 1 }, // 1
{ 1, 0 }, // 2
{ 0, 1 }, // 0
{ 1, 0 }, // 2
{ 0, 0 }, // 3
},
};
public LodQuadBuilder(boolean enableSkylightCulling, int skyLightCullingBelow)
{
for (int i = 0; i < 6; i++)
quads[i] = new ArrayList<>();
this.skipQuadsWithZeroSkylight = enableSkylightCulling;
this.skyLightCullingBelow = (short) (skyLightCullingBelow - LodBuilder.MIN_WORLD_HEIGHT);
}
public void addQuadAdj(LodDirection dir, short x, short y, short z,
short widthEastWest, short widthNorthSouthOrUpDown,
int color, byte skylight, byte blocklight)
{
if (dir.ordinal() <= LodDirection.DOWN.ordinal())
throw new IllegalArgumentException("addQuadAdj() is only for adj direction! Not UP or Down!");
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[dir.ordinal()].add(new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, skylight, blocklight, dir));
}
// XZ
public void addQuadUp(short x, short y, short z, short width, short wz, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.UP.ordinal()].add(new BufferQuad(x, y, z, width, wz, color, skylight, blocklight, LodDirection.UP));
}
public void addQuadDown(short x, short y, short z, short width, short wz, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.DOWN.ordinal()].add(new BufferQuad(x, y, z, width, wz, color, skylight, blocklight, LodDirection.DOWN));
}
// XY
public void addQuadN(short x, short y, short z, short width, short wy, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.NORTH.ordinal()].add(new BufferQuad(x, y, z, width, wy, color, skylight, blocklight, LodDirection.NORTH));
}
public void addQuadS(short x, short y, short z, short width, short wy, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.SOUTH.ordinal()].add(new BufferQuad(x, y, z, width, wy, color, skylight, blocklight, LodDirection.SOUTH));
}
// ZY
public void addQuadW(short x, short y, short z, short width, short wy, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.WEST.ordinal()].add(new BufferQuad(x, y, z, width, wy, color, skylight, blocklight, LodDirection.WEST));
}
public void addQuadE(short x, short y, short z, short width, short wy, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.EAST.ordinal()].add(new BufferQuad(x, y, z, width, wy, color, skylight, blocklight, LodDirection.EAST));
}
private static void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte skylight, byte blocklight, int mx, int my, int mz)
{
skylight %= 16;
blocklight %= 16;
bb.putShort(x);
bb.putShort(y);
bb.putShort(z);
short meta = 0;
meta |= (skylight | (blocklight << 4));
byte mirco = 0;
// mirco offset which is a xyz 2bit value
// 0b00 = no offset
// 0b01 = positive offset
// 0b11 = negative offset
// format is: 0b00zzyyxx
if (mx != 0) mirco |= mx > 0 ? 0b01 : 0b11;
if (my != 0) mirco |= my > 0 ? 0b0100 : 0b1100;
if (mz != 0) mirco |= mz > 0 ? 0b010000 : 0b110000;
meta |= mirco << 8;
bb.putShort(meta);
byte r = (byte) ColorUtil.getRed(color);
byte g = (byte) ColorUtil.getGreen(color);
byte b = (byte) ColorUtil.getBlue(color);
byte a = (byte) ColorUtil.getAlpha(color);
bb.put(r);
bb.put(g);
bb.put(b);
bb.put(a);
}
private static void putQuad(ByteBuffer bb, BufferQuad quad)
{
int[][] quadBase = LodRenderer.ENABLE_IBO ? DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()] : DIRECTION_VERTEX_QUAD[quad.direction.ordinal()];
short widthEastWest = quad.widthEastWest;
short widthNorthSouth = quad.widthNorthSouthOrUpDown;
Axis axis = quad.direction.getAxis();
for (int i = 0; i < quadBase.length; i++)
{
short dx, dy, dz;
int mx, my, mz;
switch (axis)
{
case X: // ZY
dx = 0;
dy = quadBase[i][1] == 1 ? widthNorthSouth : 0;
dz = quadBase[i][0] == 1 ? widthEastWest : 0;
mx = 0;
my = quadBase[i][1] == 1 ? 1 : -1;
mz = quadBase[i][0] == 1 ? 1 : -1;
break;
case Y: // XZ
dx = quadBase[i][0] == 1 ? widthEastWest : 0;
dy = 0;
dz = quadBase[i][1] == 1 ? widthNorthSouth : 0;
mx = quadBase[i][0] == 1 ? 1 : -1;
my = 0;
mz = quadBase[i][1] == 1 ? 1 : -1;
break;
case Z: // XY
dx = quadBase[i][0] == 1 ? widthEastWest : 0;
dy = quadBase[i][1] == 1 ? widthNorthSouth : 0;
dz = 0;
mx = quadBase[i][0] == 1 ? 1 : -1;
my = quadBase[i][1] == 1 ? 1 : -1;
mz = 0;
break;
default:
throw new IllegalArgumentException("Invalid Axis enum: " + axis);
}
putVertex(bb, (short) (quad.x + dx), (short) (quad.y + dy), (short) (quad.z + dz),
quad.hasError ? ColorUtil.RED : quad.color,
quad.hasError ? 15 : quad.skyLight,
quad.hasError ? 15 : quad.blockLight,
mx, my, mz);
}
}
/** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads()
{
long mergeCount = 0;
long preQuadsCount = getCurrentQuadsCount();
if (preQuadsCount <= 1)
return;
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
mergeCount += mergeQuadsInternal(directionIndex, BufferMergeDirectionEnum.EastWest);
// only merge after the top has been merged
if (directionIndex == 1)
{
long pass2 = mergeQuadsInternal(directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
mergeCount += pass2;
}
}
long postQuadsCount = getCurrentQuadsCount();
//if (mergeCount != 0)
EVENT_LOGGER.debug("Merged {}/{}({}) quads", mergeCount, preQuadsCount, mergeCount / (double) preQuadsCount);
}
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
private long mergeQuadsInternal(int directionIndex, BufferMergeDirectionEnum mergeDirection)
{
if (quads[directionIndex].size() <= 1)
return 0;
quads[directionIndex].sort( (objOne, objTwo) -> objOne.compare(objTwo, mergeDirection) );
long mergeCount = 0;
ListIterator<BufferQuad> iter = quads[directionIndex].listIterator();
BufferQuad currentQuad = iter.next();
while (iter.hasNext())
{
BufferQuad nextQuad = iter.next();
if (currentQuad.tryMerge(nextQuad, mergeDirection))
{
// merge successful, attempt to merge the next quad
mergeCount++;
iter.set(null);
}
else
{
// merge fail, move on to the next quad
currentQuad = nextQuad;
}
}
quads[directionIndex].removeIf(o -> o == null);
return mergeCount;
}
public Iterator<ByteBuffer> makeVertexBuffers()
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(LodBufferBuilderFactory.FULL_SIZED_BUFFER)
.order(ByteOrder.nativeOrder());
int dir = skipEmpty(0);
int quad = 0;
private int skipEmpty(int d)
{
while (d < 6 && quads[d].isEmpty())
d++;
return d;
}
@Override
public boolean hasNext()
{
return dir < 6;
}
@Override
public ByteBuffer next()
{
if (dir >= 6)
{
return null;
}
bb.clear();
bb.limit(LodBufferBuilderFactory.FULL_SIZED_BUFFER);
while (bb.hasRemaining() && dir < 6)
{
writeData();
}
bb.limit(bb.position());
bb.rewind();
return bb;
}
private void writeData()
{
int i = quad;
for (; i < quads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, quads[dir].get(i));
}
if (i >= quads[dir].size())
{
quad = 0;
dir++;
dir = skipEmpty(dir);
}
else
{
quad = i;
}
}
};
}
public interface BufferFiller
{
/** If true: more data needs to be filled */
boolean fill(GLVertexBuffer vbo);
}
public BufferFiller makeBufferFiller(GpuUploadMethod method)
{
return new BufferFiller()
{
int dir = 0;
int quad = 0;
public boolean fill(GLVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.setVertexCount(0);
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER)
numOfQuads = LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * LodBufferBuilderFactory.QUADS_BYTE_SIZE, method,
LodBufferBuilderFactory.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * LodBufferBuilderFactory.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer();
vbo.setVertexCount(LodRenderer.ENABLE_IBO ? numOfQuads*4 : numOfQuads*6);
return dir < 6;
}
private int _countRemainingQuads()
{
int a = quads[dir].size() - quad;
for (int i = dir + 1; i < quads.length; i++)
{
a += quads[i].size();
}
return a;
}
private void writeData(ByteBuffer bb)
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < quads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, quads[dir].get(i));
}
if (i >= quads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && quads[dir].isEmpty())
dir++;
}
else
{
quad = i;
}
}
};
}
public int getCurrentQuadsCount()
{
int i = 0;
for (ArrayList<BufferQuad> qs : quads)
i += qs.size();
return i;
}
/** Returns how many Buffers will be needed to render everything in this builder. */
public int getCurrentNeededVertexBufferCount()
{
return LodUtil.ceilDiv(getCurrentQuadsCount(), LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER);
}
}
@@ -1,266 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2021 Tom Lee (TomTheFurry) & James Seibel (Original code)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.worldGeneration;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.GenerationPriority;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.PosToGenerateContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvionmentWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvionmentWrapper.Steps;
public class BatchGenerator {
public static final boolean ENABLE_GENERATOR_STATS_LOGGING = false;
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
public AbstractBatchGenerationEnvionmentWrapper generationGroup;
public LodDimension targetLodDim;
public static final int generationGroupSize = 4;
public static int previousThreadCount = CONFIG.client().advanced().threading()._getWorldGenerationThreadPoolSize();
private int estimatedSampleNeeded = 128;
private int estimatedPointsToQueue = 1;
public BatchGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension) {
IWorldWrapper world = LodUtil.getServerWorldFromDimension(newLodDimension.dimension);
targetLodDim = newLodDimension;
generationGroup = FACTORY.createBatchGenerator(newLodBuilder, newLodDimension, world);
MC.sendChatMessage("NOTE: You are currently using Distant Horizon's Batch Chunk Pre-Generator.");
ApiShared.LOGGER.info("Batch Chunk Generator initialized");
}
@SuppressWarnings("unused")
public void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder) {
if (lodDim != targetLodDim) {
stop(false);
IWorldWrapper dim = LodUtil.getServerWorldFromDimension(lodDim.dimension);
generationGroup = FACTORY.createBatchGenerator(lodBuilder, lodDim, dim);
targetLodDim = lodDim;
ApiShared.LOGGER.info("1.18 Experimental Chunk Generator reinitialized");
}
DistanceGenerationMode mode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
int newThreadCount = CONFIG.client().advanced().threading()._getWorldGenerationThreadPoolSize();
if (newThreadCount != previousThreadCount) {
generationGroup.resizeThreadPool(newThreadCount);
previousThreadCount = newThreadCount;
}
if (estimatedPointsToQueue < newThreadCount)
estimatedPointsToQueue = newThreadCount;
GenerationPriority priority = CONFIG.client().worldGenerator().getGenerationPriority();
if (priority == GenerationPriority.AUTO)
priority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
generationGroup.updateAllFutures();
if (!MC.hasSinglePlayerServer())
return;
if (!LodUtil.checkRamUsage(0.1, 64)) return;
int eventsCount = generationGroup.getEventCount();
// If we still all jobs running, return.
if (eventsCount >= estimatedPointsToQueue) {
estimatedPointsToQueue--;
if (estimatedPointsToQueue < newThreadCount)
estimatedPointsToQueue = newThreadCount;
return;
}
final int targetToGenerate = estimatedPointsToQueue - eventsCount;
int toGenerate = targetToGenerate;
int positionGoneThough = 0;
// round the player's block position down to the nearest chunk BlockPos
int playerPosX = MC.getPlayerBlockPos().getX();
int playerPosZ = MC.getPlayerBlockPos().getZ();
double runTimeRatio = CONFIG.client().advanced().threading()._getWorldGenerationPartialRunTime();
PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate(estimatedSampleNeeded, playerPosX, playerPosZ,
priority, mode);
if (eventsCount == 0 && posToGenerate.getNumberOfPos() >= estimatedSampleNeeded) {
estimatedPointsToQueue++;
if (estimatedPointsToQueue > newThreadCount * 10)
estimatedPointsToQueue = newThreadCount * 10;
}
// ApiShared.LOGGER.info("PosToGenerate: {}", posToGenerate);
// Find the max number of iterations we need to go though.
// We are checking one FarPos, and one NearPos per iterations. This ensure we
// aren't just
// always picking one or the other.
Steps targetStep;
switch (mode) {
case NONE:
targetStep = Steps.Empty; // NOTE: Only load in existing chunks. No new chunk generation
break;
case BIOME_ONLY:
targetStep = Steps.Biomes; // NOTE: No block. Require fake height in LodBuilder
break;
case BIOME_ONLY_SIMULATE_HEIGHT:
targetStep = Steps.Noise; // NOTE: Stone only. Require fake surface
break;
case SURFACE:
targetStep = Steps.Surface; // Carvers or Surface???
break;
case FEATURES:
case FULL:
targetStep = Steps.Features;
break;
default:
assert false;
return;
}
if (ENABLE_GENERATOR_STATS_LOGGING)
ApiShared.LOGGER.info("WorldGen. Near:" + posToGenerate.getNumberOfNearPos() + " Far:"
+ posToGenerate.getNumberOfFarPos());
if (priority == GenerationPriority.FAR_FIRST || priority == GenerationPriority.BALANCED) {
int nearCount = posToGenerate.getNumberOfNearPos();
int farCount = posToGenerate.getNumberOfFarPos();
if (ENABLE_GENERATOR_STATS_LOGGING)
ApiShared.LOGGER.info("WorldGen. Near:" + nearCount + " Far:" + farCount);
int maxIteration = Math.max(nearCount, farCount);
for (int i = 0; i < maxIteration; i++) {
// We have farPos to go though
if (i < farCount && posToGenerate.getNthDetail(i, false) != 0) {
positionGoneThough++;
byte detailLevel = (byte) (posToGenerate.getNthDetail(i, false) - 1);
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, false));
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, false));
int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, false, runTimeRatio)) {
toGenerate--;
}
}
if (toGenerate <= 0)
break;
// We have nearPos to go though
if (i < nearCount && posToGenerate.getNthDetail(i, true) != 0) {
positionGoneThough++;
byte detailLevel = (byte) (posToGenerate.getNthDetail(i, true) - 1);
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, true));
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, true));
int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, true, runTimeRatio)) {
toGenerate--;
}
}
if (toGenerate <= 0)
break;
}
} else {
int nearCount = posToGenerate.getNumberOfNearPos();
for (int i = 0; i < nearCount; i++) {
// We have nearPos to go though
if (posToGenerate.getNthDetail(i, true) != 0) {
positionGoneThough++;
byte detailLevel = (byte) (posToGenerate.getNthDetail(i, true) - 1);
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, true));
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, true));
int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, true, runTimeRatio)) {
toGenerate--;
}
if (toGenerate <= 0)
break;
}
}
// Only do far gen if toGenerate is non 0 and that we have requested all samples
// we can get.
if (toGenerate > 0 && estimatedSampleNeeded > posToGenerate.getNumberOfPos()) {
int farCount = posToGenerate.getNumberOfFarPos();
for (int i = 0; i < farCount; i++) {
// We have farPos to go though
if (posToGenerate.getNthDetail(i, false) != 0) {
positionGoneThough++;
byte detailLevel = (byte) (posToGenerate.getNthDetail(i, false) - 1);
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, false));
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, false));
int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, false, runTimeRatio)) {
toGenerate--;
}
}
if (toGenerate <= 0)
break;
}
}
}
if (targetToGenerate != toGenerate && ENABLE_GENERATOR_STATS_LOGGING) {
if (toGenerate <= 0) {
ApiShared.LOGGER.info(
"WorldGenerator: Sampled " + posToGenerate.getNumberOfPos() + " out of " + estimatedSampleNeeded
+ " points, started all targeted " + targetToGenerate + " generations.");
} else {
ApiShared.LOGGER.info("WorldGenerator: Sampled " + posToGenerate.getNumberOfPos() + " out of "
+ estimatedSampleNeeded + " points, started " + (targetToGenerate - toGenerate)
+ " out of targeted " + targetToGenerate + " generations.");
}
}
if (toGenerate > 0 && estimatedSampleNeeded <= posToGenerate.getNumberOfPos()) {
// We failed to generate enough points from the samples.
// Let's increase the estimatedSampleNeeded.
estimatedSampleNeeded *= 1.3;
// Ensure wee don't go to basically infinity
if (estimatedSampleNeeded > 32768)
estimatedSampleNeeded = 32768;
if (ENABLE_GENERATOR_STATS_LOGGING)
ApiShared.LOGGER.info("WorldGenerator: Increasing estimatedSampleNeeeded to " + estimatedSampleNeeded);
} else if (toGenerate <= 0 && positionGoneThough * 1.5 < posToGenerate.getNumberOfPos()) {
// We haven't gone though half of them and it's already enough.
// Let's shink the estimatedSampleNeeded.
estimatedSampleNeeded /= 1.2;
// Ensure we don't go to near zero.
if (estimatedSampleNeeded < 4)
estimatedSampleNeeded = 4;
if (ENABLE_GENERATOR_STATS_LOGGING)
ApiShared.LOGGER.info("WorldGenerator: Decreasing estimatedSampleNeeeded to " + estimatedSampleNeeded);
}
}
public void stop(boolean blocking) {
ApiShared.LOGGER.info("1.18 Experimental Chunk Generator shutting down...");
generationGroup.stop(blocking);
}
}
@@ -0,0 +1,373 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.worldGeneration;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.objects.PosToGenerateContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractExperimentalWorldGeneratorWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
/**
* A singleton that handles all long distance LOD world generation.
* @author Leonardo Amato
* @author James Seibel
* @version 12-11-2021
*/
public class LodWorldGenerator
{
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory WRAPPER_FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final IVersionConstants VERSION_CONSTANTS = SingletonHandler.get(IVersionConstants.class);
/** This holds the thread used to create LOD generation requests off the main thread. */
private final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " world generator"));
private ExecutorService genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
/** we only want to queue up one generator thread at a time */
private boolean generatorThreadRunning = false;
/**
* This keeps track of how many chunk generation requests are on going. This is
* to limit how many chunks are queued at once. To prevent chunks from being
* generated for a long time in an area the player is no longer in.
*/
public final AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0);
public final Set<AbstractChunkPosWrapper> positionsWaitingToBeGenerated = new HashSet<>();
/**
* Singleton copy of this object
*/
public static final LodWorldGenerator INSTANCE = new LodWorldGenerator();
public AbstractExperimentalWorldGeneratorWrapper experimentalWorldGenerator;
private LodWorldGenerator() {}
/**
* Queues up LodNodeGenWorkers for the given lodDimension.
* renderer needed so the LodNodeGenWorkers can flag that the
* buffers need to be rebuilt.
*/
public void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder)
{
IWorldWrapper world = LodUtil.getServerWorldFromDimension(lodDim.dimension);
// TODO: Rename the config option
if (CONFIG.client().worldGenerator().getAllowUnstableFeatureGeneration()) {
if (experimentalWorldGenerator == null) {
experimentalWorldGenerator = WRAPPER_FACTORY.createExperimentalWorldGenerator(lodBuilder, lodDim, world);
if (experimentalWorldGenerator == null) CONFIG.client().worldGenerator().setAllowUnstableFeatureGeneration(false);
}
} else {
if (experimentalWorldGenerator != null) {
experimentalWorldGenerator.stop();
experimentalWorldGenerator = null;
}
}
if (experimentalWorldGenerator != null) {
experimentalWorldGenerator.queueGenerationRequests(lodDim, lodBuilder);
return;
}
// TODO: This currently doesn't use the DetailDistanceUtil.getDistanceGenerationMode(int detail) to get the mode.
// This is fine currently since DistanceGenerationMode doesn't care about the detail level for now.
// However, If that was to be changed, This will need to be fixed.
DistanceGenerationMode mode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
if (mode != DistanceGenerationMode.NONE
&& !generatorThreadRunning
&& MC.hasSinglePlayerServer())
{
// the thread is now running, don't queue up another thread
generatorThreadRunning = true;
/**
* How many chunks to generate outside the player's view distance at one
* time. (or more specifically how many requests to make at one time). I
* multiply by 8 to make sure there is always a buffer of chunk requests, to
* make sure the CPU is always busy, and we can generate LODs as quickly as
* possible.
*/
int genRequestPerThread = VERSION_CONSTANTS.getWorldGenerationCountPerThread();
int maxChunkGenRequests;
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode))
maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads() * genRequestPerThread;
else maxChunkGenRequests = genRequestPerThread;
Runnable generatorFunc = (() ->
{
try
{
// round the player's block position down to the nearest chunk BlockPos
int playerPosX = MC.getPlayerBlockPos().getX();
int playerPosZ = MC.getPlayerBlockPos().getZ();
//=======================================//
// fill in positionsWaitingToBeGenerated //
//=======================================//
IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate(
maxChunkGenRequests,
playerPosX,
playerPosZ);
byte detailLevel;
int posX;
int posZ;
int nearIndex = 0;
int farIndex = 0;
for (int i = 0; i < posToGenerate.getNumberOfPos(); i++)
{
// I wish there was a way to compress this code, but I'm not aware of
// an easy way to do so.
// add the near positions
if (nearIndex < posToGenerate.getNumberOfNearPos() && posToGenerate.getNthDetail(nearIndex, true) != 0)
{
detailLevel = (byte) (posToGenerate.getNthDetail(nearIndex, true) - 1);
posX = posToGenerate.getNthPosX(nearIndex, true);
posZ = posToGenerate.getNthPosZ(nearIndex, true);
nearIndex++;
AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
break;
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
}
// add the far positions
if (farIndex < posToGenerate.getNumberOfFarPos() && posToGenerate.getNthDetail(farIndex, false) != 0)
{
detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1);
posX = posToGenerate.getNthPosX(farIndex, false);
posZ = posToGenerate.getNthPosZ(farIndex, false);
farIndex++;
AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
continue;
//break;
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
}
}
}
catch (RuntimeException e)
{
// this shouldn't ever happen, but just in case
e.printStackTrace();
}
finally
{
generatorThreadRunning = false;
}
});
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode))
{
generatorFunc.run();
}
else
{
mainGenThread.execute(generatorFunc);
}
} // if distanceGenerationMode != DistanceGenerationMode.NONE && !generatorThreadRunning
} // queueGenerationRequests
private void queueWork(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, IWorldWrapper serverWorld)
{
// just a few sanity checks
if (newPos == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
if (newLodBuilder == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
if (newLodDimension == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
if (serverWorld == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
Runnable method = (() -> {generateChunk(newPos, newGenerationMode,
newLodBuilder, newLodDimension, serverWorld);});
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(newGenerationMode))
{
// --Note: This is now using version constants--
// if we are using FULL generation there is no reason
// to queue up a bunch of generation requests,
// because MC's internal server (as of 1.16.5) only
// responds with a single thread. And we don't
// want to cause more lag than necessary or queue up
// requests that may end up being unneeded.
// In 1.17+, world generation becomes completely single
// threaded. So to allow that, we check the boolean for
// whether the wrapper requires single thread
method.run();
}
else
{
// Every other method can
// be done asynchronously
genSubThreads.execute(method);
}
// useful for debugging
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
// ClientProxy.LOGGER.info(genThreads.toString());
}
private void generateChunk(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode,
LodBuilder newLodBuilder, LodDimension lodDim, IWorldWrapper worldWrapper)
{
// try
{
AbstractWorldGeneratorWrapper worldGenWrapper = WRAPPER_FACTORY.createWorldGenerator(newLodBuilder, lodDim, worldWrapper);
// only generate LodChunks if they can
// be added to the current LodDimension
if (lodDim.regionIsInRange(pos.getX() / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.getZ() / LodUtil.REGION_WIDTH_IN_CHUNKS))
{
switch (generationMode)
{
case NONE:
// don't generate
break;
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
worldGenWrapper.generateBiomesOnly(pos, generationMode);
break;
case SURFACE:
// faster
worldGenWrapper.generateSurface(pos);
break;
case FEATURES:
// fast
worldGenWrapper.generateFeatures(pos);
break;
case FULL:
// very slow
worldGenWrapper.generateFull(pos);
break;
}
// boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z));
// if (dataExistence)
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
// else
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
// shows the pool size, active threads, queued tasks and completed tasks
// ClientProxy.LOGGER.info(genThreads.toString());
}// if in range
}
// catch (Exception e)
// {
// ClientApi.LOGGER.error(LodWorldGenerator.class.getSimpleName() + ": ran into an error: " + e.getMessage());
// e.printStackTrace();
// }
// finally
{
// decrement how many threads are running
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1);
// this position is no longer being generated
LodWorldGenerator.INSTANCE.positionsWaitingToBeGenerated.remove(pos);
}
}// run
/**
* Stops the current genThreads if they are running
* and then recreates the Executor service. <br><br>
* <p>
* This is done to clear any outstanding tasks
* that may exist after the player leaves their current world.
* If this isn't done unfinished tasks may be left in the queue
* preventing new LodChunks form being generated.
*/
public void restartExecutorService()
{
if (experimentalWorldGenerator != null) {
experimentalWorldGenerator.stop();
experimentalWorldGenerator = null;
}
if (genSubThreads != null && !genSubThreads.isShutdown())
{
genSubThreads.shutdownNow();
}
genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
}
}
@@ -1,22 +1,3 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.config;
import java.lang.annotation.ElementType;
@@ -26,62 +7,26 @@ import java.lang.annotation.Target;
/**
* Where the annotations for the config are defined
* If there is no annotation then the config will not touch it
*
* REMOVED IN a1.7
*
* @author coolGi
* @version 02-07-2022
* @author coolGi2007
* @version 12-28-2021
*/
@Deprecated
public class ConfigAnnotations {
/** A textField, button, etc. that can be interacted with */
/** a textField, button, etc. that can be interacted with */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Entry
{
String name() default "";
@Deprecated
int width() default 150;
@Deprecated
double minValue() default Double.MIN_NORMAL;
@Deprecated
double maxValue() default Double.MAX_VALUE;
}
/** For making categories */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Category
{
}
/** Makes text (looks like @Entry but dosnt save and has no button */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Comment
{
}
/**
* Adds a comment to the file,
* This should only be used in special cases where comments from an entry cant reach
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FileComment
{
}
/** DONT USE AS IT WILL BE REMOVED IN THE REWORK OF THE CONFIG */
@Deprecated
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ScreenEntry
@@ -90,4 +35,22 @@ public class ConfigAnnotations {
int width() default 100;
}
/** Used when sorting the configs in the menu */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Category
{
String value();
}
/** Makes text (looks like @Entry but dosnt save and has no button */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Comment
{
}
}
@@ -0,0 +1,502 @@
package com.seibel.lod.core.dataFormat;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.ThreadMapUtil;
import static com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory.skyLightPlayer;
public class BlockDataFormat
{
/*
|a |a |a |a |r |r |r |r |
|r |r |r |r |g |g |g |g |
|g |g |g |g |b |b |b |b |
|b |b |b |b |h |h |h |h |
|h |h |h |h |h |h |d |d |
|d |d |d |d |d |d |d |d |
|bl |bl |bl |bl |sl |sl |sl |sl |
|l |l |f |g |g |g |v |e |
*/
// Reminder: bytes have range of [-128, 127].
// When converting to or from an int a 128 should be added or removed.
// If there is a bug with color then it's probably caused by this.
//To be used in the future for negative value
//public final static int MIN_DEPTH = -64;
//public final static int MIN_HEIGHT = -64;
public final static int EMPTY_DATA = 0;
public static final short VERTICAL_OFFSET = -64;
public static int WORLD_HEIGHT = 1024;
public final static int ALPHA_DOWNSIZE_SHIFT = 4;
//public final static int BLUE_COLOR_SHIFT = 0;
//public final static int GREEN_COLOR_SHIFT = 8;
//public final static int RED_COLOR_SHIFT = 16;
//public final static int ALPHA_COLOR_SHIFT = 24;
public final static int BLUE_SHIFT = 36;
public final static int GREEN_SHIFT = BLUE_SHIFT + 8;
public final static int RED_SHIFT = BLUE_SHIFT + 16;
public final static int ALPHA_SHIFT = BLUE_SHIFT + 24;
public final static int COLOR_SHIFT = 36;
public final static int HEIGHT_SHIFT = 26;
public final static int DEPTH_SHIFT = 16;
public final static int BLOCK_LIGHT_SHIFT = 12;
public final static int SKY_LIGHT_SHIFT = 8;
//public final static int LIGHTS_SHIFT = SKY_LIGHT_SHIFT;
//public final static int VERTICAL_INDEX_SHIFT = 6;
public final static int FLAG_SHIFT = 5;
public final static int GEN_TYPE_SHIFT = 2;
public final static int VOID_SHIFT = 1;
public final static int EXISTENCE_SHIFT = 0;
public final static long ALPHA_MASK = 0b1111;
public final static long RED_MASK = 0b1111_1111;
public final static long GREEN_MASK = 0b1111_1111;
public final static long BLUE_MASK = 0b1111_1111;
public final static long COLOR_MASK = 0b11111111_11111111_11111111;
public final static long HEIGHT_MASK = 0b11_1111_1111;
public final static long DEPTH_MASK = 0b11_1111_1111;
//public final static long LIGHTS_MASK = 0b1111_1111;
public final static long BLOCK_LIGHT_MASK = 0b1111;
public final static long SKY_LIGHT_MASK = 0b1111;
//public final static long VERTICAL_INDEX_MASK = 0b11;
public final static long FLAG_MASK = 0b1;
public final static long GEN_TYPE_MASK = 0b111;
public final static long VOID_MASK = 1;
public final static long EXISTENCE_MASK = 1;
public static long createVoidDataPoint(int generationMode)
{
long dataPoint = 0;
dataPoint += (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT;
dataPoint += VOID_MASK << VOID_SHIFT;
dataPoint += EXISTENCE_MASK << EXISTENCE_SHIFT;
return dataPoint;
}
public static long createDataPoint(int height, int depth, int color, int lightSky, int lightBlock, int generationMode, boolean flag)
{
return createDataPoint(
ColorUtil.getAlpha(color),
ColorUtil.getRed(color),
ColorUtil.getGreen(color),
ColorUtil.getBlue(color),
height, depth, lightSky, lightBlock, generationMode, flag);
}
public static long createDataPoint(int alpha, int red, int green, int blue, int height, int depth, int lightSky, int lightBlock, int generationMode, boolean flag)
{
long dataPoint = 0;
dataPoint += (long) (alpha >>> ALPHA_DOWNSIZE_SHIFT) << ALPHA_SHIFT;
dataPoint += (red & RED_MASK) << RED_SHIFT;
dataPoint += (green & GREEN_MASK) << GREEN_SHIFT;
dataPoint += (blue & BLUE_MASK) << BLUE_SHIFT;
dataPoint += (height & HEIGHT_MASK) << HEIGHT_SHIFT;
dataPoint += (depth & DEPTH_MASK) << DEPTH_SHIFT;
dataPoint += (lightBlock & BLOCK_LIGHT_MASK) << BLOCK_LIGHT_SHIFT;
dataPoint += (lightSky & SKY_LIGHT_MASK) << SKY_LIGHT_SHIFT;
dataPoint += (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT;
if (flag)
dataPoint += FLAG_MASK << FLAG_SHIFT;
dataPoint += EXISTENCE_MASK << EXISTENCE_SHIFT;
return dataPoint;
}
public static short getHeight(long dataPoint)
{
return (short) ((dataPoint >>> HEIGHT_SHIFT) & HEIGHT_MASK);
}
public static short getDepth(long dataPoint)
{
return (short) ((dataPoint >>> DEPTH_SHIFT) & DEPTH_MASK);
}
public static short getAlpha(long dataPoint)
{
return (short) ((((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK) << ALPHA_DOWNSIZE_SHIFT) | 0b1111);
}
public static short getRed(long dataPoint)
{
return (short) ((dataPoint >>> RED_SHIFT) & RED_MASK);
}
public static short getGreen(long dataPoint)
{
return (short) ((dataPoint >>> GREEN_SHIFT) & GREEN_MASK);
}
public static short getBlue(long dataPoint)
{
return (short) ((dataPoint >>> BLUE_SHIFT) & BLUE_MASK);
}
public static byte getLightSky(long dataPoint)
{
return (byte) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
}
public static byte getLightSkyAlt(long dataPoint)
{
if (skyLightPlayer == 0 && ((dataPoint >>> FLAG_SHIFT) & FLAG_MASK) == 1)
return 0;
else
return (byte) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
}
public static byte getLightBlock(long dataPoint)
{
return (byte) ((dataPoint >>> BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK);
}
public static boolean getFlag(long dataPoint)
{
return ((dataPoint >>> FLAG_SHIFT) & FLAG_MASK) == 1;
}
public static byte getGenerationMode(long dataPoint)
{
return (byte) ((dataPoint >>> GEN_TYPE_SHIFT) & GEN_TYPE_MASK);
}
public static boolean isVoid(long dataPoint)
{
return (((dataPoint >>> VOID_SHIFT) & VOID_MASK) == 1);
}
public static boolean doesItExist(long dataPoint)
{
return (((dataPoint >>> EXISTENCE_SHIFT) & EXISTENCE_MASK) == 1);
}
public static int getColor(long dataPoint)
{
return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | (/*((dataPoint >>> (ALPHA_SHIFT - ALPHA_DOWNSIZE_SHIFT)) | 0b1111)*/255 << 24));
}
/** This is used to convert a dataPoint to string (useful for the print function) */
@SuppressWarnings("unused")
public static String toString(long dataPoint)
{
return getHeight(dataPoint) + " " +
getDepth(dataPoint) + " " +
getAlpha(dataPoint) + " " +
getRed(dataPoint) + " " +
getBlue(dataPoint) + " " +
getGreen(dataPoint) + " " +
getLightBlock(dataPoint) + " " +
getLightSky(dataPoint) + " " +
getGenerationMode(dataPoint) + " " +
isVoid(dataPoint) + " " +
doesItExist(dataPoint) + '\n';
}
public static void shrinkArray(short[] array, int packetSize, int start, int length, int arraySize)
{
start *= packetSize;
length *= packetSize;
arraySize *= packetSize;
for (int i = 0; i < arraySize - start; i++)
{
array[start + i] = array[start + length + i];
//remove comment to not leave garbage at the end
//array[start + packetSize + i] = 0;
}
}
public static void extendArray(short[] array, int packetSize, int start, int length, int arraySize)
{
start *= packetSize;
length *= packetSize;
arraySize *= packetSize;
for (int i = arraySize - start - 1; i >= 0; i--)
{
array[start + length + i] = array[start + i];
array[start + i] = 0;
}
}
/**
* This method merge column of multiple data together
* @param dataToMerge one or more columns of data
* @param inputVerticalData vertical size of an input data
* @param maxVerticalData max vertical size of the merged data
* @return one column of correctly parsed data
*/
public static long[] mergeMultiData(long[] dataToMerge, int inputVerticalData, int maxVerticalData)
{
int size = dataToMerge.length / inputVerticalData;
// We initialize the arrays that are going to be used
short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((WORLD_HEIGHT / 2 + 1) * 2);
long[] dataPoint = ThreadMapUtil.getVerticalDataArray(DetailDistanceUtil.getMaxVerticalData(0));
int genMode = DistanceGenerationMode.FULL.complexity;
boolean allEmpty = true;
boolean allVoid = true;
boolean allDefault;
long singleData;
short depth;
short height;
int count = 0;
int i;
int ii;
int dataIndex;
//We collect the indexes of the data, ordered by the depth
for (int index = 0; index < size; index++)
{
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData))
{
genMode = Math.min(genMode, getGenerationMode(singleData));
allEmpty = false;
if (!isVoid(singleData))
{
allVoid = false;
depth = getDepth(singleData);
height = getHeight(singleData);
int botPos = -1;
int topPos = -1;
//values fall in between and possibly require extension of array
boolean botExtend = false;
boolean topExtend = false;
for (i = 0; i < count; i++)
{
if (depth <= heightAndDepth[i * 2] && depth >= heightAndDepth[i * 2 + 1])
{
botPos = i;
break;
}
else if (depth < heightAndDepth[i * 2 + 1] && ((i + 1 < count && depth > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
{
botPos = i;
botExtend = true;
break;
}
}
for (i = 0; i < count; i++)
{
if (height <= heightAndDepth[i * 2] && height >= heightAndDepth[i * 2 + 1])
{
topPos = i;
break;
}
else if (height < heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
{
topPos = i;
topExtend = true;
break;
}
}
if (topPos == -1)
{
if (botPos == -1)
{
//whole block falls above
extendArray(heightAndDepth, 2, 0, 1, count);
heightAndDepth[0] = height;
heightAndDepth[1] = depth;
count++;
}
else if (!botExtend)
{
//only top falls above extending it there, while bottom is inside existing
shrinkArray(heightAndDepth, 2, 0, botPos, count);
heightAndDepth[0] = height;
count -= botPos;
}
else
{
//top falls between some blocks, extending those as well
shrinkArray(heightAndDepth, 2, 0, botPos, count);
heightAndDepth[0] = height;
heightAndDepth[1] = depth;
count -= botPos;
}
}
else if (!topExtend)
{
if (!botExtend)
//both top and bottom are within some exiting blocks, possibly merging them
heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1];
else
//top falls between some blocks, extending it there
heightAndDepth[topPos * 2 + 1] = depth;
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
}
else
{
if (!botExtend)
{
//only top is within some exiting block, extending it
topPos++; //to make it easier
heightAndDepth[topPos * 2] = height;
heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1];
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
}
else
{
//both top and bottom are outside existing blocks
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
extendArray(heightAndDepth, 2, topPos + 1, 1, count);
count++;
heightAndDepth[topPos * 2 + 2] = height;
heightAndDepth[topPos * 2 + 3] = depth;
}
}
}
}
else
break;
}
}
//We check if there is any data that's not empty or void
if (allEmpty)
return dataPoint;
if (allVoid)
{
dataPoint[0] = createVoidDataPoint(genMode);
return dataPoint;
}
//we limit the vertical portion to maxVerticalData
int j = 0;
while (count > maxVerticalData)
{
ii = WORLD_HEIGHT - VERTICAL_OFFSET;
for (i = 0; i < count - 1; i++)
{
if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii)
{
ii = heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2];
j = i;
}
}
heightAndDepth[j * 2 + 1] = heightAndDepth[(j + 1) * 2 + 1];
for (i = j + 1; i < count - 1; i++)
{
heightAndDepth[i * 2] = heightAndDepth[(i + 1) * 2];
heightAndDepth[i * 2 + 1] = heightAndDepth[(i + 1) * 2 + 1];
}
//System.arraycopy(heightAndDepth, j + 1, heightAndDepth, j, count - j - 1);
count--;
}
//As standard the vertical lods are ordered from top to bottom
for (j = count - 1; j >= 0; j--)
{
height = heightAndDepth[j * 2];
depth = heightAndDepth[j * 2 + 1];
if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2)
break;
int numberOfChildren = 0;
int tempAlpha = 0;
int tempRed = 0;
int tempGreen = 0;
int tempBlue = 0;
int tempLightBlock = 0;
int tempLightSky = 0;
byte tempGenMode = DistanceGenerationMode.FULL.complexity;
allEmpty = true;
allVoid = true;
allDefault = true;
long data = 0;
for (int index = 0; index < size; index++)
{
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData) && !isVoid(singleData))
{
if ((depth <= getDepth(singleData) && getDepth(singleData) <= height)
|| (depth <= getHeight(singleData) && getHeight(singleData) <= height))
{
if (getHeight(singleData) > getHeight(data))
data = singleData;
}
}
else
break;
}
if (!doesItExist(data))
{
singleData = dataToMerge[index * inputVerticalData];
data = createVoidDataPoint(getGenerationMode(singleData));
}
if (doesItExist(data))
{
allEmpty = false;
if (!isVoid(data))
{
numberOfChildren++;
allVoid = false;
tempAlpha += getAlpha(data);
tempRed += getRed(data);
tempGreen += getGreen(data);
tempBlue += getBlue(data);
tempLightBlock += getLightBlock(data);
tempLightSky += getLightSky(data);
if (!getFlag(data))
allDefault = false;
}
tempGenMode = (byte) Math.min(tempGenMode, getGenerationMode(data));
}
else
tempGenMode = (byte) Math.min(tempGenMode, DistanceGenerationMode.NONE.complexity);
}
if (allEmpty)
//no child has been initialized
dataPoint[j] = EMPTY_DATA;
else if (allVoid)
//all the children are void
dataPoint[j] = createVoidDataPoint(tempGenMode);
else
{
//we have at least 1 child
tempAlpha = tempAlpha / numberOfChildren;
tempRed = tempRed / numberOfChildren;
tempGreen = tempGreen / numberOfChildren;
tempBlue = tempBlue / numberOfChildren;
tempLightBlock = tempLightBlock / numberOfChildren;
tempLightSky = tempLightSky / numberOfChildren;
dataPoint[j] = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault);
}
}
return dataPoint;
}
}
@@ -1,22 +1,3 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.dataFormat;
public class ColorFormat
@@ -1,25 +1,5 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.dataFormat;
@Deprecated //Unused
public class DataMergeUtil
{
public static void shrinkArray(short[] array, int packetSize, int start, int length, int arraySize)
@@ -1,22 +1,3 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.dataFormat;
public class LightFormat
@@ -1,22 +1,3 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.dataFormat;
public class PositionDataFormat
@@ -1,22 +1,3 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.dataFormat;
public class VerticalDataFormat
@@ -1,22 +1,3 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.enums;
import java.util.Arrays;
@@ -28,18 +9,11 @@ import java.util.stream.Collectors;
import com.seibel.lod.core.objects.math.Vec3i;
/**
* An (almost) exact copy of Minecraft's
* A (almost) exact copy of Minecraft's
* Direction enum.
*
* Up <Br>
* Down <Br>
* North <Br>
* South <Br>
* East <Br>
* West <Br>
*
*
* @author James Seibel
* @version 2021-11-13
* @version 11-13-2021
*/
public enum LodDirection
{
@@ -49,27 +23,7 @@ public enum LodDirection
SOUTH(3, 2, 0, "south", LodDirection.AxisDirection.POSITIVE, LodDirection.Axis.Z, new Vec3i(0, 0, 1)),
WEST(4, 5, 1, "west", LodDirection.AxisDirection.NEGATIVE, LodDirection.Axis.X, new Vec3i(-1, 0, 0)),
EAST(5, 4, 3, "east", LodDirection.AxisDirection.POSITIVE, LodDirection.Axis.X, new Vec3i(1, 0, 0));
public static final LodDirection[] DIRECTIONS = new LodDirection[] {
LodDirection.UP,
LodDirection.DOWN,
LodDirection.WEST,
LodDirection.EAST,
LodDirection.NORTH,
LodDirection.SOUTH };
public static final LodDirection[] OPPOSITE_DIRECTIONS = new LodDirection[] {
LodDirection.UP,
LodDirection.DOWN,
LodDirection.SOUTH,
LodDirection.NORTH,
LodDirection.EAST,
LodDirection.WEST };
/** North, South, East, West */
public static final LodDirection[] ADJ_DIRECTIONS = new LodDirection[] {
LodDirection.EAST,
LodDirection.WEST,
LodDirection.SOUTH,
LodDirection.NORTH };
// private final int data3d;
// private final int oppositeIndex;
// private final int data2d;
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -31,8 +31,6 @@ package com.seibel.lod.core.enums.config;
*/
public enum BufferRebuildTimes
{
CONSTANT(0, 0, 0, 1),
FREQUENT(1000, 500, 2500, 1),
NORMAL(2000, 1000, 5000, 4),
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,6 +19,8 @@
package com.seibel.lod.core.enums.config;
import org.jetbrains.annotations.Nullable;
/**
* NONE <br>
* BIOME_ONLY <br>
@@ -36,9 +38,9 @@ package com.seibel.lod.core.enums.config;
public enum DistanceGenerationMode
{
/**
* Don't generate anything except just load in already existing chunks
* Don't generate anything
*/
NONE((byte) 1),
NONE((byte) 0),
/**
* Only generate the biomes and use biome
@@ -47,7 +49,7 @@ public enum DistanceGenerationMode
* Doesn't generate height, everything is shown at sea level.
* Multithreaded - Fastest (2-5 ms)
*/
BIOME_ONLY((byte) 2),
BIOME_ONLY((byte) 1),
/**
* Same as BIOME_ONLY, except instead
@@ -55,7 +57,7 @@ public enum DistanceGenerationMode
* different biome types (mountain, ocean, forest, etc.)
* use predetermined heights to simulate having height data.
*/
BIOME_ONLY_SIMULATE_HEIGHT((byte) 3),
BIOME_ONLY_SIMULATE_HEIGHT((byte) 2),
/**
* Generate the world surface,
@@ -63,7 +65,7 @@ public enum DistanceGenerationMode
* or structures.
* Multithreaded - Faster (10-20 ms)
*/
SURFACE((byte) 4),
SURFACE((byte) 3),
/**
* Generate everything except structures.
@@ -71,7 +73,7 @@ public enum DistanceGenerationMode
* since some features cause concurrentModification exceptions.
* Multithreaded - Fast (15-20 ms)
*/
FEATURES((byte) 5),
FEATURES((byte) 4),
/**
* Ask the server to generate/load each chunk.
@@ -80,9 +82,8 @@ public enum DistanceGenerationMode
* are adding the mod on a pre-existing world.
* Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms)
*/
FULL((byte) 6);
FULL((byte) 5);
public static DistanceGenerationMode RENDERABLE = DistanceGenerationMode.BIOME_ONLY;
/**
* The higher the number the more complete the generation is.
@@ -95,6 +96,7 @@ public enum DistanceGenerationMode
}
// Note: return null if out of range
@Nullable
public static DistanceGenerationMode previous(DistanceGenerationMode mode) {
switch (mode) {
case FULL:
@@ -108,14 +110,18 @@ public enum DistanceGenerationMode
case BIOME_ONLY:
return DistanceGenerationMode.NONE;
case NONE:
return null;
default:
return null;
}
}
// Note: return null if out of range
@Nullable
public static DistanceGenerationMode next(DistanceGenerationMode mode) {
switch (mode) {
case FULL:
return null;
case FEATURES:
return DistanceGenerationMode.FULL;
case SURFACE:
@@ -126,7 +132,6 @@ public enum DistanceGenerationMode
return DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
case NONE:
return DistanceGenerationMode.BIOME_ONLY;
case FULL:
default:
return null;
}
@@ -1,49 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2022 Tom Lee (TomTheFurry)
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.enums.config;
/**
* AUTO <br>
* SMOOTH_DROPOFF <br>
* PERFORMANCE_FOCUSED <br>
* <br>
* Determines how lod level drop off should be done
*
* @author Tom Lee
* @version 7-1-2022
*/
public enum DropoffQuality {
/** SMOOTH_DROPOFF when <128 lod view distance, or PERFORMANCE_FOCUSED otherwise */
AUTO(-1),
SMOOTH_DROPOFF(10),
PERFORMANCE_FOCUSED(0);
public final int fastModeSwitch;
DropoffQuality(int fastModeSwitch) {
this.fastModeSwitch = fastModeSwitch;
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,12 +32,10 @@ package com.seibel.lod.core.enums.config;
*/
public enum GenerationPriority
{
/** NEAR_FIRST when connected to servers and BALANCED when on single player */
/** NEAR_FIRST when connected to servers and FAR_FIRST when on single player */
AUTO,
NEAR_FIRST,
BALANCED,
FAR_FIRST
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,7 +20,7 @@
package com.seibel.lod.core.enums.config;
/**
* Auto, BUFFER_STORAGE_MAPPING, Buffer_Storage, Sub_Data, Buffer_Mapping, Data
* Auto, Buffer_Storage, Sub_Data, Buffer_Mapping, Data
*
* @author James Seibel
* @version 12-1-2021
@@ -28,23 +28,19 @@ package com.seibel.lod.core.enums.config;
public enum GpuUploadMethod
{
/** Picks the best option based on the GPU the user has. */
AUTO(false, false),
/*
*/
BUFFER_STORAGE_MAPPING(true, true),
AUTO,
/**
* Default for NVIDIA if OpenGL 4.5 is supported. <br>
* Fast rendering, no stuttering.
*/
BUFFER_STORAGE(false, true),
BUFFER_STORAGE,
/**
* Backup option for NVIDIA. <br>
* Fast rendering but may stutter when uploading.
*/
SUB_DATA(false, false),
SUB_DATA,
/**
* Default option for AMD/Intel. <br>
@@ -52,19 +48,12 @@ public enum GpuUploadMethod
* Fast rending if in GPU memory, slow if in system memory, <br>
* but won't stutter when uploading.
*/
BUFFER_MAPPING(true, false),
BUFFER_MAPPING,
/**
* Backup option for AMD/Intel. <br>
* Fast rendering but may stutter when uploading.
*/
DATA(false, false);
public final boolean useEarlyMapping;
public final boolean useBufferStorage;
GpuUploadMethod(boolean useEarlyMapping, boolean useBufferStorage) {
this.useEarlyMapping = useEarlyMapping;
this.useBufferStorage = useBufferStorage;
}
DATA,
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,30 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.enums.config;
public enum LightGenerationMode
{
// Fake in light values based on height maps
FAST,
// Run the light engine though the chunk to generate proper light values
FANCY
}
@@ -1,44 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.enums.config;
import org.apache.logging.log4j.Level;
public enum LoggerMode {
DISABLED(Level.OFF, Level.OFF),
LOG_ALL_TO_FILE(Level.ALL, Level.OFF),
LOG_ERROR_TO_CHAT(Level.ALL, Level.ERROR),
LOG_WARNING_TO_CHAT(Level.ALL, Level.WARN),
LOG_INFO_TO_CHAT(Level.ALL, Level.INFO),
LOG_DEBUG_TO_CHAT(Level.ALL, Level.DEBUG),
LOG_ALL_TO_CHAT(Level.ALL, Level.ALL),
LOG_ERROR_TO_CHAT_AND_FILE(Level.ERROR, Level.ERROR),
LOG_WARNING_TO_CHAT_AND_FILE(Level.WARN, Level.WARN),
LOG_INFO_TO_CHAT_AND_FILE(Level.INFO, Level.INFO),
LOG_DEBUG_TO_CHAT_AND_FILE(Level.DEBUG, Level.DEBUG),
LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.WARN),
LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.ERROR),
;
public final Level levelForFile;
public final Level levelForChat;
LoggerMode(Level levelForFile, Level levelForChat) {
this.levelForFile = levelForFile;
this.levelForChat = levelForChat;
}
}
@@ -1,69 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2022 Tom Lee (TomTheFurry)
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.enums.config;
/**
* AUTO <br>
* NAME_ONLY <br>
* NAME_IP <br>
* NAME_IP_PORT <br>
* <br>
* Determines how the multiplayer folders should be named.
*
* @author James Seibel
* @version 3-7-2022
*/
public enum ServerFolderNameMode
{
/**
* NAME_IP for LAN connections <Br>
* NAME_IP_PORT for all others
*/
AUTO,
/** Only use the server name */
NAME_ONLY,
/**
* {SERVER_NAME} IP {IP} <br>
* Minecraft Server IP 192.168.1.40
*/
NAME_IP,
/**
* {SERVER_NAME} IP {IP} <br>
* Minecraft Server IP 192.168.1.40:25565
*/
NAME_IP_PORT,
/**
* {SERVER_NAME} IP {IP} <br>
* Minecraft Server IP 192.168.1.40:25565 GameVersion 1.16.5 <Br> <br>
*
* Not normally recommended, since the game version can change if the
* server installs paper or some other jar. <br>
* This is just here to provide backwards compatibility.
*
* TODO add this to config desc
*/
NAME_IP_PORT_MC_VERSION;
}
@@ -1,22 +1,3 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.enums.config;
/**
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -31,12 +31,15 @@ package com.seibel.lod.core.enums.config;
*/
public enum VanillaOverdraw
{
/** Dont draw LODs where a minecraft chunk could be. Use Overdraw Offset to tweak the border thickness */
/** Never draw LODs where a minecraft chunk could be. */
NEVER,
/** Draw LODs over the farther minecraft chunks. Dynamically decides the border thickness */
/** Draw LODs over the farther minecraft chunks. */
DYNAMIC,
/** Draw LODs over all minecraft chunks. */
ALWAYS,
/** Draw LODs over border chunks. */
BORDER,
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,101 +19,94 @@
package com.seibel.lod.core.enums.config;
import org.jetbrains.annotations.Nullable;
/**
* heightmap <br>
* multi_lod <br>
*
*
* @author Leonardo Amato
* @version 2022-3-26
* @version 10-07-2021
*/
public enum VerticalQuality
{
LOW(
new int[] { 4, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1 },
2
new int[] { 2,
2,
2,
2,
1,
1,
1,
1,
1,
1,
1 }
),
MEDIUM(
new int[] { 6, 4, 3, 2, 2, 1, 1, 1, 1, 1, 1 },
4
new int[] { 4,
4,
2,
2,
2,
1,
1,
1,
1,
1,
1 }
),
HIGH(
new int[] { 8, 6, 4, 2, 2, 2, 2, 1, 1, 1, 1 },
6
),
ULTRA(
new int[] { 16, 16, 8, 8, 4, 4, 4, 1, 1, 1, 1 },
12
new int[] {
8,
8,
4,
4,
2,
2,
2,
1,
1,
1,
1 }
);
public final int[] maxVerticalData;
@Deprecated // Will find other ways to optimize
public final int maxConnectedLods;
VerticalQuality(int[] maxVerticalData, int maxConnectedLods)
VerticalQuality(int[] maxVerticalData)
{
this.maxVerticalData = maxVerticalData;
this.maxConnectedLods = maxConnectedLods;
}
/** returns null if out of range */
public static VerticalQuality previous(VerticalQuality mode)
{
switch (mode)
{
case ULTRA:
return VerticalQuality.HIGH;
// Note: return null if out of range
@Nullable
public static VerticalQuality previous(VerticalQuality mode) {
switch (mode) {
case HIGH:
return VerticalQuality.MEDIUM;
case MEDIUM:
return VerticalQuality.LOW;
case LOW:
return null;
default:
return null;
}
}
/** returns null if out of range */
public static VerticalQuality next(VerticalQuality mode)
{
switch (mode)
{
// Note: return null if out of range
@Nullable
public static VerticalQuality next(VerticalQuality mode) {
switch (mode) {
case HIGH:
return null;
case MEDIUM:
return VerticalQuality.HIGH;
case LOW:
return VerticalQuality.MEDIUM;
case HIGH:
return VerticalQuality.ULTRA;
case ULTRA:
default:
return null;
}
}
/**
* Returns the value with the given name, case-insensitive. <br>
* Returns null if no enums match the name. <br>
* Similar to valueOf(String value)
*/
public static VerticalQuality getByName(String name)
{
switch (name.toUpperCase())
{
case "ULTRA":
return VerticalQuality.ULTRA;
case "HIGH":
return VerticalQuality.HIGH;
case "MEDIUM":
return VerticalQuality.MEDIUM;
case "LOW":
return VerticalQuality.LOW;
default:
return null;
}
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -29,41 +29,21 @@ public enum DebugMode
{
/** LODs are rendered normally */
OFF,
/** LOD draws in wireframe. */
SHOW_WIREFRAME,
/** LOD colors are based on their detail */
SHOW_DETAIL,
/** LOD colors are based on their detail, and draws in wireframe. */
SHOW_DETAIL_WIREFRAME,
/** LOD colors are based on their gen mode. */
SHOW_GENMODE,
/** LOD colors are based on their gen mode, and draws in wireframe. */
SHOW_GENMODE_WIREFRAME,
/** Only draw overlapping LOD quads. */
SHOW_OVERLAPPING_QUADS,
/** Only draw overlapping LOD quads, and draws in wireframe. */
SHOW_OVERLAPPING_QUADS_WIREFRAME;
SHOW_DETAIL_WIREFRAME;
/** used when cycling through the different modes */
private DebugMode next;
static
{
OFF.next = SHOW_WIREFRAME;
SHOW_WIREFRAME.next = SHOW_DETAIL;
OFF.next = SHOW_DETAIL;
SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME;
SHOW_DETAIL_WIREFRAME.next = SHOW_GENMODE;
SHOW_GENMODE.next = SHOW_GENMODE_WIREFRAME;
SHOW_GENMODE_WIREFRAME.next = SHOW_OVERLAPPING_QUADS;
SHOW_OVERLAPPING_QUADS.next = SHOW_OVERLAPPING_QUADS_WIREFRAME;
SHOW_OVERLAPPING_QUADS_WIREFRAME.next = OFF;
SHOW_DETAIL_WIREFRAME.next = OFF;
}
/** returns the next debug mode */
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,78 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.enums.rendering;
import java.util.Objects;
/**
* Contains all configurable options related to fog.
*
* @version 2022-4-13
*/
public class FogSetting
{
/** a FogSetting object with 0 for every value */
public static final FogSetting EMPTY = new FogSetting(0, 0, 0, 0,0, FogSetting.FogType.LINEAR);
public final double start;
public final double end;
public final double min;
public final double max;
public final double density;
public final FogType fogType;
public FogSetting(double start, double end, double min, double max, double density, FogType fogType)
{
this.start = start;
this.end = end;
this.min = min;
this.max = max;
this.density = density;
this.fogType = fogType;
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
FogSetting that = (FogSetting) o;
return Double.compare(that.start, start) == 0 && Double.compare(that.end, end) == 0 && Double.compare(that.min, min) == 0 && Double.compare(that.max, max) == 0 && Double.compare(that.density, density) == 0 && fogType == that.fogType;
}
@Override
public int hashCode()
{
return Objects.hash(start, end, min, max, density, fogType);
}
public enum FogType
{
LINEAR,
EXPONENTIAL,
EXPONENTIAL_SQUARED,
// TEXTURE_BASED, // TODO: Impl this
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,49 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.enums.rendering;
/**
* basic <br>
* Ignore_Height <br>
* Addition <br>
* Max <br>
* Multiply <br>
* Inverse_Multiply <br>
* Limited_Addition <br>
* Multiply_Addition <br>
* Inverse_Multiply_Addition <br>
* Average <br>
*
* @author Leetom
* @version 2022-4-14
*/
public enum HeightFogMixMode
{
BASIC,
IGNORE_HEIGHT,
ADDITION,
MAX,
MULTIPLY,
INVERSE_MULTIPLY,
LIMITED_ADDITION,
MULTIPLY_ADDITION,
INVERSE_MULTIPLY_ADDITION,
AVERAGE,
}
@@ -1,41 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.enums.rendering;
public enum HeightFogMode
{
ABOVE_CAMERA(true, true, false),
BELOW_CAMERA(true, false, true),
ABOVE_AND_BELOW_CAMERA(true, true, true),
ABOVE_SET_HEIGHT(false, true, false),
BELOW_SET_HEIGHT(false, false, true),
ABOVE_AND_BELOW_SET_HEIGHT(false, true, true);
public final boolean basedOnCamera;
public final boolean above;
public final boolean below;
HeightFogMode(boolean basedOnCamera, boolean above, boolean below)
{
this.basedOnCamera = basedOnCamera;
this.above = above;
this.below = below;
}
}
@@ -1,43 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.enums.rendering;
public enum RendererType {
DEFAULT,
DEBUG,
DISABLED,
;
public static RendererType next(RendererType type) {
switch (type) {
case DEFAULT: return DEBUG;
case DEBUG: return DISABLED;
default: return DEFAULT;
}
}
public static RendererType previous(RendererType type) {
switch (type) {
case DEFAULT: return DISABLED;
case DEBUG: return DEFAULT;
default: return DEBUG;
}
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@
package com.seibel.lod.core.handlers;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.handlers.dependencyInjection.IBindable;
/**
* A singleton used to get variables from methods
@@ -35,9 +34,9 @@ import com.seibel.lod.core.handlers.dependencyInjection.IBindable;
* different MC versions.
*
* @author James Seibel
* @version 3-5-2022
* @version 12-14-2021
*/
public interface IReflectionHandler extends IBindable
public interface IReflectionHandler
{
/** @returns Whether Optifine is set to render fog or not. */
FogDrawMode getFogDrawMode();
@@ -47,6 +46,4 @@ public interface IReflectionHandler extends IBindable
/** @returns if Sodium (or a sodium like) mod is present. Attempts to find the "SodiumWorldRenderer" class. */
boolean sodiumPresent();
boolean optifinePresent();
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,38 +21,26 @@ package com.seibel.lod.core.handlers;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.api.ClientApi;
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
import org.jetbrains.annotations.Nullable;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.objects.lod.LevelContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.LodRegion;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.lod.VerticalLevelContainer;
import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.logging.SpamReducedLogger;
import com.seibel.lod.core.util.UnitBytes;
import org.apache.logging.log4j.LogManager;
import com.seibel.lod.core.util.ThreadMapUtil;
/**
* This object handles creating LodRegions
@@ -61,31 +49,22 @@ import org.apache.logging.log4j.LogManager;
*
* @author James Seibel
* @author Cola
* @version 2022-3-30
* @version 9-25-2021
*/
public class LodDimensionFileHandler
{
private static final ILodConfigWrapperSingleton config = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(LodDimensionFileHandler.class),
() -> config.client().advanced().debugging().debugSwitch().getLogFileReadWriteEvent());
public static final boolean ENABLE_SAVE_THREAD_LOGGING = true;
public static final boolean ENABLE_SAVE_REGION_LOGGING = false;
/** This is the dimension that owns this file handler */
private final LodDimension lodDimension;
private LodDimension lodDimension;
public final File dimensionDataSaveFolder;
private final File dimensionDataSaveFolder;
/** lod */
private static final String FILE_NAME_PREFIX = "lod";
/** .xz */
/** .txt */
private static final String FILE_EXTENSION = ".xz";
/** detail- */
private static final String DETAIL_FOLDER_NAME_PREFIX = "detail-";
public static final String MULTIPLAYER_FOLDER_NAME = "Distant_Horizons_server_data";
/**
* .tmp <br>
* Added to the end of the file path when saving to prevent
@@ -95,29 +74,20 @@ public class LodDimensionFileHandler
*/
private static final String TMP_FILE_EXTENSION = ".tmp";
/**
* This is compression level set in XZ
* lower values are faster
* difference in size should be only 2x
*/
private static final int COMPRESSION_LEVEL = 1;
/**
* This is the file version currently accepted by this
* file handler, older versions (smaller numbers) will be deleted and overwritten,
* newer versions (larger numbers) will be ignored and won't be read.
*/
public static final int LOD_SAVE_FILE_VERSION = 9;
public static final int LOD_SAVE_FILE_VERSION = 8;
/**
* Allow saving asynchronously, but never try to save multiple regions
* at a time
*/
private final AtomicBoolean isFileWritingThreadRunning = new AtomicBoolean(false);
private ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(
new LodThreadFactory(this.getClass().getSimpleName(), Thread.NORM_PRIORITY + 1));
private final ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
private final ConcurrentHashMap<RegionPos, LodRegion> regionToSave = new ConcurrentHashMap<RegionPos, LodRegion>();
public LodDimensionFileHandler(File newSaveFolder, LodDimension newLodDimension)
@@ -127,67 +97,9 @@ public class LodDimensionFileHandler
dimensionDataSaveFolder = newSaveFolder;
lodDimension = newLodDimension;
checkForOldSaveStructure();
}
private ReentrantLock mergeOldFileLock = new ReentrantLock();
private void checkForOldSaveStructure()
{
File file = new File(getFileBasePath());
if (!file.exists())
return;
File[] vertQualFiles = file.listFiles();
for (File vertQualFile : vertQualFiles)
{
if (!vertQualFile.isDirectory())
continue;
if (vertQualFile.getName().equals(VerticalQuality.HIGH.toString()) ||
vertQualFile.getName().equals(VerticalQuality.MEDIUM.toString()) ||
vertQualFile.getName().equals(VerticalQuality.LOW.toString()))
{
File[] subFiles = vertQualFile.listFiles();
for (File subFile : subFiles)
{
if (!subFile.isDirectory())
continue;
if (subFile.getName().equals(DistanceGenerationMode.FULL.toString()) ||
subFile.getName().equals(DistanceGenerationMode.FEATURES.toString()) ||
subFile.getName().equals(DistanceGenerationMode.SURFACE.toString()) ||
subFile.getName().equals(DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT.toString()) ||
subFile.getName().equals(DistanceGenerationMode.BIOME_ONLY.toString()) ||
subFile.getName().equals(DistanceGenerationMode.NONE.toString()))
{
LOGGER.info("Noticed old save structure files. Starting merge process...");
LodDimensionOldFileStructureHandler oldFileStructHandler = new LodDimensionOldFileStructureHandler(this);
if (mergeOldFileLock.tryLock())
{
// I got the lock to merge file.
LOGGER.info("Updating VerticalQuality LOW...");
oldFileStructHandler.mergeOldFileStructureForVertQuality(VerticalQuality.LOW);
LOGGER.info("Updating VerticalQuality MEDIUM...");
oldFileStructHandler.mergeOldFileStructureForVertQuality(VerticalQuality.MEDIUM);
LOGGER.info("Updating VerticalQuality HIGH...");
oldFileStructHandler.mergeOldFileStructureForVertQuality(VerticalQuality.HIGH);
LOGGER.info("Update completed.");
}
else
{
// Someone is already doing it. I just need to wait until they are done.
mergeOldFileLock.lock();
mergeOldFileLock.unlock();
}
LOGGER.info("Merge process completed.");
return;
}
}
}
}
}
@@ -196,53 +108,24 @@ public class LodDimensionFileHandler
//================//
/**
* Returns a new LodRegion at the given coordinates.
* Returns the LodRegion at the given coordinates.
* Returns an empty region if the file doesn't exist.
*/
public LodRegion loadRegionFromFile(byte detailLevel, RegionPos regionPos, VerticalQuality verticalQuality)
public LodRegion loadRegionFromFile(byte detailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
{
// Get one from the region hot cache
LodRegion region = regionToSave.get(regionPos);
if (region != null && region.getMinDetailLevel() <= detailLevel &&
region.getVerticalQuality().compareTo(verticalQuality) >= 0)
return region; // The current hot cache to-be-saved region match our requirement.
region = new LodRegion((byte) (LodUtil.REGION_DETAIL_LEVEL + 1), regionPos, verticalQuality);
return loadRegionFromFile(detailLevel, region, verticalQuality);
}
/**
* Returns the LodRegion that is filled at the given coordinates.
* Returns an empty region if the file doesn't exist.
*/
public LodRegion loadRegionFromFile(byte detailLevel, LodRegion region, VerticalQuality verticalQuality)
{
if (region.getVerticalQuality().compareTo(verticalQuality) < 0)
{
regionToSave.put(region.getRegionPos(), region); //FIXME: The hashMap key should prob be a {regionPos,VertQual} pair.
region = new LodRegion((byte) (LodUtil.REGION_DETAIL_LEVEL + 1), region.getRegionPos(), verticalQuality);
}
int regionX = region.regionPosX;
int regionZ = region.regionPosZ;
int regionX = regionPos.x;
int regionZ = regionPos.z;
LodRegion region = new LodRegion(LodUtil.REGION_DETAIL_LEVEL, regionPos, generationMode, verticalQuality);
for (byte tempDetailLevel = (byte) (region.getMinDetailLevel() - 1); tempDetailLevel >= detailLevel; tempDetailLevel--)
for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--)
{
File file = getBestMatchingRegionFile(tempDetailLevel, regionX, regionZ, verticalQuality);
if (file == null)
{
region.addLevelContainer(new VerticalLevelContainer(tempDetailLevel));
continue; // Failed to find the file for this detail level. continue and try next one
}
File file = getBestMatchingRegionFile(tempDetailLevel, regionX, regionZ, generationMode, verticalQuality);
if (file == null) continue; // Failed to find the file for this detail level. continue and try next one
long fileSize = file.length();
if (fileSize == 0)
if (fileSize == 0) continue; // file is empty. Let's not try parsing empty files
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(file)))
{
region.addLevelContainer(new VerticalLevelContainer(tempDetailLevel));
continue; // file is empty. Let's not try parsing empty files
}
try (FileInputStream fileInStream = new FileInputStream(file))
{
XZCompressorInputStream inputStream = new XZCompressorInputStream(fileInStream);
int fileVersion;
fileVersion = inputStream.read();
@@ -253,12 +136,12 @@ public class LodDimensionFileHandler
// close the reader and delete the file.
inputStream.close();
file.delete();
LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")[" + tempDetailLevel + "]"
ClientApi.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". File has been deleted.");
// This should not break, but be continue to see whether other detail levels can be loaded or updated
region.addLevelContainer(new VerticalLevelContainer(tempDetailLevel));
continue;
}
else if (fileVersion > LOD_SAVE_FILE_VERSION)
{
@@ -266,43 +149,40 @@ public class LodDimensionFileHandler
// close the reader and ignore the file, we don't
// want to accidentally delete anything the user may want.
inputStream.close();
LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")[" + tempDetailLevel + "]"
ClientApi.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ " this region will not be written to in order to protect the newer file.");
// This should not break, but be continue to see whether other detail levels can be loaded or updated
region.addLevelContainer(new VerticalLevelContainer(tempDetailLevel));
continue;
}
else if (fileVersion < LOD_SAVE_FILE_VERSION)
{
LOGGER.info("Old LOD region file for region: (" + regionX + "," + regionZ + ")[" + tempDetailLevel + "]"
ClientApi.LOGGER.debug("Old LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". File will be loaded and updated to new format in next save.");
// this is old, but readable version
// read and add the data to our region
DataInputStream dataStream = new DataInputStream(inputStream);
region.addLevelContainer(new VerticalLevelContainer(dataStream, fileVersion, tempDetailLevel));
dataStream.close();
region.addLevelContainer(new VerticalLevelContainer(new DataInputStream(inputStream), fileVersion));
inputStream.close();
}
else
} else
{
LOGGER.debug("Loading LOD region file for region: (" + regionX + "," + regionZ + ")[" + tempDetailLevel + "]");
// this file is a readable version,
// read and add the data to our region
DataInputStream dataStream = new DataInputStream(inputStream);
region.addLevelContainer(new VerticalLevelContainer(dataStream, LOD_SAVE_FILE_VERSION, tempDetailLevel));
dataStream.close();
region.addLevelContainer(new VerticalLevelContainer(new DataInputStream(inputStream), LOD_SAVE_FILE_VERSION));
inputStream.close();
}
}
catch (IOException ioEx)
{
LOGGER.error("LOD file read error. Unable to read xz compressed file [" + file + "]: ", ioEx);
region.addLevelContainer(new VerticalLevelContainer(tempDetailLevel));
ClientApi.LOGGER.error("LOD file read error. Unable to read xz compressed file [" + file + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
} // for each detail level
}// for each detail level
if (region.getMinDetailLevel() >= detailLevel)
region.growTree(detailLevel);
return region;
}
@@ -312,226 +192,33 @@ public class LodDimensionFileHandler
// Save to File //
//==============//
public void saveDirect(int posX, int posZ, VerticalQuality vertQual, VerticalLevelContainer dataContainer)
/** Save all dirty regions in this LodDimension to file */
public void saveDirtyRegionsToFileAsync()
{
fileWritingThreadPool.execute(saveDirtyRegionsThread);
}
private final Thread saveDirtyRegionsThread = new Thread(() ->
{
File file = new File(getFileBasePath() + vertQual + File.separatorChar +
DETAIL_FOLDER_NAME_PREFIX + dataContainer.detailLevel + File.separatorChar +
FILE_NAME_PREFIX + "." + posX + "." + posZ + FILE_EXTENSION);
if (file.exists())
{
LOGGER.warn("LOD file write warn. Unable to write [" + file + "] because the newer version file already exist! Skipping this position...");
return;
}
if (!file.getParentFile().exists())
file.getParentFile().mkdirs();
try
{
file.createNewFile();
}
catch (IOException e)
{
LOGGER.error("LOD file write error. Unable to create parent directory for [" + file + "]: ", e);
return;
}
try (FileOutputStream fileOutStream = new FileOutputStream(file))
{
XZCompressorOutputStream outputStream = new XZCompressorOutputStream(fileOutStream, COMPRESSION_LEVEL);
// add the version of this file
outputStream.write(LOD_SAVE_FILE_VERSION);
// add each LodChunk to the file
DataOutputStream dataStream = new DataOutputStream(outputStream);
dataContainer.writeData(dataStream);
dataStream.close();
outputStream.close();
}
catch (IOException e)
{
LOGGER.error("LOD file write error. Unable to write to temp file [" + file + "]: ", e);
}
}
public void addRegionsToSave(LodRegion r)
{
regionToSave.put(r.getRegionPos(), r);
}
private final SpamReducedLogger ramLogger = new SpamReducedLogger(1);
public void dumpBufferMemoryUsage()
{
if (!ramLogger.canMaybeLog())
return;
ArrayList<LodRegion> regions = new ArrayList<LodRegion>(regionToSave.values());
ramLogger.info("Dumping Ram Usage for file writer for {} with {} regions...",
lodDimension.dimension.getDimensionName(), regions.size());
int nonNullRegionCount = 0;
int nonDirtiedRegionCount = 0;
int writingRegionCount = 0;
long totalUsage = 0;
int[] detailCount = new int[LodUtil.DETAIL_OPTIONS];
long[] detailUsage = new long[LodUtil.DETAIL_OPTIONS];
for (LodRegion r : regions)
{
if (r == null)
continue;
nonNullRegionCount++;
if (!r.needSaving)
nonDirtiedRegionCount++;
if (r.isWriting.get() != 0)
writingRegionCount++;
LevelContainer[] container = r.debugGetDataContainers().clone();
if (container == null || container.length != LodUtil.DETAIL_OPTIONS)
for (int i = 0; i < lodDimension.getWidth(); i++)
{
LOGGER.error("DumpRamUsage encountered an invalid region!");
continue;
}
for (int i = 0; i < LodUtil.DETAIL_OPTIONS; i++)
{
if (container[i] == null)
continue;
detailCount[i]++;
long byteUsage = container[i].getRoughRamUsage();
detailUsage[i] += byteUsage;
totalUsage += byteUsage;
}
}
ramLogger.info("================================================");
ramLogger.info("Non Null Regions: [{}], Non-Dirtied Regions: [{}], Writing Regions: [{}], Bytes: [{}]",
nonNullRegionCount, nonDirtiedRegionCount, writingRegionCount, new UnitBytes(totalUsage));
ramLogger.info("------------------------------------------------");
for (int i = 0; i < LodUtil.DETAIL_OPTIONS; i++)
{
ramLogger.info("DETAIL {}: Containers: [{}], Bytes: [{}]", i, detailCount[i], new UnitBytes(detailUsage[i]));
}
ramLogger.info("================================================");
ramLogger.incLogTries();
}
/** Save all dirty regions in this LodDimension to file */
public void saveDirtyRegionsToFile(boolean blockUntilFinished)
{
// determine the regions to save
for (int i = 0; i < lodDimension.getWidth(); i++)
{
for (int j = 0; j < lodDimension.getWidth(); j++)
{
LodRegion r = lodDimension.getRegionByArrayIndex(i, j);
// FIXME: Note that the isWriting is a crude attempt at syncing. It won't work.
// It just reduces the chance of a race condition
if (r != null && r.needSaving)
for (int j = 0; j < lodDimension.getWidth(); j++)
{
regionToSave.put(r.getRegionPos(), r);
if (lodDimension.GetIsRegionDirty(i, j) && lodDimension.getRegionByArrayIndex(i, j) != null)
{
saveRegionToFile(lodDimension.getRegionByArrayIndex(i, j));
lodDimension.SetIsRegionDirty(i, j, false);
}
}
}
}
// save the dimension data
ClientApi.DIMENSION_FINDER.saveDimensionPlayerData(this.dimensionDataSaveFolder);
trySaveRegionsToBeSaved();
// wait for the saving to finish if requested
if (blockUntilFinished)
catch (Exception e)
{
if (ENABLE_SAVE_THREAD_LOGGING)
LOGGER.info("Blocking until lod file save finishes!");
try
{
fileWritingThreadPool.shutdown();
boolean worked = fileWritingThreadPool.awaitTermination(30, TimeUnit.SECONDS);
if (!worked)
LOGGER.error("File writing timed out! File data may not be saved correctly and may cause corruptions!!!");
}
catch (InterruptedException e)
{
LOGGER.error("File writing wait is interrupted! File data may not be saved correctly and may cause corruptions!!!: ", e);
}
finally
{
fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName(), Thread.NORM_PRIORITY + 1));
}
e.printStackTrace();
}
}
public void trySaveRegionsToBeSaved()
{
if (regionToSave.isEmpty())
return;
// Use Memory order Acquire to acquire any memory changes on getting this boolean
// (Corresponding call is the this::writerMain(...)::...setRelease(false);)
//boolean haventStarted = !isFileWritingThreadRunning.compareAndExchangeAcquire(false, true);
// The above needs java 9!
boolean haventStarted = isFileWritingThreadRunning.compareAndSet(false, true);
if (haventStarted)
{
// We acquired the atomic lock.
fileWritingThreadPool.execute(this::writerMain);
}
}
private void writerMain()
{
// Use Memory order Relaxed as no additional memory changes needed to be visible.
// (This is just a safety checks)
// boolean isStarted = isFileWritingThreadRunning.getPlain();
// The above needs java 9!
boolean isStarted = isFileWritingThreadRunning.get();
if (!isStarted)
throw new ConcurrentModificationException("WriterMain Triggered but the thead state is not started!?");
if (ENABLE_SAVE_THREAD_LOGGING)
LOGGER.info("Lod File Writer started. To-be-written-regions: " + regionToSave.size());
Instant start = Instant.now();
// Note: Since regionToSave is a ConcurrentHashMap, and the .values() return one that support concurrency,
// this for loop should be safe and loop until all values are gone.
while (!regionToSave.isEmpty())
{
for (LodRegion r : regionToSave.values())
{
try
{
if (r.isWriting.getAndIncrement() > 0)
continue;
//Check if the data has been swapped out right under me. Otherwise remove it from the entry
if (!regionToSave.remove(r.getRegionPos(), r))
continue;
r.needSaving = false;
Instant i = Instant.now();
if (ENABLE_SAVE_REGION_LOGGING)
LOGGER.info("Lod: Saving Region " + r.getRegionPos());
saveRegionToFile(r);
Instant j = Instant.now();
Duration d = Duration.between(i, j);
if (ENABLE_SAVE_REGION_LOGGING)
LOGGER.info("Lod: Region " + r.getRegionPos() + " save finish. Took " + d);
}
catch (Exception e)
{
LOGGER.error("Lod: UNCAUGHT exception when saving region " + r.getRegionPos() + ": ", e);
}
finally
{
r.isWriting.decrementAndGet();
}
}
}
Instant end = Instant.now();
if (ENABLE_SAVE_THREAD_LOGGING)
LOGGER.info("Lod File Writer completed. Took " + Duration.between(start, end));
// Use Memory order Release to release any memory changes on setting this boolean
// (Corresponding call is the this::saveRegions(...)::...compareAndExchangeAcquire(false, true);)
// isFileWritingThreadRunning.setRelease(false);
// The above needs java 9!
isFileWritingThreadRunning.set(false);
}
});
/**
* Save a specific region to disk.<br>
@@ -546,9 +233,8 @@ public class LodDimensionFileHandler
for (byte detailLevel = region.getMinDetailLevel(); detailLevel <= LodUtil.REGION_DETAIL_LEVEL; detailLevel++)
{
// Get the old file
File oldFile = getRegionFile(region.regionPosX, region.regionPosZ, detailLevel, region.getVerticalQuality());
if (ENABLE_SAVE_REGION_LOGGING)
LOGGER.debug("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] detail " + detailLevel + " to file.");
File oldFile = getRegionFile(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality());
ClientApi.LOGGER.debug("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] to file.");
boolean isFileFullyGened = false;
// make sure the file and folder exists
@@ -558,14 +244,10 @@ public class LodDimensionFileHandler
// create it and the folder if need be
if (!oldFile.getParentFile().exists())
oldFile.getParentFile().mkdirs();
try
{
try {
oldFile.createNewFile();
}
catch (IOException e)
{
LOGGER.error("LOD file write error. Unable to create parent directory for [" + oldFile + "] error [" + e.getMessage() + "]: ");
} catch (IOException e) {
ClientApi.LOGGER.error("LOD file write error. Unable to create parent directory for [" + oldFile + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
continue;
}
@@ -577,9 +259,8 @@ public class LodDimensionFileHandler
// (to make sure we don't overwrite a newer
// version file if it exists)
int fileVersion = LOD_SAVE_FILE_VERSION;
try (FileInputStream fileInStream = new FileInputStream(oldFile))
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(oldFile)))
{
XZCompressorInputStream inputStream = new XZCompressorInputStream(fileInStream);
fileVersion = inputStream.read();
inputStream.skip(1);
isFileFullyGened = (inputStream.read() & 0b10000000) != 0;
@@ -587,7 +268,7 @@ public class LodDimensionFileHandler
}
catch (IOException e)
{
LOGGER.warn("LOD file write warning. Unable to read existing file [" + oldFile + "] version. Treating it as latest version. [" + e.getMessage() + "]: ");
ClientApi.LOGGER.warn("LOD file write warning. Unable to read existing file [" + oldFile + "] version. Treating it as latest version. [" + e.getMessage() + "]: ");
e.printStackTrace();
}
@@ -604,16 +285,21 @@ public class LodDimensionFileHandler
}
// Now create a new temporary save file
File tempFile = new File(oldFile.getPath() + TMP_FILE_EXTENSION);
try (FileOutputStream fileOutStream = new FileOutputStream(tempFile))
File tempFile;
try {
tempFile = File.createTempFile(oldFile.getName(), TMP_FILE_EXTENSION);
} catch (IOException e) {
ClientApi.LOGGER.error("LOD file write error. Unable to create temp file for [" + oldFile + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
continue;
}
tempFile.deleteOnExit(); // Mark it to be deleted on exit if any unexcepted terminations happen
try (XZCompressorOutputStream outputStream = new XZCompressorOutputStream(new FileOutputStream(tempFile), 3))
{
XZCompressorOutputStream outputStream = new XZCompressorOutputStream(fileOutStream, 3);
// add the version of this file
outputStream.write(LOD_SAVE_FILE_VERSION);
// add each LodChunk to the file
DataOutputStream dataStream = new DataOutputStream(outputStream);
boolean isNewDataFullyGened = region.getLevel(detailLevel).writeData(dataStream);
dataStream.close();
boolean isNewDataFullyGened = region.getLevel(detailLevel).writeData(new DataOutputStream(outputStream));
outputStream.close();
if (!isNewDataFullyGened && isFileFullyGened)
@@ -621,13 +307,10 @@ public class LodDimensionFileHandler
// existing file is complete while new one is only partially generate
// this can happen is for some reason loading failed
// this doesn't fix the bug, but at least protects old data
LOGGER.error("LOD file write error. Attempted to overwrite complete region with incomplete one [" + oldFile + "]");
try
{
ClientApi.LOGGER.error("LOD file write error. Attempted to overwrite complete region with incomplete one [" + oldFile + "]");
try {
tempFile.delete();
}
catch (SecurityException e)
{
} catch (SecurityException e) {
// Failed to delete temp file... just continue.
}
continue;
@@ -635,18 +318,17 @@ public class LodDimensionFileHandler
}
catch (IOException e)
{
LOGGER.error("LOD file write error. Unable to write to temp file [" + tempFile + "]: ", e);
ClientApi.LOGGER.error("LOD file write error. Unable to write to temp file [" + tempFile + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
continue;
}
// overwrite the old file with the new one
try
{
try {
Files.move(tempFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
}
catch (IOException e)
{
LOGGER.error("LOD file write error. Unable to update file [" + oldFile + "]: ", e);
} catch (IOException e) {
ClientApi.LOGGER.error("LOD file write error. Unable to update file [" + oldFile + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
}
}
}
@@ -655,24 +337,6 @@ public class LodDimensionFileHandler
// helper methods //
//================//
/**
* Returns the save folder used for this dimension.
*
* @throws RuntimeException if there was an error getting the folder
*/
private String getFileBasePath() throws RuntimeException
{
try
{
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar;
}
catch (IOException e)
{
LOGGER.warn("Unable to get the base save file path. Error: " + e.getMessage(), e);
throw new RuntimeException("DistantHorizons Get Save File Path Failure");
}
}
/**
* Return the name of the file that should contain the
* region at the given x and z. <br>
@@ -682,27 +346,38 @@ public class LodDimensionFileHandler
* <p>
* Returns null if there is an IO or security Exception.
*/
private File getRegionFile(int regionX, int regionZ, byte detail, VerticalQuality vertQuality)
{
private String getFileBasePath() {
try {
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar;
} catch (IOException e) {
ClientApi.LOGGER.warn("Unable to get the base save file path. One possible cause is that"
+ " the process failed to read the current path location due to security configs.");
throw new RuntimeException("DistantHorizons Get Save File Path Failure");
}
}
private File getRegionFile(int regionX, int regionZ, DistanceGenerationMode genMode, byte detail, VerticalQuality vertQuality) {
return new File(getFileBasePath() + vertQuality + File.separatorChar +
genMode + File.separatorChar +
DETAIL_FOLDER_NAME_PREFIX + detail + File.separatorChar +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION);
}
/** Returns null if no file is found */
private File getBestMatchingRegionFile(byte detailLevel, int regionX, int regionZ, VerticalQuality targetVertQuality)
{
// Search from least vertQuality to max vertQuality
do
{
File file = getRegionFile(regionX, regionZ, detailLevel, targetVertQuality);
if (file.exists())
return file; // Found target file.
targetVertQuality = VerticalQuality.next(targetVertQuality);
}
while (targetVertQuality != null);
// Return null if no file found
@Nullable
private File getBestMatchingRegionFile(byte detailLevel, int regionX, int regionZ, DistanceGenerationMode targetGenMode, VerticalQuality targetVertQuality) {
DistanceGenerationMode genMode = targetGenMode;
// Search from least GenMode to max GenMode, than least vertQuality to max vertQuality
do {
File file = getRegionFile(regionX, regionZ, genMode, detailLevel, targetVertQuality);
if (file.exists()) return file; // Found target file.
targetGenMode = DistanceGenerationMode.next(targetGenMode);
if (targetGenMode == null) { // Failed to find any files for this vertQuality. Try next one up.
targetGenMode = genMode;
targetVertQuality = VerticalQuality.next(targetVertQuality);
}
} while (targetVertQuality != null);
return null;
}
@@ -1,585 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.handlers;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.handlers.dimensionFinder.PlayerData;
import com.seibel.lod.core.handlers.dimensionFinder.SubDimCompare;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.builders.lodBuilding.LodBuilderConfig;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.LodRegion;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import org.apache.logging.log4j.LogManager;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Comparator;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Used to guess the world folder for the player's current dimension.
*
* @author James Seibel
* @version 2022-3-31
*/
public class LodDimensionFinder
{
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(LodDimensionFinder.class),
() -> CONFIG.client().advanced().debugging().debugSwitch().getLogFileSubDimEvent());
/** Increasing this will increase accuracy but increase calculation time */
private static final VerticalQuality VERTICAL_QUALITY_TO_TEST_WITH = VerticalQuality.LOW;
public static final String THREAD_NAME = "Sub-Dimension-Finder";
public static final String DEFAULT_SAVE_DIMENSION_FOLDER = "_Default-Sub-Dimension";
private PlayerData playerData = new PlayerData(MC);
private PlayerData firstSeenPlayerData = null;
private volatile LodDimension foundLodDimension = null;
/** If true the LodDimensionFileHelper is attempting to determine the folder for this dimension */
private AtomicBoolean determiningWorldFolder = new AtomicBoolean(false);
public LodDimensionFinder()
{
}
/** Returns true if a LodDimension has been found */
public boolean isDone()
{
return foundLodDimension != null;
}
/** Returns the found LodDimension */
public LodDimension getAndClearFoundLodDimension()
{
// clear the found dimension
LodDimension returnDim = this.foundLodDimension;
this.foundLodDimension = null;
return returnDim;
}
public void AttemptToDetermineSubDimensionAsync(IDimensionTypeWrapper dimensionTypeWrapper)
{
// prevent multiple threads running at the same time
if (determiningWorldFolder.getAndSet(true))
return;
// run asynchronously since this could take a while
Thread thread = new Thread(() ->
{
try
{
// attempt to get the file handler
File saveDir;
if (CONFIG.client().multiplayer().getMultiDimensionRequiredSimilarity() == 0)
{
// only allow 1 sub dimension per world
saveDir = getDefaultSubDimensionFolder(dimensionTypeWrapper);
}
else
{
saveDir = attemptToDetermineSubDimensionFolder();
}
if (saveDir == null)
return;
foundLodDimension = new LodDimension(dimensionTypeWrapper, ApiShared.lodBuilder.defaultDimensionWidthInRegions, saveDir);
}
catch (IOException e)
{
ApiShared.LOGGER.error("Unable to set the dimension file handler for dimension type [" + dimensionTypeWrapper.getDimensionName() + "]. Error: " + e.getMessage(), e);
}
finally
{
// make sure we unlock this method
determiningWorldFolder.set(false);
}
});
thread.setName(THREAD_NAME);
thread.start();
}
/**
* Returns the default save folder if it exists
* otherwise the first valid subDimension folder lexicographically.
*
* @throws IOException if the folder doesn't exist or can't be accessed
*/
public File getDefaultSubDimensionFolder(IDimensionTypeWrapper dimensionTypeWrapper) throws IOException
{
File subDimFolder = null;
LOGGER.info("Attempting to determine default sub-dimension for [" + MC.getCurrentDimension().getDimensionName() + "]");
// move any old data folders if they exist
File dimensionFolder = GetDimensionFolder(MC.getCurrentDimension(), "");
moveOldSaveFoldersIfNecessary(dimensionFolder, MC.getCurrentDimension(), DEFAULT_SAVE_DIMENSION_FOLDER);
// check if a sub dimension folder exists
if (dimensionFolder.listFiles() != null)
{
// at least one folder exists
LOGGER.info("Potential Sub Dimension folders: [" + dimensionFolder.listFiles(File::isDirectory).length + "]");
// search for a valid sub dimension folder
File[] potentialSubDimFolders = dimensionFolder.listFiles();
Arrays.sort(potentialSubDimFolders); // listFiles isn't necessarily sorted
for (File potentialSubDim : potentialSubDimFolders)
{
if (isValidSubDimensionDirectory(potentialSubDim))
{
if (potentialSubDim.getName().equals(DEFAULT_SAVE_DIMENSION_FOLDER))
{
// use the default save folder if possible
subDimFolder = potentialSubDim;
break;
}
else if (subDimFolder == null)
{
// only get the first non-default sub folder
subDimFolder = potentialSubDim;
}
}
}
}
// if no valid sub dimension was found, create a new one
if (subDimFolder == null)
{
subDimFolder = GetDimensionFolder(dimensionTypeWrapper, DEFAULT_SAVE_DIMENSION_FOLDER);
LOGGER.info("Default Sub Dimension not found. Creating: [" + DEFAULT_SAVE_DIMENSION_FOLDER + "]");
}
else
{
LOGGER.info("Default Sub Dimension set to: [" + LodUtil.shortenString(subDimFolder.getName(), 8) + "...]");
}
return subDimFolder;
}
/**
* Currently this method checks a single chunk (where the player is)
* and compares it against the same chunk position in the other dimension worlds to
* guess which world the player is in.
*
* @throws IOException if the folder doesn't exist or can't be accessed
*/
public File attemptToDetermineSubDimensionFolder() throws IOException
{
if (firstSeenPlayerData == null)
{
firstSeenPlayerData = playerData;
playerData = new PlayerData(MC);
}
// relevant positions
AbstractChunkPosWrapper playerChunkPos = FACTORY.createChunkPos(playerData.playerBlockPos);
int startingBlockPosX = playerChunkPos.getMinBlockX();
int startingBlockPosZ = playerChunkPos.getMinBlockZ();
RegionPos playerRegionPos = new RegionPos(playerChunkPos);
// chunk from the newly loaded dimension
IChunkWrapper newlyLoadedChunk = MC.getWrappedClientWorld().tryGetChunk(playerChunkPos);
// check if this chunk is valid to test
if (!CanDetermineDimensionFolder(newlyLoadedChunk))
return null;
// create a temporary dimension to store the test LOD
LodDimension newlyLoadedDim = new LodDimension(MC.getCurrentDimension(), 1, null, false);
newlyLoadedDim.move(playerRegionPos);
newlyLoadedDim.regions.set(playerRegionPos.x, playerRegionPos.z, new LodRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerRegionPos, VERTICAL_QUALITY_TO_TEST_WITH));
// generate a LOD to test against
boolean lodGenerated = ApiShared.lodBuilder.generateLodNodeFromChunk(newlyLoadedDim, newlyLoadedChunk, new LodBuilderConfig(DistanceGenerationMode.FULL), true, true);
if (!lodGenerated)
return null;
// log the start of this attempt
LOGGER.info("Attempting to determine sub-dimension for [" + MC.getCurrentDimension().getDimensionName() + "]");
LOGGER.info("Player block pos in dimension: [" + playerData.playerBlockPos.getX() + "," + playerData.playerBlockPos.getY() + "," + playerData.playerBlockPos.getZ() + "]");
// new chunk data
long[][][] newChunkData = new long[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH][];
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
long[] array = newlyLoadedDim.getRegion(playerRegionPos.x, playerRegionPos.z).getAllData(LodUtil.BLOCK_DETAIL_LEVEL, x + startingBlockPosX, z + startingBlockPosZ);
newChunkData[x][z] = array;
}
}
boolean newChunkHasData = !isDataEmpty(newChunkData);
// check if the chunk is actually empty
if (!newChunkHasData)
{
if (newlyLoadedChunk.getHeight() != 0)
{
// the chunk isn't empty but the LOD is...
String message = "Error: the chunk at (" + playerChunkPos.getX() + "," + playerChunkPos.getZ() + ") has a height of [" + newlyLoadedChunk.getHeight() + "] but the LOD generated is empty!";
LOGGER.error(message);
}
else
{
String message = "Warning: The chunk at (" + playerChunkPos.getX() + "," + playerChunkPos.getZ() + ") is empty.";
LOGGER.warn(message);
}
return null;
}
// get every folder (world) we have for this dimension
File dimensionFolder = GetDimensionFolder(newlyLoadedDim.dimension, "");
// check if the folder exists
if (dimensionFolder.listFiles() == null)
{
if (!dimensionFolder.exists())
{
// create the directory since it doesn't exist
dimensionFolder.mkdirs();
}
}
// move any old data folders if they exist
moveOldSaveFoldersIfNecessary(dimensionFolder, MC.getCurrentDimension(), UUID.randomUUID().toString());
// compare each world with the newly loaded one
SubDimCompare mostSimilarSubDim = null;
LOGGER.info("Potential Sub Dimension folders: [" + dimensionFolder.listFiles(File::isDirectory).length + "]");
for (File testDimFolder : dimensionFolder.listFiles())
{
if (!testDimFolder.isDirectory())
continue;
LOGGER.info("Testing sub dimension: [" + LodUtil.shortenString(testDimFolder.getName(), 8) + "]");
try
{
// get a LOD from this dimension folder
LodDimension tempLodDim = new LodDimension(null, 1, null, false);
tempLodDim.move(playerRegionPos);
LodDimensionFileHandler tempFileHandler = new LodDimensionFileHandler(testDimFolder, tempLodDim);
LodRegion testRegion = tempFileHandler.loadRegionFromFile(LodUtil.BLOCK_DETAIL_LEVEL, playerRegionPos, VERTICAL_QUALITY_TO_TEST_WITH);
// get data from this LOD
long[][][] testChunkData = new long[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH][];
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
long[] array = testRegion.getAllData(LodUtil.BLOCK_DETAIL_LEVEL, x + startingBlockPosX, z + startingBlockPosZ);
testChunkData[x][z] = array;
}
}
// get the player data for this dimension folder
PlayerData testPlayerData = new PlayerData(testDimFolder);
LOGGER.info("Last known player pos: [" + testPlayerData.playerBlockPos.getX() + "," + testPlayerData.playerBlockPos.getY() + "," + testPlayerData.playerBlockPos.getZ() + "]");
// check if the block positions are close
int playerBlockDist = testPlayerData.playerBlockPos.getManhattanDistance(playerData.playerBlockPos);
ApiShared.LOGGER.info("Player block position distance between saved sub dimension and first seen is [" + playerBlockDist + "]");
// check if the chunk is actually empty
if (isDataEmpty(testChunkData))
{
String message = "The test chunk for dimension folder [" + LodUtil.shortenString(testDimFolder.getName(), 8) + "] and chunk pos (" + playerChunkPos.getX() + "," + playerChunkPos.getZ() + ") is empty. This is expected if the position is outside the sub-dimension's generated area.";
LOGGER.info(message);
continue;
}
// compare the two LODs
int equalDataPoints = 0;
int totalDataPointCount = 0;
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
for (int y = 0; y < newChunkData[x][z].length; y++)
{
if (newChunkData[x][z][y] == testChunkData[x][z][y])
{
equalDataPoints++;
}
totalDataPointCount++;
if (!DataPointUtil.doesItExist(newChunkData[x][z][y]) || !DataPointUtil.doesItExist(testChunkData[x][z][y]))
break;
}
}
}
// determine if this world is closer to the newly loaded world
SubDimCompare subDimCompare = new SubDimCompare(equalDataPoints, totalDataPointCount, playerBlockDist, testDimFolder);
if (mostSimilarSubDim == null || subDimCompare.compareTo(mostSimilarSubDim) > 0)
{
mostSimilarSubDim = subDimCompare;
}
LOGGER.info("Sub dimension [" + LodUtil.shortenString(testDimFolder.getName(), 8) + "...] is current dimension probability: " + LodUtil.shortenString(subDimCompare.getPercentEqual() + "", 5) + " (" + equalDataPoints + "/" + totalDataPointCount + ")");
}
catch (Exception e)
{
// this sub dimension isn't formatted correctly
// for now we are just assuming it is an unrelated file
}
}
// TODO if two sub dimensions contain the same LODs merge them
// the first seen player data is no longer needed, the sub dimension has been determined
firstSeenPlayerData = null;
if (mostSimilarSubDim != null && mostSimilarSubDim.isValidSubDim())
{
// we found a world folder that is similar, use it
LOGGER.info("Sub Dimension set to: [" + LodUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]");
return mostSimilarSubDim.folder;
}
else
{
// no world folder was found, create a new one
double highestEqualityPercent = mostSimilarSubDim != null ? mostSimilarSubDim.getPercentEqual() : 0;
String newId = UUID.randomUUID().toString();
String message = "No suitable sub dimension found. The highest equality was [" + LodUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + LodUtil.shortenString(newId, 8) + "...";
LOGGER.info(message);
return GetDimensionFolder(newlyLoadedDim.dimension, newId);
}
}
/**
* Returns the dimension folder with the specific ID if specified. <br>
* If the worldId is empty or null this returns the dimension parent folder <br>
* Example folder names: "dim_overworld/worldId", "dim_the_nether/worldId"
*/
public static File GetDimensionFolder(IDimensionTypeWrapper newDimensionType, String worldId)
{
// prevent null pointers
if (worldId == null)
worldId = "";
// make sure the ID is a valid path
worldId = worldId.replaceAll(LodUtil.INVALID_FILE_CHARACTERS_REGEX, "");
try
{
if (MC.hasSinglePlayerServer())
{
// local world
IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(newDimensionType);
return new File(serverWorld.getSaveFolder().getCanonicalFile().getPath() + File.separatorChar + "lod" + File.separatorChar + worldId);
}
else
{
// multiplayer
return new File(MC.getGameDirectory().getCanonicalFile().getPath() +
File.separatorChar + "Distant_Horizons_server_data" + File.separatorChar + MC.getCurrentDimensionId() + File.separatorChar + worldId);
}
}
catch (IOException e)
{
LOGGER.error("Unable to get dimension folder for dimension [" + newDimensionType.getDimensionName() + "]", e);
return null;
}
}
/** Returns true if the given chunk is valid to test */
public boolean CanDetermineDimensionFolder(IChunkWrapper chunk)
{
// we can only guess if the given chunk can be converted into a LOD
return LodBuilder.canGenerateLodFromChunk(chunk);
}
/** Used for debugging, returns true if every data point is 0 */
private static boolean isDataEmpty(long[][][] chunkData)
{
for (long[][] xArray : chunkData)
{
for (long[] zArray : xArray)
{
for (long dataPoint : zArray)
{
if (dataPoint != 0)
{
return false;
}
}
}
}
return true;
}
/** Returns true if the given folder holds valid Lod Dimension data */
public static boolean isValidSubDimensionDirectory(File potentialFolder)
{
if (!potentialFolder.isDirectory())
// it needs to be a folder
return false;
if (potentialFolder.listFiles() == null)
// it needs to have folders in it
return false;
// check if there is at least one VerticalQuality folder in this directory
for (File internalFolder : potentialFolder.listFiles())
{
if (VerticalQuality.getByName(internalFolder.getName()) != null)
{
// one of the internal folders is a VerticalQuality folder
return true;
}
}
return false;
}
/**
* Moves any folders from the old save location
* (directly under the dimension type)
* to a sub-dimension folder with the given name.
*/
private void moveOldSaveFoldersIfNecessary(File dimensionFolder, IDimensionTypeWrapper dimensionType, String subDimensionName) throws IOException
{
// if there are no files this will return null
if (dimensionFolder.listFiles() == null)
return;
for (File folder : dimensionFolder.listFiles())
{
if (VerticalQuality.getByName(folder.getName()) != null)
{
// this is a LOD save folder
// create a new sub dimension and move the data into it
File newDimension = GetDimensionFolder(dimensionType, subDimensionName);
newDimension.mkdirs();
File oldDataNewPath = new File(newDimension.getPath() + File.separatorChar + folder.getName());
Files.move(folder.toPath(), oldDataNewPath.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
else
{
// ignore this folder
}
}
}
public void updatePlayerData()
{
playerData.updateData(MC);
}
/** saves any necessary player data to the given world folder */
public void saveDimensionPlayerData(File worldFolder)
{
// get and create the file and path if they don't exist
File file = PlayerData.getFileForDimensionFolder(worldFolder);
if (!file.exists())
{
try
{
file.getParentFile().mkdirs();
file.createNewFile();
}
catch (IOException e)
{
LOGGER.error("Unable to save player dimension data for world folder [" + worldFolder.getPath() + "].", e);
return;
}
}
// determine the playerData
IMinecraftClientWrapper mc = SingletonHandler.get(IMinecraftClientWrapper.class);
PlayerData playerdata = new PlayerData(mc);
// write the data to file
CommentedFileConfig toml = CommentedFileConfig.builder(file).build();
playerdata.toTomlFile(toml);
}
}
@@ -1,277 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.handlers;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.locks.ReentrantLock;
import com.seibel.lod.core.api.ApiShared;
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.lod.VerticalLevelContainer;
import com.seibel.lod.core.util.LodUtil;
public class LodDimensionOldFileStructureHandler
{
/** This is the dimension that owns this file handler */
private final File dimensionDataSaveFolder;
private final LodDimensionFileHandler newFileHandler;
enum OldDistanceGenerationMode {
NONE,
BIOME_ONLY,
BIOME_ONLY_SIMULATE_HEIGHT,
SURFACE,
FEATURES,
FULL
}
/** lod */
private static final String FILE_NAME_PREFIX = "lod";
/** .txt */
private static final String FILE_EXTENSION = ".xz";
/** detail- */
private static final String DETAIL_FOLDER_NAME_PREFIX = "detail-";
private static final String RETIRED_OLD_STRUCT_POSTFIX = "-RETIRED-CAN-BE-DELETED";
public static final int LOD_SAVE_FILE_VERSION = 8;
public static final ExecutorService mergerThreads = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private static class TempLodRegion {
final VerticalLevelContainer[] containers;
final VerticalQuality vertQual;
final int posX;
final int posZ;
TempLodRegion(VerticalQuality vertQual, RegionPos pos) {
this.vertQual = vertQual;
posX = pos.x;
posZ = pos.z;
containers = new VerticalLevelContainer[LodUtil.REGION_DETAIL_LEVEL+1];
}
}
public LodDimensionOldFileStructureHandler(LodDimensionFileHandler fileHandler)
{
dimensionDataSaveFolder = fileHandler.dimensionDataSaveFolder;
newFileHandler = fileHandler;
}
private void loadGenModeToRegion(TempLodRegion region, OldDistanceGenerationMode genMode)
{
int regionX = region.posX;
int regionZ = region.posZ;
for (byte detail = LodUtil.REGION_DETAIL_LEVEL; detail >= 0; detail--) {
File file = new File(getFileBasePath() + region.vertQual + File.separatorChar +
genMode + File.separatorChar + DETAIL_FOLDER_NAME_PREFIX + detail + File.separatorChar +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION);
if (!file.exists()) continue;
if (!file.isFile()) continue;
long fileSize = file.length();
if (fileSize == 0) continue;
try (FileInputStream fileInStream = new FileInputStream(file))
{
XZCompressorInputStream inputStream = new XZCompressorInputStream(fileInStream);
int fileVersion;
fileVersion = inputStream.read();
// check if this file can be read by this file handler
if (fileVersion < 6)
{
// the file we are reading is too old.
// close the reader and delete the file.
inputStream.close();
ApiShared.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". this region file will not be read and merged into the new save structure.");
continue;
}
else if (fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// close the reader and ignore the file, we don't
// want to accidentally delete anything the user may want.
inputStream.close();
ApiShared.LOGGER.info("Unexpected newer LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ " this region file will not be read and merged into the new save structure.");
continue;
}
else if (fileVersion < LOD_SAVE_FILE_VERSION)
{
ApiShared.LOGGER.debug("Old LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". this region file be read, updated, and merged into the new save structure.");
}
VerticalLevelContainer data = new VerticalLevelContainer(new DataInputStream(inputStream), fileVersion, detail);
if (region.containers[detail] == null) {
region.containers[detail] = data;
} else {
region.containers[detail].addChunkOfData(data.dataContainer, 0, 0, data.size, data.size, false);
}
inputStream.close();
}
catch (IOException ioEx)
{
ApiShared.LOGGER.error("LOD file read error. Unable to read xz compressed file [" + file + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
}
}
private void saveRegion(TempLodRegion region) {
for (int detail=0; detail<=LodUtil.REGION_DETAIL_LEVEL; detail++) {
if (region.containers[detail] == null) continue;
newFileHandler.saveDirect(region.posX, region.posZ, region.vertQual, region.containers[detail]);
}
}
private void loadAndMergeAndSaveRegion(VerticalQuality verticalQuality, RegionPos regionPos)
{
ApiShared.LOGGER.info("Merging region "+regionPos+" at "+verticalQuality+"...");
TempLodRegion region = new TempLodRegion(verticalQuality, regionPos);
ApiShared.LOGGER.info("Reading data...");
loadGenModeToRegion(region, OldDistanceGenerationMode.FULL);
loadGenModeToRegion(region, OldDistanceGenerationMode.FEATURES);
loadGenModeToRegion(region, OldDistanceGenerationMode.SURFACE);
loadGenModeToRegion(region, OldDistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT);
loadGenModeToRegion(region, OldDistanceGenerationMode.BIOME_ONLY);
loadGenModeToRegion(region, OldDistanceGenerationMode.NONE);
ApiShared.LOGGER.info("Writing data...");
saveRegion(region);
ApiShared.LOGGER.info("region "+regionPos+" at "+verticalQuality+" merged");
}
private RegionPos parseFileName(String fileName) {
if (!fileName.endsWith(FILE_EXTENSION)) return null;
if (!fileName.startsWith(FILE_NAME_PREFIX)) return null;
String[] array = fileName.split("\\."); // Array content: "lod", "-1", "1", ".xz"
if (array.length!=4) return null;
try {
return new RegionPos(Integer.parseInt(array[1]), Integer.parseInt(array[2]));
} catch (NumberFormatException e) {
return null;
}
}
private HashSet<RegionPos> scanOldRegionFiles(VerticalQuality vertQual, OldDistanceGenerationMode genMode) {
HashSet<RegionPos> result = new HashSet<RegionPos>();
File baseBaseFolder = new File(getFileBasePath() + vertQual + File.separatorChar + genMode);
if (!baseBaseFolder.exists()) return result;
for (byte detail=0; detail <= LodUtil.REGION_DETAIL_LEVEL; detail++) {
File baseFolder = new File(getFileBasePath() + vertQual + File.separatorChar +
genMode + File.separatorChar + DETAIL_FOLDER_NAME_PREFIX + detail);
if (!baseFolder.exists()) continue;
if (!baseFolder.isDirectory()) continue;
File[] subFiles = baseFolder.listFiles();
for (File subFile : subFiles) {
if (!subFile.isFile()) continue;
if (!subFile.canRead()) continue;
RegionPos pos = parseFileName(subFile.getName());
if (pos != null) result.add(pos);
}
}
return result;
}
private void renameOldFileStructure(VerticalQuality vertQual, OldDistanceGenerationMode genMode) {
File baseBaseFolder = new File(getFileBasePath() + vertQual + File.separatorChar + genMode);
if (!baseBaseFolder.exists()) return;
baseBaseFolder.renameTo(new File(getFileBasePath() + vertQual + File.separatorChar + genMode + RETIRED_OLD_STRUCT_POSTFIX));
}
public void mergeOldFileStructureForVertQuality(VerticalQuality vertQual) {
File baseFile = new File(getFileBasePath() + vertQual);
if (!baseFile.exists()) return;
if (!baseFile.isDirectory()) return;
HashSet<RegionPos> totalPos = new HashSet<RegionPos>();
totalPos.addAll(scanOldRegionFiles(vertQual, OldDistanceGenerationMode.NONE));
totalPos.addAll(scanOldRegionFiles(vertQual, OldDistanceGenerationMode.BIOME_ONLY));
totalPos.addAll(scanOldRegionFiles(vertQual, OldDistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT));
totalPos.addAll(scanOldRegionFiles(vertQual, OldDistanceGenerationMode.SURFACE));
totalPos.addAll(scanOldRegionFiles(vertQual, OldDistanceGenerationMode.FEATURES));
totalPos.addAll(scanOldRegionFiles(vertQual, OldDistanceGenerationMode.FULL));
ArrayList<Future<?>> futures = new ArrayList<Future<?>>();
for (RegionPos pos : totalPos) {
futures.add(mergerThreads.submit(() -> {
loadAndMergeAndSaveRegion(vertQual, pos);
return true;
}));
}
futures.forEach(t ->
{
try
{
t.get();
}
catch (Exception e)
{
e.printStackTrace();
}
});
renameOldFileStructure(vertQual, OldDistanceGenerationMode.NONE);
renameOldFileStructure(vertQual, OldDistanceGenerationMode.BIOME_ONLY);
renameOldFileStructure(vertQual, OldDistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT);
renameOldFileStructure(vertQual, OldDistanceGenerationMode.SURFACE);
renameOldFileStructure(vertQual, OldDistanceGenerationMode.FEATURES);
renameOldFileStructure(vertQual, OldDistanceGenerationMode.FULL);
}
private String getFileBasePath()
{
try
{
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar;
}
catch (IOException e)
{
ApiShared.LOGGER.warn("Unable to get the base save file path. Error: " + e.getMessage(), e);
throw new RuntimeException("DistantHorizons Get Save File Path Failure");
}
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -46,7 +46,6 @@ public class ReflectionHandler implements IReflectionHandler
private final Object mcOptionsObject;
private Boolean sodiumPresent = null;
private boolean optifinePresent = false;
@@ -88,7 +87,6 @@ public class ReflectionHandler implements IReflectionHandler
{
if (field.getName().equals("ofFogType"))
{
optifinePresent = true;
ofFogField = field;
return;
}
@@ -160,11 +158,7 @@ public class ReflectionHandler implements IReflectionHandler
}
return false;
}
@Override
public boolean optifinePresent()
{
return optifinePresent;
}
@@ -1,164 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.handlers.dependencyInjection;
import java.util.HashMap;
import java.util.Map;
/**
* This class takes care of tracking objects used in dependency injection.
*
* @author James Seibel
* @version 3-4-2022
*/
public class DependencyHandler
{
private final Map<Class<?>, Object> dependencies = new HashMap<Class<?>, Object>();
private boolean bindingFinished = false;
/**
* Links the given implementation object to an interface so it can be referenced later.
*
* @param depenencyInterface The interface the implementation object should implement.
* @param dependencyImplementation A object that implements the depenencyInterface interface.
* @throws IllegalStateException if the implementation object doesn't implement
* the interface or the interface has already been bound.
*/
public void bind(Class<?> depenencyInterface, Object dependencyImplementation) throws IllegalStateException
{
// only allow binding before the finishBinding method is called
if (bindingFinished)
{
throw new IllegalStateException("The dependency [" + depenencyInterface.getSimpleName() + "] cannot be bound, Binding is finished for [" + this.getClass().getSimpleName() + "]. Make sure your bindings are happening before the [bindingFinished] method is being called.");
}
// make sure we haven't already bound this dependency
if (dependencies.containsKey(depenencyInterface))
{
throw new IllegalStateException("The dependency [" + depenencyInterface.getSimpleName() + "] has already been bound.");
}
// make sure the given dependency implements the necessary interfaces
boolean implementsInterface = checkIfClassImplements(dependencyImplementation.getClass(), depenencyInterface);
boolean implementsBindable = checkIfClassImplements(dependencyImplementation.getClass(), IBindable.class);
// display any errors
if (!implementsInterface)
{
throw new IllegalStateException("The dependency [" + dependencyImplementation.getClass().getSimpleName() + "] doesn't implement the interface [" + depenencyInterface.getSimpleName() + "].");
}
if (!implementsBindable)
{
throw new IllegalStateException("The dependency [" + dependencyImplementation.getClass().getSimpleName() + "] doesn't implement the interface [" + IBindable.class.getSimpleName() + "].");
}
dependencies.put(depenencyInterface, dependencyImplementation);
}
/**
* Checks if classToTest (or one of its ancestors)
* implements the given interface.
*/
private boolean checkIfClassImplements(Class<?> classToTest, Class<?> interfaceToLookFor)
{
// check the parent class (if applicable)
if (classToTest.getSuperclass() != Object.class && classToTest.getSuperclass() != null)
{
if (checkIfClassImplements(classToTest.getSuperclass(), interfaceToLookFor))
{
return true;
}
}
// check interfaces
for (Class<?> implementationInterface : classToTest.getInterfaces())
{
// recurse to check interface parents if necessary
if (implementationInterface.getInterfaces().length != 0)
{
if (checkIfClassImplements(implementationInterface, interfaceToLookFor))
{
return true;
}
}
if (implementationInterface.equals(interfaceToLookFor))
{
return true;
}
}
return false;
}
/**
* Returns a dependency of type T if one has been bound.
* Returns null otherwise.
*
* @param <T> class of the dependency
* (inferred from the objectClass parameter)
* @param interfaceClass Interface of the dependency
* @return the dependency of type T
* @throws ClassCastException If the dependency isn't able to be cast to type T.
* (this shouldn't normally happen, unless the bound object changed somehow)
*/
@SuppressWarnings("unchecked")
public <T extends IBindable> T get(Class<?> interfaceClass) throws ClassCastException
{
// getting dependencies should only happen after everything has been bound
if (!bindingFinished)
{
throw new IllegalStateException("Binding hasn't been finished for [" + this.getClass().getSimpleName() + "]. Make sure you are calling the [bindingFinished] method before calling [get].");
}
return (T) dependencies.get(interfaceClass);
}
/**
* Should only be called after all Binds have been done.
* Calls the delayedSetup method for each dependency. <br> <br>
*
* This is done so we can have circular dependencies.
*/
public void finishBinding()
{
// (yes technically the binding isn't finished,
// but this needs to be set to "true" so we can use "get")
bindingFinished = true;
for (Class<?> interfaceKey : dependencies.keySet())
{
IBindable concreteObject = get(interfaceKey);
concreteObject.finishDelayedSetup();
}
}
/** returns whether the finishBinding method has been called */
public boolean getBindingFinished()
{
return bindingFinished;
}
}
@@ -1,44 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.handlers.dependencyInjection;
/**
* Necessary for all singletons that can be dependency injected.
*
* @author James Seibel
* @version 3-4-2022
*/
public interface IBindable
{
/**
* Finish initializing this object. <br> <br>
*
* Generally this should just used for getting other objects through
* dependency injection and is specifically designed to allow
* for circular references. <br><br>
*
* If no circular dependencies are required this method
* doesn't have to be implemented.
*/
public default void finishDelayedSetup()
{
}
}
@@ -1,91 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.handlers.dependencyInjection;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.wrapperInterfaces.modAccessor.IModAccessor;
/**
* This class takes care of dependency injection for mods accessors. (for mod compatibility
* support).
*
* This is basically the same as the SingletonHandler, except it can return null.
* Getting null either means the mod isn't loaded in the game
* or it hasn't been implemented for the given Minecraft version.
*
* @author James Seibel
* @author Leetom
* @version 3-1-2022
*/
public class ModAccessorHandler
{
private static final DependencyHandler dependencyHandler = new DependencyHandler();
/**
* Links the given mod accessor to an interface so it can be referenced later.
*
* @param interfaceClass The interface the mod accessor should implement.
* @param modAccessor An object that implements the interfaceClass interface.
* @throws IllegalStateException if the mod accessor doesn't implement
* the interface or the interface has already been bound.
*/
public static void bind(Class<? extends IModAccessor> interfaceClass, IModAccessor modAccessor)
throws IllegalStateException
{
dependencyHandler.bind(interfaceClass, modAccessor);
ApiShared.LOGGER.info("Registored mod comatibility accessor for " + modAccessor.getModName());
}
/**
* Returns a mod accessor of type T if one has been bound.
* Returns null otherwise.
*
* @param <T> class of the mod accessor
* (inferred from the objectClass parameter)
* @param interfaceClass Interface of the mod accessor
* @return the dependency of type T
* @throws ClassCastException If the mod accessor isn't able to be cast to type T.
* (this shouldn't normally happen, unless the bound object changed somehow)
*/
public static <T extends IModAccessor> T get(Class<T> objectClass) throws ClassCastException
{
return dependencyHandler.get(objectClass);
}
/**
* Should only be called after all Binds have been done.
* Calls the delayedSetup method for each dependency. <br> <br>
*
* This is done so we can have circular dependencies.
*/
public static void finishBinding()
{
dependencyHandler.finishBinding();
}
/** returns whether the finishBinding method has been called */
public static boolean bindingFinished()
{
return dependencyHandler.getBindingFinished();
}
}
@@ -1,90 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.handlers.dependencyInjection;
/**
* This class takes care of dependency injection
* for singletons.
*
* @author James Seibel
* @version 3-5-2022
*/
public class SingletonHandler
{
private static final DependencyHandler dependencyHandler = new DependencyHandler();
/**
* Links the given implementation object to an interface so it can be referenced later.
*
* @param depenencyInterface The interface the implementation object should implement.
* @param dependencyImplementation A object that implements the depenencyInterface interface.
* @throws IllegalStateException if the implementation object doesn't implement
* the interface or the interface has already been bound.
*/
public static void bind(Class<?> interfaceClass, Object singletonReference) throws IllegalStateException
{
dependencyHandler.bind(interfaceClass, singletonReference);
}
/**
* Returns a dependency of type T if one has been bound.
* Returns null otherwise.
*
* @param <T> class of the dependency
* (inferred from the objectClass parameter)
* @param interfaceClass Interface of the dependency
* @return the dependency of type T
* @throws NullPointerException If no dependency was bound.
* @throws ClassCastException If the dependency isn't able to be cast to type T.
* (this shouldn't normally happen, unless the bound object changed somehow)
*/
public static <T> T get(Class<T> interfaceClass) throws NullPointerException, ClassCastException
{
T foundObject = dependencyHandler.get(interfaceClass);
// throw an error if the given singleton doesn't exist.
if (foundObject == null)
{
throw new NullPointerException("The singleton [" + interfaceClass.getSimpleName() + "] was never bound. If you are calling [bind], make sure it is happening before you call [get].");
}
return foundObject;
}
/**
* Should only be called after all Binds have been done.
* Calls the delayedSetup method for each dependency. <br> <br>
*
* This is done so we can have circular dependencies.
*/
public static void finishBinding()
{
dependencyHandler.finishBinding();
}
/** returns whether the finishBinding method has been called */
public static boolean getBindingFinished()
{
return dependencyHandler.getBindingFinished();
}
}
@@ -1,131 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.handlers.dimensionFinder;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import java.io.File;
/**
* Data container for any player data we can use to differentiate one dimension from another.
*
* @author James Seibel
* @version 2022-3-26
*/
public class PlayerData
{
public static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final String playerDataFileName = "_playerData.toml";
public static final String PLAYER_BLOCK_POS_X_PATH = "playerBlockPosX";
public static final String PLAYER_BLOCK_POS_Y_PATH = "playerBlockPosY";
public static final String PLAYER_BLOCK_POS_Z_PATH = "playerBlockPosZ";
public AbstractBlockPosWrapper playerBlockPos;
// not implemented yet
public static final String WORLD_SPAWN_POS_X_PATH = "worldSpawnBlockPosX";
public static final String WORLD_SPAWN_POS_Y_PATH = "worldSpawnBlockPosY";
public static final String WORLD_SPAWN_POS_Z_PATH = "worldSpawnBlockPosZ";
/**
* The client world has access to a spawn point, so this should be possible to fill in.
* I'm not sure what this will look like for worlds that don't have a spawn point.
*/
public AbstractBlockPosWrapper worldSpawnPointBlockPos;
public PlayerData(IMinecraftClientWrapper mc)
{
updateData(mc);
}
public PlayerData(File dimensionFolder)
{
File file = getFileForDimensionFolder(dimensionFolder);
CommentedFileConfig toml = CommentedFileConfig.builder(file).build();
toml.load();
// get the player block pos if it is specified
if (toml.contains(PLAYER_BLOCK_POS_X_PATH)
&& toml.contains(PLAYER_BLOCK_POS_Y_PATH)
&& toml.contains(PLAYER_BLOCK_POS_Z_PATH))
{
int x = toml.getIntOrElse(PLAYER_BLOCK_POS_X_PATH, 0);
int y = toml.getIntOrElse(PLAYER_BLOCK_POS_Y_PATH, 0);
int z = toml.getIntOrElse(PLAYER_BLOCK_POS_Z_PATH, 0);
this.playerBlockPos = FACTORY.createBlockPos(x, y, z);
}
else
{
this.playerBlockPos = FACTORY.createBlockPos(0, 0, 0);
}
}
public static File getFileForDimensionFolder(File file)
{
return new File(file.getPath() + File.separatorChar + playerDataFileName);
}
/** Should be called often to make sure this object is up to date with the player's info */
public void updateData(IMinecraftClientWrapper mc)
{
if (mc.playerExists())
{
this.playerBlockPos = mc.getPlayerBlockPos();
}
}
/** Writes everything from this object to the file given. */
public void toTomlFile(CommentedFileConfig toml)
{
if (playerBlockPos == null)
{
toml.remove(PLAYER_BLOCK_POS_X_PATH);
toml.remove(PLAYER_BLOCK_POS_Y_PATH);
toml.remove(PLAYER_BLOCK_POS_Z_PATH);
} else {
// player block pos
toml.add(PLAYER_BLOCK_POS_X_PATH, playerBlockPos.getX());
toml.add(PLAYER_BLOCK_POS_Y_PATH, playerBlockPos.getY());
toml.add(PLAYER_BLOCK_POS_Z_PATH, playerBlockPos.getZ());
}
toml.save();
}
@Override
public String toString()
{
return "PlayerBlockPos: [" + playerBlockPos.getX() + "," + playerBlockPos.getY() + "," + playerBlockPos.getZ() + "]";
}
}
@@ -1,85 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.handlers.dimensionFinder;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import org.jetbrains.annotations.NotNull;
import java.io.File;
/**
* Contains data used to compare different sub LodDimensions.
* Sub Dimensions are the different folders under a dimension.
* For example: "\Distant_Horizons_server_data\server_1\dim_the_nether\6fb97c01-e4c7-4634-87db-36b1e490e4c3"
* is a sub dimension for the server "server_1" in the nether.
*
* @author James Seibel
* @version 2022-3-26
*/
public class SubDimCompare implements Comparable<SubDimCompare>
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public int equalDataPoints = 0;
public int totalDataPoints = 0;
public int playerPosDist = 0;
public File folder = null;
public SubDimCompare(int newEqualDataPoints, int newTotalDataPoints, int newPlayerPosDistance, File newSubDimFolder)
{
this.equalDataPoints = newEqualDataPoints;
this.totalDataPoints = newTotalDataPoints;
this.playerPosDist = newPlayerPosDistance;
this.folder = newSubDimFolder;
}
/** returns a number between 0 (not equal) and 1 (totally equal) */
public double getPercentEqual()
{
return (double) equalDataPoints / (double) totalDataPoints;
}
@Override
public int compareTo(@NotNull SubDimCompare other)
{
if (this.equalDataPoints != other.equalDataPoints)
{
// compare based on data points
return Integer.compare(this.equalDataPoints, other.equalDataPoints);
}
else
{
// break ties based on player position
return Integer.compare(this.playerPosDist, other.playerPosDist);
}
}
/** Returns true if this sub dimension is close enough to be considered a valid sub dimension */
public boolean isValidSubDim()
{
double minimumSimilarityRequired = CONFIG.client().multiplayer().getMultiDimensionRequiredSimilarity();
return this.getPercentEqual() >= minimumSimilarityRequired || this.playerPosDist <= 3;
}
}
@@ -1,92 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.logging;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.config.LoggerMode;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.Message;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Supplier;
public class ConfigBasedLogger {
public static final List<WeakReference<ConfigBasedLogger>> loggers
= Collections.synchronizedList(new LinkedList<WeakReference<ConfigBasedLogger>>());
public static synchronized void updateAll() {
loggers.removeIf((logger) -> logger.get()==null);
loggers.forEach((logger) -> {
ConfigBasedLogger l = logger.get();
if (l!=null) l.update();
});
}
private LoggerMode mode;
private final Supplier<LoggerMode> getter;
private final Logger logger;
public ConfigBasedLogger(Logger logger, Supplier<LoggerMode> configQuery) {
getter = configQuery;
mode = getter.get();
this.logger = logger;
loggers.add(new WeakReference<>(this));
}
public void update() {
mode = getter.get();
}
public boolean canMaybeLog() {return mode != LoggerMode.DISABLED;}
public void log(Level level, String str, Object... param) {
Message msg = logger.getMessageFactory().newMessage(str, param);
String msgStr = msg.getFormattedMessage();
if (mode.levelForFile.isLessSpecificThan(level)) {
Level logLevel = level.isLessSpecificThan(Level.INFO) ? Level.INFO : level;
if (param.length > 0 && param[param.length-1] instanceof Throwable)
logger.log(logLevel, msgStr, (Throwable)param[param.length-1]);
else logger.log(logLevel, msgStr);
}
if (mode.levelForChat.isLessSpecificThan(level)) {
if (param.length > 0 && param[param.length-1] instanceof Throwable)
ClientApi.logToChat(level, msgStr + "\nat\n" + Arrays.toString(((Throwable) param[param.length - 1]).getStackTrace()));
else ClientApi.logToChat(level, msgStr);
}
}
public void error(String str, Object... param) {
log(Level.ERROR, str, param);
}
public void warn(String str, Object... param) {
log(Level.WARN, str, param);
}
public void info(String str, Object... param) {
log(Level.INFO, str, param);
}
public void debug(String str, Object... param) {
log(Level.DEBUG, str, param);
}
public void trace(String str, Object... param) {
log(Level.TRACE, str, param);
}
}
@@ -1,139 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.logging;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.config.LoggerMode;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.Message;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public class ConfigBasedSpamLogger {
public static final List<WeakReference<ConfigBasedSpamLogger>> loggers
= Collections.synchronizedList(new LinkedList<WeakReference<ConfigBasedSpamLogger>>());
public static synchronized void updateAll(boolean flush) {
loggers.removeIf((logger) -> logger.get()==null);
loggers.forEach((logger) -> {
ConfigBasedSpamLogger l = logger.get();
if (l!=null) l.update();
if (l!=null && flush) l.reset();
});
}
private LoggerMode mode;
private final Supplier<LoggerMode> getter;
private final int maxLogCount;
private final AtomicInteger logTries = new AtomicInteger(0);
private final Logger logger;
public ConfigBasedSpamLogger(Logger logger, Supplier<LoggerMode> configQuery, int maxLogPerSec) {
getter = configQuery;
mode = getter.get();
maxLogCount = maxLogPerSec;
this.logger = logger;
loggers.add(new WeakReference<>(this));
}
public void reset() {logTries.set(0);}
public boolean canMaybeLog() {return mode != LoggerMode.DISABLED && logTries.get() < maxLogCount;}
public void update() {
mode = getter.get();
}
public void log(Level level, String str, Object... param) {
if (logTries.get() >= maxLogCount) return;
Message msg = logger.getMessageFactory().newMessage(str, param);
String msgStr = msg.getFormattedMessage();
if (mode.levelForFile.isLessSpecificThan(level)) {
Level logLevel = level.isLessSpecificThan(Level.INFO) ? Level.INFO : level;
if (param.length > 0 && param[param.length-1] instanceof Throwable)
logger.log(logLevel, msgStr, (Throwable)param[param.length-1]);
else logger.log(logLevel, msgStr);
}
if (mode.levelForChat.isLessSpecificThan(level)) {
if (param.length > 0 && param[param.length - 1] instanceof Throwable)
ClientApi.logToChat(level, msgStr + "\nat\n" + Arrays.toString(((Throwable) param[param.length - 1]).getStackTrace()));
else ClientApi.logToChat(level, msgStr);
}
}
public void error(String str, Object... param) {
log(Level.ERROR, str, param);
}
public void warn(String str, Object... param) {
log(Level.WARN, str, param);
}
public void info(String str, Object... param) {
log(Level.INFO, str, param);
}
public void debug(String str, Object... param) {
log(Level.DEBUG, str, param);
}
public void trace(String str, Object... param) {
log(Level.TRACE, str, param);
}
public void incLogTries() {
logTries.getAndIncrement();
}
public void logInc(Level level, String str, Object... param) {
if (logTries.getAndIncrement() >= maxLogCount) return;
Message msg = logger.getMessageFactory().newMessage(str, param);
String msgStr = msg.getFormattedMessage();
if (mode.levelForFile.isLessSpecificThan(level)) {
Level logLevel = level.isLessSpecificThan(Level.INFO) ? Level.INFO : level;
if (param.length > 0 && param[param.length-1] instanceof Throwable)
logger.log(logLevel, msgStr, (Throwable)param[param.length-1]);
else logger.log(logLevel, msgStr);
}
if (mode.levelForChat.isLessSpecificThan(level)) {
if (param.length > 0 && param[param.length - 1] instanceof Throwable)
ClientApi.logToChat(level, msgStr + "\nat\n" + Arrays.toString(((Throwable) param[param.length - 1]).getStackTrace()));
else ClientApi.logToChat(level, msgStr);
}
}
public void errorInc(String str, Object... param) {
logInc(Level.ERROR, str, param);
}
public void warnInc(String str, Object... param) {
logInc(Level.WARN, str, param);
}
public void infoInc(String str, Object... param) {
logInc(Level.INFO, str, param);
}
public void debugInc(String str, Object... param) {
logInc(Level.DEBUG, str, param);
}
public void traceInc(String str, Object... param) {
logInc(Level.TRACE, str, param);
}
}
@@ -1,97 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.logging;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import com.seibel.lod.core.api.ApiShared;
import org.apache.logging.log4j.Level;
public class SpamReducedLogger {
public static final List<WeakReference<SpamReducedLogger>> loggers
= Collections.synchronizedList(new LinkedList<WeakReference<SpamReducedLogger>>());
public static synchronized void flushAll() {
loggers.removeIf((logger) -> logger.get()==null);
loggers.forEach((logger) -> {
SpamReducedLogger l = logger.get();
if (l!=null) l.reset();
});
}
private final int maxLogCount;
private final AtomicInteger logTries = new AtomicInteger(0);
public SpamReducedLogger(int maxLogPerSec) {
maxLogCount = maxLogPerSec;
loggers.add(new WeakReference<SpamReducedLogger>(this));
}
public void reset() {logTries.set(0);}
public boolean canMaybeLog() {return logTries.get() < maxLogCount;}
public void log(Level level, String str, Object... param) {
if (logTries.get() >= maxLogCount) return;
ApiShared.LOGGER.log(level.isLessSpecificThan(Level.INFO) ? Level.INFO : level, str, param);
}
public void error(String str, Object... param) {
log(Level.ERROR, str, param);
}
public void warn(String str, Object... param) {
log(Level.WARN, str, param);
}
public void info(String str, Object... param) {
log(Level.INFO, str, param);
}
public void debug(String str, Object... param) {
log(Level.DEBUG, str, param);
}
public void trace(String str, Object... param) {
log(Level.TRACE, str, param);
}
public void incLogTries() {
logTries.getAndIncrement();
}
public void logInc(Level level, String str, Object... param) {
if (logTries.getAndIncrement() >= maxLogCount) return;
ApiShared.LOGGER.log(level.isLessSpecificThan(Level.INFO) ? Level.INFO : level, str, param);
}
public void errorInc(String str, Object... param) {
logInc(Level.ERROR, str, param);
}
public void warnInc(String str, Object... param) {
logInc(Level.WARN, str, param);
}
public void infoInc(String str, Object... param) {
logInc(Level.INFO, str, param);
}
public void debugInc(String str, Object... param) {
logInc(Level.DEBUG, str, param);
}
public void traceInc(String str, Object... param) {
logInc(Level.TRACE, str, param);
}
}
@@ -1,43 +1,24 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects;
/*
import com.seibel.lod.core.wrapperInterfaces.block.BlockDetail;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class BlockBiomeCouple
{
public static final ConcurrentMap<BlockDetail, BlockBiomeCouple> noBiomeIstanceCache = new ConcurrentHashMap<>();
public static ConcurrentMap<IBiomeWrapper, ConcurrentMap<BlockDetail, BlockBiomeCouple>> withBiomeIstanceCache = new ConcurrentHashMap<>();
public static ConcurrentMap<IBlockColorWrapper, BlockBiomeCouple> noBiomeIstanceCache = new ConcurrentHashMap<>();
public static ConcurrentMap<IBiomeWrapper, ConcurrentMap<IBlockColorWrapper, BlockBiomeCouple>> withBiomeIstanceCache = new ConcurrentHashMap<>();
String blockName;
String biomeName;
String coupleName;
IBiomeWrapper biomeColor;
BlockDetail blockColor;
IBlockColorWrapper blockColor;
public static void addBlockBiomeToCache(IBlockColorWrapper blockColor){
}
@@ -121,4 +102,3 @@ public class BlockBiomeCouple
}
*/
@@ -1,26 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects;
public final class BoolType {
public static final BoolType TRUE = new BoolType();
public static final BoolType FALSE = null;
private BoolType() {}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,109 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects;
/**
* Represents an IP and includes a couple helper methods.
*
* @author James Seibel
* @version 3-7-2022
*/
public class ParsedIp
{
/** can be used to find numeric IPs, IE: "192.168.1.19" */
public static final String NUMERIC_IP_REGEX = "^[0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*(:[0-9]*)?$";
/**
* Can be used to find if a numeric IP is a LAN IP
*
* Ip list source: <br>
* https://networkengineering.stackexchange.com/questions/5825/why-192-168-for-local-addresses
*/
public static final String LAN_IP_REGEX = "(10|172\\.16|192\\.168).*";
/** Examples: "192.168.1.19", "mc.hypixel.net", or "localhost" */
public final String ip;
/**
* null if the ip isn't numeric (IE: "mc.hypixel.net" or "localhost") <br>
* Example: "25586"
*/
public final String port;
public final boolean isNumeric;
/** parses a standard IP string */
public ParsedIp(String fullIp)
{
fullIp = fullIp.trim();
isNumeric = fullIp.matches(NUMERIC_IP_REGEX);
if (isNumeric)
{
// attempt to separate the IP and the Port
String[] list = fullIp.split(":");
if (list.length == 2)
{
// IP and Port successfully separated
ip = list[0];
port = list[1];
}
else
{
// this IP must not have a port
ip = fullIp;
port = null;
}
}
else
{
// text based IP, IE: "localhost"
ip = fullIp;
port = null;
}
}
public ParsedIp(String newIp, String newPort)
{
ip = newIp;
port = newPort;
isNumeric = ip.matches(NUMERIC_IP_REGEX);
}
/** Returns if this IP is for a Local Area Network connection */
public boolean isLan()
{
return ip.toLowerCase().equals("localhost") || ip.matches(LAN_IP_REGEX);
}
@Override
public String toString()
{
return ip +
// only print the ":port" if a port is present
(port != null ? (":" + port) : "");
}
}
@@ -1,29 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects;
public final class Pos2D {
public final int x;
public final int y;
public Pos2D(int x, int y) {
this.x = x;
this.y = y;
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -31,6 +31,7 @@ public class PosToGenerateContainer
{
private final int playerPosX;
private final int playerPosZ;
private final byte farMinDetail;
private int nearSize;
private int farSize;
@@ -38,10 +39,14 @@ public class PosToGenerateContainer
private final int[][] nearPosToGenerate;
private final int[][] farPosToGenerate;
public PosToGenerateContainer(int maxDataToGenerate, int playerPosX, int playerPosZ)
public PosToGenerateContainer(byte farMinDetail, int maxDataToGenerate, int playerPosX, int playerPosZ)
{
this.playerPosX = playerPosX;
this.playerPosZ = playerPosZ;
this.farMinDetail = farMinDetail;
nearSize = 0;
farSize = 0;
nearPosToGenerate = new int[maxDataToGenerate][4];
@@ -51,57 +56,20 @@ public class PosToGenerateContainer
// TODO what is going on in this method?
public void addNearPosToGenerate(byte detailLevel, int posX, int posZ, boolean sort)
public void addPosToGenerate(byte detailLevel, int posX, int posZ)
{
// FIXME: This is a cast from double to int!!! OVERFLOW MAY HAPPEN!
int distance = (int)LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ);
int distance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ);
int index;
//We are introducing a position in the near array
index = nearSize;
if (index == nearPosToGenerate.length) {
if (Integer.compare(distance, nearPosToGenerate[index - 1][3]) > 0) {
return;
}
index--;
} else nearSize++;
if (sort) {
while (index > 0 && Integer.compare(distance, nearPosToGenerate[index - 1][3]) <= 0)
{
nearPosToGenerate[index][0] = nearPosToGenerate[index - 1][0];
nearPosToGenerate[index][1] = nearPosToGenerate[index - 1][1];
nearPosToGenerate[index][2] = nearPosToGenerate[index - 1][2];
nearPosToGenerate[index][3] = nearPosToGenerate[index - 1][3];
index--;
}
}
nearPosToGenerate[index][0] = detailLevel + 1;
nearPosToGenerate[index][1] = posX;
nearPosToGenerate[index][2] = posZ;
nearPosToGenerate[index][3] = distance;
}
// TODO what is going on in this method?
public void addFarPosToGenerate(byte detailLevel, int posX, int posZ, boolean sort)
{
// FIXME: This is a cast from double to int!!! OVERFLOW MAY HAPPEN!
int distance = (int)LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ);
int index;
// We are introducing a position in the far array
index = farSize;
if (index == farPosToGenerate.length) {
if (Integer.compare(distance, farPosToGenerate[index - 1][3]) > 0) {
return;
}
index--;
} else farSize++;
if (sort) {
while (index > 0 && Integer.compare(distance, farPosToGenerate[index - 1][3]) <= 0)
if (detailLevel >= farMinDetail)
{
// We are introducing a position in the far array
if (farSize < farPosToGenerate.length)
farSize++;
index = farSize - 1;
while (index > 0 && LevelPosUtil.compareDistance(distance, farPosToGenerate[index - 1][3]) <= 0)
{
farPosToGenerate[index][0] = farPosToGenerate[index - 1][0];
farPosToGenerate[index][1] = farPosToGenerate[index - 1][1];
@@ -109,15 +77,42 @@ public class PosToGenerateContainer
farPosToGenerate[index][3] = farPosToGenerate[index - 1][3];
index--;
}
if (index != farSize - 1 || farSize != farPosToGenerate.length)
{
farPosToGenerate[index][0] = detailLevel + 1;
farPosToGenerate[index][1] = posX;
farPosToGenerate[index][2] = posZ;
farPosToGenerate[index][3] = distance;
}
}
else
{
//We are introducing a position in the near array
if (nearSize < nearPosToGenerate.length)
nearSize++;
index = nearSize - 1;
while (index > 0 && LevelPosUtil.compareDistance(distance, nearPosToGenerate[index - 1][3]) <= 0)
{
nearPosToGenerate[index][0] = nearPosToGenerate[index - 1][0];
nearPosToGenerate[index][1] = nearPosToGenerate[index - 1][1];
nearPosToGenerate[index][2] = nearPosToGenerate[index - 1][2];
nearPosToGenerate[index][3] = nearPosToGenerate[index - 1][3];
index--;
}
if (index != nearSize - 1 || nearSize != nearPosToGenerate.length)
{
nearPosToGenerate[index][0] = detailLevel + 1;
nearPosToGenerate[index][1] = posX;
nearPosToGenerate[index][2] = posZ;
nearPosToGenerate[index][3] = distance;
}
}
farPosToGenerate[index][0] = detailLevel + 1;
farPosToGenerate[index][1] = posX;
farPosToGenerate[index][2] = posZ;
farPosToGenerate[index][3] = distance;
}
public boolean isFull() {
return nearSize == nearPosToGenerate.length && farSize == farPosToGenerate.length;
}
@@ -137,16 +132,6 @@ public class PosToGenerateContainer
return farSize;
}
public int getMaxNumberOfNearPos()
{
return nearPosToGenerate.length;
}
public int getMaxNumberOfFarPos()
{
return farPosToGenerate.length;
}
// TODO what does getNth mean? could the name be more descriptive or is it just a index?
public int getNthDetail(int n, boolean near)
{
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,7 +21,7 @@ package com.seibel.lod.core.objects;
import java.util.Arrays;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
@@ -60,7 +60,7 @@ public class PosToRenderContainer
{
// This is might be due to dimensions having a different width
// when first loading in
ApiShared.LOGGER.error("Unable to addPosToRender. numberOfPosToRender [" + numberOfPosToRender + "] detailLevel [" + detailLevel + "] Pos [" + posX + "," + posZ + "]");
ClientApi.LOGGER.error("Unable to addPosToRender. numberOfPosToRender [" + numberOfPosToRender + "] detailLevel [" + detailLevel + "] Pos [" + posX + "," + posZ + "]");
numberOfPosToRender++; // incrementing so we can see how many pos over the limit we would go
return;
}
@@ -0,0 +1,590 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.objects.math.Vec3i;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
/**
* This class handles all the vertex optimization that's needed for a column of lods. W
* @author Leonardo Amato
* @version 10-2-2021
*/
public class VertexOptimizer
{
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public static final int ADJACENT_HEIGHT_INDEX = 0;
public static final int ADJACENT_DEPTH_INDEX = 1;
public static final int X = 0;
public static final int Y = 1;
public static final int Z = 2;
public static final int MIN = 0;
public static final int MAX = 1;
public static final int VOID_FACE = 0;
/** The six cardinal directions */
public static final LodDirection[] DIRECTIONS = new LodDirection[] {
LodDirection.UP,
LodDirection.DOWN,
LodDirection.WEST,
LodDirection.EAST,
LodDirection.NORTH,
LodDirection.SOUTH };
/** North, South, East, West */
public static final LodDirection[] ADJ_DIRECTIONS = new LodDirection[] {
LodDirection.EAST,
LodDirection.WEST,
LodDirection.SOUTH,
LodDirection.NORTH };
/** All the faces and vertices of a cube. This is used to extract the vertex from the column */
@SuppressWarnings("serial")
public static final Map<LodDirection, int[][]> DIRECTION_VERTEX_MAP = new HashMap<LodDirection, int[][]>()
{{
put(LodDirection.UP, new int[][] {
{ 0, 1, 0 }, // 0
{ 0, 1, 1 }, // 1
{ 1, 1, 1 }, // 2
{ 0, 1, 0 }, // 0
{ 1, 1, 1 }, // 2
{ 1, 1, 0 } // 3
});
put(LodDirection.DOWN, new int[][] {
{ 1, 0, 0 }, // 0
{ 1, 0, 1 }, // 1
{ 0, 0, 1 }, // 2
{ 1, 0, 0 }, // 0
{ 0, 0, 1 }, // 2
{ 0, 0, 0 } // 3
});
put(LodDirection.EAST, new int[][] {
{ 1, 1, 0 }, // 0
{ 1, 1, 1 }, // 1
{ 1, 0, 1 }, // 2
{ 1, 1, 0 }, // 0
{ 1, 0, 1 }, // 2
{ 1, 0, 0 } }); // 3
put(LodDirection.WEST, new int[][] {
{ 0, 0, 0 }, // 0
{ 0, 0, 1 }, // 1
{ 0, 1, 1 }, // 2
{ 0, 0, 0 }, // 0
{ 0, 1, 1 }, // 2
{ 0, 1, 0 } // 3
});
put(LodDirection.SOUTH, new int[][] {
{ 1, 0, 1 }, // 0
{ 1, 1, 1 }, // 1
{ 0, 1, 1 }, // 2
{ 1, 0, 1 }, // 0
{ 0, 1, 1 }, // 2
{ 0, 0, 1 } // 3
});
put(LodDirection.NORTH, new int[][] {
{ 0, 0, 0 }, // 0
{ 0, 1, 0 }, // 1
{ 1, 1, 0 }, // 2
{ 0, 0, 0 }, // 0
{ 1, 1, 0 }, // 2
{ 1, 0, 0 } // 3
});
}};
/**
* This indicates which position is invariable in the DIRECTION_VERTEX_MAP.
* Is used to extract the vertex
*/
@SuppressWarnings("serial")
public static final Map<LodDirection, int[]> FACE_DIRECTION = new HashMap<LodDirection, int[]>()
{{
put(LodDirection.UP, new int[] { Y, MAX });
put(LodDirection.DOWN, new int[] { Y, MIN });
put(LodDirection.EAST, new int[] { X, MAX });
put(LodDirection.WEST, new int[] { X, MIN });
put(LodDirection.SOUTH, new int[] { Z, MAX });
put(LodDirection.NORTH, new int[] { Z, MIN });
}};
/**
* This is a map from Direction to the relative normal vector
* we are using this since I'm not sure if the getNormal create new object at every call
*/
@SuppressWarnings("serial")
public static final Map<LodDirection, Vec3i> DIRECTION_NORMAL_MAP = new HashMap<LodDirection, Vec3i>()
{{
put(LodDirection.UP, LodDirection.UP.getNormal());
put(LodDirection.DOWN, LodDirection.DOWN.getNormal());
put(LodDirection.EAST, LodDirection.EAST.getNormal());
put(LodDirection.WEST, LodDirection.WEST.getNormal());
put(LodDirection.SOUTH, LodDirection.SOUTH.getNormal());
put(LodDirection.NORTH, LodDirection.NORTH.getNormal());
}};
/** We use this index for all array that are going to */
@SuppressWarnings("serial")
public static final Map<LodDirection, Integer> DIRECTION_INDEX = new HashMap<LodDirection, Integer>()
{{
put(LodDirection.UP, 0);
put(LodDirection.DOWN, 1);
put(LodDirection.EAST, 2);
put(LodDirection.WEST, 3);
put(LodDirection.SOUTH, 4);
put(LodDirection.NORTH, 5);
}};
@SuppressWarnings("serial")
public static final Map<LodDirection, Integer> ADJ_DIRECTION_INDEX = new HashMap<LodDirection, Integer>()
{{
put(LodDirection.EAST, 0);
put(LodDirection.WEST, 1);
put(LodDirection.SOUTH, 2);
put(LodDirection.NORTH, 3);
}};
/** holds the box's x, y, z offset */
public final int[] boxOffset;
/** holds the box's x, y, z width */
public final int[] boxWidth;
/** Holds each direction's color */
public final int[] colorMap;
/** The original color (before shading) of this box */
public int color;
/**
*
*/
public final Map<LodDirection, int[]> adjHeight;
public final Map<LodDirection, int[]> adjDepth;
public final Map<LodDirection, byte[]> skyLights;
public byte blockLight;
boolean skipTop;
boolean skipBot;
/** creates an empty box */
@SuppressWarnings("serial")
public VertexOptimizer()
{
boxOffset = new int[3];
boxWidth = new int[3];
colorMap = new int[6];
skyLights = new HashMap<LodDirection, byte[]>()
{{
put(LodDirection.UP, new byte[1]);
put(LodDirection.DOWN, new byte[1]);
put(LodDirection.EAST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.WEST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.SOUTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.NORTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
adjHeight = new HashMap<LodDirection, int[]>()
{{
put(LodDirection.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
adjDepth = new HashMap<LodDirection, int[]>()
{{
put(LodDirection.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(LodDirection.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
}
/** Set the light of the columns */
public void setLights(int skyLight, int blockLight)
{
this.blockLight = (byte) blockLight;
skyLights.get(LodDirection.UP)[0] = (byte) skyLight;
}
/**
* Set the color of the columns
* @param color color to add
* @param adjShadeDisabled this array indicates which face does not need shading
*/
public void setColor(int color, boolean[] adjShadeDisabled)
{
this.color = color;
for (LodDirection lodDirection : DIRECTIONS)
{
if (!adjShadeDisabled[DIRECTION_INDEX.get(lodDirection)])
colorMap[DIRECTION_INDEX.get(lodDirection)] = ColorUtil.applyShade(color, MC.getShade(lodDirection));
else
colorMap[DIRECTION_INDEX.get(lodDirection)] = color;
}
}
/**
* @param lodDirection of the face of which we want to get the color
* @return color of the face
*/
public int getColor(LodDirection lodDirection)
{
if (CONFIG.client().advanced().debugging().getDebugMode() != DebugMode.SHOW_DETAIL)
return colorMap[DIRECTION_INDEX.get(lodDirection)];
else
return ColorUtil.applyShade(color, MC.getShade(lodDirection));
}
/**
*/
public byte getSkyLight(LodDirection lodDirection, int verticalIndex)
{
if(lodDirection == LodDirection.UP || lodDirection == LodDirection.DOWN)
return skyLights.get(lodDirection)[0];
else
return skyLights.get(lodDirection)[verticalIndex];
}
/**
*/
public int getBlockLight()
{
return blockLight;
}
/** clears this box, resetting everything to default values */
public void reset()
{
Arrays.fill(boxWidth, 0);
Arrays.fill(boxOffset, 0);
Arrays.fill(colorMap, 0);
blockLight = 0;
for (LodDirection lodDirection : ADJ_DIRECTIONS)
{
for (int i = 0; i < adjHeight.get(lodDirection).length; i++)
{
adjHeight.get(lodDirection)[i] = VOID_FACE;
adjDepth.get(lodDirection)[i] = VOID_FACE;
skyLights.get(lodDirection)[i] = 0;
}
}
}
/**
* This method create all the shared face culling based on the adjacent data
* @param adjData data adjacent to the column we are going to render
*/
public void setAdjData(Map<LodDirection, long[]> adjData)
{
int height;
int depth;
int minY = getMinY();
int maxY = getMaxY();
long singleAdjDataPoint;
// TODO transparency uncomment final condition to see ocean floor
//Up direction case
singleAdjDataPoint = adjData.get(LodDirection.UP)[0];
skipTop = DataPointUtil.doesItExist(singleAdjDataPoint) && DataPointUtil.getDepth(singleAdjDataPoint) == maxY;// && DataPointUtil.getAlpha(singleAdjDataPoint) == 255;
//Down direction case
singleAdjDataPoint = adjData.get(LodDirection.DOWN)[0];
skipBot = DataPointUtil.doesItExist(singleAdjDataPoint) && DataPointUtil.getHeight(singleAdjDataPoint) == minY;// && DataPointUtil.getAlpha(singleAdjDataPoint) == 255;
if(DataPointUtil.doesItExist(singleAdjDataPoint))
skyLights.get(LodDirection.DOWN)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
else
skyLights.get(LodDirection.DOWN)[0] = skyLights.get(LodDirection.UP)[0];
//other sided
//TODO clean some similar cases
for (LodDirection lodDirection : ADJ_DIRECTIONS)
{
long[] dataPoint = adjData.get(lodDirection);
if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0]))
{
adjHeight.get(lodDirection)[0] = maxY;
adjDepth.get(lodDirection)[0] = minY;
adjHeight.get(lodDirection)[1] = VOID_FACE;
adjDepth.get(lodDirection)[1] = VOID_FACE;
skyLights.get(lodDirection)[0] = 15; //in void set full skylight
continue;
}
int i;
int faceToDraw = 0;
boolean firstFace = true;
boolean toFinish = false;
int toFinishIndex = 0;
boolean allAbove = true;
// TODO transparency ocean floor fix
//boolean isOpaque = ((colorMap[0] >> 24) & 0xFF) == 255;
for (i = 0; i < dataPoint.length; i++)
{
singleAdjDataPoint = dataPoint[i];
if (DataPointUtil.isVoid(singleAdjDataPoint) || !DataPointUtil.doesItExist(singleAdjDataPoint))
break;
// TODO transparency ocean floor fix
//if (isOpaque && DataPointUtil.getAlpha(singleAdjDataPoint) != 255)
// continue;
height = DataPointUtil.getHeight(singleAdjDataPoint);
depth = DataPointUtil.getDepth(singleAdjDataPoint);
if (depth < maxY)
{
allAbove = false;
if (height < minY)
{
// the adj data is lower than the current data
if (firstFace)
{
adjHeight.get(lodDirection)[0] = getMaxY();
adjDepth.get(lodDirection)[0] = getMinY();
skyLights.get(lodDirection)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); //skyLights.get(Direction.UP)[0];
}
else
{
adjDepth.get(lodDirection)[faceToDraw] = getMinY();
skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
}
faceToDraw++;
toFinish = false;
// break since all the other data will be lower
break;
}
else if (depth <= minY)
{
if (height >= maxY)
{
// the adj data is inside the current data
// don't draw the face
adjHeight.get(lodDirection)[0] = VOID_FACE;
adjDepth.get(lodDirection)[0] = VOID_FACE;
}
else // height < maxY
{
// the adj data intersects the lower part of the current data
// if this is the only face, use the maxY and break,
// if there was another face we finish the last one and break
if (firstFace)
{
adjHeight.get(lodDirection)[0] = getMaxY();
adjDepth.get(lodDirection)[0] = height;
skyLights.get(lodDirection)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); //skyLights.get(Direction.UP)[0];
}
else
{
adjDepth.get(lodDirection)[faceToDraw] = height;
skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
}
toFinish = false;
faceToDraw++;
}
break;
}
else if (height >= maxY)// && depth > minY
{
// the adj data intersects the higher part of the current data
// we start the creation of a new face
adjHeight.get(lodDirection)[faceToDraw] = depth;
//skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
firstFace = false;
toFinish = true;
toFinishIndex = i + 1;
}
else
{
// if (depth > minY && height < maxY)
// the adj data is contained in the current data
if (firstFace)
{
adjHeight.get(lodDirection)[0] = getMaxY();
}
adjDepth.get(lodDirection)[faceToDraw] = height;
skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
faceToDraw++;
adjHeight.get(lodDirection)[faceToDraw] = depth;
firstFace = false;
toFinish = true;
toFinishIndex = i + 1;
}
}
}
if (allAbove)
{
adjHeight.get(lodDirection)[0] = getMaxY();
adjDepth.get(lodDirection)[0] = getMinY();
skyLights.get(lodDirection)[0] = skyLights.get(LodDirection.UP)[0];
faceToDraw++;
}
else if (toFinish)
{
adjDepth.get(lodDirection)[faceToDraw] = minY;
if(toFinishIndex < dataPoint.length)
{
singleAdjDataPoint = dataPoint[toFinishIndex];
if (DataPointUtil.doesItExist(singleAdjDataPoint))
skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
else
skyLights.get(lodDirection)[faceToDraw] = skyLights.get(LodDirection.UP)[0];
}
faceToDraw++;
}
adjHeight.get(lodDirection)[faceToDraw] = VOID_FACE;
adjDepth.get(lodDirection)[faceToDraw] = VOID_FACE;
}
}
/** We use this method to set the various width of the column */
public void setWidth(int xWidth, int yWidth, int zWidth)
{
boxWidth[X] = xWidth;
boxWidth[Y] = yWidth;
boxWidth[Z] = zWidth;
}
/** We use this method to set the various offset of the column */
public void setOffset(int xOffset, int yOffset, int zOffset)
{
boxOffset[X] = xOffset;
boxOffset[Y] = yOffset;
boxOffset[Z] = zOffset;
}
/** returns true if the given direction should be rendered. */
public boolean shouldRenderFace(LodDirection lodDirection, int adjIndex)
{
if (lodDirection == LodDirection.UP)
return adjIndex == 0 && !skipTop;
if (lodDirection == LodDirection.DOWN)
return adjIndex == 0 && !skipBot;
return !(adjHeight.get(lodDirection)[adjIndex] == VOID_FACE && adjDepth.get(lodDirection)[adjIndex] == VOID_FACE);
}
/**
* @param lodDirection direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @return position x of the relative vertex
*/
public int getX(LodDirection lodDirection, int vertexIndex)
{
return boxOffset[X] + boxWidth[X] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][X];
}
/**
* @param lodDirection direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @return position y of the relative vertex
*/
public int getY(LodDirection lodDirection, int vertexIndex)
{
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y];
}
/**
* @param lodDirection direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @param adjIndex, index of the n-th culled face of this direction
* @return position x of the relative vertex
*/
public int getY(LodDirection lodDirection, int vertexIndex, int adjIndex)
{
if (lodDirection == LodDirection.DOWN || lodDirection == LodDirection.UP)
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y];
else
{
// this could probably be cleaned up more,
// but it still works
if (1 - DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y] == ADJACENT_HEIGHT_INDEX)
return adjHeight.get(lodDirection)[adjIndex];
else
return adjDepth.get(lodDirection)[adjIndex];
}
}
/**
* @param lodDirection direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @return position z of the relative vertex
*/
public int getZ(LodDirection lodDirection, int vertexIndex)
{
return boxOffset[Z] + boxWidth[Z] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Z];
}
public int getMinX()
{
return boxOffset[X];
}
public int getMaxX()
{
return boxOffset[X] + boxWidth[X];
}
public int getMinY()
{
return boxOffset[Y];
}
public int getMaxY()
{
return boxOffset[Y] + boxWidth[Y];
}
public int getMinZ()
{
return boxOffset[Z];
}
public int getMaxZ()
{
return boxOffset[Z] + boxWidth[Z];
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@ package com.seibel.lod.core.objects.lod;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* A level container is a quad tree level
@@ -42,15 +43,9 @@ public interface LevelContainer
* @param data actual data to add in an array of long[] format.
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return true if correctly changed, false otherwise
* @return true if correctly added, false otherwise
*/
boolean addVerticalData(long[] data, int posX, int posZ, boolean override);
/**
* With this you can add a square of data to the level container
* @return true if anything changed, false otherwise
*/
boolean addChunkOfData(long[] data, int posX, int posZ, int widthX, int widthZ, boolean override);
boolean addVerticalData(long[] data, int posX, int posZ);
/**
* With this you can add data to the level container
@@ -68,14 +63,6 @@ public interface LevelContainer
* @return the data in long array format
*/
long getData(int posX, int posZ, int index);
/**
* With this you can get data from the level container
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return the data in long array format
*/
long[] getAllData(int posX, int posZ);
/**
* With this you can get data from the level container
@@ -129,11 +116,4 @@ public interface LevelContainer
* @return data as a String
*/
int getMaxNumberOfLods();
/**
* This will return a ram usage estimation of this object
* @return long as byte
*/
long getRoughRamUsage();
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,14 +22,15 @@ package com.seibel.lod.core.objects.lod;
import java.util.Hashtable;
import java.util.Map;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
/**
* This stores all LODs for a given world.
*
* @author James Seibel
* @author Leonardo Amato
* @version 2022-3-29
* @version 9-27-2021
*/
public class LodWorld
{
@@ -43,7 +44,7 @@ public class LodWorld
private boolean isWorldLoaded = false;
/** the name given to the world if it isn't loaded */
public static final String NO_WORLD_LOADED = "";
public static final String NO_WORLD_LOADED = "No world loaded";
@@ -64,20 +65,25 @@ public class LodWorld
*/
public void selectWorld(String newWorldName)
{
ApiShared.LOGGER.info("Selecting world {} while in world {}", newWorldName, worldName);
if (newWorldName.isEmpty())
{
deselectWorld();
return;
}
if (worldName.equals(newWorldName))
// don't recreate everything if we
// didn't actually change worlds
return;
deselectWorld();
worldName = newWorldName;
lodDimensions = new Hashtable<>();
isWorldLoaded = true;
}
/**
* Clear the lodDimensions Map. <br>
* Set the worldName to "No world loaded"
* and clear the lodDimensions Map. <br>
* This should be done whenever unloaded a world. <br><br>
* <p>
* Note a System.gc() call may be in order after calling this <Br>
@@ -85,9 +91,7 @@ public class LodWorld
*/
public void deselectWorld()
{
ApiShared.LOGGER.info("Deselecting world {}", worldName);
worldName = NO_WORLD_LOADED;
saveAllDimensions(true); // Make sure all dims are saved. This will block threads
lodDimensions = null;
isWorldLoaded = false;
}
@@ -101,11 +105,8 @@ public class LodWorld
{
if (lodDimensions == null)
return;
ApiShared.LOGGER.info("Adding dim {} to world {}", newDimension, worldName);
LodDimension oldDim = lodDimensions.put(newDimension.dimension, newDimension);
if (oldDim != null)
oldDim.saveDirtyRegionsToFile(true);
lodDimensions.put(newDimension.dimension, newDimension);
}
/**
@@ -128,7 +129,7 @@ public class LodWorld
if (lodDimensions == null)
return;
saveAllDimensions(true); //block until saving is done
saveAllDimensions();
for (IDimensionTypeWrapper key : lodDimensions.keySet())
lodDimensions.get(key).setRegionWidth(newRegionWidth);
@@ -137,35 +138,17 @@ public class LodWorld
/**
* Requests all dimensions save any dirty regions they may have.
*/
public void saveAllDimensions(boolean isBlocking)
public void saveAllDimensions()
{
if (lodDimensions == null)
return;
// TODO we should only print this if lods were actually saved to file
// but that requires a LodDimension.hasDirtyRegions() method or something similar
ApiShared.LOGGER.info("Saving LODs");
ClientApi.LOGGER.info("Saving LODs");
for (IDimensionTypeWrapper key : lodDimensions.keySet())
{
lodDimensions.get(key).saveDirtyRegionsToFile(isBlocking);
}
//FIXME: This should block until file is saved.
}
/**
* Requests all dimensions to shutdown
*/
public void shutdownAllDimensions()
{
if (lodDimensions == null)
return;
// TODO: Add parallel shutdowns.
for (IDimensionTypeWrapper key : lodDimensions.keySet())
{
lodDimensions.get(key).shutdown();
}
lodDimensions.get(key).saveDirtyRegionsToFileAsync();
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,8 +19,8 @@
package com.seibel.lod.core.objects.lod;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
@@ -82,39 +82,10 @@ public class RegionPos
.offset(LodUtil.CHUNK_WIDTH / 2, 0, LodUtil.CHUNK_WIDTH / 2);
}
@Override
public boolean equals(Object o) {
// If the object is compared with itself then return true
if (o == this) {
return true;
}
// Check if o is an instance of RegionPos or not
if (!(o instanceof RegionPos)) {
return false;
}
RegionPos c = (RegionPos) o;
return c.x==x &&c.z==z;
}
@Override
public String toString()
{
return "(" + x + "," + z + ")";
}
public static long asLong(int i, int j) {
return (long)i & 0xFFFFFFFFL | ((long)j & 0xFFFFFFFFL) << 32;
}
public static int getX(long l) {
return (int)(l & 0xFFFFFFFFL);
}
public static int getZ(long l) {
return (int)(l >>> 32 & 0xFFFFFFFFL);
}
@Override
public int hashCode() {
return Long.hashCode(asLong(x,z));
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class LodQuadTree
{
public LodSection[][][] quadTreeStructure;
public QuadTreeProperties properties;
public LodQuadTree()
{
}
}
@@ -0,0 +1,115 @@
package com.seibel.lod.core.objects.lod.quadtree;
/**
A lod section rappresent a distinct section in a precise level of the quadtree.
The section holds all the lods information as color (computed with the blockBiome object referenced by the ID), size, light...
The save and load of a section is handled by the section itself together with a handler class.
*/
public class LodSection
{
//level of detail of this section
public final int detail;
//level position of this section
public final int sectionPosX;
public final int sectionPosZ;
//horizontal size of this section
public final int horizontalSize;
//vertical size of this section
public final int verticalSize;
//how many id we save for each lod
public final int idPerLod;
//What generation mode should be used for chunk in this section
public final byte generationMode;
//if present in region file, use pregenerated chunk in lod creation
public boolean usePregeneratedChunk;
//Position data hold information about each level position like generation mode used, number of vertical lods in that area...
private byte[] positionData;
//Position data hold vertical information about each lod, like minY and maxY...
private int[] verticalData;
//Lights data hold lights information about each lod, like skylight and block light values...
private byte[] lightsData;
//BlockBiomeId hold a unique ID for each lod relative to a block-biome couple. This couple is capable of generating color
//We could just reference
private int[] BlockBiomeId;
//BlockBiomeFrequency for each lod BlockBiomeId
private byte[] BlockBiomeFrequency;
/**
*
* @param detail
* @param horizontalSize
* @param verticalSize
* @param levelPosX
* @param levelPosZ
* @param generationMode
* @param avoidPregeneratedChunk
* @param idPerLod
*/
public LodSection(int detail, int horizontalSize, int verticalSize, int levelPosX, int levelPosZ, byte generationMode, boolean avoidPregeneratedChunk, int idPerLod)
{
this.detail = detail;
this.sectionPosX = levelPosX;
this.sectionPosZ = levelPosZ;
this.horizontalSize = horizontalSize;
this.verticalSize = verticalSize;
this.generationMode = generationMode;
this.usePregeneratedChunk = avoidPregeneratedChunk;
this.idPerLod = idPerLod;
//Now we should search if there is a file with this values
//if so we load it and end the process
//Otherwise we initialize this section
positionData = new byte[horizontalSize*horizontalSize];
verticalData = new int[horizontalSize*horizontalSize*verticalSize];
lightsData = new byte[horizontalSize*horizontalSize*verticalSize];
BlockBiomeId = new int[horizontalSize*horizontalSize*verticalSize*idPerLod];
BlockBiomeFrequency = new byte[horizontalSize*horizontalSize*verticalSize*idPerLod];
}
/**
*
* @param otherSection
* @param inputStartX
* @param inputStartZ
* @param inputEndX
* @param inputEndZ
* @param targetStartX
* @param targetStartZ
* @param targetEndX
* @param targetEndZ
*/
public void mergeAndAdd(LodSection otherSection,
int inputStartX, int inputStartZ, int inputEndX, int inputEndZ,
int targetStartX, int targetStartZ, int targetEndX, int targetEndZ)
{
}
public short getPositionData(int posX, int posZ)
{
return 0;
}
public long[] getData(int posX, int posZ)
{
return null;
}
public void save()
{
}
public void tryload()
{
}
}
@@ -0,0 +1,11 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class QuadTreeMover
{
public static void move(LodQuadTree lqt, RenderQuadTree rqt, int centerX, int centerZ)
{
}
}
@@ -0,0 +1,36 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class QuadTreeProperties
{
public int MAX_NUMBER_OF_DETAIL=23;
public int SECTION_SIZE=128;
public int[] absoluteDetailCircleSize = new int[MAX_NUMBER_OF_DETAIL];
public int[] relativeDetailCircleSize = new int[MAX_NUMBER_OF_DETAIL];
public int[] generationModeOfDetail = new int[MAX_NUMBER_OF_DETAIL];
public LodQuadTree lodQuadTree;
public RenderQuadTree renderQuadTree;
public void update()
{
for(int detail = 0; detail < MAX_NUMBER_OF_DETAIL; detail++)
{
//Compute circle distance for this detail in term of blocks
absoluteDetailCircleSize[detail] = DetailDistanceUtil.getDrawDistanceFromDetail(detail);
//Compute circle distance in terms of number of section
relativeDetailCircleSize[detail] = (int) Math.ceil(absoluteDetailCircleSize[detail]/(SECTION_SIZE*2^detail));
}
updateGridSize();
}
private void updateGridSize()
{
for(int detail = 0; detail < MAX_NUMBER_OF_DETAIL; detail++)
{
//Use this value to change to update the size of the matrices
//relativeDetailCircleSize[detail];
}
}
}
@@ -0,0 +1,13 @@
package com.seibel.lod.core.objects.lod.quadtree;
import com.seibel.lod.core.util.DetailDistanceUtil;
public class RenderQuadTree
{
//public RenderSection[][][] quadTreeStructure;
public QuadTreeProperties properties;
public RenderQuadTree()
{
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -397,7 +397,6 @@ public class Mat4f
/**
* TODO: what kind of translation is this?
* and how is this different from "multiplyTranslationMatrix"?
* Answer: This is faster and direct (but only if this is pure translation matrix without rotate)
*/
public void translate(Vec3f vec)
{
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,14 +30,14 @@ import com.google.common.collect.ImmutableList;
*/
public class DefaultLodVertexFormats
{
public static final LodVertexFormatElement ELEMENT_POSITION = new LodVertexFormatElement(3, LodVertexFormatElement.DataType.USHORT, 3, false);
public static final LodVertexFormatElement ELEMENT_COLOR = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 4, false);
public static final LodVertexFormatElement ELEMENT_UV = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.FLOAT, 2, false);
public static final LodVertexFormatElement ELEMENT_LIGHT_MAP_UV = new LodVertexFormatElement(1, LodVertexFormatElement.DataType.SHORT, 2, false);
public static final LodVertexFormatElement ELEMENT_NORMAL = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 3, false);
public static final LodVertexFormatElement ELEMENT_PADDING = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 1, true);
public static final LodVertexFormatElement ELEMENT_POSITION = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.FLOAT, 3);
public static final LodVertexFormatElement ELEMENT_COLOR = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 4);
public static final LodVertexFormatElement ELEMENT_UV = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.FLOAT, 2);
public static final LodVertexFormatElement ELEMENT_LIGHT_MAP_UV = new LodVertexFormatElement(1, LodVertexFormatElement.DataType.SHORT, 2);
public static final LodVertexFormatElement ELEMENT_NORMAL = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 3);
public static final LodVertexFormatElement ELEMENT_PADDING = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 1);
public static final LodVertexFormatElement ELEMENT_LIGHT = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 1, false);
public static final LodVertexFormatElement ELEMENT_BLOCK_LIGHT = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 1);
public static final LodVertexFormat POSITION = new LodVertexFormat(ImmutableList.<LodVertexFormatElement>builder().add(ELEMENT_POSITION).build());
@@ -47,7 +47,5 @@ public class DefaultLodVertexFormats
public static final LodVertexFormat POSITION_COLOR_TEX = new LodVertexFormat(ImmutableList.<LodVertexFormatElement>builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_UV).build());
public static final LodVertexFormat POSITION_COLOR_TEX_LIGHTMAP = new LodVertexFormat(ImmutableList.<LodVertexFormatElement>builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_UV).add(ELEMENT_LIGHT_MAP_UV).build());
public static final LodVertexFormat POSITION_COLOR_BLOCK_LIGHT_SKY_LIGHT = new LodVertexFormat(ImmutableList.<LodVertexFormatElement>builder()
.add(ELEMENT_POSITION).add(ELEMENT_PADDING).add(ELEMENT_LIGHT)
.add(ELEMENT_COLOR).build());
public static final LodVertexFormat POSITION_COLOR_BLOCK_LIGHT_SKY_LIGHT = new LodVertexFormat(ImmutableList.<LodVertexFormatElement>builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_BLOCK_LIGHT).add(ELEMENT_BLOCK_LIGHT).build());
}
@@ -1,317 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects.opengl;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
public class LodBox
{
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
public static void addBoxQuadsToBuilder(LodQuadBuilder builder, short xSize, short ySize, short zSize, short x,
short y, short z, int color, byte skyLight, byte blockLight, long topData, long botData, long[][][] adjData,
boolean[] adjFillBlack)
{
short maxX = (short) (x + xSize);
short maxY = (short) (y + ySize);
short maxZ = (short) (z + zSize);
byte skyLightTop = skyLight;
boolean isTransparent = ColorUtil.getAlpha(color)<255;
boolean isTopTransparent = DataPointUtil.isTransparent(topData);
boolean isBotTransparent = DataPointUtil.isTransparent(botData);
byte skyLightBot = DataPointUtil.doesItExist(botData) ? DataPointUtil.getLightSky(botData) : 0;
// Up direction case
boolean skipTop = DataPointUtil.doesItExist(topData) && (
(isTransparent && (DataPointUtil.getDepth(topData) == maxY)) ||
(!isTransparent && (DataPointUtil.getDepth(topData) == maxY) && !isTopTransparent));
boolean skipBot = DataPointUtil.doesItExist(botData) && (
(isTransparent && (DataPointUtil.getDepth(botData) == maxY)) ||
(!isTransparent && (DataPointUtil.getDepth(botData) == maxY) && !isBotTransparent));
// DataPointUtil.getAlpha(singleAdjDataPoint)
// == 255;
//boolean skipBot = DataPointUtil.doesItExist(botData) && DataPointUtil.getHeight(botData) == y;// &&
// DataPointUtil.getAlpha(singleAdjDataPoint)
// == 255;
if (!skipTop)
builder.addQuadUp(x, maxY, z, xSize, zSize, ColorUtil.applyShade(color, MC.getShade(LodDirection.UP)), skyLightTop, blockLight);
if (!skipBot)
builder.addQuadDown(x, y, z, xSize, zSize, ColorUtil.applyShade(color, MC.getShade(LodDirection.DOWN)), skyLightBot, blockLight);
//If the adj pos is at the same level we cull the faces normally, otherwise we divide the face in two and cull the two part separately
//NORTH face vertex creation
{
long[][] adjDataNorth = adjData[LodDirection.NORTH.ordinal() - 2];
int adjOverlapNorth = adjFillBlack[LodDirection.NORTH.ordinal() - 2] ? ColorUtil.BLACK : ColorUtil.TRANSPARENT;
if (adjDataNorth == null)
{
builder.addQuadAdj(LodDirection.NORTH, x, y, z, xSize, ySize, color, (byte) 15, blockLight);
}
else if (adjDataNorth.length == 1)
{
makeAdjQuads(builder, adjDataNorth[0], LodDirection.NORTH, x, y, z, xSize, ySize,
color, adjOverlapNorth, skyLightTop, blockLight);
}
else
{
makeAdjQuads(builder, adjDataNorth[0], LodDirection.NORTH, x, y, z, (short) (xSize / 2), ySize,
color, adjOverlapNorth, skyLightTop, blockLight);
makeAdjQuads(builder, adjDataNorth[1], LodDirection.NORTH, (short) (x + xSize / 2), y, z, (short) (xSize / 2), ySize,
color, adjOverlapNorth, skyLightTop, blockLight);
}
}
//SOUTH face vertex creation
{
long[][] adjDataSouth = adjData[LodDirection.SOUTH.ordinal() - 2];
int adjOverlapSouth = adjFillBlack[LodDirection.SOUTH.ordinal() - 2] ? ColorUtil.BLACK : ColorUtil.TRANSPARENT;
if (adjDataSouth == null)
{
builder.addQuadAdj(LodDirection.SOUTH, x, y, maxZ, xSize, ySize, color, (byte) 15, blockLight);
}
else if (adjDataSouth.length == 1)
{
makeAdjQuads(builder, adjDataSouth[0], LodDirection.SOUTH, x, y, maxZ, xSize, ySize,
color, adjOverlapSouth, skyLightTop, blockLight);
}
else
{
makeAdjQuads(builder, adjDataSouth[0], LodDirection.SOUTH, x, y, maxZ, (short) (xSize / 2), ySize,
color, adjOverlapSouth, skyLightTop, blockLight);
makeAdjQuads(builder, adjDataSouth[1], LodDirection.SOUTH, (short) (x + xSize / 2), y, maxZ, (short) (xSize / 2), ySize,
color, adjOverlapSouth, skyLightTop, blockLight);
}
}
//WEST face vertex creation
{
long[][] adjDataWest = adjData[LodDirection.WEST.ordinal() - 2];
int adjOverlapWest = adjFillBlack[LodDirection.WEST.ordinal() - 2] ? ColorUtil.BLACK : ColorUtil.TRANSPARENT;
if (adjDataWest == null)
{
builder.addQuadAdj(LodDirection.WEST, x, y, z, zSize, ySize, color, (byte) 15, blockLight);
}
else if (adjDataWest.length == 1)
{
makeAdjQuads(builder, adjDataWest[0], LodDirection.WEST, x, y, z, zSize, ySize,
color, adjOverlapWest, skyLightTop, blockLight);
}
else
{
makeAdjQuads(builder, adjDataWest[0], LodDirection.WEST, x, y, z, (short) (zSize / 2), ySize,
color, adjOverlapWest, skyLightTop, blockLight);
makeAdjQuads(builder, adjDataWest[1], LodDirection.WEST, x, y, (short) (z + zSize / 2), (short) (zSize / 2), ySize,
color, adjOverlapWest, skyLightTop, blockLight);
}
}
//EAST face vertex creation
{
long[][] adjDataEast = adjData[LodDirection.EAST.ordinal() - 2];
int adjOverlapEast = adjFillBlack[LodDirection.EAST.ordinal() - 2] ? ColorUtil.BLACK : ColorUtil.TRANSPARENT;
if (adjData[LodDirection.EAST.ordinal() - 2] == null)
{
builder.addQuadAdj(LodDirection.EAST, maxX, y, z, zSize, ySize, color, (byte) 15, blockLight);
}
else if (adjDataEast.length == 1)
{
makeAdjQuads(builder, adjDataEast[0], LodDirection.EAST, maxX, y, z, zSize, ySize,
color, adjOverlapEast, skyLightTop, blockLight);
}
else
{
makeAdjQuads(builder, adjDataEast[0], LodDirection.EAST, maxX, y, z, (short) (zSize / 2), ySize,
color, adjOverlapEast, skyLightTop, blockLight);
makeAdjQuads(builder, adjDataEast[1], LodDirection.EAST, maxX, y, (short) (z + zSize / 2), (short) (zSize / 2), ySize,
color, adjOverlapEast, skyLightTop, blockLight);
}
}
}
private static void makeAdjQuads(LodQuadBuilder builder, long[] adjData, LodDirection direction, short x, short y,
short z, short w0, short wy, int color, int overlapColor, byte upSkyLight, byte blockLight)
{
boolean isTransparent = ColorUtil.getAlpha(color)<255;
color = ColorUtil.applyShade(color, MC.getShade(direction));
long[] dataPoint = adjData;
if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0]))
{
builder.addQuadAdj(direction, x, y, z, w0, wy, color, (byte) 15, blockLight);
return;
}
int i;
boolean firstFace = true;
boolean allAbove = true;
short previousDepth = -1;
byte nextSkyLight = upSkyLight;
// TODO transparency ocean floor fix
// boolean isOpaque = ((colorMap[0] >> 24) & 0xFF) == 255;
for (i = 0; i < dataPoint.length && DataPointUtil.doesItExist(adjData[i]) && !DataPointUtil.isVoid(adjData[i]); i++)
{
long adjPoint = adjData[i];
boolean isAdjTransparent = DataPointUtil.isTransparent(adjPoint);
// TODO transparency ocean floor fix
if (!isTransparent && isAdjTransparent)
continue;
short height = DataPointUtil.getHeight(adjPoint);
short depth = DataPointUtil.getDepth(adjPoint);
// If the depth of said block is higher then our max Y, continue
// Basically: y < maxY <= _____ height
// _______&&: y < maxY <= depth
if (y + wy <= depth)
continue;
// Now: depth < maxY
allAbove = false;
if (height < y)
{
// Basically: _____ height < y < maxY
// _______&&: depth ______ < y < maxY
if (firstFace)
{
builder.addQuadAdj(direction, x, y, z, w0, wy, color, DataPointUtil.getLightSky(adjPoint),
blockLight);
}
else
{
// Now: depth < height < y < previousDepth < maxY
if (previousDepth == -1)
throw new RuntimeException("Loop error");
builder.addQuadAdj(direction, x, y, z, w0, (short) (previousDepth - y), color,
DataPointUtil.getLightSky(adjPoint), blockLight);
previousDepth = -1;
}
break;
}
if (depth <= y)
{ // AND y <= height
if (y + wy <= height)
{
// Basically: ________ y < maxY <= height
// _______&&: depth <= y < maxY
// The face is inside adj face completely.
if (overlapColor != 0)
{
builder.addQuadAdj(direction, x, y, z, w0, wy, overlapColor, (byte) 15, (byte) 15);
}
break;
}
// Otherwise: ________ y <= Height < maxY
// _______&&: depth <= y _________ < maxY
// the adj data intersects the lower part of the current data
if (height > y && overlapColor != 0)
{
builder.addQuadAdj(direction, x, y, z, w0, (short) (height - y), overlapColor, (byte) 15, (byte) 15);
}
// if this is the only face, use the maxY and break,
// if there was another face we finish the last one and break
if (firstFace)
{
builder.addQuadAdj(direction, x, height, z, w0, (short) (y + wy - height), color,
DataPointUtil.getLightSky(adjPoint), blockLight);
}
else
{
// Now: depth <= y <= height <= previousDepth < maxY
if (previousDepth == -1)
throw new RuntimeException("Loop error");
if (previousDepth > height)
{
builder.addQuadAdj(direction, x, height, z, w0, (short) (previousDepth - height), color,
DataPointUtil.getLightSky(adjPoint), blockLight);
}
previousDepth = -1;
}
break;
}
// In here always true: y < depth < maxY
// _________________&&: y < _____ (height and maxY)
if (y + wy <= height)
{
// Basically: y _______ < maxY <= height
// _______&&: y < depth < maxY
// the adj data intersects the higher part of the current data
if (overlapColor != 0)
{
builder.addQuadAdj(direction, x, depth, z, w0, (short) (y + wy - depth), overlapColor, (byte) 15, (byte) 15);
}
// we start the creation of a new face
}
else
{
// Otherwise: y < _____ height < maxY
// _______&&: y < depth ______ < maxY
if (overlapColor != 0)
{
builder.addQuadAdj(direction, x, depth, z, w0, (short) (height - depth), overlapColor, (byte) 15, (byte) 15);
}
if (firstFace)
{
builder.addQuadAdj(direction, x, height, z, w0, (short) (y + wy - height), color,
DataPointUtil.getLightSky(adjPoint), blockLight);
}
else
{
// Now: y < depth < height <= previousDepth < maxY
if (previousDepth == -1)
throw new RuntimeException("Loop error");
if (previousDepth > height)
{
builder.addQuadAdj(direction, x, height, z, w0, (short) (previousDepth - height), color,
DataPointUtil.getLightSky(adjPoint), blockLight);
}
previousDepth = -1;
}
}
// set next top as current depth
previousDepth = depth;
firstFace = false;
nextSkyLight = upSkyLight;
if (i + 1 < adjData.length && DataPointUtil.doesItExist(adjData[i + 1]))
nextSkyLight = DataPointUtil.getLightSky(adjData[i + 1]);
}
if (allAbove)
{
builder.addQuadAdj(direction, x, y, z, w0, wy, color, upSkyLight, blockLight);
}
else if (previousDepth != -1)
{
// We need to finish the last quad.
builder.addQuadAdj(direction, x, y, z, w0, (short) (previousDepth - y), color, nextSkyLight,
blockLight);
}
}
}
@@ -0,0 +1,557 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects.opengl;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
* A (almost) exact copy of Minecraft's
* BufferBuilder object. <br>
* Which allows for creating and filling
* OpenGL buffers.
*
* @author James Seibel
* @version 12-9-2021
*/
public class LodBufferBuilder
{
private static final Logger LOGGER = LogManager.getLogger();
public ByteBuffer buffer;
private final List<LodBufferBuilder.DrawState> vertexCounts = Lists.newArrayList();
private int lastRenderedCountIndex = 0;
private int totalRenderedBytes = 0;
private int nextElementByte = 0;
private int totalUploadedBytes = 0;
private int vertices;
private LodVertexFormatElement currentElement;
private int elementIndex;
private int mode;
private LodVertexFormat format;
private boolean building;
public LodBufferBuilder(int bufferSizeInBytes)
{
this.buffer = allocateByteBuffer(bufferSizeInBytes * 4);
}
/** originally from MC's GLAllocation class */
private ByteBuffer allocateByteBuffer(int bufferSizeInBytes)
{
return ByteBuffer.allocateDirect(bufferSizeInBytes).order(ByteOrder.nativeOrder());
}
/** originally from MC's GLAllocation class */
@SuppressWarnings("unused")
private FloatBuffer allocateFloatBuffer(int bufferSizeInBytes)
{
return allocateByteBuffer(bufferSizeInBytes).asFloatBuffer();
}
/** make sure the buffer doesn't overflow when inserting new elements */
private void ensureVertexCapacity()
{
this.ensureCapacity(this.format.getByteSize());
}
private void ensureCapacity(int vertexSizeInBytes)
{
if (this.nextElementByte + vertexSizeInBytes > this.buffer.capacity())
{
int i = this.buffer.capacity();
int j = i + roundUp(vertexSizeInBytes);
//LOGGER.debug("Needed to grow BufferBuilder buffer: Old size {} bytes, new size {} bytes.", i, j);
ByteBuffer bytebuffer = allocateByteBuffer(j);
this.buffer.position(0);
bytebuffer.put(this.buffer);
bytebuffer.rewind();
this.buffer = bytebuffer;
}
}
private static int roundUp(int vertexSizeInBytes)
{
int i = 2097152; // 2 ^ 21
if (vertexSizeInBytes == 0)
{
return i;
}
else
{
if (vertexSizeInBytes < 0)
{
i *= -1;
}
int j = vertexSizeInBytes % i;
return j == 0 ? vertexSizeInBytes : vertexSizeInBytes + i - j;
}
}
/* not currently needed sortQuads()
// the x,y,z location is a blockPos
public void sortQuads(float x, float y, float z)
{
((Buffer) this.buffer).clear();
FloatBuffer floatbuffer = this.buffer.asFloatBuffer();
int i = this.vertices / 4;
float[] afloat = new float[i];
for (int j = 0; j < i; ++j)
{
afloat[j] = getQuadDistanceFromPlayer(floatbuffer, x, y, z, this.format.getIntegerSize(), this.totalRenderedBytes / 4 + j * this.format.getVertexSize());
}
int[] aint = new int[i];
for (int k = 0; k < aint.length; aint[k] = k++)
{
}
IntArrays.mergeSort(aint, (p_227830_1_, p_227830_2_) ->
{
return Floats.compare(afloat[p_227830_2_], afloat[p_227830_1_]);
});
BitSet bitset = new BitSet();
FloatBuffer floatbuffer1 = allocateFloatBuffer(this.format.getIntegerSize() * 4);
for (int l = bitset.nextClearBit(0); l < aint.length; l = bitset.nextClearBit(l + 1))
{
int i1 = aint[l];
if (i1 != l)
{
this.limitToVertex(floatbuffer, i1);
((Buffer) floatbuffer1).clear();
floatbuffer1.put(floatbuffer);
int j1 = i1;
for (int k1 = aint[i1]; j1 != l; k1 = aint[k1])
{
this.limitToVertex(floatbuffer, k1);
FloatBuffer floatbuffer2 = floatbuffer.slice();
this.limitToVertex(floatbuffer, j1);
floatbuffer.put(floatbuffer2);
bitset.set(j1);
j1 = k1;
}
this.limitToVertex(floatbuffer, l);
((Buffer) floatbuffer1).flip();
floatbuffer.put(floatbuffer1);
}
bitset.set(l);
}
}
private void limitToVertex(FloatBuffer p_227829_1_, int p_227829_2_)
{
int i = this.format.getIntegerSize() * 4;
((Buffer) p_227829_1_).limit(this.totalRenderedBytes / 4 + (p_227829_2_ + 1) * i);
((Buffer) p_227829_1_).position(this.totalRenderedBytes / 4 + p_227829_2_ * i);
}
*/
/* not curerntly needed getState()
public LodBufferBuilder.State getState()
{
((Buffer) this.buffer).limit(this.nextElementByte);
((Buffer) this.buffer).position(this.totalRenderedBytes);
ByteBuffer bytebuffer = ByteBuffer.allocate(this.vertices * this.format.getVertexSize());
bytebuffer.put(this.buffer);
((Buffer) this.buffer).clear();
return new LodBufferBuilder.State(bytebuffer, this.format);
}
*/
/* not currently needed getQuadDistanceFromPlayer()
private static float getQuadDistanceFromPlayer(FloatBuffer floatBuffer, float x, float y, float z, int p_181665_4_, int p_181665_5_)
{
float f = floatBuffer.get(p_181665_5_ + p_181665_4_ * 0 + 0);
float f1 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 0 + 1);
float f2 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 0 + 2);
float f3 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 1 + 0);
float f4 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 1 + 1);
float f5 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 1 + 2);
float f6 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 2 + 0);
float f7 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 2 + 1);
float f8 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 2 + 2);
float f9 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 3 + 0);
float f10 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 3 + 1);
float f11 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 3 + 2);
float f12 = (f + f3 + f6 + f9) * 0.25F - x;
float f13 = (f1 + f4 + f7 + f10) * 0.25F - y;
float f14 = (f2 + f5 + f8 + f11) * 0.25F - z;
return f12 * f12 + f13 * f13 + f14 * f14;
}
*/
/* not currently needed restoreState()
public void restoreState(LodBufferBuilder.State bufferState)
{
((Buffer) bufferState.data).clear();
int i = bufferState.data.capacity();
this.ensureCapacity(i);
((Buffer) this.buffer).limit(this.buffer.capacity());
((Buffer) this.buffer).position(this.totalRenderedBytes);
this.buffer.put(bufferState.data);
((Buffer) this.buffer).clear();
LodVertexFormat LodVertexFormat = bufferState.format;
this.switchFormat(LodVertexFormat);
this.vertices = i / LodVertexFormat.getVertexSize();
this.nextElementByte = this.totalRenderedBytes + this.vertices * LodVertexFormat.getVertexSize();
}
*/
private void switchFormat(LodVertexFormat newFormat)
{
format = newFormat;
}
//========================================//
// methods for actually building a buffer //
//========================================//
/**
* @param openGlLodVertexFormat GL11.GL_QUADS, GL11.GL_TRIANGLES, etc.
* @param LodVertexFormat
*/
public void begin(int openGlLodVertexFormat, LodVertexFormat LodVertexFormat)
{
if (this.building)
{
throw new IllegalStateException("Already building!");
}
else
{
this.building = true;
this.mode = openGlLodVertexFormat;
this.switchFormat(LodVertexFormat);
this.currentElement = LodVertexFormat.getElements().get(0);
this.elementIndex = 0;
this.buffer.clear();
}
}
public void end()
{
if (!this.building)
{
throw new IllegalStateException("Not building!");
}
else
{
this.building = false;
this.vertexCounts.add(new LodBufferBuilder.DrawState(this.format, this.vertices, this.mode));
this.totalRenderedBytes += this.vertices * this.format.getByteSize();
this.vertices = 0;
this.currentElement = null;
this.elementIndex = 0;
}
}
public void putByte(int index, byte newByte)
{
this.buffer.put(this.nextElementByte + index, newByte);
}
public void putShort(int index, short newShort)
{
this.buffer.putShort(this.nextElementByte + index, newShort);
}
public void putFloat(int index, float newFloat)
{
this.buffer.putFloat(this.nextElementByte + index, newFloat);
}
public void endVertex()
{
if (this.elementIndex != 0)
{
throw new IllegalStateException("Not filled all elements of the vertex");
}
else
{
++this.vertices;
this.ensureVertexCapacity();
}
}
public void nextElement()
{
ImmutableList<LodVertexFormatElement> immutablelist = this.format.getElements();
this.elementIndex = (this.elementIndex + 1) % immutablelist.size();
this.nextElementByte += this.currentElement.getByteSize();
this.currentElement = immutablelist.get(this.elementIndex);
// if (LodVertexFormatelement.getUsage() == LodVertexFormatElement.Usage.PADDING)
// {
// this.nextElement();
// }
// if (this.defaultColorSet && this.currentElement.getUsage() == LodVertexFormatElement.Usage.COLOR)
// {
// color(this.defaultR, this.defaultG, this.defaultB, this.defaultA);
// }
}
public LodBufferBuilder color(int red, int green, int blue, int alpha)
{
LodVertexFormatElement LodVertexFormatelement = this.currentElement();
if (LodVertexFormatelement.getType() != LodVertexFormatElement.DataType.UBYTE)
{
throw new IllegalStateException("Color must be stored as a UBYTE");
}
else
{
this.putByte(0, (byte) red);
this.putByte(1, (byte) green);
this.putByte(2, (byte) blue);
this.putByte(3, (byte) alpha);
this.nextElement();
return this;
}
}
public LodBufferBuilder minecraftLightValue(byte lightValue)
{
LodVertexFormatElement LodVertexFormatelement = this.currentElement();
if (LodVertexFormatelement.getType() != LodVertexFormatElement.DataType.UBYTE)
{
throw new IllegalStateException("Light Color must be stored as a UBYTE");
}
else
{
this.putByte(0, lightValue);
this.nextElement();
return this;
}
}
public LodBufferBuilder position(float x, float y, float z)
{
if (this.currentElement().getType() != LodVertexFormatElement.DataType.FLOAT)
{
throw new IllegalStateException("Position verticies must be stored as a FLOAT");
}
else
{
this.putFloat(0, x);
this.putFloat(4, y);
this.putFloat(8, z);
this.nextElement();
return this;
}
}
/* not currently needed fullVertex()
* TODO James isn't sure about these names
public void vertex(float blockPosX, float blockPosY, float blockPosZ,
float red, float green, float blue, float alpha,
float textureU, float textureV,
int p_225588_10_, int p_225588_11_,
float p_225588_12_, float p_225588_13_, float p_225588_14_)
{
if (this.defaultColorSet)
{
throw new IllegalStateException();
}
else if (this.fastFormat)
{
this.putFloat(0, blockPosX);
this.putFloat(4, blockPosY);
this.putFloat(8, blockPosZ);
this.putByte(12, (byte) ((int) (red * 255.0F)));
this.putByte(13, (byte) ((int) (green * 255.0F)));
this.putByte(14, (byte) ((int) (blue * 255.0F)));
this.putByte(15, (byte) ((int) (alpha * 255.0F)));
this.putFloat(16, textureU);
this.putFloat(20, textureV);
int i;
if (this.fullFormat)
{
this.putShort(24, (short) (p_225588_10_ & '\uffff'));
this.putShort(26, (short) (p_225588_10_ >> 16 & '\uffff'));
i = 28;
}
else
{
i = 24;
}
this.putShort(i + 0, (short) (p_225588_11_ & '\uffff'));
this.putShort(i + 2, (short) (p_225588_11_ >> 16 & '\uffff'));
this.putByte(i + 4, IVertexConsumer.normalIntValue(p_225588_12_));
this.putByte(i + 5, IVertexConsumer.normalIntValue(p_225588_13_));
this.putByte(i + 6, IVertexConsumer.normalIntValue(p_225588_14_));
this.nextElementByte += i + 8;
this.endVertex();
}
else
{
super.vertex(blockPosX, blockPosY, blockPosZ, red, green, blue, alpha, textureU, textureV, p_225588_10_, p_225588_11_, p_225588_12_, p_225588_13_, p_225588_14_);
}
}
*/
/**
* James isn't sure what the difference between
* using this method and just directly getting the buffer would be.
* But this was what was being used before, so it will stay for now.
*
* If anyone figures out what is special about this, please replace this comment.
*/
public ByteBuffer getCleanedByteBuffer()
{
LodBufferBuilder.DrawState bufferbuilder$drawstate = this.vertexCounts.get(this.lastRenderedCountIndex++);
this.buffer.position(this.totalUploadedBytes);
this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getByteSize();
this.buffer.limit(this.totalUploadedBytes);
if (this.lastRenderedCountIndex == this.vertexCounts.size() && this.vertices == 0)
{
this.clear();
}
ByteBuffer bytebuffer = this.buffer.slice();
//bytebuffer.order(this.buffer.order()); // FORGE: Fix incorrect byte order
this.buffer.clear();
return bytebuffer; // the original method also returned bufferbuilder$drawstate
}
public void clear()
{
if (this.totalRenderedBytes != this.totalUploadedBytes)
{
LOGGER.warn("Bytes mismatch " + this.totalRenderedBytes + " " + this.totalUploadedBytes);
}
this.discard();
}
public void discard()
{
this.totalRenderedBytes = 0;
this.totalUploadedBytes = 0;
this.nextElementByte = 0;
this.vertexCounts.clear();
this.lastRenderedCountIndex = 0;
}
public LodVertexFormatElement currentElement()
{
if (this.currentElement == null)
{
throw new IllegalStateException("BufferBuilder not started");
}
else
{
return this.currentElement;
}
}
public boolean building()
{
return this.building;
}
//==================//
// internal classes //
//==================//
public static final class DrawState
{
private final LodVertexFormat format;
private final int vertexCount;
private final int mode;
private DrawState(LodVertexFormat p_i225905_1_, int p_i225905_2_, int p_i225905_3_)
{
this.format = p_i225905_1_;
this.vertexCount = p_i225905_2_;
this.mode = p_i225905_3_;
}
public LodVertexFormat format()
{
return this.format;
}
public int vertexCount()
{
return this.vertexCount;
}
public int mode()
{
return this.mode;
}
}
// Forge added methods
public void putBulkData(ByteBuffer buffer)
{
ensureCapacity(buffer.limit() + this.format.getByteSize());
this.buffer.position(this.vertices * this.format.getByteSize());
this.buffer.put(buffer);
this.vertices += buffer.limit() / this.format.getByteSize();
this.nextElementByte += buffer.limit();
}
public LodVertexFormat getLodVertexFormat()
{
return this.format;
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,12 +17,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.render.objects;
package com.seibel.lod.core.objects.opengl;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import org.lwjgl.opengl.GL32;
import java.nio.ByteBuffer;
import com.seibel.lod.core.enums.rendering.GLProxyContext;
import com.seibel.lod.core.render.GLProxy;
/**
* This is a container for a OpenGL
@@ -31,30 +31,27 @@ import java.nio.ByteBuffer;
* @author James Seibel
* @version 11-20-2021
*/
public class GLElementBuffer extends GLBuffer
public class LodVertexBuffer implements AutoCloseable
{
/**
* When uploading to a buffer that is too small, recreate it this many times
* bigger than the upload payload
*/
protected int indicesCount = 0;
public int getIndicesCount() { return indicesCount; }
protected int type = GL32.GL_UNSIGNED_INT;
public int getType() { return type; }
public GLElementBuffer(boolean isBufferStorage)
public int id;
public int vertexCount;
public LodVertexBuffer()
{
super(isBufferStorage);
if (GLProxy.getInstance().getGlContext() == GLProxyContext.NONE)
throw new IllegalStateException("Thread [" +Thread.currentThread().getName() + "] tried to create a [" + LodVertexBuffer.class.getSimpleName() + "] outside a OpenGL contex.");
this.id = GL32.glGenBuffers();
}
@Override
public void destroy(boolean async) {
super.destroy(async);
indicesCount = 0;
}
@Override
public int getBufferBindingTarget() {
return GL32.GL_ELEMENT_ARRAY_BUFFER;
public void close()
{
if (this.id >= 0)
{
GLProxy.getInstance().recordOpenGlCall(() -> GL32.glDeleteBuffers(this.id));
this.id = -1;
}
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -39,19 +39,13 @@ public class LodVertexFormatElement
private final int index;
private final int count;
private final int byteSize;
private final boolean isPadding;
public LodVertexFormatElement(int newIndex, LodVertexFormatElement.DataType newType, int newCount, boolean isPadding)
public LodVertexFormatElement(int newIndex, LodVertexFormatElement.DataType newType, int newCount)
{
this.dataType = newType;
this.index = newIndex;
this.count = newCount;
this.byteSize = newType.getSize() * this.count;
this.isPadding = isPadding;
}
public final boolean getIsPadding() {
return isPadding;
}
public final LodVertexFormatElement.DataType getType()
@@ -1,143 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects.opengl;
import java.util.ConcurrentModificationException;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.util.StatsMap;
public abstract class RenderBuffer implements AutoCloseable
{
private enum State {
None,
Building,
Uploading,
Closed,
}
private State owner = State.None;
private State nextOwner = State.None;
final private void _lockThread(State newOwner) {
if (owner != State.None || (nextOwner != State.None && nextOwner != newOwner))
throw new ConcurrentModificationException("RenderMethod Illegal state!");
owner = newOwner;
nextOwner = State.None;
}
final private void _unlockThread(State oldOwner) {
if (owner != oldOwner)
throw new ConcurrentModificationException("RenderMethod Illegal state!");
owner = State.None;
}
final private void _unlockThreadTo(State oldOwner, State newOwner) {
if (owner != oldOwner)
throw new ConcurrentModificationException("RenderMethod Illegal state!");
owner = State.None;
nextOwner = newOwner;
}
final public void build(Runnable r) {
_lockThread(State.Building);
try {
r.run();
} finally {
_unlockThread(State.Building);
}
}
/* Return false if current renderMethod is not suited for current builder
* This will auto close the object if returning false. */
final public boolean tryUploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod) {
_lockThread(State.Uploading);
boolean successful = false;
try {
successful = uploadBuffers(builder, uploadMethod);
return successful;
} finally {
if (!successful) {
_unlockThreadTo(State.Uploading, State.Closed);
close();
} else {
_unlockThread(State.Uploading);
}
}
}
// ======================================================================
// ====================== Methods for implementations ===================
// ======================================================================
// =========== Called by build starter thread ==========
/* Called on being reused after the object is swapped to the back
* and a new build event is triggered. Used for cleaning up non
* reusable objects sooner.
* Note: This is ran on BUILDER thread, and does not have access to
* GL Context, Use GLProxy.recordOpenGlCall() to access GL Context
* instead! */
public void onReuse() {}
// =========== Called by buffer upload thread ==========
/* Return false if current renderMethod is not suited for current builder
* If false, close call will be automatically triggered.
* If true, the object will be used (by first calling the swapBufferToFront())
* on tick render. */
protected abstract boolean uploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod);
// ========== Called by render thread ==========
/* Called on buffer first being used by a render thread. */
public void onSwapToFront() {}
/* Called on buffer no longer being used. (Life ended)
* Return false if current object cannot be reused.
* Note: This should not do too much stuff as it is ran on render thread!
* The corresponding cleanups should be done using the onReuse() to prevent
* lag spikes! If you want this buffer to not be reused, but cleanup is
* expensive, use onReuse() instead!
* Note 2: This may not be triggered on some siturations like renderer being
* terminated, or dimension changed. So implementation should NEVER assume
* that onSwapToFront() will link to a call of onSwapToBack()! */
public boolean onSwapToBack() {return true;}
/* Called on... well... rendering.
* Return false if nothing rendered. (Optional) */
public abstract boolean render(LodRenderProgram shaderProgram);
// ========== Called by any thread. (thread safe) ==========
/* Called by anyone. This method is allowed to throw exceptions, but
* are never allowed to modify any values. This should behave the same
* to other methods as if the method have never been called.
* Note: This method is PURELY for debug or stats logging ONLY! */
public abstract void debugDumpStats(StatsMap statsMap);
// ========= Called only when 1 thread is using it =======
/* This method is called when object is no longer in use.
* Called either after uploadBuffers() returned false (On buffer Upload
* thread), or by others when the object is not being used. (not in build,
* upload, or render state). */
public abstract void close();
}
@@ -1,451 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects.opengl;
import java.util.ConcurrentModificationException;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.CubicLodTemplate;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.enums.rendering.GLProxyContext;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.BoolType;
import com.seibel.lod.core.objects.PosToRenderContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.LodRegion;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.objects.math.Vec3f;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.render.RenderUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.util.gridList.PosArrayGridList;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import static com.seibel.lod.core.render.LodRenderer.EVENT_LOGGER;
public class RenderRegion implements AutoCloseable
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
/** stores if the region at the given x and z index needs to be regenerated */
// Use int because I need Tri state:
private final AtomicInteger needRegen = new AtomicInteger(2);
private enum BackState {
Unused,
Building,
Complete,
}
private enum FrontState {
Unused,
Rendering,
Invalidated,
}
final RegionPos regionPos;
RenderBuffer renderBufferBack = null;
AtomicReference<BackState> backState =
new AtomicReference<BackState>(BackState.Unused);
AtomicReference<FrontState> frontState =
new AtomicReference<FrontState>(FrontState.Unused);
RenderBuffer renderBufferFront = null;
final LodDimension lodDim;
public RenderRegion(RegionPos regPos, LodDimension lodDim) {
regionPos = regPos;
this.lodDim = lodDim;
}
public boolean canRender(LodDimension lodDim, RegionPos regPos) {
return lodDim == this.lodDim && regPos.equals(regionPos);
}
public void setNeedRegen() {
needRegen.set(2);
}
public Optional<CompletableFuture<Void>> updateStatus(Executor bufferUploader, Executor bufferBuilder, boolean alwaysRegen, int playerPosX, int playerPosZ, boolean doCaveCulling) {
if (alwaysRegen) setNeedRegen();
BackState state = backState.get();
if (state != BackState.Unused) {
EVENT_LOGGER.trace("{}: UpdateStatus rejected. Cause: BackState is {}", regionPos, state);
return Optional.empty();
}
LodRegion r = lodDim.getRegion(regionPos.x, regionPos.z);
if (r==null) {
EVENT_LOGGER.trace("{}: UpdateStatus rejected. Cause: Region is null", regionPos);
return Optional.empty();
}
if (needRegen.get() == 0) {
EVENT_LOGGER.trace("{}: UpdateStatus rejected. Cause: Region doesn't need regen", regionPos);
return Optional.empty();
}
if (!backState.compareAndSet(BackState.Unused, BackState.Building)) {
EVENT_LOGGER.trace("{}: UpdateStatus rejected. Cause: CAS on BackState failed: ", backState.get());
return Optional.empty();
}
needRegen.decrementAndGet();
return Optional.of(startBuid(bufferUploader, bufferBuilder, r, lodDim, playerPosX, playerPosZ, doCaveCulling));
}
public boolean render(LodDimension renderDim,
Vec3d cameraPos, AbstractBlockPosWrapper cameraBlockPos, Vec3f cameraDir,
boolean enableDirectionalCulling, LodRenderProgram program) {
if (!frontState.compareAndSet(FrontState.Unused, FrontState.Rendering)) return false;
try {
if (renderDim != lodDim) return false;
if (enableDirectionalCulling &&
!RenderUtil.isRegionInViewFrustum(cameraBlockPos,
cameraDir, regionPos.x, regionPos.z)) return false;
BackState state = backState.get();
if (state == BackState.Complete) {
if (renderBufferBack != null) {
EVENT_LOGGER.debug("RenderRegion swap @ {}", regionPos);
boolean shouldKeep = renderBufferFront != null && renderBufferFront.onSwapToBack();
RenderBuffer temp = shouldKeep ? renderBufferFront : null;
renderBufferFront = renderBufferBack;
renderBufferBack = temp;
if (renderBufferFront != null) renderBufferFront.onSwapToFront();
}
if (!backState.compareAndSet(BackState.Complete, BackState.Unused)) {
EVENT_LOGGER.error("RenderRegion.render() got illegal state on swapping buffer!");
}
}
if (renderBufferFront == null) return false;
program.setModelPos(new Vec3f(
(float) ((regionPos.x * LodUtil.REGION_WIDTH) - cameraPos.x),
(float) (LodBuilder.MIN_WORLD_HEIGHT - cameraPos.y),
(float) ((regionPos.z * LodUtil.REGION_WIDTH) - cameraPos.z)));
return renderBufferFront.render(program);
} finally {
frontState.compareAndSet(FrontState.Rendering, FrontState.Unused);
}
}
private void recreateBuffer(LodQuadBuilder builder) {
if (renderBufferBack != null) throw new RuntimeException("Assert Error");
boolean useSimpleBuffer = (builder.getCurrentNeededVertexBufferCount() <= 6) || true;
renderBufferBack = useSimpleBuffer ?
new SimpleRenderBuffer()
: null; //new ComplexRenderRegion(regPos);
}
private CompletableFuture<Void> startBuid(Executor bufferUploader, Executor bufferBuilder, LodRegion region, LodDimension lodDim, int playerPosX, int playerPosZ, boolean doCaveCulling) {
EVENT_LOGGER.trace("RenderRegion startBuild @ {}", regionPos);
LodRegion[] adjRegions = new LodRegion[4];
try {
if (renderBufferBack != null) renderBufferBack.onReuse();
for (LodDirection dir : LodDirection.ADJ_DIRECTIONS) {
adjRegions[dir.ordinal() - 2] = lodDim.getRegion(regionPos.x+dir.getNormal().x, regionPos.z+dir.getNormal().z);
}
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) { // HOTFIX: Error ignoring for a concurrency caused issue
setNeedRegen();
if (!backState.compareAndSet(BackState.Building, BackState.Unused)) {
EVENT_LOGGER.error("\"Lod Builder Starter\""
+ " encountered error on catching exceptions and fallback on starting build task: ",
new ConcurrentModificationException("RenderRegion Illegal State"));
}
EVENT_LOGGER.info("\"Lod Builder Starter\" failed due to possible known concurrency issue: ", e);
return CompletableFuture.completedFuture(null);
} catch (Throwable t) {
setNeedRegen();
if (!backState.compareAndSet(BackState.Building, BackState.Unused)) {
EVENT_LOGGER.error("\"Lod Builder Starter\""
+ " encountered error on catching exceptions and fallback on starting build task: ",
new ConcurrentModificationException("RenderRegion Illegal State"));
}
throw t;
}
return CompletableFuture.supplyAsync(() -> {
try {
EVENT_LOGGER.trace("RenderRegion start QuadBuild @ {}", regionPos);
int skyLightCullingBelow = CONFIG.client().graphics().advancedGraphics().getCaveCullingHeight();
// FIXME: Clamp also to the max world height.
skyLightCullingBelow = Math.max(skyLightCullingBelow, LodBuilder.MIN_WORLD_HEIGHT);
LodQuadBuilder builder = new LodQuadBuilder(doCaveCulling, skyLightCullingBelow);
Runnable buildRun = ()->{
makeLodRenderData(builder, region, adjRegions, playerPosX, playerPosZ);
};
if (renderBufferBack != null)
renderBufferBack.build(buildRun);
else
buildRun.run();
EVENT_LOGGER.trace("RenderRegion end QuadBuild @ {}", regionPos);
return builder;
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
EVENT_LOGGER.info("\"LodNodeBufferBuilder\" failed due to possible known concurrency issue: ", e);
throw e; // HOTFIX: Error ignoring for a concurrency caused issue
} catch (Throwable e3) {
EVENT_LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3);
throw e3;
}
}, bufferBuilder)
.thenAcceptAsync((builder) -> {
try {
EVENT_LOGGER.trace("RenderRegion start Upload @ {}", regionPos);
GLProxy glProxy = GLProxy.getInstance();
GpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
GLProxyContext oldContext = glProxy.getGlContext();
glProxy.setGlContext(GLProxyContext.LOD_BUILDER);
try {
if (renderBufferBack == null) recreateBuffer(builder);
if (!renderBufferBack.tryUploadBuffers(builder, method)) {
renderBufferBack = null;
recreateBuffer(builder);
if (!renderBufferBack.tryUploadBuffers(builder, method)) {
throw new RuntimeException("Newly created renderBuffer "
+ "is still returning false on tryUploadBuffers!");
}
}
} finally {
glProxy.setGlContext(oldContext);
}
EVENT_LOGGER.trace("RenderRegion end Upload @ {}", regionPos);
} catch (Throwable e3) {
EVENT_LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
throw e3;
}
}, bufferUploader).handle((v, e) -> {
if (e != null) {
setNeedRegen();
if (!backState.compareAndSet(BackState.Building, BackState.Unused)) {
EVENT_LOGGER.error("\"LodNodeBufferBuilder\""
+ " encountered error on exit: ",
new ConcurrentModificationException("RenderRegion Illegal State"));
}
} else {
if (!backState.compareAndSet(BackState.Building, BackState.Complete)) {
EVENT_LOGGER.error("\"LodNodeBufferBuilder\""
+ " encountered error on exit: ",
new ConcurrentModificationException("RenderRegion Illegal State"));
}
}
return (Void) null;
});
}
private static final int ADJACENT8[][] = {
{-1,-1},
{-1, 0},
{-1, 1},
{ 0,-1},
//{ 0, 0},
{ 0, 1},
{ 1,-1},
{ 1, 0},
{ 1, 1}
};
private static void makeLodRenderData(LodQuadBuilder quadBuilder, LodRegion region, LodRegion[] adjRegions, int playerX,
int playerZ) {
byte minDetail = region.getMinDetailLevel();
// Variable initialization
DebugMode debugMode = CONFIG.client().advanced().debugging().getDebugMode();
// We ask the lod dimension which block we have to render given the player
// position
PosToRenderContainer posToRender = new PosToRenderContainer(minDetail, region.regionPosX, region.regionPosZ);
region.getPosToRender(posToRender, playerX, playerZ);
PosArrayGridList<BoolType> chunkGrid = ClientApi.renderer.vanillaChunks;
for (int index = 0; index < posToRender.getNumberOfPos(); index++) {
byte detailLevel = posToRender.getNthDetailLevel(index);
int posX = posToRender.getNthPosX(index);
int posZ = posToRender.getNthPosZ(index);
// TODO: In the future, We don't need to ignore rendered chunks! Just build it
// and leave it for the renderer to decide!
// We don't want to render this fake block if
// The block is inside the render distance with, is not bigger than a chunk and
// is positioned in a chunk set as vanilla rendered
// The block is in the player chunk or in a chunk adjacent to the player
if (detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL) {
int chunkX = LevelPosUtil.getChunkPos(detailLevel, posX);
int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posZ);
// skip any chunks that Minecraft is going to render
if (chunkGrid != null && chunkGrid.get(chunkX, chunkZ) != null) continue;
}
long[] posData = region.getAllData(detailLevel, posX, posZ);
if (posData == null || posData.length == 0 || !DataPointUtil.doesItExist(posData[0])
|| DataPointUtil.isVoid(posData[0]))
continue;
long[][][] adjData = new long[4][][];
boolean[] adjUseBlack = new boolean[4];
// We extract the adj data in the four cardinal direction
// we first reset the adjShadeDisabled. This is used to disable the shade on the
// border when we have transparent block like water or glass
// to avoid having a "darker border" underground
// Arrays.fill(adjShadeDisabled, false);
// We check every adj block in each direction
// If the adj block is rendered in the same region and with same detail
// and is positioned in a place that is not going to be rendered by vanilla game
// then we can set this position as adj
// We avoid cases where the adjPosition is in player chunk while the position is
// not
// to always have a wall underwater
/**TODO REMOVE THIS UGLY TRANSPARENCY THING AND MAKE IT MORE SIMPLE*/
for (int transparency = 0; transparency <= 1; transparency++)
{
for (LodDirection lodDirection : LodDirection.ADJ_DIRECTIONS) {
try {
int xAdj = posX + lodDirection.getNormal().x;
int zAdj = posZ + lodDirection.getNormal().z;
int chunkXAdj = LevelPosUtil.getChunkPos(detailLevel, xAdj);
int chunkZAdj = LevelPosUtil.getChunkPos(detailLevel, zAdj);
if (chunkGrid != null && chunkGrid.get(chunkXAdj, chunkZAdj) != null) {
adjUseBlack[lodDirection.ordinal() - 2] = true;
}
boolean isCrossRegionBoundary = LevelPosUtil.getRegion(detailLevel, xAdj) != region.regionPosX ||
LevelPosUtil.getRegion(detailLevel, zAdj) != region.regionPosZ;
LodRegion adjRegion;
byte adjDetail;
int childXAdj = xAdj * 2 + (lodDirection.getNormal().x < 0 ? 1 : 0);
int childZAdj = zAdj * 2 + (lodDirection.getNormal().z < 0 ? 1 : 0);
//we check if the detail of the adjPos is equal to the correct one (region border fix)
//or if the detail is wrong by 1 value (region+circle border fix)
if (isCrossRegionBoundary) {
//we compute at which detail that position should be rendered
adjRegion = adjRegions[lodDirection.ordinal() - 2];
if (adjRegion == null) continue;
adjDetail = adjRegion.getRenderDetailLevelAt(playerX, playerZ, detailLevel, xAdj, zAdj);
} else {
adjRegion = region;
if (posToRender.contains(detailLevel, xAdj, zAdj)) adjDetail = detailLevel;
else if (detailLevel > 0 &&
posToRender.contains((byte) (detailLevel - 1), childXAdj, childZAdj))
adjDetail = (byte) (detailLevel - 1);
else if (detailLevel < LodUtil.REGION_DETAIL_LEVEL &&
posToRender.contains((byte) (detailLevel + 1), xAdj / 2, zAdj / 2))
adjDetail = (byte) (detailLevel + 1);
else continue;
}
if (adjDetail < detailLevel - 1 || adjDetail > detailLevel + 1) {
continue;
}
if (adjDetail == detailLevel || adjDetail > detailLevel) {
adjData[lodDirection.ordinal() - 2] = new long[1][];
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getAllData(adjDetail,
LevelPosUtil.convert(detailLevel, xAdj, adjDetail),
LevelPosUtil.convert(detailLevel, zAdj, adjDetail));
} else {
adjData[lodDirection.ordinal() - 2] = new long[2][];
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getAllData(adjDetail,
childXAdj, childZAdj);
adjData[lodDirection.ordinal() - 2][1] = adjRegion.getAllData(adjDetail,
childXAdj + (lodDirection.getAxis() == LodDirection.Axis.X ? 0 : 1),
childZAdj + (lodDirection.getAxis() == LodDirection.Axis.Z ? 0 : 1));
}
} catch (RuntimeException e) {
EVENT_LOGGER.warn("Failed to get adj data for [{}:{},{}] at [{}]", detailLevel, posX, posZ, lodDirection);
EVENT_LOGGER.warn("Detail exception: ", e);
}
}
// We render every vertical lod present in this position
// We only stop when we find a block that is void or non-existing block
for (int i = 0; i < posData.length; i++) {
long data = posData[i];
if (DataPointUtil.isTransparent(data) == (transparency == 1))
continue;
// If the data is not renderable (Void or non-existing) we stop since there is
// no data left in this position
if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data))
break;
long adjDataTop = i - 1 >= 0 ? posData[i - 1] : DataPointUtil.EMPTY_DATA;
long adjDataBot = i + 1 < posData.length ? posData[i + 1] : DataPointUtil.EMPTY_DATA;
// We send the call to create the vertices
CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, adjUseBlack, detailLevel,
LevelPosUtil.getRegionModule(detailLevel, posX),
LevelPosUtil.getRegionModule(detailLevel, posZ), quadBuilder, debugMode);
}
}
}// for pos to in list to render
// the thread executed successfully
// Merge all quads
quadBuilder.mergeQuads();
}
@Override
public void close()
{
if (renderBufferBack != null) renderBufferBack.close();
while (frontState.get() != FrontState.Invalidated && !frontState.compareAndSet(FrontState.Unused, FrontState.Invalidated)) {
Thread.yield(); //FIXME: If on java 9, use Thread.onSpinWait();
}
if (renderBufferFront != null) renderBufferFront.close();
}
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RenderRegions");
RenderBuffer front = renderBufferFront;
if (front!=null) {
statsMap.incStat("FrontBuffers");
front.debugDumpStats(statsMap);
}
RenderBuffer back = renderBufferBack;
if (back!=null) {
statsMap.incStat("BackBuffers");
back.debugDumpStats(statsMap);
}
}
}
@@ -1,198 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects.opengl;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.render.objects.GLVertexBuffer;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder.BufferFiller;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import static com.seibel.lod.core.render.GLProxy.GL_LOGGER;
public class SimpleRenderBuffer extends RenderBuffer
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
GLVertexBuffer[] vbos;
// public void onReuse() {}
public SimpleRenderBuffer() {
vbos = new GLVertexBuffer[0];
}
@Override
protected boolean uploadBuffers(LodQuadBuilder builder, GpuUploadMethod method)
{
// if (builder.getCurrentNeededVertexBuffers()>6) return false;
if (method.useEarlyMapping) {
_uploadBuffersMapped(builder, method);
} else {
_uploadBuffersDirect(builder, method);
}
return true;
}
// public void onSwapToFront() {}
// public void onSwapToBack() {}
@Override
public boolean render(LodRenderProgram shaderProgram)
{
boolean hasRendered = false;
for (GLVertexBuffer vbo : vbos) {
if (vbo == null) continue;
if (vbo.getVertexCount() == 0) continue;
hasRendered = true;
vbo.bind();
shaderProgram.bindVertexBuffer(vbo.getId());
if (LodRenderer.ENABLE_IBO) {
GL32.glDrawElements(GL32.GL_TRIANGLES, (vbo.getVertexCount()/4)*6, ClientApi.renderer.quadIBO.getType(), 0);
} else {
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbo.getVertexCount());
}
//LodRenderer.tickLogger.info("Vertex buffer: {}", vbo);
}
return hasRendered;
}
@Override
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RenderBuffers");
statsMap.incStat("SimpleRenderBuffers");
for (GLVertexBuffer b : vbos) {
if (b == null) continue;
statsMap.incStat("VBOs");
if (b.getSize() == LodBufferBuilderFactory.FULL_SIZED_BUFFER) {
statsMap.incStat("FullsizedVBOs");
}
if (b.getSize() == 0) GL_LOGGER.warn("VBO with size 0");
statsMap.incBytesStat("TotalUsage", b.getSize());
}
}
@Override
public void close()
{
GLProxy.getInstance().recordOpenGlCall(() -> {
for (GLVertexBuffer b : vbos) {
b.destroy(false);
}
});
}
private void _uploadBuffersDirect(LodQuadBuilder builder, GpuUploadMethod method) {
resize(builder.getCurrentNeededVertexBufferCount());
long remainingNS = 0;
long BPerNS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds();
int i = 0;
Iterator<ByteBuffer> iter = builder.makeVertexBuffers();
while (iter.hasNext()) {
if (i >= vbos.length) {
throw new RuntimeException("Too many vertex buffers!!");
}
ByteBuffer bb = iter.next();
GLVertexBuffer vbo = getOrMakeVbo(i++, method.useBufferStorage);
int size = bb.limit() - bb.position();
try {
vbo.bind();
vbo.uploadBuffer(bb, size/LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, LodBufferBuilderFactory.FULL_SIZED_BUFFER);
} catch (Exception e) {
vbos[i-1] = null;
vbo.close();
ApiShared.LOGGER.error("Failed to upload buffer: ", e);
}
if (BPerNS<=0) continue;
// upload buffers over an extended period of time
// to hopefully prevent stuttering.
remainingNS += size * BPerNS;
if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS)) {
if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
try {
Thread.sleep(remainingNS / 1000000, (int) (remainingNS % 1000000));
} catch (InterruptedException e) {
}
remainingNS = 0;
}
}
if (i < vbos.length) {
throw new RuntimeException("Too few vertex buffers!!");
}
}
private void _uploadBuffersMapped(LodQuadBuilder builder, GpuUploadMethod method)
{
resize(builder.getCurrentNeededVertexBufferCount());
for (int i=0; i<vbos.length; i++) {
if (vbos[i]==null) vbos[i] = new GLVertexBuffer(method.useBufferStorage);
}
BufferFiller func = builder.makeBufferFiller(method);
int i = 0;
while (i < vbos.length && func.fill(vbos[i++])) {}
}
private GLVertexBuffer getOrMakeVbo(int iIndex, boolean useBuffStorage) {
if (vbos[iIndex] == null) {
vbos[iIndex] = new GLVertexBuffer(useBuffStorage);
}
return vbos[iIndex];
}
private void resize(int size) {
if (vbos.length != size) {
GLVertexBuffer[] newVbos = new GLVertexBuffer[size];
if (vbos.length > size) {
for (int i=size; i<vbos.length; i++) {
if (vbos[i]!=null) vbos[i].close();
vbos[i] = null;
}
}
for (int i=0; i<newVbos.length && i<vbos.length; i++) {
newVbos[i] = vbos[i];
vbos[i] = null;
}
for (GLVertexBuffer b : vbos) {
if (b != null) throw new RuntimeException("LEAKING VBO!");
}
vbos = newVbos;
}
}
}

Some files were not shown because too many files have changed in this diff Show More