Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Vertex Normals Support #3944

Merged
merged 1 commit into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/viewer-sandbox/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,9 @@ const getStream = () => {

// v2 colored lines
// 'https://app.speckle.systems/projects/052b576a45/models/c756235fcc'

// Custom normals
// 'https://latest.speckle.systems/projects/51c449c440/models/08e97226cf'
)
}

Expand Down
11 changes: 10 additions & 1 deletion packages/viewer/src/modules/batching/InstancedMeshBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,9 @@ export class InstancedMeshBatch implements Batch {
const colors: number[] | undefined =
this.renderViews[0].renderData.geometry.attributes?.COLOR

const normals: number[] | undefined =
this.renderViews[0].renderData.geometry.attributes?.NORMAL

/** Catering to typescript
* There is no unniverse where indices or positions are undefined at this point
*/
Expand All @@ -573,6 +576,7 @@ export class InstancedMeshBatch implements Batch {
? new Uint32Array(indices)
: new Uint16Array(indices),
new Float64Array(positions),
normals ? new Float32Array(normals) : undefined,
colors ? new Float32Array(colors) : undefined
)
this.mesh = new SpeckleInstancedMesh(this.geometry)
Expand Down Expand Up @@ -631,6 +635,7 @@ export class InstancedMeshBatch implements Batch {
private makeInstancedMeshGeometry(
indices: Uint32Array | Uint16Array,
position: Float64Array,
normal?: Float32Array,
color?: Float32Array
): BufferGeometry {
this.geometry = new BufferGeometry()
Expand All @@ -655,7 +660,11 @@ export class InstancedMeshBatch implements Batch {

this.instanceGradientBuffer = new Float32Array(this.renderViews.length)

Geometry.computeVertexNormals(this.geometry, position)
if (normal) {
this.geometry
.setAttribute('normal', new Float32BufferAttribute(normal, 3))
.normalizeNormals()
} else Geometry.computeVertexNormals(this.geometry, position)

Geometry.updateRTEGeometry(this.geometry, position)

Expand Down
25 changes: 24 additions & 1 deletion packages/viewer/src/modules/batching/MeshBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ export class MeshBatch extends PrimitiveBatch {
const color = new Float32Array(hasVertexColors ? attributeCount : 0)
color.fill(1)
const batchIndices = new Float32Array(attributeCount / 3)
const normals = new Float32Array(attributeCount)

let offset = 0
let arrayOffset = 0
Expand All @@ -234,6 +235,21 @@ export class MeshBatch extends PrimitiveBatch {
)
position.set(geometry.attributes.POSITION, offset)
if (geometry.attributes.COLOR) color.set(geometry.attributes.COLOR, offset)

/** We either copy over the provided vertex normals */
if (geometry.attributes.NORMAL) {
normals.set(geometry.attributes.NORMAL, offset)
} else {
/** Either we compute them ourselves */
Geometry.computeVertexNormalsBuffer(
normals.subarray(
offset,
offset + geometry.attributes.POSITION.length
) as unknown as number[],
geometry.attributes.POSITION,
geometry.attributes.INDEX
)
}
batchIndices.fill(
k,
offset / 3,
Expand All @@ -258,6 +274,7 @@ export class MeshBatch extends PrimitiveBatch {
const geometry = this.makeMeshGeometry(
indices,
position,
normals,
batchIndices,
hasVertexColors ? color : undefined
)
Expand Down Expand Up @@ -285,6 +302,7 @@ export class MeshBatch extends PrimitiveBatch {
protected makeMeshGeometry(
indices: Uint32Array | Uint16Array,
position: Float64Array,
normals: Float32Array,
batchIndices: Float32Array,
color?: Float32Array
): BufferGeometry {
Expand All @@ -306,6 +324,12 @@ export class MeshBatch extends PrimitiveBatch {
geometry.setAttribute('position', new Float32BufferAttribute(position, 3))
}

if (normals) {
geometry
.setAttribute('normal', new Float32BufferAttribute(normals, 3))
.normalizeNormals()
}

if (batchIndices) {
geometry.setAttribute('objIndex', new Float32BufferAttribute(batchIndices, 1))
}
Expand All @@ -319,7 +343,6 @@ export class MeshBatch extends PrimitiveBatch {
this.gradientIndexBuffer.setUsage(DynamicDrawUsage)
geometry.setAttribute('gradientIndex', this.gradientIndexBuffer)

Geometry.computeVertexNormals(geometry, position)
Geometry.updateRTEGeometry(geometry, position)

return geometry
Expand Down
51 changes: 51 additions & 0 deletions packages/viewer/src/modules/converter/Geometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,57 @@ export class Geometry {
}
}

/** Only supports indexed geometry */
public static computeVertexNormalsBuffer(
buffer: number[],
position: number[],
index: number[]
) {
const pA = new Vector3(),
pB = new Vector3(),
pC = new Vector3()
const nA = new Vector3(),
nB = new Vector3(),
nC = new Vector3()
const cb = new Vector3(),
ab = new Vector3()

// indexed elements
for (let i = 0, il = index.length; i < il; i += 3) {
const vA = index[i + 0]
const vB = index[i + 1]
const vC = index[i + 2]

pA.fromArray(position, vA * 3)
pB.fromArray(position, vB * 3)
pC.fromArray(position, vC * 3)

cb.subVectors(pC, pB)
ab.subVectors(pA, pB)
cb.cross(ab)

nA.fromArray(buffer, vA * 3)
nB.fromArray(buffer, vB * 3)
nC.fromArray(buffer, vC * 3)

nA.add(cb)
nB.add(cb)
nC.add(cb)

buffer[vA * 3] = nA.x
buffer[vA * 3 + 1] = nA.y
buffer[vA * 3 + 2] = nA.z

buffer[vB * 3] = nB.x
buffer[vB * 3 + 1] = nB.y
buffer[vB * 3 + 2] = nB.z

buffer[vC * 3] = nC.x
buffer[vC * 3 + 1] = nC.y
buffer[vC * 3 + 2] = nC.z
}
}

public static computeVertexNormals(
buffer: BufferGeometry,
doublePositions: Float64Array
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,9 @@ export default class SpeckleConverter {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
node.model.raw.colors = await this.dechunk(obj.colors)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
node.model.raw.vertexNormals = await this.dechunk(obj.vertexNormals)
}

private async TextToNode(_obj: SpeckleObject, _node: TreeNode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ export class SpeckleGeometryConverter extends GeometryConverter {
const vertices = node.raw.vertices
const faces = node.raw.faces
const colorsRaw = node.raw.colors
let normals = node.raw.vertexNormals
let colors = undefined
let k = 0
while (k < faces.length) {
Expand Down Expand Up @@ -291,11 +292,21 @@ export class SpeckleGeometryConverter extends GeometryConverter {
colors = this.unpackColors(colorsRaw, true)
}

if (normals && normals.length !== 0) {
if (normals.length !== vertices.length) {
Logger.warn(
`Mesh (id ${node.raw.id}) normals are mismatched with vertice counts. The number of normals must equal the number of vertices.`
)
normals = undefined
}
}

return {
attributes: {
POSITION: vertices,
INDEX: indices,
...(colors && { COLOR: colors })
...(colors && { COLOR: colors }),
...(normals && { NORMAL: normals })
},
bakeTransform: new Matrix4().makeScale(
conversionFactor,
Expand Down
Loading