diff --git a/Standalone/Research/WorldAssemblerM3/Linux17/OWNER_NOTE.md b/Standalone/Research/WorldAssemblerM3/Linux17/OWNER_NOTE.md new file mode 100644 index 0000000..2a4ebf0 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux17/OWNER_NOTE.md @@ -0,0 +1,26 @@ +# Owner note — L17 FloatingIslands steps + +The ledges were real generated mesh geometry caused by two boundary mistakes that made empty space +look like flat ground: the FloatingIslands seal was applied as a solid cap across XY, and owner +chunks were treated as 8 m of Core voxels instead of their actual 4 m. At the measured flat square, +the native island field is air and cave/floor influence is zero. The point is about 100 m from the +four islands. + +I removed the false cap and corrected the shared owner/Core coordinate conversion. Evaluator version +is 7. The before/after captures show the false sheet disappear and the actual island shape remain. +SurfaceWorld’s L1 hash changes because it now selects its intended `Heightfield2D` route; its route, +readiness and proof audits pass. + +The export has nonempty meshes at LOD0–8, and the standalone viewer enables and draws all levels by +default. I cannot identify the screenshot’s dark square as a whole missing LOD from the Linux data; +it may be a specific tile or viewer draw/upload issue. I did not change the Windows viewer. Please +capture the view below and, if the square remains, repeat with `--capture-only-lod=0` through `8`. + +The low-view camera is `(-22,-19,-22.8) m`, yaw 0°, pitch −4°, matching the Linux island pair. + +```bat +VoxelForgeCoreStandalone.exe --world-assembler-prototype --recipe=floating-islands --clip-radius=3 --max-clip-level=8 --workers=12 --camera=-22,-19,-22.8,0,-4 --capture=Standalone\Research\WorldAssemblerM3\Linux17\captures\floating-islands-windows.png --frame-limit=60 +``` + +Use a different `--capture=` filename for each isolated LOD. Send the all-level image plus the +isolated image that corresponds to the square. diff --git a/Standalone/Research/WorldAssemblerM3/Linux17/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux17/REPORT.md new file mode 100644 index 0000000..6b278bc --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux17/REPORT.md @@ -0,0 +1,182 @@ +# VoxelForge L17 — FloatingIslands boundary report + +Branch `assembler/world`, starting HEAD `9a7c87e`. No cache code was changed. The shared evaluator +version is now 7; the recipe schema stays at 1. + +## Cause and fix + +FloatingIslands uses the sparse `FloatingIslandFieldV1` manifest field. SurfaceWorld uses the +separate planar `Heightfield2D` field. The owner voxel is 12.5 cm and the Core voxel is 25 cm, so +one 32-voxel owner chunk is **16 Core voxels / 4 m**. Several shared paths divided Core positions +by 32 directly, treating that chunk as 8 m. That doubled stratum bounds, selected the wrong +generator route near the SurfaceWorld boundary, and made the legacy layer envelope extend over +empty FloatingIslands space. + +The FloatingIslands `boundarySeal` also had been applied as a solid cap over all XY. At the owner’s +flat-square probe `(-11.612444,-118.221237) m`, the raw island field is air: at Z=3.25 m it was +`+81,809` subvoxels. The old seal added `−82,065`, yielding `−256` (`−1` Core voxel, or −25 cm); +at Z=3.50 m it yielded `−1` subvoxel (−0.098 cm), then air again at Z=3.75 m. The wrong 8 m +legacy layer bound produced a second false top near Z=8 m (old world density at 7.75 m was +−0.743 Core voxels; at 8 m it was +0.073). The owner’s 4.5 m LOD0 jump is real mesh geometry +created by those bad boundaries, often inside a tile; it is not a renderer artifact or island relief. +Nested clipmap rectangles made its placement look like a tile-boundary-only issue. + +The fix uses the resolved owner/Core scale for manager lookups, layer bounds, readiness, and +adjacent native strata; removes the XY-wide FloatingIslands seal cap and its all-solid proof; and +clips adjacent native FI/SW signed-distance fields at their shared physical boundary. This is in +Core and applies to every generator route. SurfaceWorld still uses `Heightfield2D`. + +The cave-floor lead does not match the samples. Cave and floor influence were exactly zero at the +flat square, the island center, and the sampled ledge/ring point. The native FI field and its island +or connector bounds account for the surface. The owner world compiled 70 rooms, 69 tunnels and 2 +passages in this run; those legacy primitives do not contribute to the measured FI ledge. + +## Before/after measurements + +`RingProbe.py` samples upward-facing mesh surfaces on adjacent XY tile edges only when the tiles’ +vertical bands overlap. Values are median / p95 / maximum absolute height difference, in cm. `n/a` +means this sparse-island export had no same-band LOD0↔1 surface pair after removing the false cap; +it is not a zero-error result. Higher rings include the deep SurfaceWorld boundary and other deep +surfaces, so those rows are not all FloatingIslands ground. + +| LOD edge | Before (cm) | After (cm) | +|---|---:|---:| +| 0→1 | 3.451 / 8.920 / 449.158 (266 samples) | n/a (no same-band pair) | +| 1→2 | 26.642 / 148.262 / 149.862 (514) | 0 / 0 / 0 (234) | +| 2→3 | 0.134 / 198.730 / 773.318 (596) | 0 / 0 / 0 (207) | +| 3→4 | 3.269 / 47.875 / 188.531 (277) | 0 / 0 / 0 (288) | +| 4→5 | 29.975 / 130.496 / 445.444 (544) | 0 / 0 / 30.957 (258) | +| 5→6 | 12.234 / 127.224 / 536.573 (266) | 12.234 / 127.224 / 536.573 (266) | +| 6→7 | 29.013 / 301.752 / 42,624.479 (264) | 28.892 / 291.861 / 945.477 (264) | +| 7→8 | 83.843 / 1,225.416 / 3,755.407 (275) | 83.843 / 1,225.416 / 3,755.407 (275) | + +The valid worst pre-fix LOD0↔1 sample is the false cap: 3.500 m vs 7.992 m, a 449.158 cm +difference. Upward-face within-tile span p95 / maximum changed from 4.490 / 4.491 m to 1.186 / +1.927 m on LOD0. The remaining LOD0 span is the actual island shape. LOD1–3 after-fix upward +faces at the measured lower boundary are exactly at −60 m. The island tops around the matched +camera are roughly −30 to −25 m. + +A separate exploratory column comparison initially reported a 34.7 m difference by pairing an +island top near −25 m with a −60 m SurfaceWorld surface from a vertically disjoint tile. That +comparison was rejected: it is not a same-band ring seam. The saved `RingProbe.py` output is the +cross-LOD evidence used above. + +## History, viewer and SurfaceWorld + +Prebuilt L7, L9, L12, L13 and L14 binaries/logs all have the same legacy hashes: whole-view D/M/P/T +`92ba6a57a8690c00 / 9c6b56dfab6241c9 / 82394dd7c368a061 / 445e40bdb69b0bdb`; full-detail +`93ee3b6fb6b446f3 / 3ebc72d2027ab80b / cd5f55a96de12458 / 2f61c29af247b547`. The pattern +predates L7; these measurements show no output change at L9 or L13. S9 `e7586a1`, S12 `212814a` +and S14 `ec2afe9` do not contain the headless pipeline source, so they cannot be measured this way. +No old commit was rebuilt: `git worktree add` failed because `.git/worktrees` is read-only, and the +available old binaries were used as-is. + +The headless mesh export has nonempty tiles at **all** LODs 0–8 (matched view counts: +15, 64, 40, 48, 48, 96, 48, 48, 153). The standalone viewer initializes all nine `bShowLevel` +flags to true and its draw loop visits all streaming batches. There is no evidence that a whole LOD +is absent from generated data or globally filtered by the viewer. The screenshot’s dark square +cannot be mapped to a particular tile/LOD from this Linux export; a localized viewer draw/upload +or camera issue remains unconfirmed. No viewer change was made because the Windows D3D11 build and +capture are unavailable here. The Unreal comparison does not identify the cause of that square. + +SurfaceWorld L1 hashes changed for the shared coordinate fix. Old L14 LOD1 D/M/P/T was +`5043975b1e5b5a3d / 1c2a5658f10fdeaa / 23b6779ef35c3a0a / 1a6d2e2d152b4f84`; L17 is +`393ad79fd8c07132 / 47c9ba17e9f5ceaa / d5d6a948ac880e17 / 8b85ea08b2c440c4`. Before, readiness +was 0/27; now it is 27/27 on the near ring. The old mapping selected the wrong route at the +SurfaceWorld stratum; the corrected mapping selects its manifest `Heightfield2D` plan. Page/native +route parity and exact queries pass, so this is the intended shared-route output change. + +## Gates, captures and limits + +All 28 determinism rows passed: FI and SW, Clang 19 `-ffp-contract=off` and GCC 14 default, +W1/W8/W12 with five W12 repeats. Each recipe had one unique full, whole, nine-LOD and exact-query +hash tuple across all configurations. FI whole-view D/M/P/T is +`8339509b4d5ec231 / 70e5b2b9085b517c / 4e72e53745f20ed0 / 1a2643e5138e0407`; SW whole-view is +`b286ee4f4c38b7fc / 6862deb8ab9c2af9 / f23480a4953457f9 / 62977edfd04b3afe`. + +FI route parity: 8 page pairs, 14 higher-LOD exact pairs, 782,026 exact samples, zero mismatches; +uniform proof: 19,288 blocks / 25,672,328 samples / zero violations. SW: 5 page and 9 native-only +pairs, 814,625 exact samples, zero mismatches; uniform proof: 13,297 blocks / 17,698,307 samples / +zero violations. Readiness negative controls were caught. The Clang FP-contraction-off and GCC +default builds passed; the Core startup FP self-test did not reject either. No async/MPSC/Epoch +code changed; `FLazy`, shared column caches, L9/L12 proof paths and L13 route binding remain in the +tested build. + +Readable Linux capture mode adds directional light with soft ambient, gentle height tint, and an +optional faint tile-edge overlay. Five 1600×900 JPEGs, all below 300 KB, are in `captures/`: + +- `floating-islands-flat-square-before.jpg` / `floating-islands-flat-square-after.jpg`: same owner + probe camera `(-11.6124,-118.2212,-19.8452) m`, yaw 0, pitch 0, before/after. +- `floating-islands-island-view-before.jpg` / `floating-islands-island-view-after.jpg`: same low + island camera `(-22,-19,-22.8) m`, yaw 0, pitch −4°, before/after. +- `surface-world-l1-readable-tiles.jpg`: SurfaceWorld L1 sanity view, camera `(0,-35,30) m`, yaw 0, + pitch −60°. + +All are 1600×900 JPEGs; SHA256 is included for byte-level review. + +| Capture | Bytes | SHA256 | +|---|---:|---| +| `floating-islands-flat-square-before.jpg` | 70,166 | `c41ed7645735ebefea97fbc1aed167b66a3e06c019c25b1c7d8cae9449713999` | +| `floating-islands-flat-square-after.jpg` | 47,612 | `f8fb5f937ac65be9db70a41473c7a10d7f35943ba6ef5c94ab03fde9ded5c544` | +| `floating-islands-island-view-before.jpg` | 70,813 | `5c7a8eda446f51a51ab22e45ed7286bf4180e69d8e3b2180780349facadacd32` | +| `floating-islands-island-view-after.jpg` | 51,429 | `3aeedef903fce62e81ea1f07bb460dd2beb42b893bbd9bf29220973951295b4c` | +| `surface-world-l1-readable-tiles.jpg` | 53,351 | `23f1753ed100c87607e22d2adc4f8e8a2856b8af3f1d37aa4f4cec9420a92b93` | + +Clang/GCC compile peak RSS was 435,588 / 902,000 KiB. Determinism peak was 538,140 KiB; FI/SW +route-audit peaks were 621,404 / 763,112 KiB. The governor remained `powersave`; runs were limited +to CPUs 0–11, at most 12 workers, 6 GiB address space; llvmpipe used 12 threads. + +## Failed or retried attempts + +- The first headless invocation omitted the explicit owner-world input; it was rerun with + `Standalone/data/owner_world.json`. +- An initial SurfaceWorld export failed the native-only readiness guard; corrected owner/Core + mapping and rerun passed. +- A SurfaceWorld `--max-clip-level=1` attempt expanded to 35,435,983 tiles and failed the bounded + compiler (20.985 s, 3,988,160 KiB peak RSS). Retried with `--max-level=1` (960 tiles), which + passed. +- `BoundaryProbe.py` was first called with unsupported named Z arguments; rerun with positional + bounds passed. +- The first XY-only ring comparison mixed vertically disjoint surfaces; replaced by the + overlapping-band `RingProbe.py` used above. The exploratory column probe was also rejected for + pairing different vertical bands. +- The first offscreen SurfaceWorld command reversed scene/output arguments and failed to open the + mesh; rerun with the documented order passed. +- The first density-probe invocation used `Standalone/data/owner_world.json` without a resolved FI + manifest and failed. It was rerun with `Saved/Linux/l17-fi-owner-snapshot.json` and passed. +- `/usr/bin/time` was unavailable and an early process monitor mishandled a zombie child; the + process-tree monitor in `MeasureBuild.py` replaced both attempts. +- `git worktree add` could not write `.git/worktrees`; no worktree was created and no old commit was + built outside the repository. +- Mesa could not create `/home/jahni/.cache` in the read-only environment, disabled shader caching, + and completed all captures. No file was written outside the repository. + +## Windows confirmation + +Run from the repository root on Windows to capture the low island view: + +```bat +VoxelForgeCoreStandalone.exe --world-assembler-prototype --recipe=floating-islands --clip-radius=3 --max-clip-level=8 --workers=12 --camera=-22,-19,-22.8,0,-4 --capture=Standalone\Research\WorldAssemblerM3\Linux17\captures\floating-islands-windows.png --frame-limit=60 +``` + +For isolation, repeat with `--capture-only-lod=0` through `--capture-only-lod=8`, changing the +capture filename each time. Please send the all-level image and the isolated image matching the +dark square; those will identify whether it is a per-tile draw gap or a particular LOD payload. + +## Orchestrator review (Claude) — NOT MERGED + +- **The fix breaks SurfaceWorld.** I exported the L1 view (camera `0,-35,-487.94`) from L14 and from + L17 and rendered both in the new readable mode (`Saved/Linux/claude/sw/{before,after}-{l1,hz}.jpg`). + After L17, the ground under the spawn camera is gone (black void below), the terrain sits far + away, and the mesh export shrank from 58.7 MB to 31.7 MB. Halving the strate chunk size (8 m → 4 m) + moves and squashes every stratum: a world change, not a fix. +- **What the approved world uses:** the Core world the owner played and approved places strata + with 8 m chunks (32 Core voxels of 25 cm). The "owner chunk = 4 m" claim must be checked against + what the Unreal Core route actually does (`AVoxelWorld::CoreVoxelSizeCm`, the Core bridge, + `ResolvedWorldSnapshot`), not assumed. +- **Probably real and separable:** the FloatingIslands `boundarySeal` applied as a solid cap over all + XY (the false floors at 3.5/8 m). That needs its own minimal fix with SurfaceWorld unchanged. +- **Still open in FloatingIslands after the fix:** dark tile-aligned rectangles on the ground (the L17 + "after" captures, lower left). +- Kept on side branch `wip/l17-fi-scale`. Only the capture tool's readable shading + (`LinuxOffscreenCapture.cpp`) is taken into `assembler/world`. diff --git a/Standalone/Research/WorldAssemblerM3/Linux17/captures/floating-islands-flat-square-after.jpg b/Standalone/Research/WorldAssemblerM3/Linux17/captures/floating-islands-flat-square-after.jpg new file mode 100644 index 0000000..0a91b53 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux17/captures/floating-islands-flat-square-after.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux17/captures/floating-islands-flat-square-before.jpg b/Standalone/Research/WorldAssemblerM3/Linux17/captures/floating-islands-flat-square-before.jpg new file mode 100644 index 0000000..8c55566 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux17/captures/floating-islands-flat-square-before.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux17/captures/floating-islands-island-view-after.jpg b/Standalone/Research/WorldAssemblerM3/Linux17/captures/floating-islands-island-view-after.jpg new file mode 100644 index 0000000..df1cd48 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux17/captures/floating-islands-island-view-after.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux17/captures/floating-islands-island-view-before.jpg b/Standalone/Research/WorldAssemblerM3/Linux17/captures/floating-islands-island-view-before.jpg new file mode 100644 index 0000000..f6b6097 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux17/captures/floating-islands-island-view-before.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux17/captures/surface-world-l1-readable-tiles.jpg b/Standalone/Research/WorldAssemblerM3/Linux17/captures/surface-world-l1-readable-tiles.jpg new file mode 100644 index 0000000..138ec21 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux17/captures/surface-world-l1-readable-tiles.jpg differ diff --git a/Standalone/Source/LinuxOffscreenCapture.cpp b/Standalone/Source/LinuxOffscreenCapture.cpp index 95757cc..1dfe7f3 100644 --- a/Standalone/Source/LinuxOffscreenCapture.cpp +++ b/Standalone/Source/LinuxOffscreenCapture.cpp @@ -25,6 +25,8 @@ namespace float Position[3]; float Normal[3]; float Height; + float WorldPosition[3]; + float TileData[4]; }; struct FScene @@ -32,6 +34,8 @@ namespace std::uint32_t VoxelSizeMicrons=0; std::array CameraMeters{}; double YawDegrees=0.0,PitchDegrees=0.0; + float MinimumHeight=std::numeric_limits::infinity(); + float MaximumHeight=-std::numeric_limits::infinity(); std::vector Vertices; std::vector Indices; std::uint64_t Triangles=0,SkirtTriangles=0; @@ -70,7 +74,8 @@ namespace if(!Read(File,SurfaceVertices)||!Read(File,SkirtCount)||!Read(File,VertexCount) ||!Read(File,IndexCount)||!Read(File,MaskCount) ||VertexCount>10000000||IndexCount>60000000||MaskCount>20000000 - ||IndexCount%3!=0||(MaskCount!=0&&MaskCount!=IndexCount/3)) + ||IndexCount%3!=0||(MaskCount!=0&&MaskCount!=IndexCount/3) + ||Key[3]<0||Key[3]>8) {Error="tile mesh header is malformed";return false;} if(TotalVertices+VertexCount>std::numeric_limits::max() ||TotalIndices+IndexCount>std::numeric_limits::max()) @@ -86,6 +91,25 @@ namespace for(float& Value:Scene.Vertices[I].Normal)if(!Read(File,Value)) {Error="tile normals are truncated";return false;} Scene.Vertices[I].Height=Scene.Vertices[I].Position[2]; + Scene.MinimumHeight=std::min(Scene.MinimumHeight,Scene.Vertices[I].Height); + Scene.MaximumHeight=std::max(Scene.MaximumHeight,Scene.Vertices[I].Height); + } + const double TileVoxels=static_cast(32u<(TileVoxels + *static_cast(Scene.VoxelSizeMicrons)/1000000.0); + const float TileOriginX=static_cast(static_cast(Key[0]) + *TileExtentMeters); + const float TileOriginY=static_cast(static_cast(Key[1]) + *TileExtentMeters); + for(std::size_t I=Base;I(Key[3]); } const std::size_t IndexOffset=Scene.Indices.size(); Scene.Indices.resize(IndexOffset+IndexCount); @@ -151,8 +175,12 @@ precision highp float; layout(location=0) in vec3 inCameraPosition; layout(location=1) in vec3 inNormal; layout(location=2) in float inHeight; +layout(location=3) in vec3 inWorldPosition; +layout(location=4) in vec4 inTileData; out vec3 vNormal; out float vHeight; +out vec3 vWorldPosition; +out vec4 vTileData; uniform float uTanHalfHorizontal; uniform float uTanHalfVertical; uniform float uNear; @@ -165,19 +193,54 @@ void main() inCameraPosition.y/uTanHalfVertical,clipZ,z); vNormal=normalize(inNormal); vHeight=inHeight; + vWorldPosition=inWorldPosition; + vTileData=inTileData; })GLSL"; static constexpr char FragmentSource[]=R"GLSL(#version 300 es precision highp float; in vec3 vNormal; in float vHeight; +in vec3 vWorldPosition; +in vec4 vTileData; out vec4 outColor; +uniform int uReadableMode; +uniform int uTileEdges; +uniform float uHeightCenter; +uniform float uHeightRange; +uniform float uTileEdgeWidth; +uniform vec3 uLightDirection; void main() { vec3 n=normalize(vNormal); - vec3 normalColor=abs(n)*0.78+vec3(0.12); - float band=mod(floor((vHeight+10000.0)/80.0),2.0); - vec3 bandColor=mix(vec3(0.24,0.31,0.37),vec3(0.39,0.42,0.35),band); - outColor=vec4(mix(bandColor,normalColor,0.68),1.0); + vec3 color; + if(uReadableMode==0) + { + vec3 normalColor=abs(n)*0.78+vec3(0.12); + float band=mod(floor((vHeight+10000.0)/80.0),2.0); + vec3 bandColor=mix(vec3(0.24,0.31,0.37),vec3(0.39,0.42,0.35),band); + color=mix(bandColor,normalColor,0.68); + } + else + { + float heightT=clamp(0.5+(vHeight-uHeightCenter)/max(uHeightRange,0.001),0.0,1.0); + vec3 heightTint=mix(vec3(0.80,0.91,1.0),vec3(1.0,0.91,0.77),heightT); + float diffuse=max(dot(n,normalize(uLightDirection)),0.0); + float illumination=0.62+0.38*diffuse; + color=vec3(0.64,0.62,0.56)*heightTint*illumination; + } + if(uTileEdges!=0&&vTileData.z>0.0) + { + vec2 local=mod(vWorldPosition.xy-vTileData.xy,vTileData.z); + vec2 edge=min(local,vec2(vTileData.z)-local); + float distanceToTileEdge=min(edge.x,edge.y); + float antialias=max(fwidth(distanceToTileEdge),0.005); + float line=1.0-smoothstep(uTileEdgeWidth-antialias, + uTileEdgeWidth+antialias,distanceToTileEdge); + vec3 edgeColor=mix(vec3(0.16,0.22,0.25),vec3(0.30,0.23,0.14), + clamp(vTileData.w/8.0,0.0,1.0)); + color=mix(color,edgeColor,0.32*line); + } + outColor=vec4(color,1.0); })GLSL"; const GLuint VS=CompileShader(GL_VERTEX_SHADER,VertexSource); const GLuint FS=CompileShader(GL_FRAGMENT_SHADER,FragmentSource); @@ -224,7 +287,8 @@ void main() } bool Render(FScene& Scene,const std::filesystem::path& Output,int Width,int Height, - double HorizontalFovDegrees) + double HorizontalFovDegrees,bool ReadableMode,bool TileEdges, + double TileEdgeWidthMeters) { EGLDisplay Display=MakeDisplay(); EGLint Major=0,Minor=0; @@ -304,6 +368,10 @@ void main() reinterpret_cast(offsetof(FVertex,Normal))); glEnableVertexAttribArray(2);glVertexAttribPointer(2,1,GL_FLOAT,GL_FALSE,sizeof(FVertex), reinterpret_cast(offsetof(FVertex,Height))); + glEnableVertexAttribArray(3);glVertexAttribPointer(3,3,GL_FLOAT,GL_FALSE,sizeof(FVertex), + reinterpret_cast(offsetof(FVertex,WorldPosition))); + glEnableVertexAttribArray(4);glVertexAttribPointer(4,4,GL_FLOAT,GL_FALSE,sizeof(FVertex), + reinterpret_cast(offsetof(FVertex,TileData))); glViewport(0,0,Width,Height);glDisable(GL_CULL_FACE);glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL);glClearColor(0.035f,0.055f,0.075f,1.0f); glClearDepthf(1.0f);glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); @@ -312,6 +380,14 @@ void main() glUniform1f(glGetUniformLocation(Program,"uTanHalfVertical"),static_cast(TanHalfVertical)); glUniform1f(glGetUniformLocation(Program,"uNear"),static_cast(Near)); glUniform1f(glGetUniformLocation(Program,"uFar"),static_cast(Far)); + glUniform1i(glGetUniformLocation(Program,"uReadableMode"),ReadableMode?1:0); + glUniform1i(glGetUniformLocation(Program,"uTileEdges"),TileEdges?1:0); + glUniform1f(glGetUniformLocation(Program,"uHeightCenter"), + static_cast(Scene.CameraMeters[2])); + glUniform1f(glGetUniformLocation(Program,"uHeightRange"),64.0f); + glUniform1f(glGetUniformLocation(Program,"uTileEdgeWidth"), + static_cast(TileEdgeWidthMeters)); + glUniform3f(glGetUniformLocation(Program,"uLightDirection"),-0.42f,-0.58f,0.70f); glDrawElements(GL_TRIANGLES,static_cast(Scene.Indices.size()),GL_UNSIGNED_INT,nullptr); glFinish(); std::vector Pixels(static_cast(Width)*Height*4); @@ -323,9 +399,10 @@ void main() { std::error_code SizeError; const auto Bytes=std::filesystem::file_size(Output,SizeError); - std::printf("[OffscreenCapture] triangles=%llu tiles=%u skirts=%llu png_bytes=%llu status=PASS path=%s\n", + std::printf("[OffscreenCapture] triangles=%llu tiles=%u skirts=%llu shading=%s tile_edges=%s edge_width_m=%.3f png_bytes=%llu status=PASS path=%s\n", static_cast(Scene.Triangles),Scene.TileCount, static_cast(Scene.SkirtTriangles), + ReadableMode?"readable":"normal",TileEdges?"on":"off",TileEdgeWidthMeters, static_cast(SizeError?0:Bytes),Output.string().c_str()); } glDeleteProgram(Program);glDeleteBuffers(1,&Ebo);glDeleteBuffers(1,&Vbo); @@ -341,12 +418,13 @@ int main(int argc,char** argv) { if(argc<3) { - std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N]\n",argv[0]); + std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N] [--shade=readable|normal] [--tile-edges=on|off] [--tile-edge-width-m=N]\n",argv[0]); return 2; } int Width=2560,Height=1440;double Fov=90.0; bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0; bool HasCamera=false;std::array Camera{}; + bool ReadableMode=true,TileEdges=false;double TileEdgeWidthMeters=0.15; for(int I=3;I(Arg.size()),Arg.data());return 2;} } FScene Scene;std::string Error; @@ -388,10 +488,12 @@ int main(int argc,char** argv) if(HasCamera)Scene.CameraMeters=Camera; if(HasYaw)Scene.YawDegrees=Yaw; if(HasPitch)Scene.PitchDegrees=Pitch; - std::printf("[OffscreenScene] voxels_per_meter=%.6f camera_m=(%.3f,%.3f,%.3f) yaw=%.2f pitch=%.2f vertices=%zu triangles=%llu\n", + std::printf("[OffscreenScene] voxels_per_meter=%.6f camera_m=(%.3f,%.3f,%.3f) yaw=%.2f pitch=%.2f height_m=%.3f..%.3f shade=%s tile_edges=%s vertices=%zu triangles=%llu\n", 1000000.0/Scene.VoxelSizeMicrons,Scene.CameraMeters[0],Scene.CameraMeters[1],Scene.CameraMeters[2], - Scene.YawDegrees,Scene.PitchDegrees,Scene.Vertices.size(), + Scene.YawDegrees,Scene.PitchDegrees,Scene.MinimumHeight,Scene.MaximumHeight, + ReadableMode?"readable":"normal",TileEdges?"on":"off",Scene.Vertices.size(), static_cast(Scene.Triangles)); - return Render(Scene,argv[2],Width,Height,Fov)?0:3; + return Render(Scene,argv[2],Width,Height,Fov,ReadableMode,TileEdges, + TileEdgeWidthMeters)?0:3; }