forked from mirror/ObjToSchematic
Initial commit
This commit is contained in:
commit
65f46e1994
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/node_modules
|
||||
package-lock.json
|
16
index.html
Normal file
16
index.html
Normal 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
60
main.js
Normal 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
32
package.json
Normal 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
80
resources/crystal.obj
Normal 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
BIN
resources/preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 195 KiB |
10
resources/pyramid.mtl
Normal file
10
resources/pyramid.mtl
Normal 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
25
resources/pyramid.obj
Normal 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
1510
resources/suzanne.obj
Normal file
File diff suppressed because it is too large
Load Diff
10
resources/suzanne_left.mtl
Normal file
10
resources/suzanne_left.mtl
Normal 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
1066
resources/suzanne_left.obj
Normal file
File diff suppressed because it is too large
Load Diff
10
resources/suzanne_right.mtl
Normal file
10
resources/suzanne_right.mtl
Normal 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
1067
resources/suzanne_right.obj
Normal file
File diff suppressed because it is too large
Load Diff
9965
resources/teapot.obj
Normal file
9965
resources/teapot.obj
Normal file
File diff suppressed because it is too large
Load Diff
7
shaders/shaded_fragment.fs
Normal file
7
shaders/shaded_fragment.fs
Normal 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
26
shaders/shaded_vertex.vs
Normal 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
9
shaders/test_fragment.fs
Normal 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
11
shaders/test_vertex.vs
Normal 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);
|
||||
}
|
13
shaders/unshaded_fragment.fs
Normal file
13
shaders/unshaded_fragment.fs
Normal 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);
|
||||
}
|
15
shaders/unshaded_vertex.vs
Normal file
15
shaders/unshaded_vertex.vs
Normal 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
134
src/camera.js
Normal 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
68
src/grid.js
Normal 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
61
src/main.js
Normal 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
19
src/math.js
Normal 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
103
src/mesh.js
Normal 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
37
src/mouse.js
Normal 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
0
src/parser.js
Normal file
119
src/renderer.js
Normal file
119
src/renderer.js
Normal 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
21
src/shaders.js
Normal 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
97
src/triangle.js
Normal 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
16
styles.css
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user