Skip to content

Commit

Permalink
Add multiple canvas sample. (#389)
Browse files Browse the repository at this point in the history
* Add multiple canvas sample.

Note: AFAICT the stanford dragon was getting flat shading.
If that was the intent I can remove the genarateNormals code
but I assumed the intent was smooth shading.

I do kind of wonder if we should just genearte then once
and put them in the data file 😛
  • Loading branch information
greggman authored Mar 11, 2024
1 parent c7e7f34 commit 314297f
Show file tree
Hide file tree
Showing 8 changed files with 752 additions and 19 deletions.
38 changes: 20 additions & 18 deletions meshes/stanfordDragon.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
import dragonRawData from './stanfordDragonData';
import { computeSurfaceNormals, computeProjectedPlaneUVs } from './utils';
import { computeProjectedPlaneUVs, generateNormals } from './utils';

export const mesh = {
positions: dragonRawData.positions as [number, number, number][],
triangles: dragonRawData.cells as [number, number, number][],
normals: [] as [number, number, number][],
uvs: [] as [number, number][],
};

// Compute surface normals
mesh.normals = computeSurfaceNormals(mesh.positions, mesh.triangles);
const { positions, normals, triangles } = generateNormals(
Math.PI,
dragonRawData.positions as [number, number, number][],
dragonRawData.cells as [number, number, number][]
);

// Compute some easy uvs for testing
mesh.uvs = computeProjectedPlaneUVs(mesh.positions, 'xy');
const uvs = computeProjectedPlaneUVs(positions, 'xy');

// Push indices for an additional ground plane
mesh.triangles.push(
[mesh.positions.length, mesh.positions.length + 2, mesh.positions.length + 1],
[mesh.positions.length, mesh.positions.length + 1, mesh.positions.length + 3]
triangles.push(
[positions.length, positions.length + 2, positions.length + 1],
[positions.length, positions.length + 1, positions.length + 3]
);

// Push vertex attributes for an additional ground plane
// prettier-ignore
mesh.positions.push(
positions.push(
[-100, 20, -100], //
[ 100, 20, 100], //
[-100, 20, 100], //
[ 100, 20, -100]
);
mesh.normals.push(
normals.push(
[0, 1, 0], //
[0, 1, 0], //
[0, 1, 0], //
[0, 1, 0]
);
mesh.uvs.push(
uvs.push(
[0, 0], //
[1, 1], //
[0, 1], //
[1, 0]
);

export const mesh = {
positions,
triangles,
normals,
uvs,
};
166 changes: 165 additions & 1 deletion meshes/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { vec3 } from 'wgpu-matrix';
import { vec3, Vec3 } from 'wgpu-matrix';

export function computeSurfaceNormals(
positions: [number, number, number][],
Expand Down Expand Up @@ -33,6 +33,170 @@ export function computeSurfaceNormals(
return normals;
}

function makeTriangleIndicesFn(triangles: [number, number, number][]) {
let triNdx = 0;
let vNdx = 0;
const fn = function () {
const ndx = triangles[triNdx][vNdx++];
if (vNdx === 3) {
vNdx = 0;
++triNdx;
}
return ndx;
};
fn.reset = function () {
triNdx = 0;
vNdx = 0;
};
fn.numElements = triangles.length * 3;
return fn;
}

// adapted from: https://webglfundamentals.org/webgl/lessons/webgl-3d-geometry-lathe.htmls
export function generateNormals(
maxAngle: number,
positions: [number, number, number][],
triangles: [number, number, number][]
) {
// first compute the normal of each face
const getNextIndex = makeTriangleIndicesFn(triangles);
const numFaceVerts = getNextIndex.numElements;
const numVerts = positions.length;
const numFaces = numFaceVerts / 3;
const faceNormals: Vec3[] = [];

// Compute the normal for every face.
// While doing that, create a new vertex for every face vertex
for (let i = 0; i < numFaces; ++i) {
const n1 = getNextIndex();
const n2 = getNextIndex();
const n3 = getNextIndex();

const v1 = positions[n1];
const v2 = positions[n2];
const v3 = positions[n3];

faceNormals.push(
vec3.normalize(vec3.cross(vec3.subtract(v2, v1), vec3.subtract(v3, v1)))
);
}

let tempVerts = {};
let tempVertNdx = 0;

// this assumes vertex positions are an exact match

function getVertIndex(vert: [number, number, number]): number {
const vertId = JSON.stringify(vert);
const ndx = tempVerts[vertId];
if (ndx !== undefined) {
return ndx;
}
const newNdx = tempVertNdx++;
tempVerts[vertId] = newNdx;
return newNdx;
}

// We need to figure out the shared vertices.
// It's not as simple as looking at the faces (triangles)
// because for example if we have a standard cylinder
//
//
// 3-4
// / \
// 2 5 Looking down a cylinder starting at S
// | | and going around to E, E and S are not
// 1 6 the same vertex in the data we have
// \ / as they don't share UV coords.
// S/E
//
// the vertices at the start and end do not share vertices
// since they have different UVs but if you don't consider
// them to share vertices they will get the wrong normals

const vertIndices: number[] = [];
for (let i = 0; i < numVerts; ++i) {
const vert = positions[i];
vertIndices.push(getVertIndex(vert));
}

// go through every vertex and record which faces it's on
const vertFaces: number[][] = [];
getNextIndex.reset();
for (let i = 0; i < numFaces; ++i) {
for (let j = 0; j < 3; ++j) {
const ndx = getNextIndex();
const sharedNdx = vertIndices[ndx];
let faces = vertFaces[sharedNdx];
if (!faces) {
faces = [];
vertFaces[sharedNdx] = faces;
}
faces.push(i);
}
}

// now go through every face and compute the normals for each
// vertex of the face. Only include faces that aren't more than
// maxAngle different. Add the result to arrays of newPositions,
// newTexcoords and newNormals, discarding any vertices that
// are the same.
tempVerts = {};
tempVertNdx = 0;
const newPositions: [number, number, number][] = [];
const newNormals: [number, number, number][] = [];

function getNewVertIndex(
position: [number, number, number],
normal: [number, number, number]
) {
const vertId = JSON.stringify({ position, normal });
const ndx = tempVerts[vertId];
if (ndx !== undefined) {
return ndx;
}
const newNdx = tempVertNdx++;
tempVerts[vertId] = newNdx;
newPositions.push(position);
newNormals.push(normal);
return newNdx;
}

const newTriangles: [number, number, number][] = [];
getNextIndex.reset();
const maxAngleCos = Math.cos(maxAngle);
// for each face
for (let i = 0; i < numFaces; ++i) {
// get the normal for this face
const thisFaceNormal = faceNormals[i];
// for each vertex on the face
const newTriangle: number[] = [];
for (let j = 0; j < 3; ++j) {
const ndx = getNextIndex();
const sharedNdx = vertIndices[ndx];
const faces = vertFaces[sharedNdx];
const norm = [0, 0, 0] as [number, number, number];
faces.forEach((faceNdx: number) => {
// is this face facing the same way
const otherFaceNormal = faceNormals[faceNdx];
const dot = vec3.dot(thisFaceNormal, otherFaceNormal);
if (dot > maxAngleCos) {
vec3.add(norm, otherFaceNormal, norm);
}
});
vec3.normalize(norm, norm);
newTriangle.push(getNewVertIndex(positions[ndx], norm));
}
newTriangles.push(newTriangle as [number, number, number]);
}

return {
positions: newPositions,
normals: newNormals,
triangles: newTriangles,
};
}

type ProjectedPlane = 'xy' | 'xz' | 'yz';

const projectedPlane2Ids: { [key in ProjectedPlane]: [number, number] } = {
Expand Down
47 changes: 47 additions & 0 deletions sample/multipleCanvases/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>webgpu-samples: multiple canvases</title>
<style>
html, body {
margin: 0; /* remove default margin */
height: 100%; /* make body fill the browser window */
}
#outere {
width: 100%;
height: 100%;
display: block;
overflow: auto;
}
.product {
display: inline-block;
padding: 1em;
background: #888;
margin: 1em;
}
.size0>canvas {
width: 200px;
height: 200px;
}
.size1>canvas {
width: 250px;
height: 200px;
}
.size2>canvas {
width: 300px;
height: 200px;
}
.size3>canvas {
width: 100px;
height: 200px;
}
</style>
<script defer src="main.js" type="module"></script>
<script defer type="module" src="../../js/iframe-helper.js"></script>
</head>
<body>
<div id="outer"></div>
</body>
</html>
Loading

0 comments on commit 314297f

Please sign in to comment.