Skip to content

Commit

Permalink
Merge pull request #1 from structureio/feature/metal-visualization
Browse files Browse the repository at this point in the history
Metal visualization
  • Loading branch information
makiavell authored May 22, 2023
2 parents abca800 + 5ee43a3 commit 38cf355
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 3 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## Unreleased

## 1.0.4

### Added
* Added ThickLine shader
* Option to simulate face culling in XRay shader

## 1.0.3

### Changed
Expand Down
49 changes: 49 additions & 0 deletions Sources/StructureKit/Metal/STKMeshBuffers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,53 @@ extension STKMeshBuffers {
}
}

public func update(thickLine: [vector_float3], colors: [vector_float3]) {
clear()

vertexType = vector_float3()
indexType = UInt32()

guard thickLine.count > 1 else { return }

// vertices: for every point (.) generate 4 vertices with offset for the previous and the next line segments,
// see the diagram below:
// /
// --\--| /
// \ . /
// ----\|/
let vertices: [vector_float3] = thickLine.flatMap { p in [p, p, p, p] }
nVertex = vertices.count
vertexBuffer = device.makeBuffer(
bytes: vertices, length: vertices.count * MemoryLayout<vector_float3>.stride, options: [])

// use normals buffers for line directions..
var lineDir: [vector_float3] = []
for i in 0..<thickLine.count - 1 {
lineDir.append(thickLine[i + 1] - thickLine[i])
}
lineDir.append(thickLine[thickLine.count - 1] - thickLine[thickLine.count - 2])
lineDir = lineDir.flatMap { p in [p, p, p, p] }

for i in 1..<thickLine.count - 1 {
lineDir[i * 4 + 0] = lineDir[(i - 1) * 4 + 2]
lineDir[i * 4 + 1] = lineDir[(i - 1) * 4 + 3]
}

normalBuffer = device.makeBuffer(
bytes: lineDir, length: lineDir.count * MemoryLayout<vector_float3>.stride, options: [])

// colors
if colors.count == thickLine.count {
let _colors: [vector_float3] = colors.flatMap { p in [p, p, p, p] }
colorBuffer = device.makeBuffer(
bytes: _colors, length: _colors.count * MemoryLayout<vector_float3>.stride, options: [])
}

// indices
let indices = Array((0..<UInt32(vertices.count)))

nTriangle = indices.count
indexBuffer = device.makeBuffer(bytes: indices, length: indices.count * MemoryLayout<UInt32>.stride, options: [])
}

}
108 changes: 106 additions & 2 deletions Sources/StructureKit/Metal/STKMeshRenderers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public protocol STKShader {

// Renders a mesh as a solid body, in grayscale by default, uses the mesh normals to calculate light
public class STKMeshRendererSolid: STKShader {
private var depthStencilState: MTLDepthStencilState
var depthStencilState: MTLDepthStencilState
private var pipelineState: MTLRenderPipelineState

public init(colorFormat: MTLPixelFormat, depthFormat: MTLPixelFormat, device: MTLDevice) {
Expand Down Expand Up @@ -165,7 +165,8 @@ public class STKMeshRendererWireframe: STKShader {
worldModelMatrix: float4x4,
projectionMatrix: float4x4,
useXray: Bool = true,
color: vector_float4 = vector_float4(1, 1, 1, 1)
color: vector_float4 = vector_float4(1, 1, 1, 1),
hideBackFaces: Bool = false
) {
guard node.vertexType is GLKVector3,
node.indexType is UInt32
Expand All @@ -179,7 +180,20 @@ public class STKMeshRendererWireframe: STKShader {
let normalsBuffer = node.normals()
else { return }

let solid = STKShaderManager.solid
if hideBackFaces {
// use solid shader to fill the depth buffer where necessary
commandEncoder.setDepthBias(0.01, slopeScale: 1.0, clamp: 0.01)
solid.render(
commandEncoder, node: node, worldModelMatrix: worldModelMatrix, projectionMatrix: projectionMatrix,
color: vector_float4(0, 0, 0, 0))
commandEncoder.setDepthBias(0, slopeScale: 0, clamp: 0)
}

commandEncoder.pushDebugGroup("RenderMeshXray")
if hideBackFaces {
commandEncoder.setDepthStencilState(solid.depthStencilState)
}
commandEncoder.setRenderPipelineState(pipelineState)

// set buffers
Expand Down Expand Up @@ -579,3 +593,93 @@ public class STKScanMeshRenderer {
}

}

public class STKMeshRendererThickLines: STKShader {
private var depthStencilState: MTLDepthStencilState
private var pipelineState: MTLRenderPipelineState

init(colorFormat: MTLPixelFormat, depthFormat: MTLPixelFormat, device: MTLDevice) {
depthStencilState = makeDepthStencilState(device)

let vertexDescriptor = MTLVertexDescriptor()
vertexDescriptor.attributes[0] = MTLVertexAttributeDescriptor(bufferIndex: 0, offset: 0, format: .float3) // vertices
vertexDescriptor.attributes[1] = MTLVertexAttributeDescriptor(
bufferIndex: Int(STKVertexAttrAddition.rawValue), offset: 0, format: .float3) // colors
vertexDescriptor.layouts[0].stride = MemoryLayout<vector_float3>.stride
vertexDescriptor.layouts[1].stride = MemoryLayout<vector_float3>.stride

pipelineState = makePipeline(
device,
"vertexThickLine",
"fragmentThickLine",
vertexDescriptor,
colorFormat,
depthFormat,
blending: false)
}

convenience init(view: MTKView, device: MTLDevice) {
self.init(colorFormat: view.colorPixelFormat, depthFormat: view.depthStencilPixelFormat, device: device)
}

public func render(
_ commandEncoder: MTLRenderCommandEncoder,
node: STKDrawableObject,
worldModelMatrix: float4x4,
projectionMatrix: float4x4
) {
render(
commandEncoder, node: node, worldModelMatrix: worldModelMatrix, projectionMatrix: projectionMatrix, width: 3.0)
}

public func render(
_ commandEncoder: MTLRenderCommandEncoder,
node: STKDrawableObject,
worldModelMatrix: float4x4,
projectionMatrix: float4x4,
width: Float
) {
guard node.vertexType is vector_float3,
node.indexType is UInt32
else {
assertionFailure("Type mismatch")
return
}

guard let vertexBuffer = node.vertices(),
let colorsBuffer = node.colors(),
let indexBuffer = node.indices(),
let lineDir = node.normals(),
node.triangleCount() > 0
else { return }

commandEncoder.pushDebugGroup("RenderThickLines")
commandEncoder.setRenderPipelineState(pipelineState)
commandEncoder.setDepthStencilState(depthStencilState)
commandEncoder.setCullMode(MTLCullMode.none)

commandEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: Int(STKVertexAttrPosition.rawValue))
commandEncoder.setVertexBuffer(colorsBuffer, offset: 0, index: Int(STKVertexAttrAddition.rawValue))
commandEncoder.setVertexBuffer(lineDir, offset: 0, index: 3)

// set uniforms
let indexCount = node.triangleCount()
let nodeModelMatrix = worldModelMatrix * node.modelMatrix()
var uniforms = STKUniformsThickLine(
modelViewMatrix: nodeModelMatrix, projectionMatrix: projectionMatrix, color: vector_float4(1, 1, 1, 1),
width: width)
commandEncoder.setVertexBytes(
&uniforms, length: MemoryLayout<STKUniformsThickLine>.stride, index: Int(STKVertexBufferIndexUniforms.rawValue))

// commandEncoder.setDepthBias(-1, slopeScale: 0, clamp: 0)
commandEncoder.drawIndexedPrimitives(
type: .triangleStrip,
indexCount: indexCount,
indexType: .uint32,
indexBuffer: indexBuffer,
indexBufferOffset: 0)
// commandEncoder.setDepthBias(0, slopeScale: 0, clamp: 0)

commandEncoder.popDebugGroup()
}
}
7 changes: 7 additions & 0 deletions Sources/StructureKit/Metal/STKMetalData.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ struct STKUniformsMeshWireframe
bool useXray;
};

struct STKUniformsThickLine {
matrix_float4x4 modelViewMatrix;
matrix_float4x4 projectionMatrix;
vector_float4 color;
float width;
};

struct STKVertexTex
{
vector_float3 position;
Expand Down
3 changes: 3 additions & 0 deletions Sources/StructureKit/Metal/STKShaderManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@ public class STKShaderManager {
public static let pointCloud = STKMeshRendererPoints(
colorFormat: pixelFormat, depthFormat: depthFormat, device: device)
public static let lines = STKMeshRendererLines(colorFormat: pixelFormat, depthFormat: depthFormat, device: device)
public static let thickLine = STKMeshRendererThickLines(
colorFormat: pixelFormat, depthFormat: depthFormat, device: device)

}
83 changes: 83 additions & 0 deletions Sources/StructureKit/Metal/STKThickLine.metal
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// StructureKit - A collection of extension utilities for Structure SDK
// Copyright 2022 XRPro, LLC. All rights reserved.
// http://structure.io
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of XRPro, LLC nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#include "STKMetalCommon.h"
#include "STKMetalData.h"

#include <metal_stdlib>
using namespace metal;

struct VertexOut
{
float4 position [[position]];
float4 color;
};


vertex VertexOut vertexThickLine(constant float3* vertices [[buffer(0)]],
constant float3* color [[buffer(STKVertexAttrAddition)]],
const device STKUniformsThickLine& uniforms [[buffer(STKVertexBufferIndexUniforms)]],
constant float3* dirLine [[buffer(3)]],
uint v_id [[vertex_id]])
{
float sign = v_id % 2 ? -1 : 1; // should point be up or down in line

VertexOut vert;
vert.color = float4(color[v_id], 1); //pass the color data to fragment shader
vert.position = uniforms.projectionMatrix * uniforms.modelViewMatrix * float4(vertices[v_id], 1); //position of the point

float3 pointCurrent = vertices[v_id];
float3 pointNext = pointCurrent + normalize(dirLine[v_id]);

//calculate MVP transform for both points
float4 currentProjection = uniforms.projectionMatrix * uniforms.modelViewMatrix * float4(pointCurrent, 1.0);
float4 nextProjection = uniforms.projectionMatrix * uniforms.modelViewMatrix * float4(pointNext, 1.0);

float2 aspect = float2(uniforms.projectionMatrix[1][1] / uniforms.projectionMatrix[0][0], 1); //aspect ratio

//get 2d position in screen space
float2 currentScreen = currentProjection.xy / currentProjection.w * aspect;
float2 nextScreen = nextProjection.xy / nextProjection.w * aspect;

float2 dirScreen = normalize(nextScreen - currentScreen); // line direction in screen space
float2 normal = float2(-dirScreen.y, dirScreen.x); // vector of direction of thickness
normal /= aspect;

//get thickness in pixels in screen space
float thickness = uniforms.width / 500;

//move current point up or down, by thickness, with the same distance independent on depth
vert.position += float4(sign*normal*thickness*vert.position.w, 0, 0 );

return vert;
}

fragment float4 fragmentThickLine(VertexOut in [[stage_in]])
{
return in.color;
}
2 changes: 1 addition & 1 deletion Sources/StructureKit/STKCommon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public func makeDepthStencilState(_ device: MTLDevice) -> MTLDepthStencilState {
depthStencilDescriptor.depthCompareFunction = .less
depthStencilDescriptor.isDepthWriteEnabled = true
return device.makeDepthStencilState(descriptor: depthStencilDescriptor)!
}
}

public func makePipeline(
_ device: MTLDevice,
Expand Down

0 comments on commit 38cf355

Please sign in to comment.