Initial commit

This commit is contained in:
Lucas Dower 2021-07-01 16:16:32 +01:00
commit 65f46e1994
31 changed files with 14609 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/node_modules
package-lock.json

16
index.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<title>ObjToSchematic</title>
<link rel="stylesheet" href="./styles.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body>
<canvas id="c"></canvas>
</body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script type="module" src="./src/renderer.js"></script>
</html>

60
main.js Normal file
View File

@ -0,0 +1,60 @@
const electron = require('electron');
//const fs = require('fs');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const path = require('path');
const url = require('url');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
function createWindow () {
// Create the browser window.
//const {width, height} = electron.screen.getPrimaryDisplay().workAreaSize;
const width = 1400;
const height = 800;
mainWindow = new BrowserWindow({width, height, webPreferences: {
nodeIntegration: true,
contextIsolation: false,
}});
// Load index.html
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, './index.html'),
protocol: 'file:',
slashes: true
}));
// Open the DevTools.
//mainWindow.webContents.openDevTools();
// Emitted when the window is closed.
mainWindow.on('closed', function () {
mainWindow = null;
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow();
}
});

32
package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "objtoschematic",
"version": "0.1.0",
"description": "A tool to convert .obj files into voxels and then into Minecraft Schematic files",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "electron ."
},
"repository": {
"type": "git",
"url": "git+https://github.com/LucasDower/ObjToSchematic.git"
},
"author": "Lucas Dower",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/LucasDower/ObjToSchematic/issues"
},
"homepage": "https://github.com/LucasDower/ObjToSchematic#readme",
"devDependencies": {
"electron": "^13.1.4",
"expand-vertex-data": "^1.1.2",
"load-wavefront-obj": "^0.8.0",
"obj-file-parser": "^0.5.3",
"twgl.js": "^4.19.1",
"wavefront-obj-parser": "^2.0.1"
},
"dependencies": {
"bootstrap": "^5.0.2",
"obj2json": "^0.3.0"
}
}

80
resources/crystal.obj Normal file
View File

@ -0,0 +1,80 @@
# Blender v2.93.1 OBJ File: ''
# www.blender.org
mtllib crystal.mtl
o Icosphere
v 0.000000 -1.000000 0.000000
v 0.723600 -0.447215 0.525720
v -0.276385 -0.447215 0.850640
v -0.894425 -0.447215 0.000000
v -0.276385 -0.447215 -0.850640
v 0.723600 -0.447215 -0.525720
v 0.276385 0.447215 0.850640
v -0.723600 0.447215 0.525720
v -0.723600 0.447215 -0.525720
v 0.276385 0.447215 -0.850640
v 0.894425 0.447215 0.000000
v 0.000000 1.000000 0.000000
vt 0.181819 0.000000
vt 0.272728 0.157461
vt 0.090910 0.157461
vt 0.363637 0.000000
vt 0.454546 0.157461
vt 0.909091 0.000000
vt 1.000000 0.157461
vt 0.818182 0.157461
vt 0.727273 0.000000
vt 0.636364 0.157461
vt 0.545455 0.000000
vt 0.363637 0.314921
vt 0.181819 0.314921
vt 0.909091 0.314921
vt 0.727273 0.314921
vt 0.545455 0.314921
vt 0.000000 0.314921
vt 0.272728 0.472382
vt 0.090910 0.472382
vt 0.818182 0.472382
vt 0.636364 0.472382
vt 0.454546 0.472382
vn 0.1876 -0.7947 0.5774
vn 0.6071 -0.7947 0.0000
vn -0.4911 -0.7947 0.3568
vn -0.4911 -0.7947 -0.3568
vn 0.1876 -0.7947 -0.5774
vn 0.9822 -0.1876 0.0000
vn 0.3035 -0.1876 0.9342
vn -0.7946 -0.1876 0.5774
vn -0.7946 -0.1876 -0.5774
vn 0.3035 -0.1876 -0.9342
vn 0.7946 0.1876 0.5774
vn -0.3035 0.1876 0.9342
vn -0.9822 0.1876 0.0000
vn -0.3035 0.1876 -0.9342
vn 0.7946 0.1876 -0.5774
vn 0.4911 0.7947 0.3568
vn -0.1876 0.7947 0.5774
vn -0.6071 0.7947 0.0000
vn -0.1876 0.7947 -0.5774
vn 0.4911 0.7947 -0.3568
usemtl None
s off
f 1/1/1 2/2/1 3/3/1
f 2/2/2 1/4/2 6/5/2
f 1/6/3 3/7/3 4/8/3
f 1/9/4 4/8/4 5/10/4
f 1/11/5 5/10/5 6/5/5
f 2/2/6 6/5/6 11/12/6
f 3/3/7 2/2/7 7/13/7
f 4/8/8 3/7/8 8/14/8
f 5/10/9 4/8/9 9/15/9
f 6/5/10 5/10/10 10/16/10
f 2/2/11 11/12/11 7/13/11
f 3/3/12 7/13/12 8/17/12
f 4/8/13 8/14/13 9/15/13
f 5/10/14 9/15/14 10/16/14
f 6/5/15 10/16/15 11/12/15
f 7/13/16 11/12/16 12/18/16
f 8/17/17 7/13/17 12/19/17
f 9/15/18 8/14/18 12/20/18
f 10/16/19 9/15/19 12/21/19
f 11/12/20 10/16/20 12/22/20

BIN
resources/preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

10
resources/pyramid.mtl Normal file
View File

@ -0,0 +1,10 @@
# Blender MTL File: 'None'
# Material Count: 1
newmtl None
Ns 500
Ka 0.8 0.8 0.8
Kd 0.8 0.8 0.8
Ks 0.8 0.8 0.8
d 1
illum 2

25
resources/pyramid.obj Normal file
View File

@ -0,0 +1,25 @@
# Blender v2.93.1 OBJ File: ''
# www.blender.org
mtllib pyramid.mtl
o Cone
v 0.000000 -0.707107 -1.000000
v 0.866025 -0.707107 0.500000
v -0.866025 -0.707107 0.500000
v 0.000000 0.707107 0.000000
vt 0.250000 0.490000
vt 0.250000 0.250000
vt 0.457846 0.130000
vt 0.750000 0.490000
vt 0.957846 0.130000
vt 0.542154 0.130000
vt 0.042154 0.130000
vn 0.8165 0.3333 -0.4714
vn 0.0000 -1.0000 -0.0000
vn -0.0000 0.3333 0.9428
vn -0.8165 0.3333 -0.4714
usemtl None
s off
f 1/1/1 4/2/1 2/3/1
f 1/4/2 2/5/2 3/6/2
f 2/3/3 4/2/3 3/7/3
f 3/7/4 4/2/4 1/1/4

1510
resources/suzanne.obj Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
# Blender MTL File: 'None'
# Material Count: 1
newmtl None
Ns 500
Ka 0.8 0.8 0.8
Kd 0.8 0.8 0.8
Ks 0.8 0.8 0.8
d 1
illum 2

1066
resources/suzanne_left.obj Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
# Blender MTL File: 'None'
# Material Count: 1
newmtl None
Ns 500
Ka 0.8 0.8 0.8
Kd 0.8 0.8 0.8
Ks 0.8 0.8 0.8
d 1
illum 2

1067
resources/suzanne_right.obj Normal file

File diff suppressed because it is too large Load Diff

9965
resources/teapot.obj Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
precision mediump float;
varying vec4 v_colour;
void main() {
gl_FragColor = v_colour;
}

26
shaders/shaded_vertex.vs Normal file
View File

@ -0,0 +1,26 @@
uniform mat4 u_worldViewProjection;
uniform vec3 u_lightWorldPos;
uniform mat4 u_world;
uniform mat4 u_viewInverse;
uniform mat4 u_worldInverseTranspose;
uniform vec3 u_translate;
attribute vec4 position;
attribute vec3 normal;
varying vec4 v_colour;
void main() {
//vec4 a_position = u_worldViewProjection * position;
vec4 a_position = u_worldViewProjection * vec4(position.xyz * 1.0, 1.0);
//vec4 a_position = vec4();
vec3 v_normal = (u_worldInverseTranspose * vec4(normal, 0)).xyz;
vec3 v_lightDir = normalize(u_lightWorldPos);
float lighting = dot(v_normal, v_lightDir);
v_colour = vec4((0.25 * normal) + (0.75 * vec3(lighting)), 1.0);
gl_Position = a_position;
}

9
shaders/test_fragment.fs Normal file
View File

@ -0,0 +1,9 @@
precision mediump float;
//uniform float time;
void main() {
//float alpha = (-cos(time * 0.05) + 1.0) / 4.0;
float alpha = 1.0;
gl_FragColor = vec4(1.0, 1.0, 1.0, 0.5);
}

11
shaders/test_vertex.vs Normal file
View File

@ -0,0 +1,11 @@
uniform mat4 u_worldViewProjection;
attribute vec3 position;
attribute vec3 normal;
void main() {
// Extrude the vertices outwards slightly to avoid z-fighting
vec3 translated_position = position + normal * 0.0001;
gl_Position = u_worldViewProjection * vec4(translated_position, 1.0);
}

View File

@ -0,0 +1,13 @@
precision mediump float;
varying vec3 v_colour;
varying float v_distance;
void main() {
float alpha = v_distance / 32.0;
alpha = 1.0 - clamp(alpha, 0.0, 1.0);
gl_FragColor = vec4(v_colour, alpha);
}

View File

@ -0,0 +1,15 @@
uniform mat4 u_worldViewProjection;
uniform vec3 u_scale;
attribute vec3 position;
attribute vec3 colour;
varying vec3 v_colour;
varying float v_distance;
void main() {
v_colour = colour;
v_distance = length(position);
gl_Position = u_worldViewProjection * vec4(u_scale * position * 0.5, 1.0);
}

134
src/camera.js Normal file
View File

@ -0,0 +1,134 @@
const { m4, v3: Vector3 } = require('twgl.js');
const mouseHandler = require('./mouse.js');
const mathUtil = require('./math.js');
class ArcballCamera {
constructor(fov, aspect, zNear, zFar) {
this.fov = fov * Math.PI / 180;
this.aspect = aspect;
this.zNear = zNear;
this.zFar = zFar;
this.actualDistance = 8.0;
this.actualAzimuth = 0.6;
this.actualElevation = 1.3;
this.cameraSmoothing = 0.025;
this.targetDistance = this.actualDistance;
this.targetAzimuth = this.actualAzimuth;
this.targetElevation = this.actualElevation;
this.updateCameraPosition();
this.target = [0, 0, 0];
this.up = [0, 1, 0];
this.mouseSensitivity = 0.005;
this.scrollSensitivity = 0.005;
this.zoomDistMin = 2.0;
this.zoomDistMax = 15.0;
this.isRotating = false;
}
updateCamera() {
if (!this.isRotating) {
return;
}
const mouseDelta = mouseHandler.getMouseDelta();
this.targetAzimuth += mouseDelta.dx * this.mouseSensitivity;
this.targetElevation += mouseDelta.dy * this.mouseSensitivity;
// Prevent the camera going upside-down
const eps = 0.01;
this.targetElevation = Math.max(Math.min(Math.PI - eps, this.targetElevation), eps);
this.updateCameraPosition();
}
handleScroll(e) {
this.targetDistance += e.deltaY * this.scrollSensitivity;
this.targetDistance = Math.max(Math.min(this.zoomDistMax, this.targetDistance), this.zoomDistMin);
this.updateCameraPosition();
}
updateCameraPosition() {
this.actualDistance += (this.targetDistance - this.actualDistance) * 2 * this.cameraSmoothing;
this.actualAzimuth += (this.targetAzimuth - this.actualAzimuth) * this.cameraSmoothing;
this.actualElevation += (this.targetElevation - this.actualElevation) * this.cameraSmoothing;
this.eye = [
this.actualDistance * Math.cos(this.actualAzimuth) * -Math.sin(this.actualElevation),
this.actualDistance * Math.cos(this.actualElevation),
this.actualDistance * Math.sin(this.actualAzimuth) * -Math.sin(this.actualElevation)
];
}
getCameraPosition(azimuthOffset, elevationOffset) {
const azimuth = this.actualAzimuth + azimuthOffset;
const elevation = this.actualElevation + elevationOffset;
return [
this.actualDistance * Math.cos(azimuth ) * -Math.sin(elevation),
this.actualDistance * Math.cos(elevation),
this.actualDistance * Math.sin(azimuth) * -Math.sin(elevation)
];
}
getProjectionMatrix() {
return m4.perspective(this.fov, this.aspect, this.zNear, this.zFar);
}
getCameraMatrix() {
return m4.lookAt(this.eye, this.target, this.up);
}
getViewMatrix() {
return m4.inverse(this.getCameraMatrix());
}
getViewProjection() {
return m4.multiply(this.getProjectionMatrix(), this.getViewMatrix());
}
getWorldMatrix() {
return m4.identity();
}
getWorldViewProjection() {
return m4.multiply(this.getViewProjection(), this.getWorldMatrix());
}
getWorldInverseTranspose() {
return m4.transpose(m4.inverse(this.getWorldMatrix()));
}
getInverseWorldViewProjection() {
return m4.inverse(this.getWorldViewProjection());
}
getMouseRay() {
const mousePos = mouseHandler.getMousePosNorm();
const inverseProjectionMatrix = this.getInverseWorldViewProjection();
var origin = mathUtil.multiplyMatVec4(inverseProjectionMatrix, [mousePos.x, mousePos.y, -1.0, 1.0]);
var dest = mathUtil.multiplyMatVec4(inverseProjectionMatrix, [mousePos.x, mousePos.y, 1.0, 1.0]);
origin[0] /= origin[3];
origin[1] /= origin[3];
origin[2] /= origin[3];
dest[0] /= dest[3];
dest[1] /= dest[3];
dest[2] /= dest[3];
return {origin: origin, dest: dest};
}
}
module.exports.ArcballCamera = ArcballCamera;

68
src/grid.js Normal file
View File

@ -0,0 +1,68 @@
const { v3: Vector3 } = require("twgl.js");
const twgl = require('twgl.js');
const fs = require('fs');
function getGridColour(i, j, isXAxis) {
if (i == 0 && isXAxis) {
return [0.25, 0.75, 0.25];
}
if (j == 0 && !isXAxis) {
return [0.75, 0.25, 0.25];
}
return [0.25, 0.25, 0.25];
}
function generateGridMesh() {
vertices = [];
colours = [];
indices = [];
var gridSize = 64;
for (let i = -gridSize / 2; i < gridSize / 2; i++) {
for (let j = -gridSize / 2; j < gridSize / 2; j++) {
let k = indices.length;
let colour = getGridColour(i, j, false);
vertices.push(i, 0, j);
colours.push(colour[0], colour[1], colour[2]); // .push > .concat
indices.push(k);
vertices.push(i + 1, 0, j);
colours.push(colour[0], colour[1], colour[2]);
indices.push(k + 1);
colour = getGridColour(i, j, true);
vertices.push(i, 0, j);
colours.push(colour[0], colour[1], colour[2]);
indices.push(k + 2);
vertices.push(i, 0, j + 1);
colours.push(colour[0], colour[1], colour[2]);
indices.push(k + 3);
}
}
let k = indices.length;
vertices.push(gridSize / 2, 0, -gridSize / 2);
colours.push(0.0, 1.0, 1.0);
indices.push(k);
vertices.push(gridSize / 2, 0, gridSize / 2);
colours.push(0.0, 1.0, 1.0);
indices.push(k + 1);
vertices.push(-gridSize / 2, 0, gridSize / 2);
colours.push(1.0, 0.0, 0.0);
indices.push(k + 2);
vertices.push(gridSize / 2, 0, gridSize / 2);
colours.push(1.0, 0.0, 0.0);
indices.push(k + 3);
return { position: { numComponents: 3, data: vertices }, colour: { numComponents: 3, data: colours }, indices: indices };
}
module.exports.generateGridMesh = generateGridMesh;

61
src/main.js Normal file
View File

@ -0,0 +1,61 @@
const electron = require('electron');
const fs = require('fs');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const path = require('path');
const url = require('url');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
function createWindow () {
// Create the browser window.
const {width, height} = electron.screen.getPrimaryDisplay().workAreaSize;
mainWindow = new BrowserWindow({width, height, webPreferences: {
nodeIntegration: true,
contextIsolation: false
}});
// Load index.html
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, '../index.html'),
protocol: 'file:',
slashes: true
}));
// Open the DevTools.
//mainWindow.webContents.openDevTools();
// Emitted when the window is closed.
mainWindow.on('closed', function () {
mainWindow = null;
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow();
}
});

19
src/math.js Normal file
View File

@ -0,0 +1,19 @@
// Not apart of rendering, SIMD optimisation not necessary
const { v3: Vector3 } = require('twgl.js');
function floorTo(value, base) {
return Math.floor(value / base) * base;
}
function ceilTo(value, base) {
return Math.ceil(value / base) * base;
}
module.exports.floorTo = floorTo;
module.exports.ceilTo = ceilTo;
module.exports.xAxis = Vector3.create(1.0, 0.0, 0.0);
module.exports.yAxis = Vector3.create(0.0, 1.0, 0.0);
module.exports.zAxis = Vector3.create(0.0, 0.0, 1.0);

103
src/mesh.js Normal file
View File

@ -0,0 +1,103 @@
const { v3: Vector3 } = require('twgl.js');
const { Triangle } = require('./triangle.js');
const fs = require('fs');
const twgl = require('twgl.js');
const wavefrontObjParser = require('wavefront-obj-parser');
const expandVertexData = require('expand-vertex-data');
class Mesh {
constructor(obj_path, gl) {
var wavefrontString = fs.readFileSync(obj_path).toString('utf8');
var parsedJSON = wavefrontObjParser(wavefrontString);
var expanded = expandVertexData(parsedJSON, {facesToTriangles: true});
this.model = {
position: expanded.positions,
normal: expanded.normals,
indices: expanded.positionIndices
};
this.buffers = [twgl.createBufferInfoFromArrays(gl, this.model)];
}
voxelise(voxel_size, gl) {
// Extract triangles from mesh
let triangles = [];
for (let i = 0; i < this.model.indices.length; i += 3) {
let i0 = this.model.indices[i];
let i1 = this.model.indices[i + 1];
let i2 = this.model.indices[i + 2];
let v0 = this.model.position.slice(3 * i0, 3 * i0 + 3);
let v1 = this.model.position.slice(3 * i1, 3 * i1 + 3);
let v2 = this.model.position.slice(3 * i2, 3 * i2 + 3);
triangles.push(new Triangle(v0, v1, v2));
}
// Voxelise triangles
let voxel_positions = [];
for (let triangle of triangles) {
let v = triangle.voxelise(voxel_size);
voxel_positions.push(...v);
}
console.log(triangles);
const cube = twgl.primitives.createCubeVertices(voxel_size);
const num_buffers = Math.ceil(voxel_positions.length / 1024);
// Create buffers
let buffers = new Array(num_buffers);
for (let i = 0; i < num_buffers; ++i) {
buffers[i]= {
position: { numComponents: 3, data: []},
normal: { numComponents: 3, data: []},
indices: { numComponents: 3, data: []}
};
}
// Fill buffers with voxels
for (let i = 0; i < voxel_positions.length; ++i) {
let voxel = voxel_positions[i];
let position_ = [];
for (let j = 0; j < 72; j += 3) {
position_.push(
cube.position[j + 0] + voxel[0],
cube.position[j + 1] + voxel[1],
cube.position[j + 2] + voxel[2]
);
}
const buffer_index = Math.floor(i / 1024);
let index_offset = buffers[buffer_index].indices.data.length;
index_offset /= 1.5;
let indices_ = [];
for (let j = 0; j < 36; j += 3) {
indices_.push(
cube.indices[j + 0] + index_offset,
cube.indices[j + 1] + index_offset,
cube.indices[j + 2] + index_offset
);
}
buffers[buffer_index].indices.data.push(...indices_);
buffers[buffer_index].position.data.push(...position_);
buffers[buffer_index].normal.data.push(...cube.normal);
}
let buffer_infos = new Array(num_buffers);
for (let i = 0; i < num_buffers; ++i) {
buffer_infos[i] = twgl.createBufferInfoFromArrays(gl, buffers[i]);
}
this.buffers = buffer_infos;
}
}
module.exports.Mesh = Mesh;

37
src/mouse.js Normal file
View File

@ -0,0 +1,37 @@
const gl = document.querySelector("#c").getContext("webgl");
var currentMouse = { x: -1, y: -1, buttons: 0 };
var previousMouse = { x: -1, y: -1, buttons: 0 };
const MOUSE_LEFT = 1;
const MOUSE_RIGHT = 2;
module.exports = {
handleInput: (e) => {
previousMouse = currentMouse;
currentMouse = { x: e.clientX, y: e.clientY, buttons: e.buttons };
},
isMouseLeftDown: () => {
return currentMouse.buttons & MOUSE_LEFT;
},
isMouseRightDown: () => {
return currentMouse.buttons & MOUSE_RIGHT;
},
getMouseDelta: () => {
return {
dx: currentMouse.x - previousMouse.x,
dy: -(currentMouse.y - previousMouse.y)
};
},
getMousePosNorm: () => {
let normX = 2 * (currentMouse.x / gl.canvas.width) - 1;
let normY = -(2 * (currentMouse.y / gl.canvas.height) - 1);
return { x: normX, y: normY };
},
};

0
src/parser.js Normal file
View File

119
src/renderer.js Normal file
View File

@ -0,0 +1,119 @@
const twgl = require('twgl.js');
const fs = require('fs');
const mouseHandler = require('./src/mouse.js');
const cameraHandler = require('./src/camera.js');
const shaderManager = require('./src/shaders.js');
const gridManager = require('./src/grid.js');
const { Triangle } = require('./src/triangle.js');
const wavefrontObjParser = require('wavefront-obj-parser');
const expandVertexData = require('expand-vertex-data');
const { Mesh } = require('./src/mesh.js');
const v3 = twgl.v3;
const gl = document.querySelector("#c").getContext("webgl");
let suzanne_left = new Mesh('./resources/suzanne_left.obj', gl);
let suzanne_right = new Mesh('./resources/suzanne_right.obj', gl);
suzanne_left.voxelise(0.025, gl);
const suzanne_left_buffers = suzanne_left.buffers;
const suzanne_right_buffers = suzanne_right.buffers;
var camera = new cameraHandler.ArcballCamera(30, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.5, 30.0);
const gridMesh = gridManager.generateGridMesh();
const gridBuffer = twgl.createBufferInfoFromArrays(gl, gridMesh);
gl.canvas.addEventListener('mousedown', (e) => {
camera.isRotating = true;
});
gl.canvas.addEventListener('mouseup', (e) => {
camera.isRotating = false;
});
gl.canvas.addEventListener('mousemove', (e) => {
mouseHandler.handleInput(e);
camera.updateCamera();
});
gl.canvas.addEventListener('wheel', (e) => {
camera.handleScroll(e);
});
function drawModel(model, translation) {
const uniforms = {
u_lightWorldPos: camera.getCameraPosition(0.785398, 0),
u_diffuse: model.textureUnit,
u_viewInverse: camera.getCameraMatrix(),
u_world: camera.getWorldMatrix(),
u_worldInverseTranspose: camera.getWorldInverseTranspose(),
u_worldViewProjection: camera.getWorldViewProjection(),
u_translate: translation
};
drawBufferWithShader(gl.TRIANGLES, model, uniforms, shaderManager.shadedProgram);
}
function drawGrid() {
const uniforms = {
u_worldViewProjection: camera.getWorldViewProjection(),
u_scale: v3.create(2.0/16.0, 2.0, 2.0/16.0)
};
drawBufferWithShader(gl.LINES, gridBuffer, uniforms, shaderManager.unshadedProgram);
}
function drawBufferWithShader(drawMode, buffer, uniforms, shader) {
gl.useProgram(shader.program);
twgl.setBuffersAndAttributes(gl, shader, buffer);
twgl.setUniforms(shader, uniforms);
gl.drawElements(drawMode, buffer.numElements, gl.UNSIGNED_SHORT, 0);
}
function render(time) {
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.enable(gl.BLEND);
gl.clearColor(0.1, 0.1, 0.1, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
camera.updateCameraPosition();
drawGrid();
for (const buffer_info of suzanne_right_buffers) {
drawModel(buffer_info, v3.create(0.0, 0.0, 0.0));
}
for (const buffer_info of suzanne_left_buffers) {
drawModel(buffer_info, v3.create(0.0, 0.0, 0.0));
}
//drawModel(buffer_infos[0], v3.create(0.0, 0.0, 0.0));
//for (const buffer_info of buffer_infos) {
// drawModel(buffer_info, v3.create(0.0, 0.0, 0.0));
//}
requestAnimationFrame(render);
}
requestAnimationFrame(render);

21
src/shaders.js Normal file
View File

@ -0,0 +1,21 @@
const twgl = require('twgl.js');
const fs = require('fs');
const gl = document.querySelector("#c").getContext("webgl");
const shaded_vertex_shader = fs.readFileSync('./shaders/shaded_vertex.vs', 'utf8');
const shaded_fragment_shader = fs.readFileSync('./shaders/shaded_fragment.fs', 'utf8');
const unshaded_vertex_shader = fs.readFileSync('./shaders/unshaded_vertex.vs', 'utf8');
const unshaded_fragment_shader = fs.readFileSync('./shaders/unshaded_fragment.fs', 'utf8');
const test_vertex_shader = fs.readFileSync('./shaders/test_vertex.vs', 'utf8');
const test_fragment_shader = fs.readFileSync('./shaders/test_fragment.fs', 'utf8');
const shadedProgram = twgl.createProgramInfo(gl, [shaded_vertex_shader, shaded_fragment_shader]);
const unshadedProgram = twgl.createProgramInfo(gl, [unshaded_vertex_shader, unshaded_fragment_shader]);
const testProgram = twgl.createProgramInfo(gl, [test_vertex_shader, test_fragment_shader]);
module.exports.shadedProgram = shadedProgram;
module.exports.unshadedProgram = unshadedProgram;
module.exports.testProgram = testProgram;

97
src/triangle.js Normal file
View File

@ -0,0 +1,97 @@
const { v3: Vector3 } = require('twgl.js');
const mathUtil = require('./math.js');
class Triangle {
constructor(v0, v1, v2) {
this.v0 = v0;
this.v1 = v1;
this.v2 = v2;
}
getBoundingBox(voxelSize) {
return {
minX: mathUtil.floorTo(Math.min(this.v0[0], this.v1[0], this.v2[0]), voxelSize),
minY: mathUtil.floorTo(Math.min(this.v0[1], this.v1[1], this.v2[1]), voxelSize),
minZ: mathUtil.floorTo(Math.min(this.v0[2], this.v1[2], this.v2[2]), voxelSize),
maxX: mathUtil.ceilTo(Math.max(this.v0[0], this.v1[0], this.v2[0]), voxelSize),
maxY: mathUtil.ceilTo(Math.max(this.v0[1], this.v1[1], this.v2[1]), voxelSize),
maxZ: mathUtil.ceilTo(Math.max(this.v0[2], this.v1[2], this.v2[2]), voxelSize),
};
}
voxelise(voxelSize) {
let voxels = [];
let bb = this.getBoundingBox(voxelSize);
for (let x = bb.minX; x < bb.maxX; x += voxelSize) {
for (let y = bb.minY; y < bb.maxY; y += voxelSize) {
for (let z = bb.minZ; z < bb.maxZ; z += voxelSize) {
if (this.voxelIntersect(x, y, z, voxelSize)) {
voxels.push([x, y, z]);
}
}
}
}
return voxels;
}
voxelIntersect(x, y, z, voxelSize) {
let c = Vector3.create(x, y, z);
let e = Vector3.create(voxelSize/2, voxelSize/2, voxelSize/2);
let v0_ = Vector3.subtract(this.v0, c);
let v1_ = Vector3.subtract(this.v1, c);
let v2_ = Vector3.subtract(this.v2, c);
let f0 = Vector3.subtract(v1_, v0_);
let f1 = Vector3.subtract(v2_, v1_);
let f2 = Vector3.subtract(v0_, v2_);
let a0 = Vector3.cross(f0, f2);
if (this.testAxis(v0_, v1_, v2_, a0, e)) {
return false;
}
let a1 = [mathUtil.xAxis, mathUtil.yAxis, mathUtil.zAxis];
for (let ax of a1) {
if (this.testAxis(v0_, v1_, v2_, ax, e)) {
return false;
}
}
let axis = new Array(9);
axis[0] = Vector3.cross(mathUtil.xAxis, f0);
axis[1] = Vector3.cross(mathUtil.xAxis, f1);
axis[2] = Vector3.cross(mathUtil.xAxis, f2);
axis[3] = Vector3.cross(mathUtil.yAxis, f0);
axis[4] = Vector3.cross(mathUtil.yAxis, f1);
axis[5] = Vector3.cross(mathUtil.yAxis, f2);
axis[6] = Vector3.cross(mathUtil.zAxis, f0);
axis[7] = Vector3.cross(mathUtil.zAxis, f1);
axis[8] = Vector3.cross(mathUtil.zAxis, f2);
for (let ax of axis) {
if (this.testAxis(v0_, v1_, v2_, ax, e)) {
return false;
}
}
return true;
}
testAxis(v0_, v1_, v2_, axis, e) {
let p0 = Vector3.dot(v0_, axis);
let p1 = Vector3.dot(v1_, axis);
let p2 = Vector3.dot(v2_, axis);
let r = e[0] * Math.abs(Vector3.dot(mathUtil.xAxis, axis)) +
e[1] * Math.abs(Vector3.dot(mathUtil.yAxis, axis)) +
e[2] * Math.abs(Vector3.dot(mathUtil.zAxis, axis));
return (Math.min(p0, p1, p2) > r || Math.max(p0, p1, p2) < -r);
}
}
module.exports.Triangle = Triangle;

16
styles.css Normal file
View File

@ -0,0 +1,16 @@
body {
margin: 0;
font-family: monospace;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
#b {
position: absolute;
top: 10px;
width: 100%;
text-align: center;
z-index: 2;
}