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

Add multiple canvas sample. #389

Merged
merged 3 commits into from
Mar 11, 2024
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
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
Loading