30
loading...
This website collects cookies to deliver better user experience
export const facetQuad = {
positions: [
-0.5, -0.5, 0,
0.5, -0.5, 0,
0.5, 0.5, 0,
-0.5, -0.5, 0,
0.5, 0.5, 0,
-0.5, 0.5, 0
],
colors: [
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0
],
uvs: [
0, 1,
1, 1,
1, 0,
0, 1,
1, 1,
0, 0
],
normals: [
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
],
triangles: [
0, 1, 2,
3, 4, 5
],
textureName: "smile"
};
export function triangleCentroid(pointA, pointB, pointC){
return [
pointA[0] + pointB[0] + pointC[0] / 3,
pointA[1] + pointB[1] + pointC[1] / 3,
pointA[2] + pointB[2] + pointC[2] / 3,
];
}
//facetQuad
centroids: [
0.17, -0.17, 0,
0.17, -0.17, 0,
0.17, -0.17, 0,
-0.17, 0.17, 0,
-0.17, 0.17, 0,
-0.17, 0.17, 0,
],
//gl-helper.js
export function bindAttribute(context, attributes, attributeName, size){
const attributeLocation = context.getAttribLocation(context.getParameter(context.CURRENT_PROGRAM), attributeName);
if(attributeLocation === -1) return; //bail if it doesn't exist in the shader
const attributeBuffer = context.createBuffer();
context.bindBuffer(context.ARRAY_BUFFER, attributeBuffer);
context.bufferData(context.ARRAY_BUFFER, attributes, context.STATIC_DRAW);
context.enableVertexAttribArray(attributeLocation);
context.vertexAttribPointer(attributeLocation, size, context.FLOAT, false, 0, 0);
}
//wc-geo-gl.js
bindMesh(mesh){
bindAttribute(this.context, mesh.positions, "aVertexPosition", 3);
bindAttribute(this.context, mesh.colors, "aVertexColor", 3);
bindAttribute(this.context, mesh.uvs, "aVertexUV", 2);
bindAttribute(this.context, mesh.normals, "aVertexNormal", 3);
if(mesh.centroids){
bindAttribute(this.context, mesh.centroids, "aVertexCentroid", 3);
}
this.bindIndices(mesh.triangles);
this.bindUniforms(mesh.getModelMatrix());
this.bindTexture(mesh.textureName);
}
centroid
property to Mesh
to make sure it's available for use. Since this is really esoteric and only works for faceted meshes I might delete it later but it's very useful here.//vertex shader
uniform mat4 uProjectionMatrix;
uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform lowp mat4 uLight1;
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
attribute vec2 aVertexUV;
attribute vec3 aVertexNormal;
attribute vec3 aVertexCentroid;
varying mediump vec4 vColor;
varying mediump vec2 vUV;
varying mediump vec3 vNormal;
varying mediump vec3 vPosition;
void main(){
mediump vec3 normalCentroid = vec3(uModelMatrix * vec4(aVertexCentroid, 1.0));
mediump vec3 normalNormal = normalize(vec3(uModelMatrix * vec4(aVertexNormal, 1.0)));
mediump vec3 toLight = normalize(uLight1[0].xyz - normalCentroid);
mediump float light = dot(normalNormal, toLight);
gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.0);
vUV = aVertexUV;
vColor = vec4(aVertexColor, 1.0) * uLight1[2] * vec4(light, light, light, 1);
vNormal = vec3(uModelMatrix * vec4(aVertexNormal, 1.0));
vPosition = vec3(uModelMatrix * vec4(aVertexPosition, 1.0));
}
//fragment shader
varying mediump vec4 vColor;
void main() {
gl_FragColor = vColor;
}
//facetQuad
centroids: [
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0
],
//order matters! CCW from bottom to top
export function triangleNormal(pointA, pointB, pointC){
const vector1 = subtractVector(pointC, pointA);
const vector2 = subtractVector(pointB, pointA);
return normalizeVector(crossVector(vector1, vector2));
}
export function facetSphere(density, options){
const sphere = uvSphere(density, options);
const rawTriangles = arrayChunk(sphere.triangles, 3);
const rawPositions = arrayChunk(sphere.positions, 3);
const rawUVs = arrayChunk(sphere.uvs, 2);
const rawColors = arrayChunk(sphere.colors, 3);
const positions = [];
const uvs = [];
const normals = [];
const colors = [];
const triangles = [];
const centroids = [];
let index = 0;
for(const tri of rawTriangles){
positions.push(rawPositions[tri[0]], rawPositions[tri[1]], rawPositions[tri[2]]);
uvs.push(rawUVs[tri[0]], rawUVs[tri[1]], rawUVs[tri[2]]);
colors.push(rawColors[tri[0]], rawColors[tri[1]], rawColors[tri[2]]);
const normal = triangleNormal(rawPositions[tri[0]], rawPositions[tri[1]], rawPositions[tri[2]]);
normals.push(normal, normal, normal);
const centroid = triangleCentroid(rawPositions[tri[0]], rawPositions[tri[1]], rawPositions[tri[2]]);
centroids.push(centroid, centroid, centroid);
triangles.push([index, index + 1, index + 2]);
index += 3;
}
return {
positions: positions.flat(),
colors: colors.flat(),
triangles: triangles.flat(),
uvs: uvs.flat(),
normals: normals.flat(),
centroids: centroids.flat(),
textureName: sphere.textureName
}
}
arrayChunk
is just a function to group the array into subarrays of a specific length:export function arrayChunk(array, lengthPerChunk) {
const result = [];
let chunk = [array[0]];
for (let i = 1; i < array.length; i++) {
if (i % lengthPerChunk === 0) {
result.push(chunk);
chunk = [];
}
chunk.push(array[i]);
}
if (chunk.length > 0) result.push(chunk);
return result;
}
facetSphere
. We were able to work off the original data generated from a UV sphere, but from here it's hard to figure out if a group of triangles is a triangle or a quad without redoing a lot of math, so we might as well re-implement the whole thing:export function facetSphere(density, { color, uvOffset } = {}) {
const radsPerUnit = Math.PI / density;
const sliceVertCount = density * 2;
//positions and UVs
const rawPositions = [];
let rawUvs = [];
let latitude = -Math.PI / 2;
//latitude
for (let i = 0; i <= density; i++) {
const v = inverseLerp(-QUARTER_TURN, QUARTER_TURN, -latitude);
let longitude = 0;
let vertLength = sliceVertCount + ((i > 0 && i < density) ? 1 : 0); //middle rings need extra vert for end U value
//longitude
for (let j = 0; j < vertLength; j++) {
rawPositions.push(latLngToCartesian([1, latitude, longitude]));
rawUvs.push([inverseLerp(0, TWO_PI, longitude), v]);
longitude += radsPerUnit;
}
latitude += radsPerUnit;
}
if (uvOffset) {
rawUvs = rawUvs.map(uv => [(uv[0] + uvOffset[0]) % 1, (uv[1] + uvOffset[1]) % 1]);
}
//triangles
const triangles = [];
const positions = [];
const centroids = [];
const colors = [];
const normals = [];
const uvs = [];
const vertexColor = color ?? [1,0,0];
let index = 0;
let ringStartP = 0;
for (let ring = 0; ring < density; ring++) {
const vertexBump = (ring > 0 ? 1 : 0);
for (let sliceVert = 0; sliceVert < sliceVertCount; sliceVert++) {
const thisP = ringStartP + sliceVert;
const nextP = ringStartP + sliceVert + 1;
const nextRingP = thisP + sliceVertCount + vertexBump;
const nextRingNextP = nextP + sliceVertCount + vertexBump;
if (ring === 0) {
positions.push(rawPositions[thisP], rawPositions[nextRingNextP], rawPositions[nextRingP]);
uvs.push(rawUvs[thisP], rawUvs[nextRingNextP], rawUvs[nextRingP]);
colors.push(vertexColor, vertexColor, vertexColor);
const centroid = polyCentroid([rawPositions[thisP], rawPositions[nextRingNextP], rawPositions[nextRingP]]);
centroids.push(centroid, centroid, centroid);
const normal = normalizeVector(centroid);
normals.push(normal, normal, normal);
triangles.push([index, index + 1, index + 2]);
index += 3;
}
if (ring === density - 1) {
positions.push(rawPositions[thisP], rawPositions[nextP], rawPositions[nextRingP]);
uvs.push(rawUvs[thisP], rawUvs[nextP], rawUvs[nextRingP]);
colors.push(vertexColor, vertexColor, vertexColor);
const centroid = polyCentroid([rawPositions[thisP], rawPositions[nextP], rawPositions[nextRingP]]);
centroids.push(centroid, centroid, centroid);
const normal = normalizeVector(centroid);
normals.push(normal, normal, normal);
triangles.push([index, index + 1, index + 2]);
index += 3;
}
if (ring > 0 && ring < density - 1 && density > 2) {
positions.push(
rawPositions[thisP], rawPositions[nextRingNextP], rawPositions[nextRingP],
rawPositions[thisP], rawPositions[nextP], rawPositions[nextRingNextP]
);
uvs.push(
rawUvs[thisP], rawUvs[nextRingNextP], rawUvs[nextRingP],
rawUvs[thisP], rawUvs[nextP], rawUvs[nextRingNextP]
);
colors.push(vertexColor, vertexColor, vertexColor, vertexColor, vertexColor, vertexColor);
const centroid = polyCentroid([rawPositions[thisP], rawPositions[nextP], rawPositions[nextRingNextP], rawPositions[nextRingP]]);
centroids.push(centroid, centroid, centroid, centroid, centroid, centroid);
const normal = normalizeVector(centroid);
normals.push(normal, normal, normal, normal, normal, normal);
triangles.push([index, index + 1, index + 2], [index + 3, index + 4, index + 5]);
index += 6;
}
}
if (ring === 0) {
ringStartP += sliceVertCount;
} else {
ringStartP += sliceVertCount + 1;
}
}
return {
positions: positions.flat(),
colors: colors.flat(),
centroids: centroids.flat(),
triangles: triangles.flat(),
uvs: uvs.flat(),
normals: normals.flat(),
textureName: "earth"
};
}
polyCentroid
, it takes an array of points, easy mistake to forget when you are writing similar code lines.uniform mat4 uProjectionMatrix;
uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform lowp mat4 uLight1;
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
attribute vec2 aVertexUV;
attribute vec3 aVertexNormal;
attribute vec3 aVertexCentroid;
varying mediump vec4 vColor;
varying mediump vec2 vUV;
varying mediump vec3 vNormal;
varying mediump vec3 vPosition;
void main(){
bool isPoint = uLight1[3][3] == 1.0;
if(isPoint){
mediump vec3 normalCentroid = vec3(uModelMatrix * vec4(aVertexCentroid, 1.0));
mediump vec3 normalNormal = normalize(vec3(uModelMatrix * vec4(aVertexNormal, 1.0)));
mediump vec3 toLight = normalize(uLight1[0].xyz - normalCentroid);
mediump float light = dot(normalNormal, toLight);
vColor = vec4(aVertexColor, 1.0) * uLight1[2] * vec4(light, light, light, 1);
} else {
mediump vec3 normalNormal = normalize(vec3(uModelMatrix * vec4(aVertexNormal, 1.0)));
mediump float light = dot(normalNormal, uLight1[1].xyz);
vColor = vec4(aVertexColor, 1.0) * uLight1[2] * vec4(light, light, light, 1);
}
gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.0);
vUV = aVertexUV;
vNormal = vec3(uModelMatrix * vec4(aVertexNormal, 1.0));
vPosition = vec3(uModelMatrix * vec4(aVertexPosition, 1.0));
}