Naloga 3 WIP
This commit is contained in:
132
naloga_3/engine/core/Accessor.js
Normal file
132
naloga_3/engine/core/Accessor.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
46
naloga_3/engine/core/Camera.js
Normal file
46
naloga_3/engine/core/Camera.js
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
33
naloga_3/engine/core/Material.js
Normal file
33
naloga_3/engine/core/Material.js
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
11
naloga_3/engine/core/Mesh.js
Normal file
11
naloga_3/engine/core/Mesh.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export class Mesh {
|
||||
|
||||
constructor({
|
||||
vertices = [],
|
||||
indices = [],
|
||||
} = {}) {
|
||||
this.vertices = vertices;
|
||||
this.indices = indices;
|
||||
}
|
||||
|
||||
}
|
||||
43
naloga_3/engine/core/MeshUtils.js
Normal file
43
naloga_3/engine/core/MeshUtils.js
Normal 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),
|
||||
};
|
||||
}
|
||||
9
naloga_3/engine/core/Model.js
Normal file
9
naloga_3/engine/core/Model.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export class Model {
|
||||
|
||||
constructor({
|
||||
primitives = [],
|
||||
} = {}) {
|
||||
this.primitives = primitives;
|
||||
}
|
||||
|
||||
}
|
||||
69
naloga_3/engine/core/Node.js
Normal file
69
naloga_3/engine/core/Node.js
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
11
naloga_3/engine/core/Primitive.js
Normal file
11
naloga_3/engine/core/Primitive.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export class Primitive {
|
||||
|
||||
constructor({
|
||||
mesh,
|
||||
material,
|
||||
} = {}) {
|
||||
this.mesh = mesh;
|
||||
this.material = material;
|
||||
}
|
||||
|
||||
}
|
||||
21
naloga_3/engine/core/Sampler.js
Normal file
21
naloga_3/engine/core/Sampler.js
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
42
naloga_3/engine/core/SceneUtils.js
Normal file
42
naloga_3/engine/core/SceneUtils.js
Normal 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);
|
||||
}
|
||||
21
naloga_3/engine/core/Texture.js
Normal file
21
naloga_3/engine/core/Texture.js
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
30
naloga_3/engine/core/Transform.js
Normal file
30
naloga_3/engine/core/Transform.js
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
15
naloga_3/engine/core/Vertex.js
Normal file
15
naloga_3/engine/core/Vertex.js
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
35
naloga_3/engine/core/VertexUtils.js
Normal file
35
naloga_3/engine/core/VertexUtils.js
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user