obja/js/main.js

199 lines
6.2 KiB
JavaScript

function fetchData(path, start, end, callback) {
let xhr = new XMLHttpRequest();
xhr.open('GET', path, true);
xhr.setRequestHeader('Range', 'bytes=' + start + "-" + (end - 1));
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 206) {
callback(xhr.responseText);
}
}
};
xhr.send();
}
function parseLine(line) {
let element = {};
let split = line.split(/[ \t]+/);
if (split.length === 0) {
return;
}
switch (split[0]) {
case "v":
element.type = Element.AddVertex;
element.value = new THREE.Vector3(parseFloat(split[1]), parseFloat(split[2]), parseFloat(split[3]));
return element;
case "f":
element.type = Element.AddFace;
element.value = new THREE.Face3(parseInt(split[1], 10), parseInt(split[2], 10), parseInt(split[3], 10));
return element;
case "":
case "#":
return;
default:
throw new Error(split[0] + " is not a defined macro");
}
}
const Element = {};
Element.AddVertex = "AddVertex";
Element.AddFace = "AddFace";
class Loader {
constructor(path, chunkSize = 1024, timeout = 20) {
this.path = path;
this.chunkSize = chunkSize;
this.timeout = timeout;
this.currentByte = 0;
this.remainder = "";
}
start(callback) {
this.downloadAndParseNextChunk((data) => {
callback(data);
setTimeout(() => {
this.start(callback);
}, this.timeout);
});
}
downloadAndParseNextChunk(callback) {
fetchData(this.path, this.currentByte, this.currentByte + this.chunkSize, (data) => {
if (data.length === 0) {
console.log("Loading finished");
return;
}
this.currentByte += this.chunkSize;
let elements = [];
let split = data.split('\n');
split[0] = this.remainder + split[0];
this.remainder = split.pop();
for (let line of split) {
elements.push(parseLine(line));
}
callback(elements);
});
}
}
class Model extends THREE.Mesh {
constructor() {
let geometry = new THREE.BufferGeometry();
let vertices = new Float32Array(10000000);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
let normals = new Float32Array(10000000);
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
let material = new THREE.MeshLambertMaterial({color: 0xffffff});
super(geometry, material);
this.frustumCulled = false;
this.vertices = [];
this.currentFace = 0;
}
manageElement(element) {
switch (element.type) {
case Element.AddVertex:
this.vertices.push(element.value);
break;
case Element.AddFace:
let buf = this.geometry.attributes.position.array;
buf[this.currentFace * 9 ] = this.vertices[element.value.a - 1].x;
buf[this.currentFace * 9 + 1] = this.vertices[element.value.a - 1].y;
buf[this.currentFace * 9 + 2] = this.vertices[element.value.a - 1].z;
buf[this.currentFace * 9 + 3] = this.vertices[element.value.b - 1].x;
buf[this.currentFace * 9 + 4] = this.vertices[element.value.b - 1].y;
buf[this.currentFace * 9 + 5] = this.vertices[element.value.b - 1].z;
buf[this.currentFace * 9 + 6] = this.vertices[element.value.c - 1].x;
buf[this.currentFace * 9 + 7] = this.vertices[element.value.c - 1].y;
buf[this.currentFace * 9 + 8] = this.vertices[element.value.c - 1].z;
this.geometry.attributes.position.needsUpdate = true;
let normal =
this.vertices[element.value.b - 1].clone().sub(this.vertices[element.value.a - 1])
.cross(this.vertices[element.value.c - 1].clone().sub(this.vertices[element.value.a - 1]));
normal.normalize();
buf = this.geometry.attributes.normal.array;
buf[this.currentFace * 9 ] = normal.x;
buf[this.currentFace * 9 + 1] = normal.y;
buf[this.currentFace * 9 + 2] = normal.z;
buf[this.currentFace * 9 + 3] = normal.x;
buf[this.currentFace * 9 + 4] = normal.y;
buf[this.currentFace * 9 + 5] = normal.z;
buf[this.currentFace * 9 + 6] = normal.x;
buf[this.currentFace * 9 + 7] = normal.y;
buf[this.currentFace * 9 + 8] = normal.z;
this.geometry.attributes.normal.needsUpdate = true;
this.currentFace++;
break;
default:
throw new Error("unknown element type: " + element.type);
}
this.geometry.verticesNeedUpdate = true;
this.geometry.elementsNeedUpdate = true;
}
}
let camera, scene, renderer, loader, light1, light2, controls, model;
init();
animate();
function init() {
loader = new Loader('assets/bunny_low_res_scaled.obj');
loader.start(function(elements) {
for (let element of elements) {
if (element !== undefined) {
model.manageElement(element);
}
}
});
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.0001, 1000);
camera.position.z = 3;
scene = new THREE.Scene();
model = new Model();
scene.add(model);
light1 = new THREE.AmbientLight(0x999999);
scene.add(light1);
light2 = new THREE.DirectionalLight(0xffffff, 1.0);
light2.position.set(0.0, 1.0, 0.0);
scene.add(light2);
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}