Initial commit
This commit is contained in:
parent
042ec35469
commit
fc6b0efcb6
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.swp
|
25
Makefile
Normal file
25
Makefile
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Relies on minify: https://www.npmjs.com/package/minify
|
||||||
|
|
||||||
|
# Scripts to help sed wrap our javascript in an anonymous function
|
||||||
|
# Helps with namespacing + minification
|
||||||
|
SED_JS_PREFIX = '1i(() => {'
|
||||||
|
SED_JS_SUFFIX = '$$a})();'
|
||||||
|
|
||||||
|
# Javscript build rule
|
||||||
|
# > This concatenates + wraps all matching JS files and minifies
|
||||||
|
build-js: scripts/*.js
|
||||||
|
build-js:
|
||||||
|
sed -e $(SED_JS_PREFIX) -e $(SED_JS_SUFFIX) $^ \
|
||||||
|
| minify --js \
|
||||||
|
> build/stlwebviewer2.js
|
||||||
|
|
||||||
|
# CSS build rule
|
||||||
|
# > This simply concatenates all matching CSS files and minifies
|
||||||
|
build-css: stylesheets/*.css
|
||||||
|
build-css:
|
||||||
|
cat $^ \
|
||||||
|
| minify --css \
|
||||||
|
> build/stlwebviewer2.css
|
||||||
|
|
||||||
|
build: build-css build-js
|
||||||
|
.DEFAULT_GOAL := build
|
23
README.md
23
README.md
@ -1,3 +1,24 @@
|
|||||||
# stl_web_viewer2
|
# stl_web_viewer2
|
||||||
|
|
||||||
Just a fork from https://github.com/brentyi/stl_web_viewer2
|
Demo: https://brentyi.github.io/stl_web_viewer2/
|
||||||
|
|
||||||
|
Friendly utility for embedding 3D models into webpages.
|
||||||
|
|
||||||
|
Basically the same as the [original](https://github.com/brentyi/stl_web_viewer), but with less nodejs nonsense.
|
||||||
|
|
||||||
|
### Usage:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/106/three.min.js" integrity="sha256-tAVw6WRAXc3td2Esrjd28l54s3P2y7CDFu1271mu5LE=" crossorigin="anonymous"></script>
|
||||||
|
<link rel="stylesheet" href="https://brentyi.github.io/stl_web_viewer2/build/stlwebviewer2.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="stlwv2-model" data-model-url="models/planet_gear.stl"></div>
|
||||||
|
<script src="https://brentyi.github.io/stl_web_viewer2/build/stlwebviewer2.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
1
build/stlwebviewer2.css
Normal file
1
build/stlwebviewer2.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
.stlwv2-model{position:relative;min-height:20em;border:1px solid #ccc}.stlwv2-inner{position:absolute;overflow:hidden;top:0;bottom:0;left:0;right:0}.stlwv2-inner>.stlwv2-percent{position:absolute;top:50%;transform:translateY(-50%);font-size:5em;text-align:center;width:100%;color:#ccc;animation-name:stlwv2-pulse;animation-duration:2s;animation-iteration-count:infinite}@keyframes stlwv2-pulse{0%{opacity:1}50%{opacity:.625}}.stlwv2-inner>canvas{position:absolute;top:0;left:0;opacity:0;transition:opacity .5s}.stlwv2-inner.stlwv2-loaded>canvas{opacity:1}.stlwv2-fullscreen-checkbox{display:none!important}.stlwv2-hud{display:none}.stlwv2-loaded>.stlwv2-hud{position:absolute;padding:.25em;z-index:1000;cursor:pointer;font-weight:400}.stlwv2-loaded>.stlwv2-github-link{font-size:1.2em;top:.57em;right:3em;text-decoration:none;color:#999;display:none}.stlwv2-loaded>.stlwv2-fullscreen-on{font-size:1.5em;top:0;right:.2em;transform:rotate(90deg);color:#ccc;display:block}.stlwv2-loaded>.stlwv2-fullscreen-off{font-size:2em;top:0;right:.5em;color:#c33;display:none}.stlwv2-fullscreen-checkbox:checked~.stlwv2-loaded>.stlwv2-github-link{display:block}.stlwv2-fullscreen-checkbox:checked~.stlwv2-loaded>.stlwv2-fullscreen-on{display:none}.stlwv2-fullscreen-checkbox:checked~.stlwv2-loaded>.stlwv2-fullscreen-off{display:block}
|
1
build/stlwebviewer2.js
Normal file
1
build/stlwebviewer2.js
Normal file
File diff suppressed because one or more lines are too long
37
index.html
Normal file
37
index.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/106/three.min.js" integrity="sha256-tAVw6WRAXc3td2Esrjd28l54s3P2y7CDFu1271mu5LE=" crossorigin="anonymous"></script>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" href="build/stlwebviewer2.css" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
width: 50em;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
min-height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stlwv2-model {
|
||||||
|
width: 100%;
|
||||||
|
height: 30em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="stlwv2-model" data-model-url="models/printer.stl"></div>
|
||||||
|
<script src="build/stlwebviewer2.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
BIN
models/planet_gear.stl
Normal file
BIN
models/planet_gear.stl
Normal file
Binary file not shown.
BIN
models/plotter.stl
Normal file
BIN
models/plotter.stl
Normal file
Binary file not shown.
BIN
models/printer.stl
Normal file
BIN
models/printer.stl
Normal file
Binary file not shown.
BIN
models/remote.stl
Normal file
BIN
models/remote.stl
Normal file
Binary file not shown.
69
scripts/Detector.js
Normal file
69
scripts/Detector.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* @author alteredq / http://alteredqualia.com/
|
||||||
|
* @author mr.doob / http://mrdoob.com/
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Detector = {
|
||||||
|
canvas: !!window.CanvasRenderingContext2D,
|
||||||
|
webgl: (function () {
|
||||||
|
try {
|
||||||
|
var canvas = document.createElement("canvas");
|
||||||
|
return !!(
|
||||||
|
window.WebGLRenderingContext &&
|
||||||
|
(canvas.getContext("webgl") || canvas.getContext("experimental-webgl"))
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
workers: !!window.Worker,
|
||||||
|
fileapi: window.File && window.FileReader && window.FileList && window.Blob,
|
||||||
|
|
||||||
|
getWebGLErrorMessage: function () {
|
||||||
|
var element = document.createElement("div");
|
||||||
|
element.id = "webgl-error-message";
|
||||||
|
element.style.fontFamily = "monospace";
|
||||||
|
element.style.fontSize = "13px";
|
||||||
|
element.style.fontWeight = "normal";
|
||||||
|
element.style.textAlign = "center";
|
||||||
|
element.style.background = "#fff";
|
||||||
|
element.style.color = "#000";
|
||||||
|
element.style.padding = "1.5em";
|
||||||
|
element.style.width = "400px";
|
||||||
|
element.style.margin = "5em auto 0";
|
||||||
|
|
||||||
|
if (!this.webgl) {
|
||||||
|
element.innerHTML = window.WebGLRenderingContext
|
||||||
|
? [
|
||||||
|
'Your graphics card does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">WebGL</a>.<br />',
|
||||||
|
'Find out how to get it <a href="http://get.webgl.org/" style="color:#000">here</a>.',
|
||||||
|
].join("\n")
|
||||||
|
: [
|
||||||
|
'Your browser does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">WebGL</a>.<br/>',
|
||||||
|
'Find out how to get it <a href="http://get.webgl.org/" style="color:#000">here</a>.',
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
},
|
||||||
|
|
||||||
|
addGetWebGLMessage: function (parameters) {
|
||||||
|
var parent, id, element;
|
||||||
|
|
||||||
|
parameters = parameters || {};
|
||||||
|
|
||||||
|
parent =
|
||||||
|
parameters.parent !== undefined ? parameters.parent : document.body;
|
||||||
|
id = parameters.id !== undefined ? parameters.id : "oldie";
|
||||||
|
|
||||||
|
element = Detector.getWebGLErrorMessage();
|
||||||
|
element.id = id;
|
||||||
|
|
||||||
|
parent.appendChild(element);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// browserify support
|
||||||
|
if (typeof module === "object") {
|
||||||
|
module.exports = Detector;
|
||||||
|
}
|
944
scripts/OrbitControls.js
Normal file
944
scripts/OrbitControls.js
Normal file
@ -0,0 +1,944 @@
|
|||||||
|
/**
|
||||||
|
* @author qiao / https://github.com/qiao
|
||||||
|
* @author mrdoob / http://mrdoob.com
|
||||||
|
* @author alteredq / http://alteredqualia.com/
|
||||||
|
* @author WestLangley / http://github.com/WestLangley
|
||||||
|
* @author erich666 / http://erichaines.com
|
||||||
|
* @author brent / https://github.com/brentyi
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This is a modified version of the three.js OrbitControls example, with some enhancements
|
||||||
|
// for auto-rotation & embedding.
|
||||||
|
//
|
||||||
|
// This set of controls performs orbiting, dollying (zooming), and panning.
|
||||||
|
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
|
||||||
|
//
|
||||||
|
// Orbit - left mouse / touch: one finger move
|
||||||
|
// Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
|
||||||
|
// Pan - right mouse, or arrow keys / touch: three finger swipe
|
||||||
|
|
||||||
|
var OrbitControls = function (object, domElement) {
|
||||||
|
this.object = object;
|
||||||
|
|
||||||
|
this.domElement = domElement !== undefined ? domElement : document;
|
||||||
|
|
||||||
|
// Set to false to disable this control
|
||||||
|
this.enabled = true;
|
||||||
|
|
||||||
|
// "target" sets the location of focus, where the object orbits around
|
||||||
|
this.target = new THREE.Vector3();
|
||||||
|
|
||||||
|
// How far you can dolly in and out ( PerspectiveCamera only )
|
||||||
|
this.minDistance = 0;
|
||||||
|
this.maxDistance = Infinity;
|
||||||
|
|
||||||
|
// How far you can zoom in and out ( OrthographicCamera only )
|
||||||
|
this.minZoom = 0;
|
||||||
|
this.maxZoom = Infinity;
|
||||||
|
|
||||||
|
// How far you can orbit vertically, upper and lower limits.
|
||||||
|
// Range is 0 to Math.PI radians.
|
||||||
|
this.minPolarAngle = 0; // radians
|
||||||
|
this.maxPolarAngle = Math.PI; // radians
|
||||||
|
|
||||||
|
// How far you can orbit horizontally, upper and lower limits.
|
||||||
|
// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
|
||||||
|
this.minAzimuthAngle = -Infinity; // radians
|
||||||
|
this.maxAzimuthAngle = Infinity; // radians
|
||||||
|
|
||||||
|
// Set to true to enable damping (inertia)
|
||||||
|
// If damping is enabled, you must call controls.update() in your animation loop
|
||||||
|
this.enableDamping = false;
|
||||||
|
this.dampingFactor = 0.25;
|
||||||
|
|
||||||
|
// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
|
||||||
|
// Set to false to disable zooming
|
||||||
|
this.enableZoom = true;
|
||||||
|
this.zoomSpeed = 1.0;
|
||||||
|
|
||||||
|
// Set to false to disable rotating
|
||||||
|
this.enableRotate = true;
|
||||||
|
this.rotateSpeed = 1.0;
|
||||||
|
|
||||||
|
// Set to false to disable panning
|
||||||
|
this.enablePan = true;
|
||||||
|
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
|
||||||
|
|
||||||
|
// Set to true to automatically rotate around the target
|
||||||
|
// If auto-rotate is enabled, you must call controls.update() in your animation loop
|
||||||
|
this.autoRotate = false;
|
||||||
|
this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
|
||||||
|
this.autoRotateDelay = 0.0; // how long after an action do we start rotating again?
|
||||||
|
this.autoRotateTimeout;
|
||||||
|
|
||||||
|
// Set to false to disable use of the keys
|
||||||
|
this.enableKeys = true;
|
||||||
|
|
||||||
|
// The four arrow keys
|
||||||
|
this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
|
||||||
|
|
||||||
|
// Mouse buttons
|
||||||
|
this.mouseButtons = {
|
||||||
|
ORBIT: THREE.MOUSE.LEFT,
|
||||||
|
ZOOM: THREE.MOUSE.MIDDLE,
|
||||||
|
PAN: THREE.MOUSE.RIGHT,
|
||||||
|
};
|
||||||
|
|
||||||
|
// for reset
|
||||||
|
this.target0 = this.target.clone();
|
||||||
|
this.position0 = this.object.position.clone();
|
||||||
|
this.zoom0 = this.object.zoom;
|
||||||
|
|
||||||
|
//
|
||||||
|
// public methods
|
||||||
|
//
|
||||||
|
|
||||||
|
this.getPolarAngle = function () {
|
||||||
|
return spherical.phi;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getAzimuthalAngle = function () {
|
||||||
|
return spherical.theta;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.reset = function () {
|
||||||
|
scope.target.copy(scope.target0);
|
||||||
|
scope.object.position.copy(scope.position0);
|
||||||
|
scope.object.zoom = scope.zoom0;
|
||||||
|
|
||||||
|
scope.object.updateProjectionMatrix();
|
||||||
|
scope.dispatchEvent(changeEvent);
|
||||||
|
|
||||||
|
scope.update();
|
||||||
|
|
||||||
|
state = STATE.NONE;
|
||||||
|
};
|
||||||
|
|
||||||
|
// this method is exposed, but perhaps it would be better if we can make it private...
|
||||||
|
this.update = (function () {
|
||||||
|
var offset = new THREE.Vector3();
|
||||||
|
|
||||||
|
// so camera.up is the orbit axis
|
||||||
|
var quat = new THREE.Quaternion().setFromUnitVectors(
|
||||||
|
object.up,
|
||||||
|
new THREE.Vector3(0, 1, 0)
|
||||||
|
);
|
||||||
|
var quatInverse = quat.clone().inverse();
|
||||||
|
|
||||||
|
var lastPosition = new THREE.Vector3();
|
||||||
|
var lastQuaternion = new THREE.Quaternion();
|
||||||
|
|
||||||
|
return function update() {
|
||||||
|
var position = scope.object.position;
|
||||||
|
|
||||||
|
offset.copy(position).sub(scope.target);
|
||||||
|
|
||||||
|
// rotate offset to "y-axis-is-up" space
|
||||||
|
offset.applyQuaternion(quat);
|
||||||
|
|
||||||
|
// angle from z-axis around y-axis
|
||||||
|
spherical.setFromVector3(offset);
|
||||||
|
|
||||||
|
if (scope.autoRotate && state === STATE.NONE && !wheelZoomed) {
|
||||||
|
rotateLeft(getAutoRotationAngle());
|
||||||
|
} else if (scope.autoRotate && scope.autoRotateDelay > 0) {
|
||||||
|
if (scope.autoRotateSpeed > 0.0) {
|
||||||
|
scope.autoRotateSpeedActual = scope.autoRotateSpeed;
|
||||||
|
scope.autoRotateSpeed = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(scope.autoRotateTimeout);
|
||||||
|
scope.autoRotateTimeout = setTimeout(function () {
|
||||||
|
scope.autoRotateSpeed = scope.autoRotateSpeedActual;
|
||||||
|
}, scope.autoRotateDelay);
|
||||||
|
|
||||||
|
wheelZoomed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
spherical.theta += sphericalDelta.theta;
|
||||||
|
spherical.phi += sphericalDelta.phi;
|
||||||
|
|
||||||
|
// restrict theta to be between desired limits
|
||||||
|
spherical.theta = Math.max(
|
||||||
|
scope.minAzimuthAngle,
|
||||||
|
Math.min(scope.maxAzimuthAngle, spherical.theta)
|
||||||
|
);
|
||||||
|
|
||||||
|
// restrict phi to be between desired limits
|
||||||
|
spherical.phi = Math.max(
|
||||||
|
scope.minPolarAngle,
|
||||||
|
Math.min(scope.maxPolarAngle, spherical.phi)
|
||||||
|
);
|
||||||
|
|
||||||
|
spherical.makeSafe();
|
||||||
|
|
||||||
|
spherical.radius *= scale;
|
||||||
|
|
||||||
|
// restrict radius to be between desired limits
|
||||||
|
spherical.radius = Math.max(
|
||||||
|
scope.minDistance,
|
||||||
|
Math.min(scope.maxDistance, spherical.radius)
|
||||||
|
);
|
||||||
|
|
||||||
|
// move target to panned location
|
||||||
|
scope.target.add(panOffset);
|
||||||
|
|
||||||
|
offset.setFromSpherical(spherical);
|
||||||
|
|
||||||
|
// rotate offset back to "camera-up-vector-is-up" space
|
||||||
|
offset.applyQuaternion(quatInverse);
|
||||||
|
|
||||||
|
position.copy(scope.target).add(offset);
|
||||||
|
|
||||||
|
scope.object.lookAt(scope.target);
|
||||||
|
|
||||||
|
if (scope.enableDamping === true) {
|
||||||
|
sphericalDelta.theta *= 1 - scope.dampingFactor;
|
||||||
|
sphericalDelta.phi *= 1 - scope.dampingFactor;
|
||||||
|
} else {
|
||||||
|
sphericalDelta.set(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
scale = 1;
|
||||||
|
panOffset.set(0, 0, 0);
|
||||||
|
|
||||||
|
// update condition is:
|
||||||
|
// min(camera displacement, camera rotation in radians)^2 > EPS
|
||||||
|
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
|
||||||
|
|
||||||
|
if (
|
||||||
|
zoomChanged ||
|
||||||
|
lastPosition.distanceToSquared(scope.object.position) > EPS ||
|
||||||
|
8 * (1 - lastQuaternion.dot(scope.object.quaternion)) > EPS
|
||||||
|
) {
|
||||||
|
scope.dispatchEvent(changeEvent);
|
||||||
|
|
||||||
|
lastPosition.copy(scope.object.position);
|
||||||
|
lastQuaternion.copy(scope.object.quaternion);
|
||||||
|
zoomChanged = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
this.dispose = function () {
|
||||||
|
scope.domElement.removeEventListener("contextmenu", onContextMenu, false);
|
||||||
|
scope.domElement.removeEventListener("mousedown", onMouseDown, false);
|
||||||
|
scope.domElement.removeEventListener("wheel", onMouseWheel, false);
|
||||||
|
|
||||||
|
scope.domElement.removeEventListener("touchstart", onTouchStart, false);
|
||||||
|
scope.domElement.removeEventListener("touchend", onTouchEnd, false);
|
||||||
|
scope.domElement.removeEventListener("touchmove", onTouchMove, false);
|
||||||
|
|
||||||
|
document.removeEventListener("mousemove", onMouseMove, false);
|
||||||
|
document.removeEventListener("mouseup", onMouseUp, false);
|
||||||
|
document.removeEventListener("mouseleave", onMouseUp, false);
|
||||||
|
|
||||||
|
window.removeEventListener("keydown", onKeyDown, false);
|
||||||
|
|
||||||
|
//scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// internals
|
||||||
|
//
|
||||||
|
|
||||||
|
var scope = this;
|
||||||
|
|
||||||
|
var changeEvent = { type: "change" };
|
||||||
|
var startEvent = { type: "start" };
|
||||||
|
var endEvent = { type: "end" };
|
||||||
|
|
||||||
|
var STATE = {
|
||||||
|
NONE: -1,
|
||||||
|
ROTATE: 0,
|
||||||
|
DOLLY: 1,
|
||||||
|
PAN: 2,
|
||||||
|
TOUCH_ROTATE: 3,
|
||||||
|
TOUCH_DOLLY: 4,
|
||||||
|
TOUCH_PAN: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
var state = STATE.NONE;
|
||||||
|
|
||||||
|
var EPS = 0.000001;
|
||||||
|
|
||||||
|
// current position in spherical coordinates
|
||||||
|
var spherical = new THREE.Spherical();
|
||||||
|
var sphericalDelta = new THREE.Spherical();
|
||||||
|
|
||||||
|
var scale = 1;
|
||||||
|
var panOffset = new THREE.Vector3();
|
||||||
|
var zoomChanged = false;
|
||||||
|
|
||||||
|
var wheelZoomed = false; // for auto-rotate
|
||||||
|
|
||||||
|
var rotateStart = new THREE.Vector2();
|
||||||
|
var rotateEnd = new THREE.Vector2();
|
||||||
|
var rotateDelta = new THREE.Vector2();
|
||||||
|
|
||||||
|
var panStart = new THREE.Vector2();
|
||||||
|
var panEnd = new THREE.Vector2();
|
||||||
|
var panDelta = new THREE.Vector2();
|
||||||
|
|
||||||
|
var dollyStart = new THREE.Vector2();
|
||||||
|
var dollyEnd = new THREE.Vector2();
|
||||||
|
var dollyDelta = new THREE.Vector2();
|
||||||
|
|
||||||
|
function getAutoRotationAngle() {
|
||||||
|
return ((2 * Math.PI) / 60 / 60) * scope.autoRotateSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getZoomScale() {
|
||||||
|
return Math.pow(0.95, scope.zoomSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotateLeft(angle) {
|
||||||
|
sphericalDelta.theta -= angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotateUp(angle) {
|
||||||
|
sphericalDelta.phi -= angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
var panLeft = (function () {
|
||||||
|
var v = new THREE.Vector3();
|
||||||
|
|
||||||
|
return function panLeft(distance, objectMatrix) {
|
||||||
|
v.setFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix
|
||||||
|
v.multiplyScalar(-distance);
|
||||||
|
|
||||||
|
panOffset.add(v);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
var panUp = (function () {
|
||||||
|
var v = new THREE.Vector3();
|
||||||
|
|
||||||
|
return function panUp(distance, objectMatrix) {
|
||||||
|
v.setFromMatrixColumn(objectMatrix, 1); // get Y column of objectMatrix
|
||||||
|
v.multiplyScalar(distance);
|
||||||
|
|
||||||
|
panOffset.add(v);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// deltaX and deltaY are in pixels; right and down are positive
|
||||||
|
var pan = (function () {
|
||||||
|
var offset = new THREE.Vector3();
|
||||||
|
|
||||||
|
return function pan(deltaX, deltaY) {
|
||||||
|
var element =
|
||||||
|
scope.domElement === document
|
||||||
|
? scope.domElement.body
|
||||||
|
: scope.domElement;
|
||||||
|
|
||||||
|
if (scope.object instanceof THREE.PerspectiveCamera) {
|
||||||
|
// perspective
|
||||||
|
var position = scope.object.position;
|
||||||
|
offset.copy(position).sub(scope.target);
|
||||||
|
var targetDistance = offset.length();
|
||||||
|
|
||||||
|
// half of the fov is center to top of screen
|
||||||
|
targetDistance *= Math.tan(((scope.object.fov / 2) * Math.PI) / 180.0);
|
||||||
|
|
||||||
|
// we actually don't use screenWidth, since perspective camera is fixed to screen height
|
||||||
|
panLeft(
|
||||||
|
(2 * deltaX * targetDistance) / element.clientHeight,
|
||||||
|
scope.object.matrix
|
||||||
|
);
|
||||||
|
panUp(
|
||||||
|
(2 * deltaY * targetDistance) / element.clientHeight,
|
||||||
|
scope.object.matrix
|
||||||
|
);
|
||||||
|
} else if (scope.object instanceof THREE.OrthographicCamera) {
|
||||||
|
// orthographic
|
||||||
|
panLeft(
|
||||||
|
(deltaX * (scope.object.right - scope.object.left)) /
|
||||||
|
scope.object.zoom /
|
||||||
|
element.clientWidth,
|
||||||
|
scope.object.matrix
|
||||||
|
);
|
||||||
|
panUp(
|
||||||
|
(deltaY * (scope.object.top - scope.object.bottom)) /
|
||||||
|
scope.object.zoom /
|
||||||
|
element.clientHeight,
|
||||||
|
scope.object.matrix
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// camera neither orthographic nor perspective
|
||||||
|
console.warn(
|
||||||
|
"WARNING: OrbitControls.js encountered an unknown camera type - pan disabled."
|
||||||
|
);
|
||||||
|
scope.enablePan = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
function dollyIn(dollyScale) {
|
||||||
|
if (scope.object instanceof THREE.PerspectiveCamera) {
|
||||||
|
scale /= dollyScale;
|
||||||
|
} else if (scope.object instanceof THREE.OrthographicCamera) {
|
||||||
|
scope.object.zoom = Math.max(
|
||||||
|
scope.minZoom,
|
||||||
|
Math.min(scope.maxZoom, scope.object.zoom * dollyScale)
|
||||||
|
);
|
||||||
|
scope.object.updateProjectionMatrix();
|
||||||
|
zoomChanged = true;
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
"WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."
|
||||||
|
);
|
||||||
|
scope.enableZoom = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dollyOut(dollyScale) {
|
||||||
|
if (scope.object instanceof THREE.PerspectiveCamera) {
|
||||||
|
scale *= dollyScale;
|
||||||
|
} else if (scope.object instanceof THREE.OrthographicCamera) {
|
||||||
|
scope.object.zoom = Math.max(
|
||||||
|
scope.minZoom,
|
||||||
|
Math.min(scope.maxZoom, scope.object.zoom / dollyScale)
|
||||||
|
);
|
||||||
|
scope.object.updateProjectionMatrix();
|
||||||
|
zoomChanged = true;
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
"WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."
|
||||||
|
);
|
||||||
|
scope.enableZoom = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// event callbacks - update the object state
|
||||||
|
//
|
||||||
|
|
||||||
|
function handleMouseDownRotate(event) {
|
||||||
|
//console.log( 'handleMouseDownRotate' );
|
||||||
|
|
||||||
|
rotateStart.set(event.clientX, event.clientY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseDownDolly(event) {
|
||||||
|
//console.log( 'handleMouseDownDolly' );
|
||||||
|
|
||||||
|
dollyStart.set(event.clientX, event.clientY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseDownPan(event) {
|
||||||
|
//console.log( 'handleMouseDownPan' );
|
||||||
|
|
||||||
|
panStart.set(event.clientX, event.clientY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseMoveRotate(event) {
|
||||||
|
//console.log( 'handleMouseMoveRotate' );
|
||||||
|
|
||||||
|
rotateEnd.set(event.clientX, event.clientY);
|
||||||
|
rotateDelta.subVectors(rotateEnd, rotateStart);
|
||||||
|
|
||||||
|
var element =
|
||||||
|
scope.domElement === document ? scope.domElement.body : scope.domElement;
|
||||||
|
|
||||||
|
// rotating across whole screen goes 360 degrees around
|
||||||
|
rotateLeft(
|
||||||
|
((2 * Math.PI * rotateDelta.x) / element.clientWidth) * scope.rotateSpeed
|
||||||
|
);
|
||||||
|
|
||||||
|
// rotating up and down along whole screen attempts to go 360, but limited to 180
|
||||||
|
rotateUp(
|
||||||
|
((2 * Math.PI * rotateDelta.y) / element.clientHeight) * scope.rotateSpeed
|
||||||
|
);
|
||||||
|
|
||||||
|
rotateStart.copy(rotateEnd);
|
||||||
|
|
||||||
|
scope.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseMoveDolly(event) {
|
||||||
|
//console.log( 'handleMouseMoveDolly' );
|
||||||
|
|
||||||
|
dollyEnd.set(event.clientX, event.clientY);
|
||||||
|
|
||||||
|
dollyDelta.subVectors(dollyEnd, dollyStart);
|
||||||
|
|
||||||
|
if (dollyDelta.y > 0) {
|
||||||
|
dollyIn(getZoomScale());
|
||||||
|
} else if (dollyDelta.y < 0) {
|
||||||
|
dollyOut(getZoomScale());
|
||||||
|
}
|
||||||
|
|
||||||
|
dollyStart.copy(dollyEnd);
|
||||||
|
|
||||||
|
scope.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseMovePan(event) {
|
||||||
|
//console.log( 'handleMouseMovePan' );
|
||||||
|
|
||||||
|
panEnd.set(event.clientX, event.clientY);
|
||||||
|
|
||||||
|
panDelta.subVectors(panEnd, panStart);
|
||||||
|
|
||||||
|
pan(panDelta.x, panDelta.y);
|
||||||
|
|
||||||
|
panStart.copy(panEnd);
|
||||||
|
|
||||||
|
scope.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseUp(event) {
|
||||||
|
// console.log( 'handleMouseUp' );
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseWheel(event) {
|
||||||
|
// console.log( 'handleMouseWheel' );
|
||||||
|
|
||||||
|
if (event.deltaY < 0) {
|
||||||
|
dollyOut(getZoomScale());
|
||||||
|
} else if (event.deltaY > 0) {
|
||||||
|
dollyIn(getZoomScale());
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyDown(event) {
|
||||||
|
//console.log( 'handleKeyDown' );
|
||||||
|
|
||||||
|
switch (event.keyCode) {
|
||||||
|
case scope.keys.UP:
|
||||||
|
pan(0, scope.keyPanSpeed);
|
||||||
|
scope.update();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case scope.keys.BOTTOM:
|
||||||
|
pan(0, -scope.keyPanSpeed);
|
||||||
|
scope.update();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case scope.keys.LEFT:
|
||||||
|
pan(scope.keyPanSpeed, 0);
|
||||||
|
scope.update();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case scope.keys.RIGHT:
|
||||||
|
pan(-scope.keyPanSpeed, 0);
|
||||||
|
scope.update();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchStartRotate(event) {
|
||||||
|
//console.log( 'handleTouchStartRotate' );
|
||||||
|
|
||||||
|
rotateStart.set(event.touches[0].pageX, event.touches[0].pageY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchStartDolly(event) {
|
||||||
|
//console.log( 'handleTouchStartDolly' );
|
||||||
|
|
||||||
|
var dx = event.touches[0].pageX - event.touches[1].pageX;
|
||||||
|
var dy = event.touches[0].pageY - event.touches[1].pageY;
|
||||||
|
|
||||||
|
var distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
dollyStart.set(0, distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchStartPan(event) {
|
||||||
|
//console.log( 'handleTouchStartPan' );
|
||||||
|
|
||||||
|
panStart.set(event.touches[0].pageX, event.touches[0].pageY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchMoveRotate(event) {
|
||||||
|
//console.log( 'handleTouchMoveRotate' );
|
||||||
|
|
||||||
|
rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY);
|
||||||
|
rotateDelta.subVectors(rotateEnd, rotateStart);
|
||||||
|
|
||||||
|
var element =
|
||||||
|
scope.domElement === document ? scope.domElement.body : scope.domElement;
|
||||||
|
|
||||||
|
// rotating across whole screen goes 360 degrees around
|
||||||
|
rotateLeft(
|
||||||
|
((2 * Math.PI * rotateDelta.x) / element.clientWidth) * scope.rotateSpeed
|
||||||
|
);
|
||||||
|
|
||||||
|
// rotating up and down along whole screen attempts to go 360, but limited to 180
|
||||||
|
rotateUp(
|
||||||
|
((2 * Math.PI * rotateDelta.y) / element.clientHeight) * scope.rotateSpeed
|
||||||
|
);
|
||||||
|
|
||||||
|
rotateStart.copy(rotateEnd);
|
||||||
|
|
||||||
|
scope.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchMoveDolly(event) {
|
||||||
|
//console.log( 'handleTouchMoveDolly' );
|
||||||
|
|
||||||
|
var dx = event.touches[0].pageX - event.touches[1].pageX;
|
||||||
|
var dy = event.touches[0].pageY - event.touches[1].pageY;
|
||||||
|
|
||||||
|
var distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
dollyEnd.set(0, distance);
|
||||||
|
|
||||||
|
dollyDelta.subVectors(dollyEnd, dollyStart);
|
||||||
|
|
||||||
|
if (dollyDelta.y > 0) {
|
||||||
|
dollyOut(getZoomScale());
|
||||||
|
} else if (dollyDelta.y < 0) {
|
||||||
|
dollyIn(getZoomScale());
|
||||||
|
}
|
||||||
|
|
||||||
|
dollyStart.copy(dollyEnd);
|
||||||
|
|
||||||
|
scope.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchMovePan(event) {
|
||||||
|
//console.log( 'handleTouchMovePan' );
|
||||||
|
|
||||||
|
panEnd.set(event.touches[0].pageX, event.touches[0].pageY);
|
||||||
|
|
||||||
|
panDelta.subVectors(panEnd, panStart);
|
||||||
|
|
||||||
|
pan(panDelta.x, panDelta.y);
|
||||||
|
|
||||||
|
panStart.copy(panEnd);
|
||||||
|
|
||||||
|
scope.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchEnd(event) {
|
||||||
|
//console.log( 'handleTouchEnd' );
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// event handlers - FSM: listen for events and reset state
|
||||||
|
//
|
||||||
|
|
||||||
|
function onMouseDown(event) {
|
||||||
|
if (scope.enabled === false) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (event.button === scope.mouseButtons.ORBIT) {
|
||||||
|
if (scope.enableRotate === false) return;
|
||||||
|
|
||||||
|
handleMouseDownRotate(event);
|
||||||
|
|
||||||
|
state = STATE.ROTATE;
|
||||||
|
} else if (event.button === scope.mouseButtons.ZOOM) {
|
||||||
|
if (scope.enableZoom === false) return;
|
||||||
|
|
||||||
|
handleMouseDownDolly(event);
|
||||||
|
|
||||||
|
state = STATE.DOLLY;
|
||||||
|
} else if (event.button === scope.mouseButtons.PAN) {
|
||||||
|
if (scope.enablePan === false) return;
|
||||||
|
|
||||||
|
handleMouseDownPan(event);
|
||||||
|
|
||||||
|
state = STATE.PAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state !== STATE.NONE) {
|
||||||
|
document.addEventListener("mousemove", onMouseMove, false);
|
||||||
|
document.addEventListener("mouseup", onMouseUp, false);
|
||||||
|
document.addEventListener("mouseleave", onMouseUp, false);
|
||||||
|
|
||||||
|
scope.dispatchEvent(startEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseMove(event) {
|
||||||
|
if (scope.enabled === false) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (state === STATE.ROTATE) {
|
||||||
|
if (scope.enableRotate === false) return;
|
||||||
|
|
||||||
|
handleMouseMoveRotate(event);
|
||||||
|
} else if (state === STATE.DOLLY) {
|
||||||
|
if (scope.enableZoom === false) return;
|
||||||
|
|
||||||
|
handleMouseMoveDolly(event);
|
||||||
|
} else if (state === STATE.PAN) {
|
||||||
|
if (scope.enablePan === false) return;
|
||||||
|
|
||||||
|
handleMouseMovePan(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseUp(event) {
|
||||||
|
if (scope.enabled === false) return;
|
||||||
|
|
||||||
|
handleMouseUp(event);
|
||||||
|
|
||||||
|
document.removeEventListener("mousemove", onMouseMove, false);
|
||||||
|
document.removeEventListener("mouseup", onMouseUp, false);
|
||||||
|
document.removeEventListener("mouseleave", onMouseUp, false);
|
||||||
|
|
||||||
|
scope.dispatchEvent(endEvent);
|
||||||
|
|
||||||
|
state = STATE.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseWheel(event) {
|
||||||
|
if (
|
||||||
|
scope.enabled === false ||
|
||||||
|
scope.enableZoom === false ||
|
||||||
|
(state !== STATE.NONE && state !== STATE.ROTATE)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
handleMouseWheel(event);
|
||||||
|
|
||||||
|
scope.dispatchEvent(startEvent); // not sure why these are here...
|
||||||
|
scope.dispatchEvent(endEvent);
|
||||||
|
|
||||||
|
wheelZoomed = true;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown(event) {
|
||||||
|
if (
|
||||||
|
scope.enabled === false ||
|
||||||
|
scope.enableKeys === false ||
|
||||||
|
scope.enablePan === false
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
handleKeyDown(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTouchStart(event) {
|
||||||
|
if (scope.enabled === false) return;
|
||||||
|
|
||||||
|
switch (event.touches.length) {
|
||||||
|
case 1: // one-fingered touch: rotate
|
||||||
|
if (scope.enableRotate === false) return;
|
||||||
|
|
||||||
|
handleTouchStartRotate(event);
|
||||||
|
|
||||||
|
state = STATE.TOUCH_ROTATE;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // two-fingered touch: dolly
|
||||||
|
if (scope.enableZoom === false) return;
|
||||||
|
|
||||||
|
handleTouchStartDolly(event);
|
||||||
|
|
||||||
|
state = STATE.TOUCH_DOLLY;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: // three-fingered touch: pan
|
||||||
|
if (scope.enablePan === false) return;
|
||||||
|
|
||||||
|
handleTouchStartPan(event);
|
||||||
|
|
||||||
|
state = STATE.TOUCH_PAN;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
state = STATE.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state !== STATE.NONE) {
|
||||||
|
scope.dispatchEvent(startEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTouchMove(event) {
|
||||||
|
if (scope.enabled === false) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
switch (event.touches.length) {
|
||||||
|
case 1: // one-fingered touch: rotate
|
||||||
|
if (scope.enableRotate === false) return;
|
||||||
|
if (state !== STATE.TOUCH_ROTATE) return; // is this needed?...
|
||||||
|
|
||||||
|
handleTouchMoveRotate(event);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // two-fingered touch: dolly
|
||||||
|
if (scope.enableZoom === false) return;
|
||||||
|
if (state !== STATE.TOUCH_DOLLY) return; // is this needed?...
|
||||||
|
|
||||||
|
handleTouchMoveDolly(event);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: // three-fingered touch: pan
|
||||||
|
if (scope.enablePan === false) return;
|
||||||
|
if (state !== STATE.TOUCH_PAN) return; // is this needed?...
|
||||||
|
|
||||||
|
handleTouchMovePan(event);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
state = STATE.NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTouchEnd(event) {
|
||||||
|
if (scope.enabled === false) return;
|
||||||
|
|
||||||
|
handleTouchEnd(event);
|
||||||
|
|
||||||
|
scope.dispatchEvent(endEvent);
|
||||||
|
|
||||||
|
state = STATE.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onContextMenu(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
scope.domElement.addEventListener("contextmenu", onContextMenu, false);
|
||||||
|
|
||||||
|
scope.domElement.addEventListener("mousedown", onMouseDown, false);
|
||||||
|
scope.domElement.addEventListener("wheel", onMouseWheel, false);
|
||||||
|
|
||||||
|
scope.domElement.addEventListener("touchstart", onTouchStart, false);
|
||||||
|
scope.domElement.addEventListener("touchend", onTouchEnd, false);
|
||||||
|
scope.domElement.addEventListener("touchmove", onTouchMove, false);
|
||||||
|
|
||||||
|
window.addEventListener("keydown", onKeyDown, false);
|
||||||
|
|
||||||
|
// force an update at start
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
OrbitControls.prototype = Object.create(THREE.EventDispatcher.prototype);
|
||||||
|
OrbitControls.prototype.constructor = OrbitControls;
|
||||||
|
|
||||||
|
Object.defineProperties(OrbitControls.prototype, {
|
||||||
|
center: {
|
||||||
|
get: function () {
|
||||||
|
console.warn("OrbitControls: .center has been renamed to .target");
|
||||||
|
return this.target;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// backward compatibility
|
||||||
|
|
||||||
|
noZoom: {
|
||||||
|
get: function () {
|
||||||
|
console.warn(
|
||||||
|
"OrbitControls: .noZoom has been deprecated. Use .enableZoom instead."
|
||||||
|
);
|
||||||
|
return !this.enableZoom;
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function (value) {
|
||||||
|
console.warn(
|
||||||
|
"OrbitControls: .noZoom has been deprecated. Use .enableZoom instead."
|
||||||
|
);
|
||||||
|
this.enableZoom = !value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
noRotate: {
|
||||||
|
get: function () {
|
||||||
|
console.warn(
|
||||||
|
"OrbitControls: .noRotate has been deprecated. Use .enableRotate instead."
|
||||||
|
);
|
||||||
|
return !this.enableRotate;
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function (value) {
|
||||||
|
console.warn(
|
||||||
|
"OrbitControls: .noRotate has been deprecated. Use .enableRotate instead."
|
||||||
|
);
|
||||||
|
this.enableRotate = !value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
noPan: {
|
||||||
|
get: function () {
|
||||||
|
console.warn(
|
||||||
|
"OrbitControls: .noPan has been deprecated. Use .enablePan instead."
|
||||||
|
);
|
||||||
|
return !this.enablePan;
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function (value) {
|
||||||
|
console.warn(
|
||||||
|
"OrbitControls: .noPan has been deprecated. Use .enablePan instead."
|
||||||
|
);
|
||||||
|
this.enablePan = !value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
noKeys: {
|
||||||
|
get: function () {
|
||||||
|
console.warn(
|
||||||
|
"OrbitControls: .noKeys has been deprecated. Use .enableKeys instead."
|
||||||
|
);
|
||||||
|
return !this.enableKeys;
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function (value) {
|
||||||
|
console.warn(
|
||||||
|
"OrbitControls: .noKeys has been deprecated. Use .enableKeys instead."
|
||||||
|
);
|
||||||
|
this.enableKeys = !value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
staticMoving: {
|
||||||
|
get: function () {
|
||||||
|
console.warn(
|
||||||
|
"OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead."
|
||||||
|
);
|
||||||
|
return !this.enableDamping;
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function (value) {
|
||||||
|
console.warn(
|
||||||
|
"OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead."
|
||||||
|
);
|
||||||
|
this.enableDamping = !value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
dynamicDampingFactor: {
|
||||||
|
get: function () {
|
||||||
|
console.warn(
|
||||||
|
"OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead."
|
||||||
|
);
|
||||||
|
return this.dampingFactor;
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function (value) {
|
||||||
|
console.warn(
|
||||||
|
"OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead."
|
||||||
|
);
|
||||||
|
this.dampingFactor = value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
255
scripts/STLLoader.js
Normal file
255
scripts/STLLoader.js
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
/**
|
||||||
|
* @author aleeper / http://adamleeper.com/
|
||||||
|
* @author mrdoob / http://mrdoob.com/
|
||||||
|
* @author gero3 / https://github.com/gero3
|
||||||
|
* @author Mugen87 / https://github.com/Mugen87
|
||||||
|
*
|
||||||
|
* Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs.
|
||||||
|
*
|
||||||
|
* Supports both binary and ASCII encoded files, with automatic detection of type.
|
||||||
|
*
|
||||||
|
* The loader returns a non-indexed buffer geometry.
|
||||||
|
*
|
||||||
|
* Limitations:
|
||||||
|
* Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL).
|
||||||
|
* There is perhaps some question as to how valid it is to always assume little-endian-ness.
|
||||||
|
* ASCII decoding assumes file is UTF-8.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* var loader = new STLLoader();
|
||||||
|
* loader.load( './models/stl/slotted_disk.stl', function ( geometry ) {
|
||||||
|
* scene.add( new THREE.Mesh( geometry ) );
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* For binary STLs geometry might contain colors for vertices. To use it:
|
||||||
|
* // use the same code to load STL as above
|
||||||
|
* if (geometry.hasColors) {
|
||||||
|
* material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: THREE.VertexColors });
|
||||||
|
* } else { .... }
|
||||||
|
* var mesh = new THREE.Mesh( geometry, material );
|
||||||
|
*/
|
||||||
|
|
||||||
|
var STLLoader = function (manager) {
|
||||||
|
this.manager = manager !== undefined ? manager : THREE.DefaultLoadingManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
STLLoader.prototype = {
|
||||||
|
constructor: STLLoader,
|
||||||
|
|
||||||
|
load: function (url, onLoad, onProgress, onError) {
|
||||||
|
var scope = this;
|
||||||
|
|
||||||
|
var loader = new THREE.FileLoader(scope.manager);
|
||||||
|
loader.setResponseType("arraybuffer");
|
||||||
|
loader.load(
|
||||||
|
url,
|
||||||
|
function (text) {
|
||||||
|
onLoad(scope.parse(text));
|
||||||
|
},
|
||||||
|
onProgress,
|
||||||
|
onError
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
parse: function (data) {
|
||||||
|
var isBinary = function () {
|
||||||
|
var expect, face_size, n_faces, reader;
|
||||||
|
reader = new DataView(binData);
|
||||||
|
face_size = (32 / 8) * 3 + (32 / 8) * 3 * 3 + 16 / 8;
|
||||||
|
n_faces = reader.getUint32(80, true);
|
||||||
|
expect = 80 + 32 / 8 + n_faces * face_size;
|
||||||
|
|
||||||
|
if (expect === reader.byteLength) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// some binary files will have different size from expected,
|
||||||
|
// checking characters higher than ASCII to confirm is binary
|
||||||
|
var fileLength = reader.byteLength;
|
||||||
|
for (var index = 0; index < fileLength; index++) {
|
||||||
|
if (reader.getUint8(index, false) > 127) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
var binData = this.ensureBinary(data);
|
||||||
|
|
||||||
|
return isBinary()
|
||||||
|
? this.parseBinary(binData)
|
||||||
|
: this.parseASCII(this.ensureString(data));
|
||||||
|
},
|
||||||
|
|
||||||
|
parseBinary: function (data) {
|
||||||
|
var reader = new DataView(data);
|
||||||
|
var faces = reader.getUint32(80, true);
|
||||||
|
|
||||||
|
var r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
hasColors = false,
|
||||||
|
colors;
|
||||||
|
var defaultR, defaultG, defaultB, alpha;
|
||||||
|
|
||||||
|
// process STL header
|
||||||
|
// check for default color in header ("COLOR=rgba" sequence).
|
||||||
|
|
||||||
|
for (var index = 0; index < 80 - 10; index++) {
|
||||||
|
if (
|
||||||
|
reader.getUint32(index, false) == 0x434f4c4f /*COLO*/ &&
|
||||||
|
reader.getUint8(index + 4) == 0x52 /*'R'*/ &&
|
||||||
|
reader.getUint8(index + 5) == 0x3d /*'='*/
|
||||||
|
) {
|
||||||
|
hasColors = true;
|
||||||
|
colors = [];
|
||||||
|
|
||||||
|
defaultR = reader.getUint8(index + 6) / 255;
|
||||||
|
defaultG = reader.getUint8(index + 7) / 255;
|
||||||
|
defaultB = reader.getUint8(index + 8) / 255;
|
||||||
|
alpha = reader.getUint8(index + 9) / 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataOffset = 84;
|
||||||
|
var faceLength = 12 * 4 + 2;
|
||||||
|
|
||||||
|
var geometry = new THREE.BufferGeometry();
|
||||||
|
|
||||||
|
var vertices = [];
|
||||||
|
var normals = [];
|
||||||
|
|
||||||
|
for (var face = 0; face < faces; face++) {
|
||||||
|
var start = dataOffset + face * faceLength;
|
||||||
|
var normalX = reader.getFloat32(start, true);
|
||||||
|
var normalY = reader.getFloat32(start + 4, true);
|
||||||
|
var normalZ = reader.getFloat32(start + 8, true);
|
||||||
|
|
||||||
|
if (hasColors) {
|
||||||
|
var packedColor = reader.getUint16(start + 48, true);
|
||||||
|
|
||||||
|
if ((packedColor & 0x8000) === 0) {
|
||||||
|
// facet has its own unique color
|
||||||
|
|
||||||
|
r = (packedColor & 0x1f) / 31;
|
||||||
|
g = ((packedColor >> 5) & 0x1f) / 31;
|
||||||
|
b = ((packedColor >> 10) & 0x1f) / 31;
|
||||||
|
} else {
|
||||||
|
r = defaultR;
|
||||||
|
g = defaultG;
|
||||||
|
b = defaultB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 1; i <= 3; i++) {
|
||||||
|
var vertexstart = start + i * 12;
|
||||||
|
|
||||||
|
vertices.push(reader.getFloat32(vertexstart, true));
|
||||||
|
vertices.push(reader.getFloat32(vertexstart + 4, true));
|
||||||
|
vertices.push(reader.getFloat32(vertexstart + 8, true));
|
||||||
|
|
||||||
|
normals.push(normalX, normalY, normalZ);
|
||||||
|
|
||||||
|
if (hasColors) {
|
||||||
|
colors.push(r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry.addAttribute(
|
||||||
|
"position",
|
||||||
|
new THREE.BufferAttribute(new Float32Array(vertices), 3)
|
||||||
|
);
|
||||||
|
geometry.addAttribute(
|
||||||
|
"normal",
|
||||||
|
new THREE.BufferAttribute(new Float32Array(normals), 3)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasColors) {
|
||||||
|
geometry.addAttribute(
|
||||||
|
"color",
|
||||||
|
new THREE.BufferAttribute(new Float32Array(colors), 3)
|
||||||
|
);
|
||||||
|
geometry.hasColors = true;
|
||||||
|
geometry.alpha = alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
return geometry;
|
||||||
|
},
|
||||||
|
|
||||||
|
parseASCII: function (data) {
|
||||||
|
var geometry,
|
||||||
|
length,
|
||||||
|
patternFace,
|
||||||
|
patternNormal,
|
||||||
|
patternVertex,
|
||||||
|
result,
|
||||||
|
text;
|
||||||
|
geometry = new THREE.BufferGeometry();
|
||||||
|
patternFace = /facet([\s\S]*?)endfacet/g;
|
||||||
|
|
||||||
|
var vertices = [];
|
||||||
|
var normals = [];
|
||||||
|
|
||||||
|
var normal = new THREE.Vector3();
|
||||||
|
|
||||||
|
while ((result = patternFace.exec(data)) !== null) {
|
||||||
|
text = result[0];
|
||||||
|
patternNormal = /normal[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;
|
||||||
|
|
||||||
|
while ((result = patternNormal.exec(text)) !== null) {
|
||||||
|
normal.x = parseFloat(result[1]);
|
||||||
|
normal.y = parseFloat(result[3]);
|
||||||
|
normal.z = parseFloat(result[5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
patternVertex = /vertex[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;
|
||||||
|
|
||||||
|
while ((result = patternVertex.exec(text)) !== null) {
|
||||||
|
vertices.push(
|
||||||
|
parseFloat(result[1]),
|
||||||
|
parseFloat(result[3]),
|
||||||
|
parseFloat(result[5])
|
||||||
|
);
|
||||||
|
normals.push(normal.x, normal.y, normal.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry.addAttribute(
|
||||||
|
"position",
|
||||||
|
new THREE.BufferAttribute(new Float32Array(vertices), 3)
|
||||||
|
);
|
||||||
|
geometry.addAttribute(
|
||||||
|
"normal",
|
||||||
|
new THREE.BufferAttribute(new Float32Array(normals), 3)
|
||||||
|
);
|
||||||
|
|
||||||
|
return geometry;
|
||||||
|
},
|
||||||
|
|
||||||
|
ensureString: function (buf) {
|
||||||
|
if (typeof buf !== "string") {
|
||||||
|
var array_buffer = new Uint8Array(buf);
|
||||||
|
var strArray = [];
|
||||||
|
for (var i = 0; i < buf.byteLength; i++) {
|
||||||
|
strArray.push(String.fromCharCode(array_buffer[i])); // implicitly assumes little-endian
|
||||||
|
}
|
||||||
|
return strArray.join("");
|
||||||
|
} else {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
ensureBinary: function (buf) {
|
||||||
|
if (typeof buf === "string") {
|
||||||
|
var array_buffer = new Uint8Array(buf.length);
|
||||||
|
for (var i = 0; i < buf.length; i++) {
|
||||||
|
array_buffer[i] = buf.charCodeAt(i) & 0xff; // implicitly assumes little-endian
|
||||||
|
}
|
||||||
|
return array_buffer.buffer || array_buffer;
|
||||||
|
} else {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
306
scripts/STLWebViewer2.js
Normal file
306
scripts/STLWebViewer2.js
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
/*
|
||||||
|
* Helper for embedding STL files into webpages!
|
||||||
|
* brentyi@berkeley.edu
|
||||||
|
*/
|
||||||
|
|
||||||
|
$(() => {
|
||||||
|
// Load and view all STLs
|
||||||
|
$(".stlwv2-model").each(function () {
|
||||||
|
let $container = $(this);
|
||||||
|
let modelUrl = $container.data("model-url");
|
||||||
|
new STLWebViewer2(modelUrl, $container);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disable fullscreen when the user presses Escape
|
||||||
|
$(document).keyup(function (e) {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
$(".stlwv2-model .stlwv2-fullscreen-checkbox").each(function () {
|
||||||
|
$(this).prop("checked") &&
|
||||||
|
$(this).prop("checked", false).trigger("change");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function STLWebViewer2(modelUrl, $container) {
|
||||||
|
// Set initial attributes
|
||||||
|
this.modelUrl = modelUrl;
|
||||||
|
this.$container = $container;
|
||||||
|
|
||||||
|
// Check for WebGl support
|
||||||
|
if (!Detector.webgl) {
|
||||||
|
Detector.addGetWebGLMessage({ parent: this.$container[0] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build out viewer DOM elements
|
||||||
|
STLWebViewer2.instanceCount = (STLWebViewer2.instanceCount || 0) + 1;
|
||||||
|
let checkboxId =
|
||||||
|
"stlwv2-fullscreen-checkbox-" + (STLWebViewer2.instanceCount - 1);
|
||||||
|
this.$container.append(
|
||||||
|
[
|
||||||
|
'<input class="stlwv2-fullscreen-checkbox" id="' +
|
||||||
|
checkboxId +
|
||||||
|
'" type="checkbox"></input>',
|
||||||
|
'<div class="stlwv2-inner">',
|
||||||
|
' <div class="stlwv2-percent"></div>',
|
||||||
|
' <label class="stlwv2-hud stlwv2-fullscreen-on" title="Fullscreen" for="' +
|
||||||
|
checkboxId +
|
||||||
|
'">',
|
||||||
|
" ⇱</label>",
|
||||||
|
' <label class="stlwv2-hud stlwv2-fullscreen-off" title="Close" for="' +
|
||||||
|
checkboxId +
|
||||||
|
'">',
|
||||||
|
" ×</label>",
|
||||||
|
' <a class="stlwv2-hud stlwv2-github-link" target="_blank" href="https://github.com/brentyi/stl_web_viewer2">',
|
||||||
|
" STL Web Viewer</a>",
|
||||||
|
"</div>",
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
this.$innerContainer = this.$container.children(".stlwv2-inner");
|
||||||
|
|
||||||
|
// Fullscreen-mode toggle logic
|
||||||
|
this.$fullscreenCheckbox = $("#" + checkboxId);
|
||||||
|
this.$fullscreenCheckbox.on(
|
||||||
|
"change",
|
||||||
|
this.fullscreenToggleHandler.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up threejs scene, camera, renderer
|
||||||
|
this.scene = new THREE.Scene();
|
||||||
|
this.camera = new THREE.PerspectiveCamera(
|
||||||
|
40,
|
||||||
|
this.$innerContainer.width() / this.$innerContainer.height(),
|
||||||
|
1,
|
||||||
|
15000
|
||||||
|
);
|
||||||
|
this.cameraTarget = new THREE.Vector3();
|
||||||
|
this.renderer = this.makeRenderer((antialias = true));
|
||||||
|
this.$innerContainer.append(this.renderer.domElement);
|
||||||
|
|
||||||
|
// Orbit this.controls
|
||||||
|
this.controls = new OrbitControls(this.camera, this.$innerContainer.get(0));
|
||||||
|
this.controls.target = this.cameraTarget;
|
||||||
|
this.controls.enableDamping = true;
|
||||||
|
this.controls.enableKeys = false;
|
||||||
|
this.controls.rotateSpeed = 0.15;
|
||||||
|
this.controls.dampingFactor = 0.125;
|
||||||
|
this.controls.enableZoom = true;
|
||||||
|
this.controls.autoRotate = true;
|
||||||
|
this.controls.autoRotateSpeed = 0.25;
|
||||||
|
this.controls.autoRotateDelay = 5000;
|
||||||
|
|
||||||
|
// Lights: hemisphere light attached to the world
|
||||||
|
this.hemisphereLight = new THREE.HemisphereLight(0x999999, 0x555555);
|
||||||
|
this.scene.add(this.hemisphereLight);
|
||||||
|
|
||||||
|
// Lights: point light attached to the camera
|
||||||
|
this.pointLight = new THREE.PointLight(0xdddddd, 0.75, 0);
|
||||||
|
this.camera.add(this.pointLight);
|
||||||
|
this.scene.add(this.camera);
|
||||||
|
|
||||||
|
// Load STL file and add to scene
|
||||||
|
new STLLoader().load(
|
||||||
|
this.modelUrl,
|
||||||
|
this.stlLoadedCallback.bind(this),
|
||||||
|
this.updateProgress.bind(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress callback -- (for % loaded indicator)
|
||||||
|
STLWebViewer2.prototype.updateProgress = function (event) {
|
||||||
|
console.log(
|
||||||
|
"Loading " + this.modelUrl + ": " + event.loaded + "/" + event.total
|
||||||
|
);
|
||||||
|
this.$innerContainer
|
||||||
|
.children(".stlwv2-percent")
|
||||||
|
.text(Math.floor((event.loaded / event.total) * 100.0) + "%");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Callback for when our mesh has been fully loaded
|
||||||
|
STLWebViewer2.prototype.stlLoadedCallback = function (geometry) {
|
||||||
|
// Define (shaded) mesh and add to this.scene
|
||||||
|
let material = new THREE.MeshPhongMaterial({
|
||||||
|
color: 0xf7f8ff,
|
||||||
|
specular: 0x111111,
|
||||||
|
shininess: 0,
|
||||||
|
wireframe: false,
|
||||||
|
polygonOffset: true,
|
||||||
|
polygonOffsetFactor: 1,
|
||||||
|
polygonOffsetUnits: 1,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.85,
|
||||||
|
});
|
||||||
|
let mesh = new THREE.Mesh(geometry, material);
|
||||||
|
mesh.position.set(0, 0, 0);
|
||||||
|
mesh.castShadow = false;
|
||||||
|
mesh.receiveShadow = false;
|
||||||
|
this.scene.add(mesh);
|
||||||
|
|
||||||
|
// Add model edges
|
||||||
|
let edges = new THREE.EdgesGeometry(geometry, 29);
|
||||||
|
let line = new THREE.LineSegments(
|
||||||
|
edges,
|
||||||
|
new THREE.LineBasicMaterial({
|
||||||
|
color: 0x666666,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.scene.add(line);
|
||||||
|
|
||||||
|
// Update model bounding box and sphere
|
||||||
|
geometry.computeBoundingSphere();
|
||||||
|
geometry.computeBoundingBox();
|
||||||
|
this.cameraTarget.copy(geometry.boundingSphere.center);
|
||||||
|
|
||||||
|
// Set light, camera, and orbit control parameters based on model size
|
||||||
|
let r = geometry.boundingSphere.radius;
|
||||||
|
this.controls.maxDistance = r * 10;
|
||||||
|
this.pointLight.position.set(0, r, 0);
|
||||||
|
this.camera.position.set(
|
||||||
|
r * 1.5 + this.cameraTarget.x,
|
||||||
|
r * 1.5 + this.cameraTarget.y,
|
||||||
|
r * 1.5 + this.cameraTarget.z
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render & animate scene
|
||||||
|
this.animate();
|
||||||
|
|
||||||
|
// Update CSS styles
|
||||||
|
this.$innerContainer.addClass("stlwv2-loaded");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper for animating 3D model, updating controls, etc
|
||||||
|
STLWebViewer2.prototype.animate = function () {
|
||||||
|
// Performance check: disable anti-aliasing if too slow
|
||||||
|
this.animateLoops = (this.animateLoops || 0) + 1;
|
||||||
|
if (!this.performanceChecked) {
|
||||||
|
if (this.animateLoops == 5) {
|
||||||
|
this.performanceCheckStartTime = performance.now();
|
||||||
|
} else if (this.animateLoops > 5) {
|
||||||
|
let delta = performance.now() - this.performanceCheckStartTime;
|
||||||
|
// Check framerate after 2 seconds
|
||||||
|
if (delta > 2000) {
|
||||||
|
let framerate = (1000 * (this.animateLoops - 5)) / delta;
|
||||||
|
console.log("Cumulative framerate: " + framerate);
|
||||||
|
if (framerate < 30) {
|
||||||
|
console.log("Disabling anti-aliasing");
|
||||||
|
this.renderer.domElement.remove();
|
||||||
|
delete this.renderer;
|
||||||
|
this.renderer = this.makeRenderer((antialias = false));
|
||||||
|
this.$innerContainer.append(this.renderer.domElement);
|
||||||
|
}
|
||||||
|
this.performanceChecked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update camera & renderer
|
||||||
|
this.camera.aspect =
|
||||||
|
this.$innerContainer.width() / this.$innerContainer.height();
|
||||||
|
this.camera.updateProjectionMatrix();
|
||||||
|
this.renderer.setSize(
|
||||||
|
this.$innerContainer.width(),
|
||||||
|
this.$innerContainer.height()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render :)
|
||||||
|
requestAnimationFrame(this.animate.bind(this));
|
||||||
|
this.controls.update();
|
||||||
|
this.renderer.render(this.scene, this.camera);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper for creating a WebGL renderer
|
||||||
|
STLWebViewer2.prototype.makeRenderer = function (antialias) {
|
||||||
|
let renderer = new THREE.WebGLRenderer({
|
||||||
|
antialias: antialias,
|
||||||
|
});
|
||||||
|
renderer.setClearColor(0xffffff);
|
||||||
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||||
|
renderer.gammaInput = true;
|
||||||
|
renderer.gammaOutput = true;
|
||||||
|
renderer.shadowMap.enabled = true;
|
||||||
|
return renderer;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper for handling fullscreen toggle
|
||||||
|
// Contains all animation logic, etc
|
||||||
|
STLWebViewer2.prototype.fullscreenToggleHandler = function () {
|
||||||
|
// Location and dimensions of viewer outer container
|
||||||
|
let top = this.$container.position().top - ScrollHelpers.top() + 1;
|
||||||
|
let left = this.$container.position().left - ScrollHelpers.left() + 1;
|
||||||
|
let bottom = $(window).height() - (top + this.$container.innerHeight()) + 1;
|
||||||
|
let width = this.$container.width() - 2;
|
||||||
|
|
||||||
|
// We're storing state in an invisible checkbox; poll the 'checked' property
|
||||||
|
// to determine if we're going to or from fullscreen mode
|
||||||
|
if (this.$fullscreenCheckbox.prop("checked")) {
|
||||||
|
// Seamless position:absolute => position:fixed transition
|
||||||
|
// Also fade out a little for dramatic effect
|
||||||
|
this.$innerContainer.css({
|
||||||
|
top: top + "px",
|
||||||
|
bottom: bottom + "px",
|
||||||
|
left: left + "px",
|
||||||
|
width: width + "px",
|
||||||
|
position: "fixed",
|
||||||
|
opacity: "0.5",
|
||||||
|
"z-index": 2000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Expand to fill screen :)
|
||||||
|
this.$innerContainer.animate(
|
||||||
|
{
|
||||||
|
top: "0",
|
||||||
|
bottom: "0",
|
||||||
|
left: "0",
|
||||||
|
width: "100%",
|
||||||
|
opacity: "1",
|
||||||
|
},
|
||||||
|
300,
|
||||||
|
() => {
|
||||||
|
// ...and fade back in
|
||||||
|
this.$innerContainer.animate(
|
||||||
|
{
|
||||||
|
opacity: "1",
|
||||||
|
},
|
||||||
|
500
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Fade out a little for dramatic effect
|
||||||
|
this.$innerContainer.css({
|
||||||
|
opacity: "0.5",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Shrink to fill outer container
|
||||||
|
this.$innerContainer.animate(
|
||||||
|
{
|
||||||
|
top: top + "px",
|
||||||
|
bottom: bottom + "px",
|
||||||
|
left: left + "px",
|
||||||
|
width: width + "px",
|
||||||
|
},
|
||||||
|
300,
|
||||||
|
() => {
|
||||||
|
// Reset all styles to original values in CSS file
|
||||||
|
// Seamless position:fixed => position:absolute transition
|
||||||
|
this.$innerContainer.css({
|
||||||
|
position: "",
|
||||||
|
top: "",
|
||||||
|
bottom: "",
|
||||||
|
left: "",
|
||||||
|
width: "",
|
||||||
|
"z-index": "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// ...and fade back in
|
||||||
|
this.$innerContainer.animate(
|
||||||
|
{
|
||||||
|
opacity: "1",
|
||||||
|
},
|
||||||
|
500
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
23
scripts/ScrollHelpers.js
Normal file
23
scripts/ScrollHelpers.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Helpers for getting scroll position
|
||||||
|
// Based on:
|
||||||
|
// https://stackoverflow.com/questions/2717252/document-body-scrolltop-is-always-0-in-ie-even-when-scrolling
|
||||||
|
var ScrollHelpers = {
|
||||||
|
top: function () {
|
||||||
|
return typeof window.pageYOffset != "undefined"
|
||||||
|
? window.pageYOffset
|
||||||
|
: document.documentElement.scrollTop
|
||||||
|
? document.documentElement.scrollTop
|
||||||
|
: document.body.scrollTop
|
||||||
|
? document.body.scrollTop
|
||||||
|
: 0;
|
||||||
|
},
|
||||||
|
left: function () {
|
||||||
|
return typeof window.pageXOffset != "undefined"
|
||||||
|
? window.pageXOffset
|
||||||
|
: document.documentElement.scrollLeft
|
||||||
|
? document.documentElement.scrollLeft
|
||||||
|
: document.body.scrollLeft
|
||||||
|
? document.body.scrollLeft
|
||||||
|
: 0;
|
||||||
|
},
|
||||||
|
};
|
109
stylesheets/style.css
Normal file
109
stylesheets/style.css
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
.stlwv2-model {
|
||||||
|
position: relative;
|
||||||
|
min-height: 20em;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stlwv2-inner {
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stlwv2-inner>.stlwv2-percent {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-size: 5em;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
color: #ccc;
|
||||||
|
animation-name: stlwv2-pulse;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes stlwv2-pulse {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0.625;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stlwv2-inner>canvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stlwv2-inner.stlwv2-loaded>canvas {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HUD styles
|
||||||
|
*/
|
||||||
|
.stlwv2-fullscreen-checkbox {
|
||||||
|
/* This checkbox is just for storing state and should never be displayed */
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stlwv2-hud {
|
||||||
|
/* Hide HUD elements until viewer is finished loading */
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stlwv2-loaded>.stlwv2-hud {
|
||||||
|
position: absolute;
|
||||||
|
padding: 0.25em;
|
||||||
|
z-index: 1000;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stlwv2-loaded>.stlwv2-github-link {
|
||||||
|
font-size: 1.2em;
|
||||||
|
top: 0.57em;
|
||||||
|
right: 3em;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #999;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stlwv2-loaded>.stlwv2-fullscreen-on {
|
||||||
|
font-size: 1.5em;
|
||||||
|
top: 0;
|
||||||
|
right: 0.2em;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
/* unicode icon rotate hack */
|
||||||
|
color: #ccc;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stlwv2-loaded>.stlwv2-fullscreen-off {
|
||||||
|
font-size: 2em;
|
||||||
|
top: 0;
|
||||||
|
right: 0.5em;
|
||||||
|
color: #c33;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stlwv2-fullscreen-checkbox:checked~.stlwv2-loaded>.stlwv2-github-link {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stlwv2-fullscreen-checkbox:checked~.stlwv2-loaded>.stlwv2-fullscreen-on {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stlwv2-fullscreen-checkbox:checked~.stlwv2-loaded>.stlwv2-fullscreen-off {
|
||||||
|
display: block;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user