Skip to content

Commit

Permalink
Merge pull request #340 from RagnarokResearchLab/334-normals-debug-vi…
Browse files Browse the repository at this point in the history
…sualization

Enable visualizing surface normals with "pointy" triangles (via a new DebugDraw primitive)
  • Loading branch information
rdw-software authored Jan 27, 2024
2 parents 576b7f0 + 3b2d585 commit dff72db
Show file tree
Hide file tree
Showing 14 changed files with 157 additions and 5 deletions.
7 changes: 7 additions & 0 deletions Core/FileFormats/RagnarokMap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ local RagnarokGND = require("Core.FileFormats.RagnarokGND")
local RagnarokGRF = require("Core.FileFormats.RagnarokGRF")
local RagnarokRSW = require("Core.FileFormats.RagnarokRSW")

local NormalsVisualization = require("Core.NativeClient.DebugDraw.NormalsVisualization")

local uv = require("uv")

local format = string.format
Expand All @@ -12,6 +14,7 @@ local RagnarokMap = {
MAP_DATABASE = require("DB.Maps"),
ERROR_INVALID_MAP_ID = "No such entry exists in the map database",
ERROR_INVALID_FILE_SYSTEM = "Cannot fetch resources without a registered file system handler",
DEBUG_TERRAIN_NORMALS = false,
}

function RagnarokMap:Construct(mapID, fileSystem)
Expand All @@ -33,6 +36,10 @@ function RagnarokMap:Construct(mapID, fileSystem)
local groundMeshSections = self:LoadTerrainGeometry(mapID)
for sectionID, groundMeshSection in ipairs(groundMeshSections) do
table_insert(scene.meshes, groundMeshSection)
if self.DEBUG_TERRAIN_NORMALS then
local normalsVisualization = NormalsVisualization(groundMeshSection)
table_insert(scene.meshes, normalsVisualization)
end
end

local waterPlanes = self:LoadWaterSurface(mapID)
Expand Down
17 changes: 14 additions & 3 deletions Core/NativeClient/DebugDraw/Box.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ function Box:Construct(creationOptions)
Vector3D(-halfWidth + translation.x, halfHeight + translation.y, halfDepth + translation.z),
}

local faceNormals = {
Vector3D(0, 0, -1), -- Front
Vector3D(0, 0, 1), -- Back
Vector3D(0, -1, 0), -- Bottom
Vector3D(0, 1, 0), -- Top
Vector3D(-1, 0, 0), -- Left
Vector3D(1, 0, 0), -- Right
}

local faceColors = {
Color.RED,
Color.GREEN,
Expand All @@ -49,6 +58,7 @@ function Box:Construct(creationOptions)

for faceID, face in ipairs(faceIndices) do
local faceColor = faceColors[faceID]
local normal = faceNormals[faceID]
for _, index in ipairs(face) do
local vertex = cornerVertices[index]
tinsert(boxMesh.vertexPositions, vertex.x)
Expand All @@ -58,6 +68,10 @@ function Box:Construct(creationOptions)
tinsert(boxMesh.vertexColors, faceColor.red)
tinsert(boxMesh.vertexColors, faceColor.green)
tinsert(boxMesh.vertexColors, faceColor.blue)

tinsert(boxMesh.surfaceNormals, normal.x)
tinsert(boxMesh.surfaceNormals, normal.y)
tinsert(boxMesh.surfaceNormals, normal.z)
end

local baseIndex = (faceID - 1) * 4
Expand All @@ -72,9 +86,6 @@ function Box:Construct(creationOptions)
for _ = 1, #boxMesh.vertexPositions / 3 do
tinsert(boxMesh.diffuseTextureCoords, 0)
tinsert(boxMesh.diffuseTextureCoords, 0)
tinsert(boxMesh.surfaceNormals, 0)
tinsert(boxMesh.surfaceNormals, 1) -- Placeholder (uses unlit material, anyway)
tinsert(boxMesh.surfaceNormals, 0)
end

return boxMesh
Expand Down
71 changes: 71 additions & 0 deletions Core/NativeClient/DebugDraw/NormalsVisualization.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
local Mesh = require("Core.NativeClient.WebGPU.Mesh")

local table_insert = table.insert

local NormalsVisualization = {
TRIANGLE_WIDTH = 0.1,
TRIANGLE_LENGTH = 1,
color = {
red = 1,
green = 0x7F / 255,
blue = 1,
},
}
function NormalsVisualization:Construct(mesh)
local visualizationMesh = Mesh(mesh.displayName .. "NormalsVisualization")

for index = 1, #mesh.surfaceNormals, 3 do
local vertex = {
x = mesh.vertexPositions[index + 0],
y = mesh.vertexPositions[index + 1],
z = mesh.vertexPositions[index + 2],
}
local surfaceNormal = {
x = mesh.surfaceNormals[index + 0],
y = mesh.surfaceNormals[index + 1],
z = mesh.surfaceNormals[index + 2],
}

local nextAvailableVertexID = #visualizationMesh.vertexPositions / 3
table_insert(visualizationMesh.vertexPositions, vertex.x)
table_insert(visualizationMesh.vertexPositions, vertex.y)
table_insert(visualizationMesh.vertexPositions, vertex.z)
table_insert(visualizationMesh.vertexPositions, vertex.x + surfaceNormal.x * self.TRIANGLE_LENGTH)
-- There's no ceilings, so all flat surfaces must be facing up (normal will have a positive Y component)
table_insert(visualizationMesh.vertexPositions, vertex.y + surfaceNormal.y * self.TRIANGLE_LENGTH)
table_insert(visualizationMesh.vertexPositions, vertex.z + surfaceNormal.z * self.TRIANGLE_LENGTH)
table_insert(visualizationMesh.vertexPositions, vertex.x - self.TRIANGLE_WIDTH)
table_insert(visualizationMesh.vertexPositions, vertex.y - self.TRIANGLE_WIDTH)
table_insert(visualizationMesh.vertexPositions, vertex.z - self.TRIANGLE_WIDTH)

for i = 1, 3, 1 do
table_insert(visualizationMesh.vertexColors, self.color.red)
table_insert(visualizationMesh.vertexColors, self.color.green)
table_insert(visualizationMesh.vertexColors, self.color.blue)
end

table_insert(visualizationMesh.triangleConnections, nextAvailableVertexID)
table_insert(visualizationMesh.triangleConnections, nextAvailableVertexID + 1)
table_insert(visualizationMesh.triangleConnections, nextAvailableVertexID + 2)

for i = 1, 3, 1 do
table_insert(visualizationMesh.diffuseTextureCoords, 0)
table_insert(visualizationMesh.diffuseTextureCoords, 0)
end

for i = 1, 3, 1 do
-- Not used (material is unlit)
table_insert(visualizationMesh.surfaceNormals, 0)
table_insert(visualizationMesh.surfaceNormals, 0)
table_insert(visualizationMesh.surfaceNormals, 0)
end
end

return visualizationMesh
end

NormalsVisualization.__call = NormalsVisualization.Construct
NormalsVisualization.__index = NormalsVisualization
setmetatable(NormalsVisualization, NormalsVisualization)

return NormalsVisualization
3 changes: 3 additions & 0 deletions Core/NativeClient/DebugDraw/Scenes/cube3d.lua
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
local Box = require("Core.NativeClient.DebugDraw.Box")
local Plane = require("Core.NativeClient.DebugDraw.Plane")
local NormalsVisualization = require("Core.NativeClient.DebugDraw.NormalsVisualization")
local WorldAxis = require("Core.NativeClient.DebugDraw.WorldAxis")

local worldAxesVisualizationMesh = WorldAxis()

local cubeMesh = Box({ translation = { x = 0, y = 0, z = 0 } })
local groundMesh = Plane({ dimensions = { x = 20, z = 20 }, translation = { x = 0, y = -2, z = 0 } })
local normalsVisualization = NormalsVisualization(cubeMesh)

local scene = {
displayName = "Cube3D (demo)",
meshes = {
worldAxesVisualizationMesh,
cubeMesh,
groundMesh,
normalsVisualization,
},
}

Expand Down
3 changes: 2 additions & 1 deletion Core/NativeClient/WebGPU/GPU.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ local ffi_string = ffi.string
local GPU = {
MAX_VERTEX_COUNT = 200000, -- Should be configurable (later)
MAX_TEXTURE_ARRAY_SIZE = 32,
MAX_BUFFER_SIZE = 256 * 1024 * 1024,
}

-- The FFI bindings don't provide enums for native extensions yet (requires a fix in the runtime)
Expand Down Expand Up @@ -116,7 +117,7 @@ function GPU:RequestLogicalDevice(adapter, options)
maxVertexAttributes = 4, -- Vertex positions, vertex colors, diffuse texture UVs, normals
maxVertexBuffers = 4, -- Vertex positions, vertex colors, diffuse texture UVs, normals
maxInterStageShaderComponents = 8, -- #(vec3f color, vec2f diffuseTextureCoords, float alpha), normal(vec3f)
maxBufferSize = self.MAX_VERTEX_COUNT * 5 * ffi.sizeof("float"), -- #(vec3f position, vec2f transform)
maxBufferSize = GPU.MAX_BUFFER_SIZE, -- DEFAULT
maxVertexBufferArrayStride = 20, -- #(Rml::Vertex)
maxBindGroups = 3, -- Camera, material, transforms
maxUniformBuffersPerShaderStage = 1, -- Camera properties (increase for material, soon?)
Expand Down
2 changes: 2 additions & 0 deletions Tests/Fixtures/Snapshots/normals-visualization-indices.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- stylua: ignore
return{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71}
2 changes: 2 additions & 0 deletions Tests/Fixtures/Snapshots/normals-visualization-positions.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- stylua: ignore
return{-0.5,-0.5,-0.5,-0.5,-0.5,-1.5,-0.6,-0.6,-0.6,0.5,-0.5,-0.5,0.5,-0.5,-1.5,0.4,-0.6,-0.6,0.5,0.5,-0.5,0.5,0.5,-1.5,0.4,0.4,-0.6,-0.5,0.5,-0.5,-0.5,0.5,-1.5,-0.6,0.4,-0.6,-0.5,-0.5,0.5,-0.5,-0.5,1.5,-0.6,-0.6,0.4,0.5,-0.5,0.5,0.5,-0.5,1.5,0.4,-0.6,0.4,0.5,0.5,0.5,0.5,0.5,1.5,0.4,0.4,0.4,-0.5,0.5,0.5,-0.5,0.5,1.5,-0.6,0.4,0.4,-0.5,-0.5,-0.5,-0.5,-1.5,-0.5,-0.6,-0.6,-0.6,0.5,-0.5,-0.5,0.5,-1.5,-0.5,0.4,-0.6,-0.6,0.5,-0.5,0.5,0.5,-1.5,0.5,0.4,-0.6,0.4,-0.5,-0.5,0.5,-0.5,-1.5,0.5,-0.6,-0.6,0.4,0.5,0.5,-0.5,0.5,1.5,-0.5,0.4,0.4,-0.6,-0.5,0.5,-0.5,-0.5,1.5,-0.5,-0.6,0.4,-0.6,-0.5,0.5,0.5,-0.5,1.5,0.5,-0.6,0.4,0.4,0.5,0.5,0.5,0.5,1.5,0.5,0.4,0.4,0.4,-0.5,-0.5,-0.5,-1.5,-0.5,-0.5,-0.6,-0.6,-0.6,-0.5,0.5,-0.5,-1.5,0.5,-0.5,-0.6,0.4,-0.6,-0.5,0.5,0.5,-1.5,0.5,0.5,-0.6,0.4,0.4,-0.5,-0.5,0.5,-1.5,-0.5,0.5,-0.6,-0.6,0.4,0.5,-0.5,-0.5,1.5,-0.5,-0.5,0.4,-0.6,-0.6,0.5,0.5,-0.5,1.5,0.5,-0.5,0.4,0.4,-0.6,0.5,0.5,0.5,1.5,0.5,0.5,0.4,0.4,0.4,0.5,-0.5,0.5,1.5,-0.5,0.5,0.4,-0.6,0.4}
2 changes: 2 additions & 0 deletions Tests/Fixtures/Snapshots/scaled-cube-normals.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- stylua: ignore
return{0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0}
2 changes: 2 additions & 0 deletions Tests/Fixtures/Snapshots/translated-cube-normals.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- stylua: ignore
return{0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0}
2 changes: 2 additions & 0 deletions Tests/Fixtures/Snapshots/unit-cube-normals.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- stylua: ignore
return {0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0}
6 changes: 6 additions & 0 deletions Tests/NativeClient/DebugDraw/Box.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ describe("Box", function()

local expectedVertexPositions = require("Tests.Fixtures.Snapshots.unit-cube-positions")
local expectedVertexIndices = require("Tests.Fixtures.Snapshots.unit-cube-indices")
local expectedSurfaceNormals = require("Tests.Fixtures.Snapshots.unit-cube-normals")

assertEquals(boxGeometry.vertexPositions, expectedVertexPositions)
assertEquals(boxGeometry.triangleConnections, expectedVertexIndices)
assertEquals(boxGeometry.surfaceNormals, expectedSurfaceNormals)
end)

it("should generate a scaled box if non-unit dimensions were passed", function()
Expand All @@ -18,9 +20,11 @@ describe("Box", function()

local expectedVertexPositions = require("Tests.Fixtures.Snapshots.scaled-cube-positions")
local expectedVertexIndices = require("Tests.Fixtures.Snapshots.scaled-cube-indices")
local expectedSurfaceNormals = require("Tests.Fixtures.Snapshots.scaled-cube-normals")

assertEquals(boxGeometry.vertexPositions, expectedVertexPositions)
assertEquals(boxGeometry.triangleConnections, expectedVertexIndices)
assertEquals(boxGeometry.surfaceNormals, expectedSurfaceNormals)
end)

it("should bake in the translation vector if a table value was passed", function()
Expand All @@ -29,9 +33,11 @@ describe("Box", function()

local expectedVertexPositions = require("Tests.Fixtures.Snapshots.translated-cube-positions")
local expectedVertexIndices = require("Tests.Fixtures.Snapshots.translated-cube-indices")
local expectedSurfaceNormals = require("Tests.Fixtures.Snapshots.translated-cube-normals")

assertEquals(boxGeometry.vertexPositions, expectedVertexPositions)
assertEquals(boxGeometry.triangleConnections, expectedVertexIndices)
assertEquals(boxGeometry.surfaceNormals, expectedSurfaceNormals)
end)
end)
end)
42 changes: 42 additions & 0 deletions Tests/NativeClient/DebugDraw/NormalsVisualization.spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
local Box = require("Core.NativeClient.DebugDraw.Box")
local NormalsVisualization = require("Core.NativeClient.DebugDraw.NormalsVisualization")

local function table_fill(value, count)
local filledTable = table.new(count, 0)
for index = 1, count, 1 do
filledTable[index] = value
end
return filledTable
end

describe("NormalsVisualization", function()
describe("Construct", function()
it("should return a new mesh that includes triangles aligned with all normals of the input mesh", function()
local box = Box()
local visualizationMesh = NormalsVisualization(box)

assertEquals(visualizationMesh.displayName, "BoxNormalsVisualization")
assertEquals(
visualizationMesh.vertexPositions,
require("Tests.Fixtures.Snapshots.normals-visualization-positions")
)
assertEquals(
visualizationMesh.triangleConnections,
require("Tests.Fixtures.Snapshots.normals-visualization-indices")
)

local expectedColors = {}
for index = 1, #visualizationMesh.vertexPositions / 3, 1 do
table.insert(expectedColors, NormalsVisualization.color.red)
table.insert(expectedColors, NormalsVisualization.color.green)
table.insert(expectedColors, NormalsVisualization.color.blue)
end
assertEquals(visualizationMesh.vertexColors, expectedColors)
assertEquals(
visualizationMesh.diffuseTextureCoords,
table_fill(0, 2 * #visualizationMesh.vertexPositions / 3)
)
assertEquals(visualizationMesh.surfaceNormals, table_fill(0, 3 * #visualizationMesh.vertexPositions / 3))
end)
end)
end)
2 changes: 1 addition & 1 deletion Tests/NativeClient/Renderer.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ describe("Renderer", function()
it("should all meshes to the scene", function()
local scene = require("Core.NativeClient.DebugDraw.Scenes.cube3d")
Renderer:LoadSceneObjects(scene)
assertEquals(#Renderer.meshes, 3)
assertEquals(#Renderer.meshes, 4)
Renderer:LoadSceneObjects(scene)
end)

Expand Down
1 change: 1 addition & 0 deletions Tests/unit-test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ local specFiles = {
"Tests/NativeClient/DebugDraw/Cone.spec.lua",
"Tests/NativeClient/DebugDraw/Cylinder.spec.lua",
"Tests/NativeClient/DebugDraw/DebugScene.spec.lua",
"Tests/NativeClient/DebugDraw/NormalsVisualization.spec.lua",
"Tests/NativeClient/DebugDraw/Plane.spec.lua",
"Tests/NativeClient/DebugDraw/Pyramid.spec.lua",
"Tests/NativeClient/DebugDraw/Sphere.spec.lua",
Expand Down

0 comments on commit dff72db

Please sign in to comment.