Naloga 3 WIP

This commit is contained in:
Gašper Dobrovoljc
2024-12-28 19:58:17 +01:00
parent 7ad330422b
commit a20a45ebd0
51 changed files with 3327 additions and 28 deletions

View File

@@ -0,0 +1,132 @@
export class Accessor {
constructor({
buffer,
viewLength,
viewOffset = 0,
offset = 0,
stride = componentSize,
componentType = 'int',
componentCount = 1,
componentSize = 1,
componentSigned = false,
componentNormalized = false,
} = {}) {
this.buffer = buffer;
this.offset = offset;
this.stride = stride;
this.componentType = componentType;
this.componentCount = componentCount;
this.componentSize = componentSize;
this.componentSigned = componentSigned;
this.componentNormalized = componentNormalized;
const viewType = this.getViewType({
componentType,
componentSize,
componentSigned,
});
if (viewLength !== undefined) {
this.view = new viewType(buffer, viewOffset, viewLength / viewType.BYTES_PER_ELEMENT);
} else {
this.view = new viewType(buffer, viewOffset);
}
this.offsetInElements = offset / viewType.BYTES_PER_ELEMENT;
this.strideInElements = stride / viewType.BYTES_PER_ELEMENT;
this.count = Math.floor((this.view.length - this.offsetInElements) / this.strideInElements);
this.normalize = this.getNormalizer({
componentType,
componentSize,
componentSigned,
componentNormalized,
});
this.denormalize = this.getDenormalizer({
componentType,
componentSize,
componentSigned,
componentNormalized,
});
}
get(index) {
const start = index * this.strideInElements + this.offsetInElements;
const end = start + this.componentCount;
return [...this.view.slice(start, end)].map(this.normalize);
}
set(index, value) {
const start = index * this.strideInElements + this.offsetInElements;
this.view.set(value.map(this.denormalize), start);
}
getNormalizer({
componentType,
componentSize,
componentSigned,
componentNormalized,
}) {
if (!componentNormalized || componentType === 'float') {
return x => x;
}
const multiplier = componentSigned
? 2 ** ((componentSize * 8) - 1) - 1
: 2 ** (componentSize * 8) - 1;
return x => Math.max(x / multiplier, -1);
}
getDenormalizer({
componentType,
componentSize,
componentSigned,
componentNormalized,
}) {
if (!componentNormalized || componentType === 'float') {
return x => x;
}
const multiplier = componentSigned
? 2 ** ((componentSize * 8) - 1) - 1
: 2 ** (componentSize * 8) - 1;
const min = componentSigned ? -1 : 0;
const max = 1;
return x => Math.floor(0.5 + multiplier * Math.min(Math.max(x, min), max));
}
getViewType({
componentType,
componentSize,
componentSigned,
}) {
if (componentType === 'float') {
if (componentSize === 4) {
return Float32Array;
}
} else if (componentType === 'int') {
if (componentSigned) {
switch (componentSize) {
case 1: return Int8Array;
case 2: return Int16Array;
case 4: return Int32Array;
}
} else {
switch (componentSize) {
case 1: return Uint8Array;
case 2: return Uint16Array;
case 4: return Uint32Array;
}
}
}
}
}

View File

@@ -0,0 +1,46 @@
import { mat4 } from 'glm';
export class Camera {
constructor({
orthographic = 0,
aspect = 1,
fovy = 1,
halfy = 1,
near = 0.01,
far = 1000,
} = {}) {
this.orthographic = orthographic;
this.aspect = aspect;
this.fovy = fovy;
this.halfy = halfy;
this.near = near;
this.far = far;
}
get projectionMatrix() {
if (this.orthographic === 0) {
return this.perspectiveMatrix;
} else if (this.orthographic === 1) {
return this.orthographicMatrix;
} else {
const a = this.orthographicMatrix;
const b = this.perspectiveMatrix;
return mat4.add(mat4.create(),
mat4.multiplyScalar(a, a, this.orthographic),
mat4.multiplyScalar(b, b, 1 - this.orthographic));
}
}
get orthographicMatrix() {
const { halfy, aspect, near, far } = this;
const halfx = halfy * aspect;
return mat4.orthoZO(mat4.create(), -halfx, halfx, -halfy, halfy, near, far);
}
get perspectiveMatrix() {
const { fovy, aspect, near, far } = this;
return mat4.perspectiveZO(mat4.create(), fovy, aspect, near, far);
}
}

View File

@@ -0,0 +1,33 @@
export class Material {
constructor({
baseTexture,
emissionTexture,
normalTexture,
occlusionTexture,
roughnessTexture,
metalnessTexture,
baseFactor = [1, 1, 1, 1],
emissionFactor = [0, 0, 0],
normalFactor = 1,
occlusionFactor = 1,
roughnessFactor = 1,
metalnessFactor = 1,
} = {}) {
this.baseTexture = baseTexture;
this.emissionTexture = emissionTexture;
this.normalTexture = normalTexture;
this.occlusionTexture = occlusionTexture;
this.roughnessTexture = roughnessTexture;
this.metalnessTexture = metalnessTexture;
this.baseFactor = baseFactor;
this.emissionFactor = emissionFactor;
this.normalFactor = normalFactor;
this.occlusionFactor = occlusionFactor;
this.roughnessFactor = roughnessFactor;
this.metalnessFactor = metalnessFactor;
}
}

View File

@@ -0,0 +1,11 @@
export class Mesh {
constructor({
vertices = [],
indices = [],
} = {}) {
this.vertices = vertices;
this.indices = indices;
}
}

View File

@@ -0,0 +1,43 @@
import { quat, vec3, vec4, mat3, mat4 } from 'glm';
export function transformVertex(vertex, matrix,
normalMatrix = mat3.normalFromMat4(mat3.create(), matrix),
tangentMatrix = mat3.fromMat4(mat3.create(), matrix),
) {
vec3.transformMat4(vertex.position, vertex.position, matrix);
vec3.transformMat3(vertex.normal, vertex.normal, normalMatrix);
vec3.transformMat3(vertex.tangent, vertex.tangent, tangentMatrix);
}
export function transformMesh(mesh, matrix,
normalMatrix = mat3.normalFromMat4(mat3.create(), matrix),
tangentMatrix = mat3.fromMat4(mat3.create(), matrix),
) {
for (const vertex of mesh.vertices) {
transformVertex(vertex, matrix, normalMatrix, tangentMatrix);
}
}
export function calculateAxisAlignedBoundingBox(mesh) {
const initial = {
min: vec3.clone(mesh.vertices[0].position),
max: vec3.clone(mesh.vertices[0].position),
};
return {
min: mesh.vertices.reduce((a, b) => vec3.min(a, a, b.position), initial.min),
max: mesh.vertices.reduce((a, b) => vec3.max(a, a, b.position), initial.max),
};
}
export function mergeAxisAlignedBoundingBoxes(boxes) {
const initial = {
min: vec3.clone(boxes[0].min),
max: vec3.clone(boxes[0].max),
};
return {
min: boxes.reduce(({ min: amin }, { min: bmin }) => vec3.min(amin, amin, bmin), initial),
max: boxes.reduce(({ max: amax }, { max: bmax }) => vec3.max(amax, amax, bmax), initial),
};
}

View File

@@ -0,0 +1,9 @@
export class Model {
constructor({
primitives = [],
} = {}) {
this.primitives = primitives;
}
}

View File

@@ -0,0 +1,69 @@
export class Node {
constructor() {
this.children = [];
this.parent = null;
this.components = [];
}
addChild(node) {
node.parent?.removeChild(node);
this.children.push(node);
node.parent = this;
}
removeChild(node) {
const index = this.children.indexOf(node);
if (index >= 0) {
this.children.splice(index, 1);
node.parent = null;
}
}
traverse(before, after) {
before?.(this);
for (const child of this.children) {
child.traverse(before, after);
}
after?.(this);
}
linearize() {
const array = [];
this.traverse(node => array.push(node));
return array;
}
filter(predicate) {
return this.linearize().filter(predicate);
}
find(predicate) {
return this.linearize().find(predicate);
}
map(transform) {
return this.linearize().map(transform);
}
addComponent(component) {
this.components.push(component);
}
removeComponent(component) {
this.components = this.components.filter(c => c !== component);
}
removeComponentsOfType(type) {
this.components = this.components.filter(component => !(component instanceof type));
}
getComponentOfType(type) {
return this.components.find(component => component instanceof type);
}
getComponentsOfType(type) {
return this.components.filter(component => component instanceof type);
}
}

View File

@@ -0,0 +1,11 @@
export class Primitive {
constructor({
mesh,
material,
} = {}) {
this.mesh = mesh;
this.material = material;
}
}

View File

@@ -0,0 +1,21 @@
export class Sampler {
constructor({
minFilter = 'linear',
magFilter = 'linear',
mipmapFilter = 'linear',
addressModeU = 'clamp-to-edge',
addressModeV = 'clamp-to-edge',
addressModeW = 'clamp-to-edge',
maxAnisotropy = 1,
} = {}) {
this.minFilter = minFilter;
this.magFilter = magFilter;
this.mipmapFilter = mipmapFilter;
this.addressModeU = addressModeU;
this.addressModeV = addressModeV;
this.addressModeW = addressModeW;
this.maxAnisotropy = maxAnisotropy;
}
}

View File

@@ -0,0 +1,42 @@
import { mat4 } from 'glm';
import { Camera } from './Camera.js';
import { Model } from './Model.js';
import { Transform } from './Transform.js';
export function getLocalModelMatrix(node) {
const matrix = mat4.create();
for (const transform of node.getComponentsOfType(Transform)) {
mat4.mul(matrix, matrix, transform.matrix);
}
return matrix;
}
export function getGlobalModelMatrix(node) {
if (node.parent) {
const parentMatrix = getGlobalModelMatrix(node.parent);
const modelMatrix = getLocalModelMatrix(node);
return mat4.multiply(parentMatrix, parentMatrix, modelMatrix);
} else {
return getLocalModelMatrix(node);
}
}
export function getLocalViewMatrix(node) {
const matrix = getLocalModelMatrix(node);
return mat4.invert(matrix, matrix);
}
export function getGlobalViewMatrix(node) {
const matrix = getGlobalModelMatrix(node);
return mat4.invert(matrix, matrix);
}
export function getProjectionMatrix(node) {
const camera = node.getComponentOfType(Camera);
return camera ? camera.projectionMatrix : mat4.create();
}
export function getModels(node) {
return node.getComponentsOfType(Model);
}

View File

@@ -0,0 +1,21 @@
export class Texture {
constructor({
image,
sampler,
isSRGB = false,
} = {}) {
this.image = image;
this.sampler = sampler;
this.isSRGB = isSRGB;
}
get width() {
return this.image.width;
}
get height() {
return this.image.height;
}
}

View File

@@ -0,0 +1,30 @@
import { mat4 } from 'glm';
export class Transform {
constructor({
rotation = [0, 0, 0, 1],
translation = [0, 0, 0],
scale = [1, 1, 1],
matrix,
} = {}) {
this.rotation = rotation;
this.translation = translation;
this.scale = scale;
if (matrix) {
this.matrix = matrix;
}
}
get matrix() {
return mat4.fromRotationTranslationScale(mat4.create(),
this.rotation, this.translation, this.scale);
}
set matrix(matrix) {
mat4.getRotation(this.rotation, matrix);
mat4.getTranslation(this.translation, matrix);
mat4.getScaling(this.scale, matrix);
}
}

View File

@@ -0,0 +1,15 @@
export class Vertex {
constructor({
position = [0, 0, 0],
texcoords = [0, 0],
normal = [0, 0, 0],
tangent = [0, 0, 0],
} = {}) {
this.position = position;
this.texcoords = texcoords;
this.normal = normal;
this.tangent = tangent;
}
}

View File

@@ -0,0 +1,35 @@
import { Accessor } from './Accessor.js';
export function parseFormat(format) {
const regex = /(?<type>float|((?<sign>u|s)(?<norm>int|norm)))(?<bits>\d+)x(?<count>\d+)/;
const groups = format.match(regex).groups;
return {
componentType: groups.type === 'float' ? 'float' : 'int',
componentNormalized: groups.norm === 'norm',
componentSigned: groups.sign === 's',
componentSize: Number(groups.bits) / 8,
componentCount: Number(groups.count),
};
}
export function createVertexBuffer(vertices, layout) {
const buffer = new ArrayBuffer(layout.arrayStride * vertices.length);
const accessors = layout.attributes.map(attribute => new Accessor({
buffer,
stride: layout.arrayStride,
...parseFormat(attribute.format),
...attribute,
}));
for (let i = 0; i < vertices.length; i++) {
const vertex = vertices[i];
for (let j = 0; j < layout.attributes.length; j++) {
const accessor = accessors[j];
const attribute = layout.attributes[j].name;
accessor.set(i, vertex[attribute]);
}
}
return buffer;
}