2020-08-18 00:04:54 +02:00
|
|
|
/**
|
|
|
|
* @author Junho Jin[junho.jin@kaist.ac.kr] | https://github.com/JinJunho
|
|
|
|
* @version 1.0.0
|
|
|
|
*
|
2020-08-18 00:06:48 +02:00
|
|
|
* [Project] Madeleine.js, Pure JavaScript STL Parser & Renderer.
|
2020-08-18 00:04:54 +02:00
|
|
|
*
|
2020-08-18 00:06:48 +02:00
|
|
|
* [Description] Madeleine.js constists of three part: DataReader, Madeleine and Lily.
|
2020-08-18 00:04:54 +02:00
|
|
|
* DataReader is a helper function to read binary data, which is a customized version
|
2020-08-18 00:06:48 +02:00
|
|
|
* of DataView. Lily is a helper Object that manages created Madeleines and logs
|
2020-08-18 00:04:54 +02:00
|
|
|
* messages to console. Madeleine is the part that deals with actual parsing, rendering
|
|
|
|
* and showing your stl files. Customize them however you want. This project is under
|
|
|
|
* MIT License (see the LICENSE file for details). You are allowed to do anything with
|
2020-08-18 00:06:48 +02:00
|
|
|
* this code, as long as you leave the attribution (MUST!). It will be glad if you
|
2020-08-18 00:04:54 +02:00
|
|
|
* contact me for any bug you found or interesting ideas to do with Madeleine.js.
|
|
|
|
* I'm willing to co-work with you!
|
|
|
|
*/
|
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
|
|
|
// Madeleine constructor
|
|
|
|
Madeleine = function(options) {
|
|
|
|
|
|
|
|
var Madeleine;
|
|
|
|
|
|
|
|
// Constants for default setting
|
|
|
|
var CONVERT_TO_BINARY = true;
|
|
|
|
|
|
|
|
var OBJECT_MATERIAL = "matt";
|
|
|
|
var OBJECT_STATUS = false;
|
2020-08-18 00:06:48 +02:00
|
|
|
//var OBJECT_BACKGROUND = "DADADA";
|
|
|
|
var OBJECT_BACKGROUND = "2E323E";
|
|
|
|
var OBJECT_COLOR = "00A0E1";
|
2020-08-18 00:04:54 +02:00
|
|
|
|
|
|
|
var CAMERA_SIGHT = 45;
|
|
|
|
var CAMERA_NEARFIELD = 1;
|
|
|
|
var CAMERA_FARFIELD = 100000;
|
|
|
|
|
|
|
|
var VIEWER_THEME = "default";
|
|
|
|
var VIEWER_PREFIX = "mad-";
|
|
|
|
var VIEWER_CREATE = true;
|
2020-08-18 00:06:48 +02:00
|
|
|
var VIEWER_HEIGHT = 625; //400
|
|
|
|
var VIEWER_WIDTH = 1000; //640
|
2020-08-18 00:04:54 +02:00
|
|
|
|
|
|
|
var USER_ROTATE_SENSITIVITY = 0.005;
|
|
|
|
var USER_ZOOM_SENSITIVITY = 100;
|
|
|
|
var SCROLL_FACTOR = 10;
|
2020-08-18 00:06:48 +02:00
|
|
|
|
|
|
|
// Necessary option check
|
2020-08-18 00:04:54 +02:00
|
|
|
if (!document.getElementById(options.target)) {
|
|
|
|
console.log("MADELEINE[ERR] Target must be a valid DOM Element.");
|
|
|
|
return null;
|
|
|
|
} else if (!options.data) {
|
|
|
|
console.log("MADELEINE[ERR] Option must contain target and data.");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Construct new Madeleine
|
|
|
|
Madeleine = function(options) {
|
|
|
|
// Alias to own object
|
|
|
|
var scope = this;
|
|
|
|
// Internal properties
|
|
|
|
this.__uniqueID = Lily.push(this);
|
|
|
|
this.__containerID = options.target;
|
|
|
|
this.__timer = {start: (new Date).getTime(), end: null};
|
|
|
|
// About 3d model
|
|
|
|
this.__status = null;
|
|
|
|
this.__object = null;
|
|
|
|
this.__bounds = null;
|
|
|
|
this.__center = null;
|
|
|
|
this.__rawText = null;
|
|
|
|
this.__converted = null;
|
|
|
|
this.__arrayBuffer = null;
|
|
|
|
// Initialize data info object
|
|
|
|
this.__info = {type: null, load: this.type, vertices: 0, facets: 0, faces: 0};
|
|
|
|
// About visualization
|
|
|
|
this.__scene = new THREE.Scene();
|
|
|
|
this.__camera = null;
|
|
|
|
this.__viewer = null;
|
|
|
|
this.__canvas = null;
|
|
|
|
// About rendering
|
|
|
|
this.__geometry = null;
|
|
|
|
this.__renderer = null;
|
|
|
|
// About camera view
|
|
|
|
this.__width = null;
|
|
|
|
this.__height = null;
|
|
|
|
this.__sizeRatio = 1;
|
|
|
|
// About user interaction
|
|
|
|
this.__firstPerson = false;
|
|
|
|
this.__movable = true;
|
|
|
|
this.__zoomable = true;
|
|
|
|
this.__rotatable = true;
|
|
|
|
this.__rotating = false;
|
|
|
|
this.__trackMouse = false;
|
|
|
|
this.__mouseX = 0;
|
|
|
|
this.__mouseY = 0;
|
|
|
|
// Crucial properties to render 3d model
|
|
|
|
this.data = options.data;
|
|
|
|
this.type = options.type ? options.type : 'file';
|
|
|
|
this.container = document.getElementById(this.__containerID);
|
|
|
|
this.relPath = options.path ? options.path + (options.path[options.path.length-1] == "/" ? "" : "/") : "./";
|
2020-08-18 00:06:48 +02:00
|
|
|
// User configuration
|
2020-08-18 00:04:54 +02:00
|
|
|
this.options = Lily.extend(true, {}, { // Default option
|
|
|
|
material : OBJECT_MATERIAL,
|
|
|
|
showStatus : OBJECT_STATUS,
|
|
|
|
backgroundColor : OBJECT_BACKGROUND,
|
|
|
|
objectColor : OBJECT_COLOR,
|
|
|
|
viewer : {
|
|
|
|
create : VIEWER_CREATE, // Create new viewer?
|
|
|
|
prefix : VIEWER_PREFIX, // Viewer id prefix
|
|
|
|
height : VIEWER_HEIGHT, // Viewer height
|
|
|
|
width : VIEWER_WIDTH, // Viewer width
|
|
|
|
theme : VIEWER_THEME, // Viewer theme
|
|
|
|
},
|
|
|
|
camera : {
|
|
|
|
sight : CAMERA_SIGHT, // Vertical Field of View
|
|
|
|
near : CAMERA_NEARFIELD, // Near Field Distance
|
|
|
|
far : CAMERA_FARFIELD, // Far Field Distance
|
|
|
|
},
|
|
|
|
rotateSensitivity : USER_ROTATE_SENSITIVITY,
|
|
|
|
scrollFactor : SCROLL_FACTOR,
|
|
|
|
zoomSensitivity : USER_ZOOM_SENSITIVITY,
|
|
|
|
}, options);
|
|
|
|
|
|
|
|
// Event Listeners
|
|
|
|
this.scrollHandler = function(e) {
|
|
|
|
var delta = e.wheelDelta ? e.wheelDelta/40 : (e.detail ? -e.detail : 0);
|
|
|
|
if (delta < 0) delta -= this.options.scrollFactor;
|
|
|
|
if (delta > 0) delta += this.options.scrollFactor;
|
|
|
|
scope.cameraZoom(delta);
|
|
|
|
e.preventDefault();
|
|
|
|
}.bind(this);
|
|
|
|
this.gestureHandler = function(e) {
|
|
|
|
scope.cameraZoom(( 1 < e.scale ? "in" : "out" ));
|
|
|
|
e.preventDefault();
|
|
|
|
};
|
|
|
|
this.mouseDownHandler = function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
if ((e.which && e.which == 3) || (e.button && e.button == 2)) {
|
|
|
|
// alert("Right click on 3D viewer is disabled.");
|
|
|
|
} else {
|
|
|
|
scope.trackMouse = true;
|
|
|
|
scope.__rotating = false;
|
|
|
|
scope.mouseY = e.clientY;
|
|
|
|
scope.mouseX = e.clientX;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
this.mouseMoveHandler = function(e) {
|
|
|
|
if (scope.trackMouse) {
|
|
|
|
// Top-left corner is (0, 0)
|
|
|
|
// e.clientX grows as mouse goes down
|
|
|
|
// e.clientY grows as mouse goes right
|
2020-08-18 00:06:48 +02:00
|
|
|
|
2020-08-18 00:04:54 +02:00
|
|
|
//IMPROVED move object on wheel drag and rotate with left mouse drag
|
|
|
|
if (e.which == 1) {//left button mouse
|
|
|
|
scope.rotateObjectZ(scope.mouseX - e.clientX);
|
|
|
|
scope.rotateObjectX(scope.mouseY - e.clientY);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e.which == 2) {//whell button mouse
|
|
|
|
scope.moveObject(event, scope.mouseX - e.clientX);
|
|
|
|
scope.moveObject(event, scope.mouseY - e.clientY);
|
|
|
|
}
|
|
|
|
scope.mouseY = e.clientY;
|
|
|
|
scope.mouseX = e.clientX;
|
|
|
|
}
|
|
|
|
e.preventDefault();
|
|
|
|
};
|
|
|
|
this.mouseUpHandler = function(e) {
|
|
|
|
scope.trackMouse = false;
|
|
|
|
scope.__rotating = true;
|
|
|
|
e.preventDefault();
|
|
|
|
};
|
|
|
|
|
|
|
|
// Check if option values are correct
|
|
|
|
this.adjustUserConfiguration();
|
|
|
|
// Initialize rendering
|
|
|
|
this.init();
|
|
|
|
};
|
|
|
|
|
|
|
|
// Initialize rendering
|
|
|
|
Madeleine.prototype.init = function() {
|
2020-08-18 00:06:48 +02:00
|
|
|
|
2020-08-18 00:04:54 +02:00
|
|
|
//IMPROVE callbackstart process i.e.: show another loader in other scope
|
|
|
|
if (this.options.callbackstart) this.options.callbackstart();
|
2020-08-18 00:06:48 +02:00
|
|
|
|
|
|
|
// Get file name
|
2020-08-18 00:04:54 +02:00
|
|
|
this.__info.name = (typeof this.data == "string") ? this.data.split("/").slice(-1)[0] : this.data.name;
|
|
|
|
|
|
|
|
// If create new viewer, set canvas size to the viewer.
|
2020-08-18 00:06:48 +02:00
|
|
|
if (this.options.viewer.create) {
|
|
|
|
this.__height = this.options.viewer.height;
|
|
|
|
this.__width = this.options.viewer.width;
|
|
|
|
// Get target width and height, otherwise.
|
|
|
|
} else
|
|
|
|
if (document.defaultView && document.defaultView.getComputedStyle) {
|
2020-08-18 00:04:54 +02:00
|
|
|
this.__height = parseFloat(document.defaultView.getComputedStyle(this.container,null).getPropertyValue('height'));
|
|
|
|
this.__width = parseFloat(document.defaultView.getComputedStyle(this.container,null).getPropertyValue('width'));
|
|
|
|
} else {
|
|
|
|
this.__height = parseFloat(this.container.currentStyle.height);
|
|
|
|
this.__width = parseFloat(this.container.currentStyle.width);
|
|
|
|
}
|
|
|
|
// Create viewer
|
|
|
|
this.createViewer();
|
|
|
|
|
|
|
|
// Adjust canvas width/height ratio
|
|
|
|
this.__sizeRatio = this.__width / this.__height;
|
|
|
|
|
|
|
|
// Create camera
|
|
|
|
this.__camera = new THREE.PerspectiveCamera(
|
|
|
|
this.options.camera.sight,
|
|
|
|
this.__sizeRatio,
|
|
|
|
this.options.camera.near,
|
|
|
|
this.options.camera.far
|
2020-08-18 00:06:48 +02:00
|
|
|
);
|
2020-08-18 00:04:54 +02:00
|
|
|
|
|
|
|
// Set user's point-of-view and default camera position
|
|
|
|
this.__camera.position.set(0, 0, 0);
|
|
|
|
this.__scene.add(this.__camera);
|
|
|
|
|
|
|
|
// Add lights
|
|
|
|
if (OBJECT_MATERIAL == "shiny") {
|
|
|
|
this.addShadowedLight(1, 1, 1, 0xFFFFFF, 1.3);
|
|
|
|
this.addShadowedLight(0.5, 1, -1, 0xFFCC66, 1);
|
|
|
|
var ambientLight = new THREE.AmbientLight(0x111111);
|
|
|
|
this.__scene.add(ambientLight);
|
|
|
|
} else {
|
|
|
|
var directionalLight = new THREE.DirectionalLight(0xFFFFFF, 1);
|
|
|
|
var ambientLight = new THREE.AmbientLight(0x202020);
|
|
|
|
directionalLight.position.set(0, 1, 1).normalize();
|
|
|
|
this.__scene.add(directionalLight);
|
|
|
|
this.__scene.add(ambientLight);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start rendering
|
|
|
|
this.draw();
|
|
|
|
};
|
|
|
|
|
|
|
|
// Rendering start
|
|
|
|
Madeleine.prototype.draw = function() {
|
|
|
|
// Wait until data is fully loaded
|
|
|
|
var queued = (function(scope) {
|
|
|
|
return function() {
|
2020-08-18 00:06:48 +02:00
|
|
|
// When data ready, parse and render it.
|
2020-08-18 00:04:54 +02:00
|
|
|
scope.run(scope.relPath + "lib/MadeleineLoader.js", {
|
|
|
|
arrbuf: scope.__arrayBuffer,
|
|
|
|
rawtext: scope.__rawText
|
|
|
|
}, function(result) {
|
|
|
|
var hasColors = result.hasColors;
|
|
|
|
var vertices = result.vertices;
|
|
|
|
var normals = result.normals;
|
|
|
|
var colors = result.colors;
|
|
|
|
var alpha = result.alpha;
|
|
|
|
|
|
|
|
// Create new geometry
|
|
|
|
scope.__geometry = new THREE.Geometry();
|
|
|
|
|
|
|
|
// Parsing done. Add vertices and normals to geometry
|
|
|
|
scope.__geometry = new THREE.BufferGeometry();
|
|
|
|
scope.__geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
|
|
|
|
scope.__geometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3));
|
|
|
|
|
|
|
|
// Set color information
|
|
|
|
if (hasColors) {
|
|
|
|
scope.__geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3));
|
|
|
|
scope.__geometry.hasColors = true;
|
|
|
|
scope.__geometry.alpha = alpha;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start rendering
|
|
|
|
scope.renderObject();
|
|
|
|
|
|
|
|
// Compute time consumed in parsing and rendering
|
|
|
|
scope.__timer.end = (new Date()).getTime();
|
|
|
|
var consumed = (scope.__timer.end - scope.__timer.start) / 1000;
|
|
|
|
console.log("MADELEINE[LOG] Time spent: " + consumed + " sec.");
|
|
|
|
|
|
|
|
// Render object
|
|
|
|
scope.render();
|
|
|
|
// Log rendering status
|
|
|
|
scope.logStatus();
|
|
|
|
// Start rotating animation
|
|
|
|
scope.startAnimation();
|
|
|
|
// Enable mouse zoom action
|
|
|
|
scope.enableZoomAsMouseScroll();
|
|
|
|
// Enable mouse motion action
|
|
|
|
scope.enableUserInteraction();
|
|
|
|
|
|
|
|
//IMPROVED callback end i.e: hide loading of another scope
|
|
|
|
if (scope.options.callbackend) scope.options.callbackend();
|
2020-08-18 00:06:48 +02:00
|
|
|
|
2020-08-18 00:04:54 +02:00
|
|
|
});
|
|
|
|
};
|
|
|
|
})(this);
|
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
// Check input type and get STL Binary data
|
2020-08-18 00:04:54 +02:00
|
|
|
switch (this.type) {
|
|
|
|
case "upload":
|
|
|
|
this.getDataFromBlob(this.data, queued);break;
|
|
|
|
case "file":
|
|
|
|
this.getDataFromUrl(this.data, queued);break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Get arrayBuffer from Blob
|
|
|
|
Madeleine.prototype.getDataFromBlob = function(file, queuedWork) {
|
|
|
|
var scope = this;
|
|
|
|
|
|
|
|
if (Detector.fileapi) {
|
|
|
|
var arrbuf = new FileReader();
|
|
|
|
var rawtxt = new FileReader();
|
|
|
|
// arraybuffer onload function
|
|
|
|
arrbuf.onload = function () {
|
|
|
|
scope.__arrayBuffer = arrbuf.result
|
|
|
|
//console.log("scope.__arrayBuffer------>", scope.__arrayBuffer);
|
|
|
|
if (scope.__arrayBuffer && scope.__rawText) {//IMPROVED asynchronous onload arrbuf.onload rawtxt.onload doesnt know witch will finish first causing null __arrayBuffer or null __rawText
|
|
|
|
console.log('calling o queuedWork no arrbuf onload');
|
|
|
|
queuedWork();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// read arrayBuffer from Blob
|
|
|
|
arrbuf.readAsArrayBuffer(file);
|
|
|
|
|
|
|
|
// rawtext onload function
|
|
|
|
rawtxt.onload = function () {
|
|
|
|
scope.__rawText = rawtxt.result;
|
|
|
|
//console.log("scope.__rawText ----->", scope.__rawText);
|
|
|
|
if (scope.__arrayBuffer && scope.__rawText) {//IMPROVED asynchronous onload arrbuf.onload rawtxt.onload doesnt know witch will finish first causing null __arrayBuffer or null __rawText
|
|
|
|
console.log('calling in no rawtxt onload');
|
|
|
|
queuedWork();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// read raw text data from Blob
|
|
|
|
rawtxt.readAsText(file);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
// Get arrayBuffer from external file
|
2020-08-18 00:04:54 +02:00
|
|
|
Madeleine.prototype.getDataFromUrl = function(url, queuedWork, type) {
|
|
|
|
var scope = this;
|
|
|
|
var getArrayBuffer = function() {
|
|
|
|
var arrbuf = new XMLHttpRequest();
|
|
|
|
arrbuf.onerror = function(e) { console.log("MADELEINE[ERR] Ajax failed.") };
|
|
|
|
arrbuf.onreadystatechange = function() {
|
|
|
|
if (arrbuf.readyState == 4 && (arrbuf.status == 200 || arrbuf.status == 0)) {
|
|
|
|
scope.__arrayBuffer = arrbuf.response;
|
|
|
|
if (type == "binary") queuedWork();
|
|
|
|
if (!type) getRawText();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
arrbuf.responseType = "arraybuffer";
|
|
|
|
arrbuf.open("GET", url, true);
|
|
|
|
arrbuf.send(null);
|
|
|
|
};
|
|
|
|
var getRawText = function() {
|
|
|
|
var rawtxt = new XMLHttpRequest();
|
|
|
|
rawtxt.onerror = function(e) { console.log("MADELEINE[ERR] Ajax failed.") };
|
|
|
|
rawtxt.onreadystatechange = function() {
|
|
|
|
if (rawtxt.readyState == 4 && (rawtxt.status == 200 || rawtxt.status == 0)) {
|
|
|
|
scope.__rawText = rawtxt.response;
|
|
|
|
queuedWork();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
rawtxt.responseType = "text";
|
|
|
|
rawtxt.open("GET", url, true);
|
|
|
|
rawtxt.send(null);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Get data from external resource
|
|
|
|
switch(type) {
|
|
|
|
case "ascii":
|
|
|
|
getRawText();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
getArrayBuffer();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Render object
|
|
|
|
Madeleine.prototype.renderObject = function() {
|
|
|
|
// Create renderer
|
|
|
|
if (!this.__object) this.createRenderer();
|
|
|
|
|
|
|
|
// Get material
|
|
|
|
var material = null;
|
|
|
|
if (this.options.material == "shiny") {
|
|
|
|
material = new THREE.MeshPhongMaterial({
|
|
|
|
color: this.getHexColor(this.options.objectColor),
|
|
|
|
ambient: this.getHexColor(this.options.objectColor),
|
|
|
|
specular: 0x1F1F1F,
|
|
|
|
shininess: 25,
|
|
|
|
side: THREE.DoubleSide,
|
|
|
|
overdraw: true
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
material = new THREE.MeshLambertMaterial({
|
|
|
|
color: this.getHexColor(this.options.objectColor),
|
|
|
|
shading: THREE.FlatShading,
|
|
|
|
side: THREE.DoubleSide,
|
|
|
|
overdraw: true
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate mesh for object
|
|
|
|
this.__object = new THREE.Mesh(this.__geometry, material);
|
|
|
|
this.__geometry.computeBoundingSphere();
|
|
|
|
this.__geometry.computeVertexNormals();
|
|
|
|
this.__geometry.computeBoundingBox();
|
|
|
|
|
|
|
|
// Adjust positions of camera and object to make object fit into screen properly
|
|
|
|
var radius = this.__geometry.boundingSphere.radius;
|
|
|
|
// 1.865 times zoomed object with radius 85 practically fits best to the screen
|
|
|
|
var zoomFactor = 1.865 * 85 / radius;
|
|
|
|
|
|
|
|
// Zoom object to fit into the screen. ZoomFactor is practically best to fit
|
|
|
|
this.__object.scale.set(zoomFactor, zoomFactor, zoomFactor);
|
|
|
|
// Height 125 fits well to the screen. If taller, lower the center of y-axis
|
|
|
|
var maxY = this.__geometry.boundingBox.max.y;
|
|
|
|
var minY = this.__geometry.boundingBox.min.y;
|
|
|
|
var height = maxY - minY;
|
|
|
|
var deltaY = (height > 125 ? parseFloat(0.99 * (height - Math.max(maxY, Math.abs(minY)))) : 15);
|
|
|
|
// deltaY in some cases can make object out of screen height
|
|
|
|
//console.log(deltaY, height, minY, maxY);
|
|
|
|
if (options.XY) {//IMPROVMENT setXY mannually
|
|
|
|
if (options.XY.X) {
|
|
|
|
this.__object.position.setX(options.XY.X);
|
|
|
|
}
|
|
|
|
if (options.XY.Y) {
|
|
|
|
this.__object.position.setY(options.XY.Y);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.__object.position.setY(-deltaY);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If object is too large to fit in, make camera look further
|
|
|
|
// 500 (default camera distance) : 466 (view height)
|
|
|
|
// new distance (x) : boundingSphere radius (r) * 2
|
|
|
|
// x = (2 * r * 500) / 466 ~= 2.146 * r
|
|
|
|
if (radius < 233) this.__camera.position.z = 500;
|
|
|
|
else this.__camera.position.z = radius * 2.146;
|
|
|
|
this.__camera.updateProjectionMatrix();
|
|
|
|
|
|
|
|
// Parsing finished
|
|
|
|
this.__scene.add(this.__object);
|
|
|
|
this.__object.rotation.x = -1.2;
|
|
|
|
this.__object.rotation.z = 1.2;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Generate Renderer
|
|
|
|
Madeleine.prototype.createRenderer = function() {
|
|
|
|
// create renderer
|
|
|
|
this.__renderer = Detector.webgl ?
|
|
|
|
new THREE.WebGLRenderer({
|
|
|
|
preserveDrawingBuffer: true,
|
|
|
|
alpha: true
|
2020-08-18 00:06:48 +02:00
|
|
|
}) : new THREE.CanvasRenderer();
|
2020-08-18 00:04:54 +02:00
|
|
|
// attach canvas to viewer
|
|
|
|
this.__canvas = this.__renderer.domElement;
|
|
|
|
this.__viewer.appendChild(this.__canvas);
|
|
|
|
// renderer configuration
|
|
|
|
this.__renderer.setSize(this.__width, this.__height);
|
|
|
|
this.__renderer.setClearColor(0x000000, 0);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Generate Madeleine Viewer
|
|
|
|
Madeleine.prototype.createViewer = function() {
|
|
|
|
if (!this.options.viewer.create) this.container.appendChild(this.__renderer.domElement);
|
|
|
|
else {
|
|
|
|
// Create viewer element
|
|
|
|
this.__viewer = document.createElement("div");
|
|
|
|
this.__viewer.id = this.options.viewer.prefix + this.__uniqueID;
|
|
|
|
|
|
|
|
// Force viewer size
|
2020-08-18 00:06:48 +02:00
|
|
|
this.__viewer.style["max-width"] = "100%";
|
|
|
|
this.__viewer.style["min-width"] = "50%";
|
2020-08-18 00:04:54 +02:00
|
|
|
this.__viewer.style.height = this.__height;
|
|
|
|
this.__viewer.style.width = this.__width;
|
|
|
|
|
|
|
|
// Set default style
|
|
|
|
this.__viewer.style.background = "transparent";
|
|
|
|
this.__viewer.style.position = "relative";
|
|
|
|
this.__viewer.style.margin = "0 0 10px 0";
|
|
|
|
|
|
|
|
// Progress bar
|
|
|
|
var progress = document.createElement("div");
|
|
|
|
progress.id = "progressBar-" + this.__uniqueID;
|
|
|
|
progress.style["-webkit-transition"] = "width .5s ease-in-out, opacity 2s ease";
|
|
|
|
progress.style["-moz-transition"] = "width .5s ease-in-out, opacity 2s ease";
|
|
|
|
progress.style["-ms-transition"] = "width .5s ease-in-out, opacity 2s ease";
|
|
|
|
progress.style["-o-transition"] = "width .5s ease-in-out, opacity 2s ease";
|
|
|
|
progress.style["transition"] = "width .5s ease-in-out, opacity 2s ease";
|
|
|
|
progress.style["min-height"] = "2px";
|
|
|
|
progress.style.background = "#009999";
|
|
|
|
progress.style.position = "absolute";
|
|
|
|
progress.style.height = "2px";
|
|
|
|
progress.style.width = 0;
|
|
|
|
progress.style.left = 0;
|
|
|
|
progress.style.top = 0;
|
|
|
|
|
|
|
|
// Viewer iconGrid
|
|
|
|
var iconGrid = document.createElement("div");
|
|
|
|
iconGrid.style.cssText += "background:transparent;position:absolute;padding:15px 10px;";
|
2020-08-18 00:06:48 +02:00
|
|
|
iconGrid.style.cssText += "height:50px;width:100%;top:0;overflow:hidden;";
|
2020-08-18 00:04:54 +02:00
|
|
|
iconGrid.className += "box";
|
|
|
|
|
|
|
|
var logo = document.createElement("div");
|
|
|
|
var info = document.createElement("div");
|
|
|
|
var view = document.createElement("div");
|
|
|
|
|
|
|
|
info.id = "model-info-" + this.__uniqueID;
|
|
|
|
info.className += "model-info noselect";
|
|
|
|
info.innerHTML = this.__info.name;
|
|
|
|
|
|
|
|
logo.className += "clickable pull-left madeleine-logo";
|
|
|
|
view.className += "clickable pull-right icon-mad-view";
|
|
|
|
|
|
|
|
var rotator = document.createElement("div");
|
|
|
|
|
|
|
|
rotator.style.cssText += "background:transparent;position:absolute;padding:15px 10px;right:0;";
|
2020-08-18 00:06:48 +02:00
|
|
|
rotator.style.cssText += "height:50px;width:100%;top:0;overflow:hidden;";
|
2020-08-18 00:04:54 +02:00
|
|
|
rotator.style.cssText += "margin-top:"+(this.__height-30)+"px;";
|
|
|
|
|
|
|
|
iconGrid.appendChild(view);
|
2020-08-21 18:02:13 +02:00
|
|
|
iconGrid.appendChild(logo); //the madeleine.js logo
|
|
|
|
iconGrid.appendChild(info); //the file name
|
|
|
|
iconGrid.appendChild(rotator); //the invisible thing to allow rotating the model by mouse
|
2020-08-18 00:04:54 +02:00
|
|
|
|
|
|
|
// Append to container
|
|
|
|
if (this.options.viewer.notappend) {//IMPROVED not append a new canvas put in same
|
|
|
|
$(this.container).html(this.__viewer);
|
|
|
|
this.__viewer.appendChild(iconGrid);
|
|
|
|
this.__viewer.appendChild(progress);
|
|
|
|
} else {
|
|
|
|
this.container.appendChild(this.__viewer);
|
|
|
|
this.__viewer.appendChild(iconGrid);
|
|
|
|
this.__viewer.appendChild(progress);
|
|
|
|
}
|
2020-08-21 18:02:13 +02:00
|
|
|
this.__viewer.style.background = this.makeHexString(options.backgroundColor || OBJECT_BACKGROUND);
|
|
|
|
this.options.objectColor = options.objectColor || OBJECT_COLOR;
|
|
|
|
this.__object && this.setObjectColor();
|
2020-08-18 00:04:54 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Set canvas background color
|
|
|
|
Madeleine.prototype.setBackgroundColor = function(code) {
|
|
|
|
var code = arguments.length == 3 ? [arguments[0], arguments[1], arguments[2]] : code;
|
|
|
|
var color = this.getHexString(code);
|
|
|
|
this.__viewer.style.background = this.makeHexString(color);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Set object surface color
|
|
|
|
Madeleine.prototype.setObjectColor = function() {
|
|
|
|
var color = arguments.length != 0 ? arguments : this.options.objectColor;
|
|
|
|
this.__object.material.color.setHex(this.getHexColor(color));
|
|
|
|
};
|
|
|
|
|
|
|
|
// Generate gradation css
|
|
|
|
Madeleine.prototype.generateGradation = function(colors) {
|
|
|
|
var pos1, pos2, darker, brighter, cssText;
|
|
|
|
|
|
|
|
cssText = "background: BRIGHT;" +
|
|
|
|
"background: -moz-radial-gradient(center, ellipse cover, BRIGHT POS1%, DARK POS2%);" +
|
|
|
|
"background: -webkit-gradient(radial, center center, 0px, center center, POS2%, color-stop(POS1%,BRIGHT), color-stop(POS2%,DARK))" +
|
|
|
|
"background: -webkit-radial-gradient(center, ellipse cover, BRIGHT POS1%,DARK POS2%);" +
|
|
|
|
"background: -o-radial-gradient(center, ellipse cover, BRIGHT POS1%, DARK POS2%);" +
|
|
|
|
"background: -ms-radial-gradient(center, ellipse cover, BRIGHT POS1%, DARK POS2%);" +
|
|
|
|
"background: radial-gradient(ellipse at center, BRIGHT POS1%, DARK POS2%);" +
|
2020-08-18 00:06:48 +02:00
|
|
|
"filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='BRIGHT', endColorstr='DARK',GradientType=1)";
|
2020-08-18 00:04:54 +02:00
|
|
|
|
|
|
|
if (colors.dark) {
|
|
|
|
darker = this.getHexString(colors.dark);
|
|
|
|
brighter = this.getHexString(colors.bright ? colors.bright : this.adjustBrightness(darker));
|
|
|
|
} else {
|
|
|
|
brighter = this.getHexString(colors.bright);
|
|
|
|
darker = this.getHexString(this.adjustBrightness(brighter));
|
|
|
|
}
|
|
|
|
|
|
|
|
pos1 = colors.pos1 ? colors.pos1 : 27;
|
|
|
|
pos2 = colors.pos2 ? colors.pos2 : 100;
|
|
|
|
|
|
|
|
return cssText.replace(/BRIGHT/g, brighter).replace(/DARK/g, darker).replace(/POS1/g, pos1).replace(/POS2/g, pos2);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Get Hex color (0xXXXXXX format)
|
|
|
|
Madeleine.prototype.getHexColor = function() {
|
|
|
|
var code = arguments.length == 3 ? [arguments[0], arguments[1], arguments[2]] : arguments[0];
|
|
|
|
var color = this.getHexString(code);
|
2020-08-18 00:06:48 +02:00
|
|
|
return parseInt(color.replace(/\#/g, ''), 16);
|
2020-08-18 00:04:54 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// Get Hex-style string (#XXXXXX format)
|
|
|
|
Madeleine.prototype.getHexString = function(code) {
|
|
|
|
if (typeof code == "string") return this.makeHexString(code);
|
|
|
|
else if (typeof code == "object") return this.rgbToHex(code);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Strip # from Hex-style string
|
|
|
|
Madeleine.prototype.cutHexString = function(code) {
|
|
|
|
return code.charAt(0) == "#" ? code.substring(1) : code;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Prepend # to hex string
|
|
|
|
Madeleine.prototype.makeHexString = function(code) {
|
|
|
|
var color = code.length == 3 ? code.replace(/(.)/g, '$1$1') : code;
|
|
|
|
return color.charAt(0) != "#" ? "#" + color : color;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Convert RGB to Hex
|
|
|
|
Madeleine.prototype.rgbToHex = function(code) {
|
|
|
|
var i, color, hex = "#";
|
|
|
|
for (i = 0; i < code.length; i++) {
|
|
|
|
color = code[i].toString(16);
|
|
|
|
hex += (color.length == 1 ? "0" + color : color);
|
|
|
|
}
|
|
|
|
return hex;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Convert Hex to RGB
|
|
|
|
Madeleine.prototype.hexToRgb = function(code) {
|
|
|
|
var dec = this.cutHexString(code).toString(16);
|
|
|
|
return [parseInt(dec.substring(0, 2), 16),
|
|
|
|
parseInt(dec.substring(2, 4), 16),
|
|
|
|
parseInt(dec.substring(4, 6), 16)];
|
|
|
|
};
|
|
|
|
|
|
|
|
// Get brighter or darker color from input color
|
|
|
|
// Brightness is automatically set according to its intensity.
|
|
|
|
Madeleine.prototype.adjustBrightness = function(code) {
|
|
|
|
var adjustedR, adjustedG, adjustedB;
|
|
|
|
var intensity, color, rgb, r, g, b;
|
|
|
|
var darkFactor = 30, brightFactor = 20;
|
|
|
|
|
|
|
|
// Get color and RGB
|
|
|
|
color = this.cutHexString(code);
|
|
|
|
rgb = this.hexToRgb(color);
|
|
|
|
|
|
|
|
r = rgb[0];
|
|
|
|
g = rgb[1];
|
|
|
|
b = rgb[2];
|
|
|
|
|
|
|
|
// Get grayscale intensity
|
|
|
|
intensity = Math.sqrt(r*r + g*g + b*b) / 255 * 100;
|
|
|
|
|
|
|
|
// New color
|
|
|
|
adjustedR = intensity > 40 ? (0|(1<<8) + r * (1 - darkFactor / 100)) : (0|(1<<8) + r + (256 - r) * brightFactor / 100);
|
|
|
|
adjustedG = intensity > 40 ? (0|(1<<8) + g * (1 - darkFactor / 100)) : (0|(1<<8) + g + (256 - g) * brightFactor / 100);
|
|
|
|
adjustedB = intensity > 40 ? (0|(1<<8) + b * (1 - darkFactor / 100)) : (0|(1<<8) + b + (256 - b) * brightFactor / 100);
|
|
|
|
|
|
|
|
return (adjustedR.toString(16)).substr(1) +
|
|
|
|
(adjustedG.toString(16)).substr(1) +
|
|
|
|
(adjustedB.toString(16)).substr(1);
|
|
|
|
};
|
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
// Add new background worker
|
2020-08-18 00:04:54 +02:00
|
|
|
Madeleine.prototype.run = function(path, luggage, onload) {
|
|
|
|
var progressBar = document.getElementById("progressBar-"+this.__uniqueID);
|
|
|
|
var worker = new WorkerFacade(path);
|
|
|
|
var scope = this;
|
|
|
|
|
|
|
|
worker.postMessage(luggage);
|
|
|
|
worker.onmessage = function(event) {
|
|
|
|
var result = event.data;
|
|
|
|
switch (result.type) {
|
|
|
|
case "convert-progress":
|
|
|
|
// console.log("MADELEINE[LOG] Background converting progress: " + result.data + "%");
|
|
|
|
break;
|
|
|
|
case "convert":
|
|
|
|
if (CONVERT_TO_BINARY) scope.run(scope.relPath + "lib/MadeleineConverter.js", result.data, function(result) { scope.__converted = result });
|
|
|
|
break;
|
|
|
|
case "progress":
|
2020-08-18 00:06:48 +02:00
|
|
|
//progressBar.style.width = (scope.__width * result.data / 100) + "px";
|
|
|
|
progressBar.style.width = (result.data) + "%";
|
2020-08-18 00:04:54 +02:00
|
|
|
if (result.data == 100) {
|
|
|
|
progressBar.style["-webkit-opacity"] = 0;
|
|
|
|
progressBar.style["-moz-opacity"] = 0;
|
|
|
|
progressBar.style["opacity"] = 0;
|
|
|
|
};
|
|
|
|
// console.log("MADELEINE[LOG] Background work progress: " + result.data + "%");
|
|
|
|
break;
|
|
|
|
case "message":
|
|
|
|
console.log("MADELEINE[LOG] Background work message: " + result.data);
|
|
|
|
break;
|
|
|
|
case "error":
|
|
|
|
console.log("MADELEINE[ERR] Background work error: " + result.data);
|
|
|
|
break;
|
|
|
|
case "info":
|
|
|
|
scope.__info[result.prop] = result.data;
|
|
|
|
if (result.prop == "size") {
|
|
|
|
var infoBox = document.getElementById("model-info-" + scope.__uniqueID);
|
|
|
|
infoBox.innerHTML += " (" +result.data + ")";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "data":
|
|
|
|
scope["__" + result.prop] = result.data;
|
|
|
|
break;
|
|
|
|
case "done":
|
|
|
|
onload(result.data);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// Do nothing
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
// Start animation
|
|
|
|
Madeleine.prototype.startAnimation = function() {
|
|
|
|
if (this.__rotatable) this.__rotating = true;
|
|
|
|
this.interact(this);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Stop animation
|
|
|
|
Madeleine.prototype.stopAnimation = function() {
|
|
|
|
this.__rotating = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Start rotating object
|
|
|
|
Madeleine.prototype.startRotation = function() {
|
|
|
|
this.options.rotateSensitivity = 0.005;
|
|
|
|
this.__rotating = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Stop rotating object
|
|
|
|
Madeleine.prototype.stopRotation = function() {
|
|
|
|
this.__rotating = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Enable user interaction
|
|
|
|
Madeleine.prototype.interact = function(scope) {
|
|
|
|
requestAnimationFrame(function(){scope.interact(scope)});
|
|
|
|
scope.options.showStatus && scope.__status.update();
|
|
|
|
scope.render();
|
|
|
|
};
|
|
|
|
|
|
|
|
// Perform actual rendering object
|
|
|
|
Madeleine.prototype.render = function() {
|
|
|
|
this.__rotating && this.rotateObjectZ(-1);
|
|
|
|
this.__renderer.render(this.__scene, this.__camera);
|
|
|
|
};
|
2020-08-18 00:06:48 +02:00
|
|
|
|
2020-08-18 00:04:54 +02:00
|
|
|
// Move object in xy //IMPROVED
|
|
|
|
let oldX = 0, oldY = 0;
|
|
|
|
Madeleine.prototype.moveObject = function ($event, delta) {
|
|
|
|
if (this.__movable) {
|
|
|
|
let directionX = 0, directionY = 0, diffX = 0, diffY = 0;
|
|
|
|
let offsetmove = options.MOVE_FACTOR || 3;
|
|
|
|
|
|
|
|
if ($event.pageX < oldX) {
|
|
|
|
directionX = "left"
|
|
|
|
diffX = oldX - $event.pageX;
|
|
|
|
} else if ($event.pageX > oldX) {
|
|
|
|
directionX = "right"
|
|
|
|
diffX = $event.pageX - oldX;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($event.pageY < oldY) {
|
|
|
|
directionY = "top"
|
|
|
|
diffY = oldY - $event.pageY;
|
|
|
|
} else if ($event.pageY > oldY) {
|
|
|
|
directionY = "bottom";
|
|
|
|
diffY = $event.pageY - oldY;
|
|
|
|
}
|
|
|
|
|
|
|
|
oldX = $event.pageX;
|
|
|
|
oldY = $event.pageY;
|
|
|
|
|
|
|
|
if (directionX == "left") this.__object.position.x -= offsetmove;
|
|
|
|
if (directionX == "right") this.__object.position.x += offsetmove;
|
|
|
|
|
|
|
|
if (directionY == "bottom") this.__object.position.y -= offsetmove;
|
|
|
|
if (directionY == "top") this.__object.position.y += offsetmove;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Rotate object in X direction
|
|
|
|
Madeleine.prototype.rotateObjectX = function(delta) {
|
|
|
|
if (this.__movable) this.__object.rotation.x -= delta * this.options.rotateSensitivity;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Rotate object in Y direction
|
|
|
|
Madeleine.prototype.rotateObjectY = function(delta) {
|
|
|
|
if (this.__movable) this.__object.rotation.y -= delta * this.options.rotateSensitivity;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Rotate object in Z direction
|
|
|
|
Madeleine.prototype.rotateObjectZ = function(delta) {
|
|
|
|
if (this.__movable) this.__object.rotation.z -= delta * this.options.rotateSensitivity;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Make animation faster as much as 'delta'
|
|
|
|
Madeleine.prototype.animationFaster = function(delta) {
|
|
|
|
this.options.rotateSensitivity *= (delta ? delta : 2);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Make animation slower as much as 'delta'
|
|
|
|
Madeleine.prototype.animationSlower = function(delta) {
|
|
|
|
this.options.rotateSensitivity /= (delta ? delta : 2);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Disable Madeline Viewer to be zoomed by mouse scroll
|
|
|
|
Madeleine.prototype.disableZoomAsMouseScroll = function() {
|
|
|
|
var target = this.container;
|
|
|
|
// remove event handler
|
|
|
|
Lily.remove(this.container, "mousewheel", this.scrollHandler);
|
|
|
|
Lily.remove(this.container, "DOMMouseScroll", this.scrollHandler);
|
|
|
|
Lily.remove(this.container, "gesturechange", this.gestureHandler);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Enable Madeline Viewer to be zoomed by mouse scroll
|
|
|
|
Madeleine.prototype.enableZoomAsMouseScroll = function() {
|
|
|
|
if (!this.__zoomable) return;
|
|
|
|
// attach event handler
|
|
|
|
Lily.bind(this.container, "mousewheel", this.scrollHandler);
|
|
|
|
Lily.bind(this.container, "DOMMouseScroll", this.scrollHandler);
|
|
|
|
Lily.bind(this.container, "gesturechange", this.gestureHandler);
|
2020-08-18 00:06:48 +02:00
|
|
|
};
|
2020-08-18 00:04:54 +02:00
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
// Enable Madeline Viewer to be controlled by mouse movement
|
2020-08-18 00:04:54 +02:00
|
|
|
Madeleine.prototype.enableUserInteraction = function() {
|
|
|
|
// block right click action
|
|
|
|
if (!this.__movable) return;
|
|
|
|
// attach event handler
|
|
|
|
Lily.bind(this.container, "mousedown", this.mouseDownHandler);
|
|
|
|
Lily.bind(this.container, "mousemove", this.mouseMoveHandler);
|
|
|
|
Lily.bind(this.container, "mouseup", this.mouseUpHandler);
|
2020-08-18 00:06:48 +02:00
|
|
|
};
|
2020-08-18 00:04:54 +02:00
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
// Disable Madeline Viewer to be controlled by mouse movement
|
2020-08-18 00:04:54 +02:00
|
|
|
Madeleine.prototype.disableUserInteraction = function() {
|
|
|
|
if (!this.__movable) return;
|
|
|
|
// attach event handler
|
|
|
|
Lily.remove(this.container, "mousedown", this.mouseDownHandler);
|
|
|
|
Lily.remove(this.container, "mousemove", this.mouseMoveHandler);
|
|
|
|
Lily.remove(this.container, "mouseup", this.mouseUpHandler);
|
2020-08-18 00:06:48 +02:00
|
|
|
};
|
2020-08-18 00:04:54 +02:00
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
// Enable first-person viewer mode
|
2020-08-18 00:04:54 +02:00
|
|
|
Madeleine.prototype.enableFirstPersonViewerMode = function() {
|
|
|
|
this.disableUserInteraction();
|
|
|
|
Lily.bind(document, "keypress", this.firstPersonHandler);
|
|
|
|
Lily.bind(document, "keydown", this.firstPersonHandler);
|
|
|
|
this.__firstPerson = true;
|
|
|
|
this.__rotating = false;
|
|
|
|
this.__zoomable = false;
|
|
|
|
this.__movable = false;
|
2020-08-18 00:06:48 +02:00
|
|
|
};
|
2020-08-18 00:04:54 +02:00
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
// Disable first-person viewer mode
|
2020-08-18 00:04:54 +02:00
|
|
|
Madeleine.prototype.disableFirstPersonViewerMode = function() {
|
|
|
|
this.enableUserInteraction();
|
|
|
|
this.__firstPerson = false;
|
|
|
|
this.__rotating = true;
|
|
|
|
this.__zoomable = true;
|
|
|
|
this.__movable = true;
|
2020-08-18 00:06:48 +02:00
|
|
|
};
|
2020-08-18 00:04:54 +02:00
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
// Adjust camera zoom in/out
|
2020-08-18 00:04:54 +02:00
|
|
|
Madeleine.prototype.cameraZoom = function() {
|
|
|
|
if (!this.__zoomable) return;
|
|
|
|
else {
|
|
|
|
var delta, type;
|
|
|
|
if (arguments.length == 1) {
|
|
|
|
if (typeof arguments[0] == "string") {
|
|
|
|
delta = this.options.zoomSensitivity;
|
|
|
|
type = arguments[0];
|
|
|
|
} else {
|
|
|
|
delta = arguments[0];
|
|
|
|
type = null;
|
|
|
|
}
|
|
|
|
} else if (arguments.length == 2) {
|
|
|
|
delta = arguments[0];
|
|
|
|
type = arguments[1];
|
|
|
|
}
|
|
|
|
if (type == "in") this.__camera.position.z += delta;
|
|
|
|
else this.__camera.position.z -= delta;
|
|
|
|
this.__camera.updateProjectionMatrix();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Show animation status
|
|
|
|
Madeleine.prototype.logStatus = function(target) {
|
|
|
|
if (!this.options.showStatus) return;
|
|
|
|
this.__status = new Stats();
|
|
|
|
this.__status.domElement.style.position = 'absolute';
|
|
|
|
this.__status.domElement.style.top = '0px';
|
|
|
|
this.__viewer.appendChild(this.__status.domElement);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Show animation status
|
|
|
|
Madeleine.prototype.stopLogStatus = function(target) {
|
|
|
|
this.options.showStatus = false;
|
|
|
|
this.__status = null;
|
|
|
|
};
|
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
// Adjust material, camera settings and sensitivities to have proper values
|
2020-08-18 00:04:54 +02:00
|
|
|
Madeleine.prototype.adjustUserConfiguration = function() {
|
|
|
|
this.adjustRotateSensitivity();
|
|
|
|
this.adjustZoomSensitivity();
|
|
|
|
this.adjustFocalPoint();
|
|
|
|
this.checkMaterial();
|
|
|
|
};
|
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
// Check material
|
2020-08-18 00:04:54 +02:00
|
|
|
Madeleine.prototype.checkMaterial = function() {
|
|
|
|
if (this.options.material !== "skin" || this.options.material !== "wire") {
|
|
|
|
this.options.material !== "skin";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
// Adjust camera settings to fit into proper range
|
2020-08-18 00:04:54 +02:00
|
|
|
Madeleine.prototype.adjustFocalPoint = function() {
|
|
|
|
var sight = this.options.camera.sight;
|
|
|
|
var near = this.options.camera.near;
|
|
|
|
var far = this.options.camera.far;
|
|
|
|
|
|
|
|
if (sight && typeof sight === "number" && 75 <= sight && sight <= 1000) return;
|
|
|
|
else this.options.camera.sight = CAMERA_SIGHT;
|
|
|
|
if (near && typeof near === "number" && 0 <= near && near <= 1000) return;
|
|
|
|
else this.options.camera.near = CAMERA_NEARFIELD;
|
|
|
|
if (far && typeof far === "number" && 5000 <= far && far <= 100000) return;
|
|
|
|
else this.options.camera.far = CAMERA_FARFIELD;
|
|
|
|
};
|
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
// Adjust zoom sensitivity to fit into proper range
|
2020-08-18 00:04:54 +02:00
|
|
|
Madeleine.prototype.adjustZoomSensitivity = function() {
|
|
|
|
var intensity = this.options.zoomSensitivity;
|
|
|
|
var visibleField = this.options.camera.far - this.options.camera.near;
|
|
|
|
|
|
|
|
if (intensity && typeof intensity === "number" && 0 < intensity && intensity <= visibleField/1000) return;
|
|
|
|
else this.options.zoomSensitivity = visibleField/1000;
|
|
|
|
};
|
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
// Adjust rotate sensitivity to fit into proper range
|
2020-08-18 00:04:54 +02:00
|
|
|
Madeleine.prototype.adjustRotateSensitivity = function(intensity) {
|
|
|
|
var intensity = this.options.rotateSensitivity;
|
|
|
|
|
|
|
|
if (intensity && typeof intensity === "number" && 0 < intensity && intensity < 0.05) return;
|
|
|
|
else this.options.rotateSensitivity = USER_ROTATE_SENSITIVITY;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Add directional light
|
|
|
|
Madeleine.prototype.addShadowedLight = function(x, y, z, color, intensity) {
|
|
|
|
var directionalLight = new THREE.DirectionalLight(color, intensity);
|
|
|
|
var d = 1;
|
|
|
|
|
|
|
|
directionalLight.position.set(x, y, z);
|
|
|
|
this.__scene.add(directionalLight);
|
|
|
|
|
|
|
|
// directionalLight.shadowCameraVisible = true;
|
|
|
|
directionalLight.castShadow = true;
|
|
|
|
directionalLight.shadowCameraTop = d;
|
|
|
|
directionalLight.shadowCameraLeft = -d;
|
|
|
|
directionalLight.shadowCameraRight = d;
|
|
|
|
directionalLight.shadowCameraBottom = -d;
|
|
|
|
|
|
|
|
directionalLight.shadowCameraNear = 1;
|
|
|
|
directionalLight.shadowCameraFar = 4;
|
|
|
|
|
|
|
|
directionalLight.shadowMapWidth = 1024;
|
|
|
|
directionalLight.shadowMapHeight = 1024;
|
|
|
|
|
|
|
|
directionalLight.shadowBias = -0.005;
|
|
|
|
directionalLight.shadowDarkness = 0.15;
|
|
|
|
};
|
2020-08-18 00:06:48 +02:00
|
|
|
|
2020-08-18 00:04:54 +02:00
|
|
|
//Set position
|
|
|
|
Madeleine.prototype.setXY = function (X, Y) {//IMPROVED
|
|
|
|
this.__object.position.setY(Y);
|
|
|
|
this.__object.position.setX(X);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Madeleine(options);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
// Lily helps madeleine.
|
|
|
|
window.Lily = (function() {
|
|
|
|
|
|
|
|
// Initialize
|
|
|
|
var Lily = function() {
|
|
|
|
if (!Detector.webgl) Detector.addGetWebGLMessage();
|
|
|
|
this.sisters = [];
|
|
|
|
};
|
|
|
|
|
|
|
|
// Attach Madeleine to file input
|
|
|
|
Lily.prototype.ready = function(options) {
|
|
|
|
// Check option fields
|
|
|
|
if (!options.file || !options.target) {
|
|
|
|
console.log("MADELEINE[ERR] Option must contain target and file.");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
var target = document.getElementById(options.file);
|
|
|
|
if (!target) {
|
|
|
|
console.log("MADELEINE[ERR] Please provide valid input file element.");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
// Attach file upload event handler
|
|
|
|
if (target.tagName.toLowerCase() == "input" && target.type.toLowerCase() == "file") this.onFileInputChange(target, options);
|
|
|
|
|
|
|
|
// (Optional) Attach drag-and-drop event handler
|
|
|
|
if (options.dropzone) {
|
|
|
|
var dragOverHandler = function(e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
e.dataTransfer.dropEffect = "copy";
|
|
|
|
};
|
|
|
|
var fileDropHandler = function(e) {
|
|
|
|
var i, files = e.dataTransfer.files;
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
for (i = 0; i < files.length; i++) {
|
|
|
|
// create Madeleine for each file
|
|
|
|
var _options = window.Lily.extend({}, options, {type: "upload", data: files[i]});
|
|
|
|
var madeleine = new Madeleine(_options);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Attach event handler
|
|
|
|
target = document.getElementById(options.dropzone);
|
|
|
|
this.bind(target, "dragover", dragOverHandler);
|
|
|
|
this.bind(target, "drop", fileDropHandler);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Remove attached event handler from element
|
|
|
|
Lily.prototype.remove = function(elem, event, doThis) {
|
|
|
|
var attach = (elem.removeEventListener ? "removeEventListener" : (elem.detachEvent ? "detachEvent" : ""));
|
|
|
|
var onEvent = (elem.removeEventListener ? event : "on" + event);
|
|
|
|
|
|
|
|
if (attach == "") elem[onEvent] = doThis;
|
|
|
|
else elem[attach](onEvent, doThis, false);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Attach new event handler to element
|
|
|
|
Lily.prototype.bind = function(elem, event, doThis) {
|
|
|
|
var attach = (elem.addEventListener ? "addEventListener" : (elem.attachEvent ? "attachEvent" : ""));
|
|
|
|
var onEvent = (elem.addEventListener ? event : "on" + event);
|
|
|
|
|
|
|
|
if (attach == "") elem[onEvent] = doThis;
|
|
|
|
else elem[attach](onEvent, doThis, false);
|
|
|
|
};
|
|
|
|
|
|
|
|
// jQuery source of extend function
|
|
|
|
// Reference: https://github.com/jquery/jquery/blob/master/src/core.js
|
|
|
|
Lily.prototype.extend = function() {
|
|
|
|
var options, name, src, copy, copyIsArray, clone,
|
|
|
|
target = arguments[0] || {},
|
|
|
|
i = 1,
|
|
|
|
length = arguments.length,
|
|
|
|
deep = false,
|
|
|
|
toString = Object.prototype.toString,
|
|
|
|
hasOwn = Object.prototype.hasOwnProperty,
|
|
|
|
push = Array.prototype.push,
|
|
|
|
slice = Array.prototype.slice,
|
2020-08-18 00:06:48 +02:00
|
|
|
trim = String.prototype.trim,
|
2020-08-18 00:04:54 +02:00
|
|
|
indexOf = Array.prototype.indexOf,
|
|
|
|
class2type = {"[object Boolean]": "boolean","[object Number]": "number","[object String]": "string","[object Function]": "function",
|
|
|
|
"[object Array]": "array","[object Date]": "date","[object RegExp]": "regexp","[object Object]": "object"},
|
|
|
|
helper = {
|
|
|
|
isFunction: function (obj) {return helper.type(obj) === "function"},
|
|
|
|
isArray: Array.isArray || function (obj) {return helper.type(obj) === "array"},
|
|
|
|
isWindow: function (obj) {return obj != null && obj == obj.window},
|
|
|
|
isNumeric: function (obj) {return !isNaN(parseFloat(obj)) && isFinite(obj)},
|
|
|
|
type: function (obj) {return obj == null ? String(obj) : class2type[toString.call(obj)] || "object"},
|
|
|
|
isPlainObject: function (obj) {if (!obj || helper.type(obj) !== "object" || obj.nodeType) {return false}
|
|
|
|
try { if (obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {return false}} catch (e) {return false}
|
|
|
|
var key; for (key in obj) {}; return key === undefined || hasOwn.call(obj, key)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// Handle a deep copy situation
|
|
|
|
if ( typeof target === "boolean" ) {
|
|
|
|
deep = target;
|
|
|
|
// Skip the boolean and the target
|
|
|
|
target = arguments[ i ] || {};
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
// Handle case when target is a string or something (possible in deep copy)
|
|
|
|
if ( typeof target !== "object" && !helper.isFunction(target) ) {
|
|
|
|
target = {};
|
|
|
|
}
|
|
|
|
// Extend helper itself if only one argument is passed
|
|
|
|
if ( i === length ) {
|
|
|
|
target = this;
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
for ( ; i < length; i++ ) {
|
|
|
|
// Only deal with non-null/undefined values
|
|
|
|
if ( (options = arguments[ i ]) != null ) {
|
|
|
|
// Extend the base object
|
|
|
|
for ( name in options ) {
|
|
|
|
src = target[ name ];
|
|
|
|
copy = options[ name ];
|
|
|
|
// Prevent never-ending loop
|
|
|
|
if ( target === copy ) { continue; }
|
|
|
|
// Recurse if we're merging plain objects or arrays
|
|
|
|
if ( deep && copy && ( helper.isPlainObject(copy) ||
|
|
|
|
(copyIsArray = helper.isArray(copy)) ) ) {
|
|
|
|
if ( copyIsArray ) {
|
|
|
|
copyIsArray = false;
|
|
|
|
clone = src && helper.isArray(src) ? src : [];
|
|
|
|
} else { clone = src && helper.isPlainObject(src) ? src : {}; }
|
|
|
|
// Never move original objects, clone them
|
|
|
|
target[ name ] = this.extend( deep, clone, copy );
|
|
|
|
// Don't bring in undefined values
|
|
|
|
} else if ( copy !== undefined ) {
|
|
|
|
target[ name ] = copy;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Return the modified object
|
|
|
|
return target;
|
|
|
|
};
|
|
|
|
|
|
|
|
// When user uploads files, call Madeleine immediately for each stl file.
|
|
|
|
Lily.prototype.onFileInputChange = function(input, options) {
|
|
|
|
// event handler
|
|
|
|
var fileInputChangeHandler = function() {
|
|
|
|
var i, files = this.files;
|
|
|
|
if (files.length) {
|
|
|
|
for (i = 0; i < files.length; i++) {
|
|
|
|
//IMPROVED - Only accepts stl files and invoke callbackerror and throws new error
|
|
|
|
if (files[i].type != 'stl' && files[i].name.indexOf("stl") == -1) {
|
|
|
|
if (options.callbackerror) options.callbackerror({ code: 1001, msg: "Invalid File" });
|
|
|
|
throw new Error("Invalid Format File. Only Accepts STL!");
|
|
|
|
}
|
|
|
|
// create Madeleine for each file
|
|
|
|
var _options = window.Lily.extend({}, options, {type: "upload", data: files[i]});
|
|
|
|
var madeleine = new Madeleine(_options);
|
|
|
|
// hide file input element
|
|
|
|
input.style.display = "none";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// attach event handler
|
|
|
|
this.bind(input, "change", fileInputChangeHandler);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Put madeleine to madeleine list
|
|
|
|
Lily.prototype.push = function(madeleine) {
|
|
|
|
this.sisters.push(madeleine);
|
|
|
|
return this.sisters.length-1;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Get madeleine of id 'index'
|
|
|
|
Lily.prototype.get = function(index) {
|
|
|
|
return this.sisters[index];
|
|
|
|
};
|
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
return new Lily();
|
2020-08-18 00:04:54 +02:00
|
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
})();
|
|
|
|
|
2020-08-18 00:06:48 +02:00
|
|
|
/* A facade for the Web Worker API that fakes it in case it's missing.
|
2020-08-18 00:04:54 +02:00
|
|
|
* Good when web workers aren't supported in the browser, but it's still fast enough,
|
|
|
|
* so execution doesn't hang too badly (e.g. Opera 10.5).
|
|
|
|
* By Stefan Wehrmeyer, licensed under MIT
|
|
|
|
*/
|
|
|
|
WorkerFacade = null;
|
|
|
|
if(!!window.Worker){
|
|
|
|
WorkerFacade = (function(){
|
|
|
|
return function(path){
|
|
|
|
return new window.Worker(path);
|
|
|
|
};
|
|
|
|
}());
|
|
|
|
} else {
|
|
|
|
WorkerFacade = (function(){
|
|
|
|
var workers = {}, masters = {}, loaded = false;
|
|
|
|
var that = function(path){
|
|
|
|
var theworker = {}, loaded = false, callings = [];
|
|
|
|
theworker.postToWorkerFunction = function(args){
|
|
|
|
try{
|
|
|
|
workers[path]({"data":args});
|
|
|
|
}catch(err){
|
|
|
|
theworker.onerror(err);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
theworker.postMessage = function(params){
|
|
|
|
if(!loaded){
|
|
|
|
callings.push(params);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
theworker.postToWorkerFunction(params);
|
|
|
|
};
|
|
|
|
masters[path] = theworker;
|
|
|
|
var scr = document.createElement("SCRIPT");
|
|
|
|
scr.src = path;
|
|
|
|
scr.type = "text/javascript";
|
|
|
|
scr.onload = function(){
|
|
|
|
loaded = true;
|
|
|
|
while(callings.length > 0){
|
|
|
|
theworker.postToWorkerFunction(callings[0]);
|
|
|
|
callings.shift();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
document.body.appendChild(scr);
|
2020-08-18 00:06:48 +02:00
|
|
|
|
2020-08-18 00:04:54 +02:00
|
|
|
var binaryscr = document.createElement("SCRIPT");
|
|
|
|
binaryscr.src = thingiurlbase + '/binaryReader.js';
|
|
|
|
binaryscr.type = "text/javascript";
|
|
|
|
document.body.appendChild(binaryscr);
|
2020-08-18 00:06:48 +02:00
|
|
|
|
2020-08-18 00:04:54 +02:00
|
|
|
return theworker;
|
|
|
|
};
|
|
|
|
that.fake = true;
|
|
|
|
that.add = function(pth, worker){
|
|
|
|
workers[pth] = worker;
|
|
|
|
return function(param){
|
|
|
|
masters[pth].onmessage({"data": param});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
that.toString = function(){
|
|
|
|
return "FakeWorker('"+path+"')";
|
|
|
|
};
|
|
|
|
return that;
|
|
|
|
}());
|
|
|
|
};
|