add stl mesh

This commit is contained in:
Simon Vieille 2021-07-09 23:42:38 +02:00
parent b76b9123e5
commit 54e0faabc3
38 changed files with 4427 additions and 29 deletions

View File

@ -38,7 +38,7 @@ html, body {
scroll-behavior: smooth;
}
$dicons: coffee server search project share contact list response twitter diaspora github code rss linkedin mastodon pixelfed gpg matrix murph;
$dicons: coffee server search project share contact list response twitter diaspora github code rss linkedin mastodon pixelfed gpg matrix murph cube;
.d-none {
display: none;
@ -282,7 +282,6 @@ pre[class*="language-"] {
}
}
.content hr {
border: 0;
border-bottom: 1px dashed $color-hr-border;
@ -667,6 +666,132 @@ $links: (
animation-duration: 1s;
}
.modal-open {
overflow: hidden;
.modal {
overflow-x: hidden;
overflow-y: auto;
}
}
.modal {
position: fixed;
top: 0;
left: 0;
z-index: 2000;
display: none;
width: 100%;
height: 100%;
overflow: hidden;
outline: 0;
}
.modal-header {
}
.modal-dialog {
position: relative;
width: 1280px;
margin: 100px auto;
pointer-events: none;
.modal-header {
.close {
cursor: pointer;
position: absolute;
right: 15px;
top: 13px;
width: 30px;
height: 30px;
z-index: 1000 + 4;
&:hover::before, &:hover::after {
background-color: #333;
}
&:before, &:after {
position: absolute;
left: 15px;
content: ' ';
height: 30px;
width: 2px;
background-color: #333;
z-index: 2001;
ransition-property: background-color;
transition-duration: 0.3s;
}
&:before {
transform: rotate(45deg);
}
&:after {
transform: rotate(-45deg);
}
}
}
}
.modal-content {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
pointer-events: auto;
background-color: #fff;
background-clip: padding-box;
outline: 0;
}
.modal-backdrop {
position: fixed;
display: none;
top: 0;
left: 0;
z-index: 1999;
width: 100vw;
height: 100vh;
background-color: #333;
opacity: 0.89;
}
.modal-body {
position: relative;
flex: 1 1 auto;
padding: 0;
height: calc(100vh - 300px);
iframe {
border: 0;
height: 100%;
width: 100%;
}
}
.mesh {
border: 1px solid $color-hr-border;
border-radius: 10px;
margin: 20px;
&-preview {
img {
border-radius: 10px;
}
}
&-title {
font-size: 16px;
padding: 10px;
color: $color-body-text;
}
&-description {
padding-left: 10px;
padding-right: 10px;
}
}
@media screen and (max-width: 980px) {
.quick-image img {
height: 200px;
@ -781,6 +906,19 @@ $links: (
border: 1px solid #000000;
border-radius: 0;
}
.mesh {
border-color: #86899f;
&-title {
color: #fff;
}
&-description {
color: #fff;
}
}
}
@media all and (max-height: 465px) {
@ -792,4 +930,3 @@ $links: (
}
}
}

10
assets/css/viewer.scss Normal file
View File

@ -0,0 +1,10 @@
body {
margin: 0;
padding: 0;
overflow: hidden;
}
#mesh-viewer {
width: 100vw;
height: 100vh;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -41,9 +41,9 @@
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="0.70710678"
inkscape:cx="813.74735"
inkscape:cy="51.104949"
inkscape:zoom="0.1767767"
inkscape:cx="2355.8362"
inkscape:cy="1162.0935"
inkscape:window-x="0"
inkscape:window-y="21"
inkscape:current-layer="svg5496"
@ -177,6 +177,11 @@
id="glyph1007"
unicode="murph"
d="m 137.30469,982.25195 c -52.575606,0 -94.902346,-42.32673 -94.902346,-94.90234 V 93.9707 c 0,-26.02529 10.374685,-49.53596 27.226562,-66.64843 h 0.332032 L 379.07227,336.42969 431.47461,192.73047 h 44.19922 l 137.93555,378.23047 2.96679,2.96484 71.59375,-372.16406 h 119.80664 l -95.47461,468.08789 284.99414,284.98828 c -17.12583,16.95602 -40.69709,27.41406 -66.8125,27.41406 z m 860.1914,-27.41406 c 0.15107,-0.14631 0.002,0.002 0.002,0.002 z m -764.45703,-56.1582 h 65.60156 L 414.15234,539.0293 331.38477,456.23438 287.23242,577.31641 245.91992,370.76758 227.04883,351.88867 96.787109,221.58203 Z m 363.66992,0 h 65.12891 l 18.95117,-92.92188 -7.6289,-7.63281 -157.52149,-157.57227 z" />
<glyph
glyph-name="glyphe 20"
id="glyph1001"
unicode="cube"
d="m 518.45763,958.03619 c -6.08548,-0.0798 -13.20959,-1.2371 -22.13782,-4.26504 L 170.49311,831.28346 c -26.41283,-11.69072 -49.4246,-27.91669 -50.34857,-73.59319 V 410.3969 c -0.33616,-55.18412 22.54804,-57.59151 40.02139,-72.29871 L 485.50765,179.2971 c 23.95624,-13.05801 46.13769,-6.59485 68.42467,-1.29092 l 322.75639,165.25571 c 17.67284,10.72056 36.21407,18.12146 43.89761,67.13501 l -1.29445,352.84931 c -3.55377,33.76392 -19.68358,55.79086 -49.05766,65.45539 L 543.76563,953.1257 c -7.90878,2.1834 -15.16554,5.04354 -25.308,4.91049 z M 517.61604,851.7991 818.87291,740.4233 519.44184,619.00542 219.09787,739.51038 Z M 817.5,631.33337 818.4129,421.3626 569.19131,300.85763 l 2.7387,230.05494 z" />
</font>
<symbol
id="beer">
@ -320,7 +325,7 @@
</defs>
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;vector-effect:none;fill:#1e2430;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:40;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:stroke fill markers;enable-background:accumulate"
d="m 137.30469,41.748047 c -52.575606,0 -94.902346,42.326737 -94.902346,94.902343 V 930.0293 c 0,26.02529 10.374685,49.53596 27.226562,66.64843 h 0.332032 L 379.07227,687.57031 431.47461,831.26953 h 44.19922 l 137.93555,-378.23047 2.96679,-2.96484 71.59375,372.16406 H 807.97656 L 712.50195,354.15039 997.49609,69.162109 C 980.37026,52.206085 956.799,41.748047 930.68359,41.748047 Z m 860.1914,27.414062 c 0.15107,0.14631 0.002,-0.0019 0.002,-0.002 z M 233.03906,125.32031 h 65.60156 L 414.15234,484.9707 331.38477,567.76562 287.23242,446.68359 245.91992,653.23242 227.04883,672.11133 96.787109,802.41797 Z m 363.66992,0 h 65.12891 l 18.95117,92.92188 -7.6289,7.63281 -157.52149,157.57227 z"
d="m 4832.4937,2078.2156 c -52.5756,0 -94.9023,42.3267 -94.9023,94.9023 v 793.3789 c 0,26.0253 10.3747,49.536 27.2265,66.6485 h 0.3321 l 309.1113,-309.1075 52.4023,143.6993 h 44.1993 l 137.9355,-378.2305 2.9668,-2.9648 71.5937,372.164 h 119.8067 l -95.4746,-468.0879 284.9941,-284.9883 c -17.1258,-16.956 -40.6971,-27.414 -66.8125,-27.414 z m 860.1914,27.414 c 0.1511,0.1463 0,-0 0,-0 z m -764.457,56.1582 h 65.6015 l 115.5118,359.6504 -82.7676,82.795 -44.1524,-121.0821 -41.3125,206.5489 -18.871,18.8789 -130.2618,130.3066 z m 363.6699,0 h 65.1289 l 18.9512,92.9219 -7.6289,7.6328 -157.5215,157.5723 z"
id="rect2455"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ssscccccccccccsscccccccccccccccccc" />
@ -430,4 +435,15 @@
d="m -889.13054,3204.4306 v 808.4067 h 215.79586 v -59.3455 H -816.50711 V 3263.774 h 143.17243 v -59.3434 z m 626.1962,0 v 59.3434 h 143.17243 v 689.7178 h -143.17243 v 59.3455 h 215.797948 v -808.4067 z m -247.17033,256.5367 c -16.20834,0 -30.79551,3.4966 -43.76218,10.4918 -12.96667,6.9951 -26.78823,16.7213 -41.46101,29.1761 v -31.7335 h -89.57147 v 287.3989 h 89.57147 v -203.7147 c 6.65399,-4.6066 13.48022,-8.6143 20.47541,-12.0266 6.99508,-3.4123 14.15941,-5.1188 21.49581,-5.1188 9.55444,0 16.97739,1.4499 22.26636,4.3504 5.45964,2.7298 9.46941,7.1652 12.02867,13.3073 2.55916,6.142 4.09469,14.1617 4.60655,24.0573 0.6825,9.725 1.0225,21.5834 1.0225,35.5737 v 143.5714 h 90.08379 v -203.7147 c 7.84823,-5.4596 15.10018,-9.6385 21.75406,-12.5389 6.65399,-3.0711 13.39262,-4.6065 20.21715,-4.6065 9.725,0 17.23144,1.4499 22.52042,4.3504 5.45973,2.7298 9.46951,7.1652 12.02867,13.3073 2.55926,6.142 4.09478,14.0762 4.60655,23.8012 0.51185,9.725 0.76845,21.6688 0.76845,35.8298 v 143.5714 h 90.08378 v -187.0795 c 0,-20.4736 -2.04664,-37.0219 -6.14136,-49.6474 -3.92406,-12.796 -9.89498,-23.8011 -17.91387,-33.0142 -7.33638,-8.5308 -16.29486,-14.9294 -26.87292,-19.1947 -10.57805,-4.2653 -22.51978,-6.3975 -35.82775,-6.3975 -15.69647,0 -31.22256,4.3499 -46.57775,13.0512 -15.18463,8.5306 -31.39284,20.8146 -48.62488,36.8524 -6.99519,-16.5496 -17.14699,-29.0025 -30.45485,-37.3626 -13.30787,-8.3601 -28.74836,-12.541 -46.3216,-12.541 z"
id="path1008"
inkscape:connector-curvature="0" />
<circle
id="path970"
style="fill:#000000;stroke:none"
cx="816"
cy="392.66663"
r="1.5" />
<path
style="fill:#4d4d4d;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 518.45763,65.963806 c -6.08548,0.0798 -13.20959,1.2371 -22.13782,4.26504 L 170.49311,192.71654 c -26.41283,11.69072 -49.4246,27.91669 -50.34857,73.59319 V 613.6031 c -0.33616,55.18412 22.54804,57.59151 40.02139,72.29871 L 485.50765,844.7029 c 23.95624,13.05801 46.13769,6.59485 68.42467,1.29092 L 876.68871,680.73811 c 17.67284,-10.72056 36.21407,-18.12146 43.89761,-67.13501 L 919.29187,260.75379 C 915.7381,226.98987 899.60829,204.96293 870.23421,195.2984 L 543.76563,70.874296 c -7.90878,-2.1834 -15.16554,-5.04354 -25.308,-4.91049 z M 517.61604,172.2009 818.87291,283.5767 519.44184,404.99458 219.09787,284.48962 Z M 817.5,392.66663 l 0.9129,209.97077 -249.22159,120.50497 2.7387,-230.05494 z"
id="path980"
inkscape:connector-curvature="0" />
</svg>

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -10,6 +10,7 @@ const Knmc = require('./app/knmc')
const VideoRatio = require('./app/video-ratio')
const Stats = require('./app/stats')
const Particles = require('./app/particles')
const MeshViewer = require('./app/mesh-viewer')
const app = new App([
new FormPnw(window),
@ -20,7 +21,8 @@ const app = new App([
new Knmc(window),
new VideoRatio(window),
// new Stats(),
new Particles(window)
new Particles(window),
new MeshViewer(window)
])
window.addEventListener('load', function () {

View File

@ -0,0 +1,42 @@
const MeshViewer = function (w) {
this.window = w
}
MeshViewer.prototype.init = function () {
const openers = this.window.document.querySelectorAll('*[data-modal]')
const backdrop = this.window.document.querySelector('.modal-backdrop')
const body = this.window.document.querySelector('body')
for (let i = 0, len = openers.length; i < len; i++) {
openers[i].addEventListener('click', (e) => {
e.preventDefault()
let target = e.target
if (target.tagName != 'A') {
target = target.parentNode
}
const modal = this.window.document.querySelector('#mesh-viewer')
const modalBody = modal.querySelector('.modal-body')
modal.style.display = 'block'
modal.classList.add('show')
modalBody.innerHTML = '<iframe src="' + target.getAttribute('href') + '"></iframe>'
body.classList.add('modal-open')
backdrop.style.display = 'block'
modal.querySelector('.close').addEventListener('click', () => {
modal.style.display = 'none'
modal.classList.remove('show')
body.classList.remove('modal-open')
backdrop.style.display = 'none'
modalBody.innerHTML = ''
})
})
}
}
module.exports = MeshViewer

11
assets/js/viewer.js Normal file
View File

@ -0,0 +1,11 @@
import '../css/viewer.scss'
const container = document.getElementById('mesh-viewer')
const viewer = new StlViewer(
container,
{
auto_rotate: true,
allow_drag_and_drop: true,
models: [{filename: container.getAttribute('data-file')}]
}
);

View File

@ -15,6 +15,7 @@ core:
- {name: 'Blog\PostController::rss', action: 'App\Controller\Blog\PostController::rss'}
- {name: 'Blog\PostController::jsonApi', action: 'App\Controller\Blog\PostController::jsonApi'}
- {name: 'Blog\CategoryController::categories', action: 'App\Controller\Blog\CategoryController::categories'}
- {name: 'StlMeshController::meshes', action: 'App\Controller\StlMeshController::meshes'}
pages:
App\Entity\Page\SimplePage:
name: 'Page de contenu'
@ -36,12 +37,43 @@ core:
name: 'RSS'
templates:
- {name: "Par défaut", file: "page/rss/default.xml.twig"}
App\Entity\Page\MeshPage:
name: 'Mesh'
templates:
- {name: "Par défaut", file: "page/mesh/default.html.twig"}
App\Entity\Page\TextPage:
name: 'Texte'
templates:
- {name: "Par défaut", file: "page/text/default.txt.twig"}
file_manager:
mimes:
- image/png
- image/jpg
- image/jpeg
- image/gif
- image/svg+xml
- video/mp4
- audio/mpeg3
- audio/x-mpeg-3
- multipart/x-zip
- multipart/x-gzip
- application/pdf
- application/ogg
- application/zip
- application/rar
- application/x-rar-compressed
- application/x-zip-compressed
- application/tar
- application/x-tar
- application/x-bzip
- application/x-bzip2
- application/x-gzip
- application/octet-stream
- application/msword
- text/plain
- text/css
- application/sla
path_locked:
- "%kernel.project_dir%/public/uploads/page"
- "%kernel.project_dir%/public/uploads/post"

View File

@ -16,6 +16,6 @@ framework:
php_errors:
log: true
assets:
base_urls:
- "%env(ASSET_BASE_URL)%"
# assets:
# base_urls:
# - "%env(ASSET_BASE_URL)%"

View File

@ -7,3 +7,7 @@ liip_imagine:
filters:
downscale:
max: [120, 120]
mesh_preview_filter:
filters:
downscale:
max: [300, 300]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,625 @@
/**
* @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
*/
/*global THREE, console */
// This set of controls performs orbiting, dollying (zooming), and panning. It maintains
// the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is
// supported.
//
// 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 finter swipe
//
// This is a drop-in replacement for (most) TrackballControls used in examples.
// That is, include this js file and wherever you see:
// controls = new THREE.TrackballControls( camera );
// controls.target.z = 150;
// Simple substitute "OrbitControls" and the control should work as-is.
THREE.OrbitControls = function ( object, domElement ) {
this.object = object;
this.domElement = ( domElement !== undefined ) ? domElement : document;
// API
// Set to false to disable this control
this.enabled = true;
// "target" sets the location of focus, where the control orbits around
// and where it pans with respect to.
this.target = new THREE.Vector3();
// center is old, deprecated; use "target" instead
this.center = this.target;
// This option actually enables dollying in and out; left as "zoom" for
// backwards compatibility
this.noZoom = false;
this.zoomSpeed = 1.0;
// Limits to how far you can dolly in and out
this.minDistance = 0;
this.maxDistance = Infinity;
// Set to true to disable this control
this.noRotate = false;
this.rotateSpeed = 1.0;
// Set to true to disable this control
this.noPan = false;
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
// Set to true to automatically rotate around the target
this.autoRotate = false;
this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
// 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
// Set to true to disable use of the keys
this.noKeys = false;
// The four arrow keys
this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
////////////
// internals
var scope = this;
var EPS = 0.000001;
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 panOffset = new THREE.Vector3();
var offset = new THREE.Vector3();
var dollyStart = new THREE.Vector2();
var dollyEnd = new THREE.Vector2();
var dollyDelta = new THREE.Vector2();
var phiDelta = 0;
var thetaDelta = 0;
var scale = 1;
var pan = new THREE.Vector3();
var lastPosition = new THREE.Vector3();
var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
var state = STATE.NONE;
// for reset
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
// events
var changeEvent = { type: 'change' };
var startEvent = { type: 'start'};
var endEvent = { type: 'end'};
this.rotateLeft = function ( angle ) {
if ( angle === undefined ) {
angle = getAutoRotationAngle();
}
thetaDelta -= angle;
};
this.rotateUp = function ( angle ) {
if ( angle === undefined ) {
angle = getAutoRotationAngle();
}
phiDelta -= angle;
};
// pass in distance in world space to move left
this.panLeft = function ( distance ) {
var te = this.object.matrix.elements;
// get X column of matrix
panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] );
panOffset.multiplyScalar( - distance );
pan.add( panOffset );
};
// pass in distance in world space to move up
this.panUp = function ( distance ) {
var te = this.object.matrix.elements;
// get Y column of matrix
panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] );
panOffset.multiplyScalar( distance );
pan.add( panOffset );
};
// pass in x,y of change desired in pixel space,
// right and down are positive
this.pan = function ( deltaX, deltaY ) {
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
if ( scope.object.fov !== undefined ) {
// perspective
var position = scope.object.position;
var offset = position.clone().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
scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight );
scope.panUp( 2 * deltaY * targetDistance / element.clientHeight );
} else if ( scope.object.top !== undefined ) {
// orthographic
scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth );
scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight );
} else {
// camera neither orthographic or perspective
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
}
};
this.dollyIn = function ( dollyScale ) {
if ( dollyScale === undefined ) {
dollyScale = getZoomScale();
}
scale /= dollyScale;
};
this.dollyOut = function ( dollyScale ) {
if ( dollyScale === undefined ) {
dollyScale = getZoomScale();
}
scale *= dollyScale;
};
this.update = function () {
var position = this.object.position;
offset.copy( position ).sub( this.target );
// angle from z-axis around y-axis
var theta = Math.atan2( offset.x, offset.z );
// angle from y-axis
var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
if ( this.autoRotate ) {
this.rotateLeft( getAutoRotationAngle() );
}
theta += thetaDelta;
phi += phiDelta;
// restrict phi to be between desired limits
phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
// restrict phi to be betwee EPS and PI-EPS
phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
var radius = offset.length() * scale;
// restrict radius to be between desired limits
radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
// move target to panned location
this.target.add( pan );
offset.x = radius * Math.sin( phi ) * Math.sin( theta );
offset.y = radius * Math.cos( phi );
offset.z = radius * Math.sin( phi ) * Math.cos( theta );
position.copy( this.target ).add( offset );
this.object.lookAt( this.target );
thetaDelta = 0;
phiDelta = 0;
scale = 1;
pan.set( 0, 0, 0 );
if ( lastPosition.distanceTo( this.object.position ) > 0 ) {
this.dispatchEvent( changeEvent );
lastPosition.copy( this.object.position );
}
};
this.reset = function () {
state = STATE.NONE;
this.target.copy( this.target0 );
//this.object.position.copy( this.position0 );
this.update();
};
function getAutoRotationAngle() {
return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
}
function getZoomScale() {
return Math.pow( 0.95, scope.zoomSpeed );
}
function onMouseDown( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
if ( event.button === 0 ) {
if ( scope.noRotate === true ) return;
state = STATE.ROTATE;
rotateStart.set( event.clientX, event.clientY );
} else if ( event.button === 1 ) {
if ( scope.noZoom === true ) return;
state = STATE.DOLLY;
dollyStart.set( event.clientX, event.clientY );
} else if ( event.button === 2 ) {
if ( scope.noPan === true ) return;
state = STATE.PAN;
panStart.set( event.clientX, event.clientY );
}
scope.domElement.addEventListener( 'mousemove', onMouseMove, false );
scope.domElement.addEventListener( 'mouseup', onMouseUp, false );
scope.dispatchEvent( startEvent );
}
function onMouseMove( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
if ( state === STATE.ROTATE ) {
if ( scope.noRotate === true ) return;
rotateEnd.set( event.clientX, event.clientY );
rotateDelta.subVectors( rotateEnd, rotateStart );
// rotating across whole screen goes 360 degrees around
scope.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
scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
rotateStart.copy( rotateEnd );
} else if ( state === STATE.DOLLY ) {
if ( scope.noZoom === true ) return;
dollyEnd.set( event.clientX, event.clientY );
dollyDelta.subVectors( dollyEnd, dollyStart );
if ( dollyDelta.y > 0 ) {
scope.dollyIn();
} else {
scope.dollyOut();
}
dollyStart.copy( dollyEnd );
} else if ( state === STATE.PAN ) {
if ( scope.noPan === true ) return;
panEnd.set( event.clientX, event.clientY );
panDelta.subVectors( panEnd, panStart );
scope.pan( panDelta.x, panDelta.y );
panStart.copy( panEnd );
}
scope.update();
}
function onMouseUp( /* event */ ) {
if ( scope.enabled === false ) return;
scope.domElement.removeEventListener( 'mousemove', onMouseMove, false );
scope.domElement.removeEventListener( 'mouseup', onMouseUp, false );
scope.dispatchEvent( endEvent );
state = STATE.NONE;
}
function onMouseWheel( event ) {
if ( scope.enabled === false || scope.noZoom === true ) return;
event.preventDefault();
var delta = 0;
if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9
delta = event.wheelDelta;
} else if ( event.detail !== undefined ) { // Firefox
delta = - event.detail;
}
if ( delta > 0 ) {
scope.dollyOut();
} else {
scope.dollyIn();
}
scope.update();
scope.dispatchEvent( startEvent );
scope.dispatchEvent( endEvent );
}
function onKeyDown( event ) {
if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return;
switch ( event.keyCode ) {
case scope.keys.UP:
scope.pan( 0, scope.keyPanSpeed );
scope.update();
break;
case scope.keys.BOTTOM:
scope.pan( 0, - scope.keyPanSpeed );
scope.update();
break;
case scope.keys.LEFT:
scope.pan( scope.keyPanSpeed, 0 );
scope.update();
break;
case scope.keys.RIGHT:
scope.pan( - scope.keyPanSpeed, 0 );
scope.update();
break;
}
}
function touchstart( event ) {
if ( scope.enabled === false ) return;
switch ( event.touches.length ) {
case 1: // one-fingered touch: rotate
if ( scope.noRotate === true ) return;
state = STATE.TOUCH_ROTATE;
rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;
case 2: // two-fingered touch: dolly
if ( scope.noZoom === true ) return;
state = STATE.TOUCH_DOLLY;
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 );
break;
case 3: // three-fingered touch: pan
if ( scope.noPan === true ) return;
state = STATE.TOUCH_PAN;
panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;
default:
state = STATE.NONE;
}
scope.dispatchEvent( startEvent );
}
function touchmove( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
switch ( event.touches.length ) {
case 1: // one-fingered touch: rotate
if ( scope.noRotate === true ) return;
if ( state !== STATE.TOUCH_ROTATE ) return;
rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
rotateDelta.subVectors( rotateEnd, rotateStart );
// rotating across whole screen goes 360 degrees around
scope.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
scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
rotateStart.copy( rotateEnd );
scope.update();
break;
case 2: // two-fingered touch: dolly
if ( scope.noZoom === true ) return;
if ( state !== STATE.TOUCH_DOLLY ) return;
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 ) {
scope.dollyOut();
} else {
scope.dollyIn();
}
dollyStart.copy( dollyEnd );
scope.update();
break;
case 3: // three-fingered touch: pan
if ( scope.noPan === true ) return;
if ( state !== STATE.TOUCH_PAN ) return;
panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
panDelta.subVectors( panEnd, panStart );
scope.pan( panDelta.x, panDelta.y );
panStart.copy( panEnd );
scope.update();
break;
default:
state = STATE.NONE;
}
}
function touchend( /* event */ ) {
if ( scope.enabled === false ) return;
scope.dispatchEvent( endEvent );
state = STATE.NONE;
}
this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
this.domElement.addEventListener( 'mousedown', onMouseDown, false );
this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
this.domElement.addEventListener( 'touchstart', touchstart, false );
this.domElement.addEventListener( 'touchend', touchend, false );
this.domElement.addEventListener( 'touchmove', touchmove, false );
window.addEventListener( 'keydown', onKeyDown, false );
};
THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,619 @@
/**
* @author Eberhard Graether / http://egraether.com/
* @author Mark Lundin / http://mark-lundin.com
* @author Simone Manini / http://daron1337.github.io
* @author Luca Antiga / http://lantiga.github.io
*/
THREE.TrackballControls = function ( object, domElement ) {
var _this = this;
var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
this.object = object;
this.domElement = ( domElement !== undefined ) ? domElement : document;
// API
this.enabled = true;
this.screen = { left: 0, top: 0, width: 0, height: 0 };
this.rotateSpeed = 1.0;
this.zoomSpeed = 1.2;
this.panSpeed = 0.3;
this.noRotate = false;
this.noZoom = false;
this.noPan = false;
this.staticMoving = false;
this.dynamicDampingFactor = 0.2;
this.minDistance = 0;
this.maxDistance = Infinity;
this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
// internals
this.target = new THREE.Vector3();
var EPS = 0.000001;
var lastPosition = new THREE.Vector3();
var _state = STATE.NONE,
_prevState = STATE.NONE,
_eye = new THREE.Vector3(),
_movePrev = new THREE.Vector2(),
_moveCurr = new THREE.Vector2(),
_lastAxis = new THREE.Vector3(),
_lastAngle = 0,
_zoomStart = new THREE.Vector2(),
_zoomEnd = new THREE.Vector2(),
_touchZoomDistanceStart = 0,
_touchZoomDistanceEnd = 0,
_panStart = new THREE.Vector2(),
_panEnd = new THREE.Vector2();
// for reset
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.up0 = this.object.up.clone();
// events
var changeEvent = { type: 'change' };
var startEvent = { type: 'start' };
var endEvent = { type: 'end' };
// methods
this.handleResize = function () {
if ( this.domElement === document ) {
this.screen.left = 0;
this.screen.top = 0;
this.screen.width = window.innerWidth;
this.screen.height = window.innerHeight;
} else {
var box = this.domElement.getBoundingClientRect();
// adjustments come from similar code in the jquery offset() function
var d = this.domElement.ownerDocument.documentElement;
this.screen.left = box.left + window.pageXOffset - d.clientLeft;
this.screen.top = box.top + window.pageYOffset - d.clientTop;
this.screen.width = box.width;
this.screen.height = box.height;
}
};
var getMouseOnScreen = ( function () {
var vector = new THREE.Vector2();
return function getMouseOnScreen( pageX, pageY ) {
vector.set(
( pageX - _this.screen.left ) / _this.screen.width,
( pageY - _this.screen.top ) / _this.screen.height
);
return vector;
};
}() );
var getMouseOnCircle = ( function () {
var vector = new THREE.Vector2();
return function getMouseOnCircle( pageX, pageY ) {
vector.set(
( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ),
( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional
);
return vector;
};
}() );
this.rotateCamera = ( function () {
var axis = new THREE.Vector3(),
quaternion = new THREE.Quaternion(),
eyeDirection = new THREE.Vector3(),
objectUpDirection = new THREE.Vector3(),
objectSidewaysDirection = new THREE.Vector3(),
moveDirection = new THREE.Vector3(),
angle;
return function rotateCamera() {
moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
angle = moveDirection.length();
if ( angle ) {
_eye.copy( _this.object.position ).sub( _this.target );
eyeDirection.copy( _eye ).normalize();
objectUpDirection.copy( _this.object.up ).normalize();
objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
axis.crossVectors( moveDirection, _eye ).normalize();
angle *= _this.rotateSpeed;
quaternion.setFromAxisAngle( axis, angle );
_eye.applyQuaternion( quaternion );
_this.object.up.applyQuaternion( quaternion );
_lastAxis.copy( axis );
_lastAngle = angle;
} else if ( ! _this.staticMoving && _lastAngle ) {
_lastAngle *= Math.sqrt( 1.0 - _this.dynamicDampingFactor );
_eye.copy( _this.object.position ).sub( _this.target );
quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
_eye.applyQuaternion( quaternion );
_this.object.up.applyQuaternion( quaternion );
}
_movePrev.copy( _moveCurr );
};
}() );
this.zoomCamera = function () {
var factor;
if ( _state === STATE.TOUCH_ZOOM_PAN ) {
factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
_touchZoomDistanceStart = _touchZoomDistanceEnd;
_eye.multiplyScalar( factor );
} else {
factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
if ( factor !== 1.0 && factor > 0.0 ) {
_eye.multiplyScalar( factor );
}
if ( _this.staticMoving ) {
_zoomStart.copy( _zoomEnd );
} else {
_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
}
}
};
this.panCamera = ( function () {
var mouseChange = new THREE.Vector2(),
objectUp = new THREE.Vector3(),
pan = new THREE.Vector3();
return function panCamera() {
mouseChange.copy( _panEnd ).sub( _panStart );
if ( mouseChange.lengthSq() ) {
mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x );
pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) );
_this.object.position.add( pan );
_this.target.add( pan );
if ( _this.staticMoving ) {
_panStart.copy( _panEnd );
} else {
_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
}
}
};
}() );
this.checkDistances = function () {
if ( ! _this.noZoom || ! _this.noPan ) {
if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) {
_this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) );
_zoomStart.copy( _zoomEnd );
}
if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
_this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
_zoomStart.copy( _zoomEnd );
}
}
};
this.update = function () {
_eye.subVectors( _this.object.position, _this.target );
if ( ! _this.noRotate ) {
_this.rotateCamera();
}
if ( ! _this.noZoom ) {
_this.zoomCamera();
}
if ( ! _this.noPan ) {
_this.panCamera();
}
_this.object.position.addVectors( _this.target, _eye );
_this.checkDistances();
_this.object.lookAt( _this.target );
if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) {
_this.dispatchEvent( changeEvent );
lastPosition.copy( _this.object.position );
}
};
this.reset = function () {
_state = STATE.NONE;
_prevState = STATE.NONE;
_this.target.copy( _this.target0 );
_this.object.position.copy( _this.position0 );
_this.object.up.copy( _this.up0 );
_eye.subVectors( _this.object.position, _this.target );
_this.object.lookAt( _this.target );
_this.dispatchEvent( changeEvent );
lastPosition.copy( _this.object.position );
};
// listeners
function keydown( event ) {
if ( _this.enabled === false ) return;
window.removeEventListener( 'keydown', keydown );
_prevState = _state;
if ( _state !== STATE.NONE ) {
return;
} else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) {
_state = STATE.ROTATE;
} else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) {
_state = STATE.ZOOM;
} else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) {
_state = STATE.PAN;
}
}
function keyup( event ) {
if ( _this.enabled === false ) return;
_state = _prevState;
window.addEventListener( 'keydown', keydown, false );
}
function mousedown( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
if ( _state === STATE.NONE ) {
_state = event.button;
}
if ( _state === STATE.ROTATE && ! _this.noRotate ) {
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
_movePrev.copy( _moveCurr );
} else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
_zoomEnd.copy( _zoomStart );
} else if ( _state === STATE.PAN && ! _this.noPan ) {
_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
_panEnd.copy( _panStart );
}
document.addEventListener( 'mousemove', mousemove, false );
document.addEventListener( 'mouseup', mouseup, false );
_this.dispatchEvent( startEvent );
}
function mousemove( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
if ( _state === STATE.ROTATE && ! _this.noRotate ) {
_movePrev.copy( _moveCurr );
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
} else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
} else if ( _state === STATE.PAN && ! _this.noPan ) {
_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
}
}
function mouseup( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
_state = STATE.NONE;
document.removeEventListener( 'mousemove', mousemove );
document.removeEventListener( 'mouseup', mouseup );
_this.dispatchEvent( endEvent );
}
function mousewheel( event ) {
if ( _this.enabled === false ) return;
if ( _this.noZoom === true ) return;
event.preventDefault();
event.stopPropagation();
switch ( event.deltaMode ) {
case 2:
// Zoom in pages
_zoomStart.y -= event.deltaY * 0.025;
break;
case 1:
// Zoom in lines
_zoomStart.y -= event.deltaY * 0.01;
break;
default:
// undefined, 0, assume pixels
_zoomStart.y -= event.deltaY * 0.00025;
break;
}
_this.dispatchEvent( startEvent );
_this.dispatchEvent( endEvent );
}
function touchstart( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
switch ( event.touches.length ) {
case 1:
_state = STATE.TOUCH_ROTATE;
_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
_movePrev.copy( _moveCurr );
break;
default: // 2 or more
_state = STATE.TOUCH_ZOOM_PAN;
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
_panStart.copy( getMouseOnScreen( x, y ) );
_panEnd.copy( _panStart );
break;
}
_this.dispatchEvent( startEvent );
}
function touchmove( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
switch ( event.touches.length ) {
case 1:
_movePrev.copy( _moveCurr );
_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
break;
default: // 2 or more
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
_panEnd.copy( getMouseOnScreen( x, y ) );
break;
}
}
function touchend( event ) {
if ( _this.enabled === false ) return;
switch ( event.touches.length ) {
case 0:
_state = STATE.NONE;
break;
case 1:
_state = STATE.TOUCH_ROTATE;
_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
_movePrev.copy( _moveCurr );
break;
}
_this.dispatchEvent( endEvent );
}
function contextmenu( event ) {
if ( _this.enabled === false ) return;
event.preventDefault();
}
this.dispose = function () {
this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
this.domElement.removeEventListener( 'mousedown', mousedown, false );
this.domElement.removeEventListener( 'wheel', mousewheel, false );
this.domElement.removeEventListener( 'touchstart', touchstart, false );
this.domElement.removeEventListener( 'touchend', touchend, false );
this.domElement.removeEventListener( 'touchmove', touchmove, false );
document.removeEventListener( 'mousemove', mousemove, false );
document.removeEventListener( 'mouseup', mouseup, false );
window.removeEventListener( 'keydown', keydown, false );
window.removeEventListener( 'keyup', keyup, false );
};
this.domElement.addEventListener( 'contextmenu', contextmenu, false );
this.domElement.addEventListener( 'mousedown', mousedown, false );
this.domElement.addEventListener( 'wheel', mousewheel, false );
this.domElement.addEventListener( 'touchstart', touchstart, false );
this.domElement.addEventListener( 'touchend', touchend, false );
this.domElement.addEventListener( 'touchmove', touchmove, false );
window.addEventListener( 'keydown', keydown, false );
window.addEventListener( 'keyup', keyup, false );
this.handleResize();
// force an update at start
this.update();
};
THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
THREE.TrackballControls.prototype.constructor = THREE.TrackballControls;

View File

@ -0,0 +1,48 @@
if (!String.prototype.repeat) {
String.prototype.repeat = function(count) {
'use strict';
if (this == null) {
throw new TypeError('can\'t convert ' + this + ' to object');
}
var str = '' + this;
count = +count;
if (count != count) {
count = 0;
}
if (count < 0) {
throw new RangeError('repeat count must be non-negative');
}
if (count == Infinity) {
throw new RangeError('repeat count must be less than infinity');
}
count = Math.floor(count);
if (str.length == 0 || count == 0) {
return '';
}
// Ensuring count is a 31-bit integer allows us to heavily optimize the
// main part. But anyway, most current (August 2014) browsers can't handle
// strings 1 << 28 chars or longer, so:
if (str.length * count >= 1 << 28) {
throw new RangeError('repeat count must not overflow maximum string size');
}
var rpt = '';
for (;;) {
if ((count & 1) == 1) {
rpt += str;
}
count >>>= 1;
if (count == 0) {
break;
}
str += str;
}
// Could we try:
// return Array(count + 1).join(this);
return rpt;
}
}
'use strict';(function(e){function r(b){var a=b.charCodeAt(0),c=1114112,d=0,n=b.length|0,g="";switch(a>>>4){case 12:case 13:c=(a&31)<<6|b.charCodeAt(1)&63;d=128>c?0:2;break;case 14:c=(a&15)<<12|(b.charCodeAt(1)&63)<<6|b.charCodeAt(2)&63;d=2048>c?0:3;break;case 15:30===a>>>3&&(c=(a&7)<<18|(b.charCodeAt(1)&63)<<12|(b.charCodeAt(2)&63)<<6|b.charCodeAt(3),d=65536>c?0:4)}d&&(n<d?d=0:65536>c?g=f(c):1114112>c?(c=c-65664|0,g=f((c>>>10)+55296|0,(c&1023)+56320|0)):d=0);for(;d<n;d=d+1|0)g+="\ufffd";return g}
function p(){}function t(b){var a=b.charCodeAt(0)|0;if(55296<=a&&56319>=a)if(b=b.charCodeAt(1)|0,56320<=b&&57343>=b){if(a=(a<<10)+b-56613888|0,65535<a)return f(240|a>>>18,128|a>>>12&63,128|a>>>6&63,128|a&63)}else a=65533;return 2047>=a?f(192|a>>>6,128|a&63):f(224|a>>>12,128|a>>>6&63,128|a&63)}function q(){}var f=String.fromCharCode,l={}.toString,h=e.SharedArrayBuffer,u=h?l.call(h):"",k=e.Uint8Array,m=k||Array,v=l.call((k?ArrayBuffer:m).prototype);h=q.prototype;var w=e.TextEncoder;p.prototype.decode=
function(b){var a=b&&b.buffer||b,c=l.call(a);if(c!==v&&c!==u&&void 0!==b)throw TypeError("Failed to execute 'decode' on 'TextDecoder': The provided value is not of type '(ArrayBuffer or ArrayBufferView)'");b=k?new m(a):a;a="";c=0;for(var d=b.length|0;c<d;c=c+32768|0)a+=f.apply(0,b[k?"subarray":"slice"](c,c+32768|0));return a.replace(/[\xc0-\xff][\x80-\xbf]+|[\x80-\xff]/g,r)};e.TextDecoder||(e.TextDecoder=p);h.encode=function(b){b=void 0===b?"":(""+b).replace(/[\x80-\uD7ff\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]?/g,
t);for(var a=b.length|0,c=new m(a),d=0;d<a;d=d+1|0)c[d]=b.charCodeAt(d);return c};w||(e.TextEncoder=q)})(""+void 0==typeof global?""+void 0==typeof self?this:self:global);//AnonyCo

2
public/stl_viewer/load_stl.min.js vendored Normal file
View File

@ -0,0 +1,2 @@
//=========== Stl Viewer v1.13, by Omri Rips, Viewstl.com, July 2021 ; admin@viewstl.com ===========
importScripts("parser.min.js"),MSG_DATA=0,MSG_LOAD=1,MSG_ERROR=2,MSG_STL_LOADED=3,MSG_LOAD_IN_PROGRESS=4;var filename=null,local_file=null,load_from_blob_or_ab=null,x=0,y=0,z=0,model_id=-1,get_progress=!1,jszip_path="jszip.min.js";function isNumeric(a){return!isNaN(parseFloat(a))&&isFinite(a)}function send_error(a){postMessage({msg_type:MSG_ERROR,data:a})}function download_from_local(a){download_from_local_xhr(a)}function download_from_local_xhr(a){var e=new XMLHttpRequest;get_progress&&(e.onprogress=function(a){postMessage({msg_type:MSG_LOAD_IN_PROGRESS,id:model_id,loaded:a.loaded,total:a.total})}),e.onreadystatechange=function(a){4==e.readyState&&200==e.status&&after_file_load(e.response)},e.open("GET",a,!0),e.responseType="arraybuffer",e.send(null)}function after_file_load(a){if(a)try{parse_3d_file(filename,a,after_file_parse,jszip_path)}catch(a){send_error("Error parsing the file")}else send_error("no data")}function after_file_parse(a){"string"!=typeof a?postMessage({msg_type:MSG_STL_LOADED,vertices:a.vertices,faces:a.faces,colors:a.colors}):send_error(a)}function read_file(a){var e=new FileReader;e.onerror=function(a){var e="";switch(a.target.error.code){case a.target.error.NOT_FOUND_ERR:e="File not found";break;case a.target.error.NOT_READABLE_ERR:e="Can't read file - too large?";break;case a.target.error.ABORT_ERR:e="Read operation aborted";break;case a.target.error.SECURITY_ERR:e="File is locked";break;case a.target.error.ENCODING_ERR:e="File too large";break;default:e="Error reading file"}send_error(e)},e.onprogress=function(a){postMessage({msg_type:MSG_LOAD_IN_PROGRESS,id:model_id,loaded:a.loaded,total:a.total})},e.onload=function(a){after_file_load(a.target.result)},e.readAsArrayBuffer(a)}self.addEventListener("message",function(a){switch(a.data.msg_type){case MSG_DATA:if(!a.data.data){send_error("no data");break}if(!a.data.data.filename&&!a.data.data.local_file){send_error("no file");break}a.data.jszip_path&&(jszip_path=a.data.jszip_path),a.data.data.local_file?(filename=a.data.data.local_file.name?a.data.data.local_file.name:a.data.data.filename,local_file=a.data.data.local_file?a.data.data.local_file:null):a.data.data.filename&&(filename=a.data.data.filename),a.data.data.x&&isNumeric(a.data.data.x)&&(x=a.data.data.x),a.data.data.y&&isNumeric(a.data.data.y)&&(y=a.data.data.y),a.data.data.y&&isNumeric(a.data.data.z)&&(z=a.data.data.z),load_from_blob_or_ab=null,a.data.load_from_blob_or_ab&&(load_from_blob_or_ab=a.data.load_from_blob_or_ab),model_id=a.data.data.id?a.data.data.id:-1,get_progress=!!a.data.get_progress&&a.data.get_progress;break;case MSG_LOAD:load_from_blob_or_ab?load_from_blob_or_ab instanceof ArrayBuffer?after_file_load(load_from_blob_or_ab):read_file(load_from_blob_or_ab):local_file?read_file(local_file):filename&&download_from_local(filename);break;default:console.log("invalid msg: "+a.data.msg_type)}});

2
public/stl_viewer/parser.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
Stl Viewer v1.13, July 2021
===========================
Installation:
-------------
upload those files into your web server:
stl_viewer.min.js
parser.min.js
load_stl.min.js
webgl_detector.js
CanvasRenderer.js
OrbitControls.js
TrackballControls.js
Projector.js
three.min.js
ie_polyfills.js (optional)
Usage:
------
At the html body:
<script src="stl_viewer.min.js"></script>
<div id="stl_cont"></div>
<script>
var stl_viewer=new StlViewer(document.getElementById("stl_cont"), { models: [ {id:0, filename:"mystl.stl"} ] });
</script>
Documentation & License details:
--------------------------------
https://www.viewstl.com/plugin/
by Omri Rips, Viewstl.com

2
public/stl_viewer/stl_viewer.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
public/stl_viewer/three.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,54 @@
var webgl_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 );
}
};

View File

@ -0,0 +1,139 @@
<?php
namespace App\Controller;
use App\Core\Controller\Admin\Crud\CrudController;
use App\Core\Crud\CrudConfiguration;
use App\Core\Crud\Field;
use App\Core\Entity\EntityInterface;
use App\Core\Manager\EntityManager;
use App\Entity\StlMesh as Entity;
use App\Factory\StlMeshFactory as Factory;
use App\Form\StlMeshType as Type;
use App\Repository\StlMeshRepositoryQuery as RepositoryQuery;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route;
class StlMeshAdminController extends CrudController
{
/**
* @Route("/admin/stl_mesh/{page}", name="admin_stl_mesh_index", methods={"GET"}, requirements={"page":"\d+"})
*/
public function index(int $page = 1, RepositoryQuery $query, Request $request, Session $session): Response
{
return $this->doIndex($page, $query, $request, $session);
}
/**
* @Route("/admin/stl_mesh/new", name="admin_stl_mesh_new", methods={"GET", "POST"})
*/
public function new(Factory $factory, EntityManager $entityManager, Request $request): Response
{
return $this->doNew($factory->create(), $entityManager, $request);
}
/**
* @Route("/admin/stl_mesh/show/{entity}", name="admin_stl_mesh_show", methods={"GET"})
*/
public function show(Entity $entity): Response
{
return $this->doShow($entity);
}
/**
* @Route("/admin/stl_mesh/filter", name="admin_stl_mesh_filter", methods={"GET"})
*/
public function filter(Session $session): Response
{
return $this->doFilter($session);
}
/**
* @Route("/admin/stl_mesh/edit/{entity}", name="admin_stl_mesh_edit", methods={"GET", "POST"})
*/
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doEdit($entity, $entityManager, $request);
}
/**
* @Route("/admin/stl_mesh/sort/{page}", name="admin_stl_mesh_sort", methods={"POST"}, requirements={"page":"\d+"})
*/
public function sort(int $page = 1, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
{
return $this->doSort($page, $query, $entityManager, $request, $session);
}
/**
* @Route("/admin/stl_mesh/batch/{page}", name="admin_stl_mesh_batch", methods={"POST"}, requirements={"page":"\d+"})
*/
public function batch(int $page = 1, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
{
return $this->doBatch($page, $query, $entityManager, $request, $session);
}
/**
* @Route("/admin/stl_mesh/delete/{entity}", name="admin_stl_mesh_delete", methods={"DELETE"})
*/
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doDelete($entity, $entityManager, $request);
}
protected function getConfiguration(): CrudConfiguration
{
return CrudConfiguration::create()
->setPageTitle('index', 'Mesh')
->setPageTitle('edit', '{label}')
->setPageTitle('new', 'Nouveau modèle')
->setPageTitle('show', 'View of {id}')
->setPageRoute('index', 'admin_stl_mesh_index')
->setPageRoute('new', 'admin_stl_mesh_new')
->setPageRoute('edit', 'admin_stl_mesh_edit')
->setPageRoute('show', 'admin_stl_mesh_show')
->setPageRoute('sort', 'admin_stl_mesh_sort')
->setPageRoute('batch', 'admin_stl_mesh_batch')
->setPageRoute('delete', 'admin_stl_mesh_delete')
->setPageRoute('filter', 'admin_stl_mesh_filter')
->setForm('edit', Type::class, [])
->setForm('new', Type::class)
// ->setForm('filter', Type::class)
// ->setMaxPerPage('index', 20)
->setIsSortableCollection('index', true)
// ->setSortableCollectionProperty('sortOrder')
// ->setAction('index', 'new', true)
->setAction('index', 'show', false)
// ->setAction('index', 'edit', true)
// ->setAction('index', 'delete', true)
// ->setAction('edit', 'back', true)
->setAction('edit', 'show', false)
// ->setAction('edit', 'delete', true)
// ->setAction('show', 'back', true)
// ->setAction('show', 'edit', true)
->setField('index', 'Label', Field\TextField::class, [
'property' => 'label',
'sort' => ['label', '.label'],
'attr' => ['class' => 'col-md-12'],
])
->setBatchAction('index', 'delete', 'Delete', function(EntityInterface $entity, EntityManager $manager) {
$manager->delete($entity);
})
;
}
protected function getSection(): string
{
return 'stl_mesh';
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Controller;
use App\Api\TTRssClient;
use App\Core\Controller\Site\PageController;
use App\Markdown\Parser\Post as PostParser;
use Symfony\Component\HttpFoundation\Response;
use App\Repository\StlMeshRepositoryQuery;
use App\Entity\StlMesh;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Annotation\Route;
class StlMeshController extends PageController
{
public function meshes(StlMeshRepositoryQuery $query): Response
{
$pager = $query->create()
->orderBy('.sortOrder')
->paginate(1, 200);
return $this->defaultRender($this->siteRequest->getPage()->getTemplate(), [
'pager' => $pager,
]);
}
/**
* @Route("/mesh/download/{stlMesh}", name="mesh_download")
*/
public function download(StlMesh $stlMesh): Response
{
$response = new BinaryFileResponse($stlMesh->getFile());
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
str_replace('.bin', '.stl', basename($stlMesh->getFile()))
);
return $response;
}
/**
* @Route("/mesh/viewer/{stlMesh}", name="mesh_viewer")
*/
public function viewer(StlMesh $stlMesh): Response
{
return $this->render('page/mesh/viewer.html.twig', [
'mesh' => $stlMesh,
]);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Entity\Page;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class MeshPage extends TitledPage
{
}

110
src/Entity/StlMesh.php Normal file
View File

@ -0,0 +1,110 @@
<?php
namespace App\Entity;
use App\Repository\StlMeshRepository;
use Doctrine\ORM\Mapping as ORM;
use App\Core\Entity\EntityInterface;
/**
* @ORM\Entity(repositoryClass=StlMeshRepository::class)
*/
class StlMesh implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $sortOrder;
/**
* @ORM\Column(type="string", length=255)
*/
private $label;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $description;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $file;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $preview;
public function getId(): ?int
{
return $this->id;
}
public function getSortOrder(): ?int
{
return $this->sortOrder;
}
public function setSortOrder(?int $sortOrder): self
{
$this->sortOrder = $sortOrder;
return $this;
}
public function getLabel(): ?string
{
return $this->label;
}
public function setLabel(string $label): self
{
$this->label = $label;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
public function getFile(): ?string
{
return $this->file;
}
public function setFile(?string $file): self
{
$this->file = $file;
return $this;
}
public function getPreview(): ?string
{
return $this->preview;
}
public function setPreview(?string $preview): self
{
$this->preview = $preview;
return $this;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Factory;
use App\Core\Factory\FactoryInterface;
use App\Entity\StlMesh as Entity;
class StlMeshFactory implements FactoryInterface
{
public function create(): Entity
{
return new Entity();
}
}

81
src/Form/StlMeshType.php Normal file
View File

@ -0,0 +1,81 @@
<?php
namespace App\Form;
use App\Entity\StlMesh;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use App\Core\Form\FileManager\FilePickerType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
class StlMeshType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'label',
TextType::class,
[
'label' => 'Libellé',
'required' => true,
'attr' => [
],
'constraints' => [
new NotBlank(),
],
]
);
$builder->add(
'description',
TextareaType::class,
[
'label' => 'Description',
'required' => false,
'attr' => [
'data-simplemde' => '',
'rows' => 5,
],
'constraints' => [
],
]
);
$builder->add(
'file',
FilePickerType::class,
[
'label' => 'Fichier STL',
'required' => false,
'attr' => [
],
'constraints' => [
],
]
);
$builder->add(
'preview',
FilePickerType::class,
[
'label' => 'Image d\'aperçu',
'required' => false,
'attr' => [
],
'constraints' => [
],
]
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => StlMesh::class,
]);
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Repository;
use App\Entity\StlMesh;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method StlMesh|null find($id, $lockMode = null, $lockVersion = null)
* @method StlMesh|null findOneBy(array $criteria, array $orderBy = null)
* @method StlMesh[] findAll()
* @method StlMesh[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class StlMeshRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, StlMesh::class);
}
// /**
// * @return StlMesh[] Returns an array of StlMesh objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('s')
->andWhere('s.exampleField = :val')
->setParameter('val', $value)
->orderBy('s.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?StlMesh
{
return $this->createQueryBuilder('s')
->andWhere('s.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Repository;
use App\Core\Repository\RepositoryQuery;
use Knp\Component\Pager\PaginatorInterface;
use App\Repository\StlMeshRepository as Repository;
class StlMeshRepositoryQuery extends RepositoryQuery
{
public function __construct(Repository $repository, PaginatorInterface $paginator)
{
parent::__construct($repository, 's', $paginator);
}
}

View File

@ -29,3 +29,17 @@
</a>
</li>
</ul>
<h6 class="sidebar-heading justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Impression 3d</span>
</h6>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link {{ macros_menu.active_class('stl_mesh', section) }}" href="{{ path('admin_stl_mesh_index') }}">
<span class="fa fa-pen"></span>
<span class="nav-item-label">Mesh</span>
</a>
</li>
</ul>

View File

@ -22,7 +22,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
<title>Blog - {% block meta_title %}{{ _page is defined ? _page.title.value }}{% endblock %}</title>
{% block metas %}
<meta name="description" content="{% block meta_description %}{{ _page is defined and _page ? _page.metaTitle }}{% endblock %}">
<meta name="description" content="{% block meta_description %}{{ _page is defined and _page ? _page.metaDescription }}{% endblock %}">
<link rel="openid.server" href="https://id.deblan.org/" />
<link rel="openid.delegate" href="https://www.deblan.io/" />
<link rel="openid2.provider" href="https://id.deblan.org/" />
@ -37,7 +37,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
{% if _page.ogImage %}
<meta property="og:image" content="{{ asset(_page.ogImage) }}" />
<meta property="og:image:secure_url" content="{{ image }}" />
<meta property="og:image:secure_url" content="{{ asset(_page.ogImage) }}" />
{% endif %}
{% endif %}
{% endblock %}
@ -119,6 +119,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
{% endblock %}
{{- setting('stats_umami_tag')|raw -}}
<div class="modal-backdrop"></div
</body>
</html>
{% endapply %}

View File

@ -42,11 +42,11 @@
</p>
{% if post.contentFormat == 'html' %}
{{- post.content|murph_url|post -}}
{{- post.content|murph_url|file_attributes|post -}}
{% endif %}
{% if post.contentFormat == 'markdown' %}
{{- post.content|murph_url|markdown('post') -}}
{{- post.content|murph_url|file_attributes|markdown('post') -}}
{% endif %}
{% if not full %}

View File

@ -34,17 +34,3 @@
</div>
{% endif %}
{% endblock %}
{% block metas %}
{#
{{- parent() -}}
{% if page.pager.hasPreviousPage %}
<link rel="prev" href="{{ cms_path('posts', {page: page.pager.getPreviousPage}, true) }}" />
{% endif %}
{% if page.pager.hasNextPage %}
<link rel="next" href="{{ cms_path('posts', {page: page.pager.getNextPage}, true) }}" />
{% endif %}
#}
{% endblock %}

View File

@ -0,0 +1,77 @@
{% extends 'base.html.twig' %}
{%- block meta_title -%}
{{- _page.title.value -}}
{% endblock %}
{%- block page_title -%}
{{- _page.title.value -}}
{% endblock %}
{% block page_subtitle %}
<p class="h3">
{{- _page.subTitle.value -}}
</p>
{% endblock %}
{% block body %}
<div class="row">
{% for mesh in pager %}
<div class="col-3">
<div class="mesh">
<div class="mesh-preview">
<a data-modal href="{{ path('mesh_viewer', {stlMesh: mesh.id}) }}">
<img src="{{ asset(mesh.preview)|imagine_filter('mesh_preview_filter') }}" alt="{{ mesh.label }}">
</a>
</div>
<h2 class="mesh-title">{{ mesh.label }}</h2>
<div class="mesh-description">
{{ mesh.description|murph_url|file_attributes|markdown('post') }}
<ul class="list--inline">
<li>
<a class="button small mesh-viewer" data-modal href="{{ path('mesh_viewer', {stlMesh: mesh.id}) }}">
Visualiser en 3D
</a>
</li>
<li>
<a class="button small" target="_blank" href="{{ path('mesh_download', {stlMesh: mesh.id}) }}">
Télécharger
</a>
</li>
</ul>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="modal" id="mesh-viewer">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<span class="close"></span>
</div>
<div class="modal-body">
</div>
</div>
</div>
</div>
{% if pager.getPaginationData.pageCount > 1 %}
<div class="col-12">
<div class="body">
<div class="pager align-right">
{% block pager %}
{{ include('module/_pager.html.twig', {
route: _node.routeName,
routeParams: {},
pages: pager.paginationData.endPage,
currentPage: pager.paginationData.current
}) }}
{% endblock %}
</div>
</div>
</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ mesh.label }}</title>
{{ encore_entry_link_tags('viewer') }}
</head>
<body>
<div id="mesh-viewer" data-file="{{ path('mesh_download', {stlMesh: mesh.id}) }}"></div>
<script src="{{ asset('stl_viewer/stl_viewer.min.js') }}"></script>
{{ encore_entry_script_tags('viewer') }}
</body>
</html>

View File

@ -22,6 +22,7 @@ Encore
*/
.addEntry('admin', './assets/js/admin.js')
.addEntry('app', './assets/js/app.js')
.addEntry('viewer', './assets/js/viewer.js')
// When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
// .splitEntryChunks()