working line endpt drag
|
@ -0,0 +1 @@
|
||||||
|
node_modules/
|
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 26 KiB |
|
@ -0,0 +1,70 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="favicon.ico" />
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
|
||||||
|
<meta property="og:title" content="CAD Tool" />
|
||||||
|
<meta property="og:description" content="Three.js CAD tool" />
|
||||||
|
<meta property="og:url" content="" />
|
||||||
|
<meta property="og:image" content="" />
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" href="icon-192.png" />
|
||||||
|
<link rel="manifest" href="manifest.json" />
|
||||||
|
<title>CAD Tool</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#c {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.split {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#view1 {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#view2 {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 30%;
|
||||||
|
height: 30%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<canvas id="c"></canvas>
|
||||||
|
<div class="split">
|
||||||
|
<div id="view1"></div>
|
||||||
|
<div id="view2"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="bundle.js" type="module"></script>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"short_name": "CAD Tool",
|
||||||
|
"name": "CAD Tool using Three.js",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icon-192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"purpose": "maskable any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icon-512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "maskable any"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
svg=icon
|
||||||
|
|
||||||
|
size=(16 24 32 64)
|
||||||
|
out=""
|
||||||
|
for i in ${size[@]}; do
|
||||||
|
inkscape --export-filename="./$svg-$i.png" $svg.svg -w $i -h $i
|
||||||
|
out+="$svg-$i.png "
|
||||||
|
done
|
||||||
|
|
||||||
|
size=(192 512)
|
||||||
|
for i in ${size[@]}; do
|
||||||
|
inkscape --export-filename="./$svg-$i.png" $svg.svg -w $i -h $i
|
||||||
|
done
|
||||||
|
|
||||||
|
convert $out favicon.ico
|
||||||
|
|
||||||
|
mv favicon.ico icon-192.png icon-512.png ../dist
|
After Width: | Height: | Size: 761 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.1 KiB |
|
@ -0,0 +1,103 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
enable-background="new 0 0 24 24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="black"
|
||||||
|
width="24px"
|
||||||
|
height="24px"
|
||||||
|
version="1.1"
|
||||||
|
id="svg26"
|
||||||
|
sodipodi:docname="icon.svg"
|
||||||
|
inkscape:version="1.0.2 (1.0.2+r75+1)">
|
||||||
|
<metadata
|
||||||
|
id="metadata32">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs
|
||||||
|
id="defs30" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1623"
|
||||||
|
inkscape:window-height="1236"
|
||||||
|
id="namedview28"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="21.184428"
|
||||||
|
inkscape:cx="16.030816"
|
||||||
|
inkscape:cy="15.296979"
|
||||||
|
inkscape:window-x="426"
|
||||||
|
inkscape:window-y="92"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg26"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:guide-bbox="true">
|
||||||
|
<sodipodi:guide
|
||||||
|
position="-3.6819497,21.6"
|
||||||
|
orientation="0,1"
|
||||||
|
id="guide842"
|
||||||
|
inkscape:label=""
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:color="rgb(0,0,255)" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="-1.0857031,2.4"
|
||||||
|
orientation="0,1"
|
||||||
|
id="guide846"
|
||||||
|
inkscape:label=""
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:color="rgb(0,0,255)" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<circle
|
||||||
|
style="fill:#a6a6a6;stroke-width:0.05;stroke-linecap:round"
|
||||||
|
id="path927"
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="12" />
|
||||||
|
<g
|
||||||
|
id="g875"
|
||||||
|
transform="matrix(1.2,0,0,1.2,-2.4,-2.4)">
|
||||||
|
<g
|
||||||
|
id="g868"
|
||||||
|
style="fill:#5ccff0;fill-opacity:1">
|
||||||
|
<polygon
|
||||||
|
opacity=".3"
|
||||||
|
points="13,17.17 17,14.87 17,10.24 13,12.57 "
|
||||||
|
id="polygon6"
|
||||||
|
style="opacity:1;fill:#5ccff0;fill-opacity:1" />
|
||||||
|
<polygon
|
||||||
|
opacity=".3"
|
||||||
|
points="12,6.25 8.04,8.53 12,10.84 15.96,8.53 "
|
||||||
|
id="polygon8"
|
||||||
|
style="opacity:1;fill:#5ccff0;fill-opacity:1" />
|
||||||
|
<polygon
|
||||||
|
opacity=".3"
|
||||||
|
points="7,14.87 11,17.17 11,12.57 7,10.24 "
|
||||||
|
id="polygon10"
|
||||||
|
style="opacity:1;fill:#5ccff0;fill-opacity:1" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
d="M 19,14.87 V 9.13 C 19,8.41 18.62,7.75 18,7.4 L 13,4.52 C 12.69,4.34 12.35,4.25 12,4.25 c -0.35,0 -0.69,0.09 -1,0.27 L 6,7.39 C 5.38,7.75 5,8.41 5,9.13 v 5.74 c 0,0.72 0.38,1.38 1,1.73 l 5,2.88 c 0.31,0.18 0.65,0.27 1,0.27 0.35,0 0.69,-0.09 1,-0.27 l 5,-2.88 c 0.62,-0.35 1,-1.01 1,-1.73 z m -8,2.3 -4,-2.3 v -4.63 l 4,2.33 z M 12,10.84 8.04,8.53 12,6.25 15.96,8.53 Z m 5,4.03 -4,2.3 v -4.6 l 4,-2.33 z"
|
||||||
|
id="path20" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"start": "webpack --watch --config webpack.dev.js",
|
||||||
|
"build": "webpack --config webpack.prod.js",
|
||||||
|
"get-bundle-size": "webpack --profile --json > stats.json --config webpack.prod.js && webpack-bundle-analyzer stats.json dist/"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"css-loader": "^5.1.3",
|
||||||
|
"dat.gui": "^0.7.7",
|
||||||
|
"redux": "^4.0.5",
|
||||||
|
"sass": "^1.32.8",
|
||||||
|
"sass-loader": "^11.0.1",
|
||||||
|
"stats-js": "^1.0.1",
|
||||||
|
"style-loader": "^2.0.0",
|
||||||
|
"three": "^0.126.1",
|
||||||
|
"webpack": "^5.26.3",
|
||||||
|
"webpack-bundle-analyzer": "^4.4.0",
|
||||||
|
"webpack-cli": "^4.5.0",
|
||||||
|
"webpack-merge": "^5.7.3"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,266 @@
|
||||||
|
|
||||||
|
import { Matrix4 } from 'three';
|
||||||
|
import * as THREE from '../node_modules/three/src/Three'
|
||||||
|
|
||||||
|
export class Sketcher extends THREE.Group {
|
||||||
|
constructor(camera, domElement, plane) {
|
||||||
|
super()
|
||||||
|
this.camera = camera;
|
||||||
|
this.domElement = domElement;
|
||||||
|
this.scene = scene;
|
||||||
|
this.plane = plane;
|
||||||
|
this.matrixAutoUpdate = false;
|
||||||
|
this.sketchNormal = new THREE.Vector3(0, 0, 1)
|
||||||
|
this.orientSketcher(plane)
|
||||||
|
|
||||||
|
|
||||||
|
this.add(new THREE.PlaneHelper(this.plane, 1, 0xffff00));
|
||||||
|
|
||||||
|
|
||||||
|
this.linesGroup = new THREE.Group()
|
||||||
|
this.linesArr = this.linesGroup.children
|
||||||
|
this.pointsGroup = new THREE.Group()
|
||||||
|
this.ptsArr = this.pointsGroup.children
|
||||||
|
this.add(this.linesGroup)
|
||||||
|
this.add(this.pointsGroup)
|
||||||
|
|
||||||
|
window.lg = this.linesArr
|
||||||
|
window.pg = this.ptsArr
|
||||||
|
|
||||||
|
this.pickThreshold = 100
|
||||||
|
this.grabbedObject = null
|
||||||
|
|
||||||
|
this.lineMaterial = new THREE.LineBasicMaterial({
|
||||||
|
color: 0x555,
|
||||||
|
})
|
||||||
|
this.pointMaterial = new THREE.PointsMaterial({
|
||||||
|
color: 0xAAA,
|
||||||
|
size: 3,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.pointStart = this.pointStart.bind(this);
|
||||||
|
this.pointEnd = this.pointEnd.bind(this);
|
||||||
|
this.move = this.move.bind(this);
|
||||||
|
this.keyHandler = this.keyHandler.bind(this);
|
||||||
|
this.picker = this.picker.bind(this);
|
||||||
|
this.grabbedMove = this.grabbedMove.bind(this);
|
||||||
|
this.grabEnd = this.grabEnd.bind(this);
|
||||||
|
this.raycaster = new THREE.Raycaster();
|
||||||
|
|
||||||
|
|
||||||
|
window.addEventListener('keydown', this.keyHandler)
|
||||||
|
domElement.addEventListener('pointerdown', this.picker)
|
||||||
|
|
||||||
|
|
||||||
|
this.mode = ""
|
||||||
|
this.keyTable = {
|
||||||
|
'l': this.addLine,
|
||||||
|
'Escape': this.clear
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
orientSketcher() {
|
||||||
|
|
||||||
|
const theta = this.sketchNormal.angleTo(this.plane.normal)
|
||||||
|
const axis = this.sketchNormal.clone().cross(this.plane.normal).normalize()
|
||||||
|
const rot = new THREE.Matrix4().makeRotationAxis(axis, theta)
|
||||||
|
const trans = new THREE.Matrix4().makeTranslation(0, 0, this.plane.constant)
|
||||||
|
|
||||||
|
this.matrix = rot.multiply(trans) // world matrix will auto update in next render
|
||||||
|
this.inverse = this.matrix.clone().invert()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
keyHandler(e) {
|
||||||
|
switch (e.key) {
|
||||||
|
case 'Escape':
|
||||||
|
this.clear()
|
||||||
|
this.mode = ""
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
this.addLine()
|
||||||
|
this.mode = "line"
|
||||||
|
break;
|
||||||
|
case '=':
|
||||||
|
this.plane.applyMatrix4(new Matrix4().makeRotationY(0.1))
|
||||||
|
this.orientSketcher()
|
||||||
|
|
||||||
|
this.dispatchEvent({ type: 'change' })
|
||||||
|
break;
|
||||||
|
case '-':
|
||||||
|
this.plane.applyMatrix4(new Matrix4().makeRotationY(-0.1))
|
||||||
|
this.orientSketcher()
|
||||||
|
|
||||||
|
this.dispatchEvent({ type: 'change' })
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
picker(e) {
|
||||||
|
if (this.mode || e.buttons != 1) return
|
||||||
|
this.raycaster.setFromCamera(
|
||||||
|
new THREE.Vector2(
|
||||||
|
(e.clientX / window.innerWidth) * 2 - 1,
|
||||||
|
- (e.clientY / window.innerHeight) * 2 + 1
|
||||||
|
),
|
||||||
|
this.camera
|
||||||
|
);
|
||||||
|
|
||||||
|
// console.log(this.ptsArr)
|
||||||
|
const candidates = this.raycaster.intersectObjects(this.ptsArr)
|
||||||
|
// console.log(candidates)
|
||||||
|
|
||||||
|
|
||||||
|
if (!candidates.length) return;
|
||||||
|
|
||||||
|
let minDist = candidates[0].distanceToRay
|
||||||
|
let idx = 0
|
||||||
|
|
||||||
|
for (let i = 1; i < candidates.length; i++) {
|
||||||
|
if (candidates.distanceToRay < minDist) {
|
||||||
|
minDist = candidates.distanceToRay
|
||||||
|
idx = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minDist < this.pickThreshold) {
|
||||||
|
this.grabPtIdx = this.ptsArr.indexOf(
|
||||||
|
candidates[idx].object
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.domElement.addEventListener('pointermove', this.grabbedMove);
|
||||||
|
this.domElement.addEventListener('pointerup', this.grabEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
grabbedMove(e) {
|
||||||
|
const mouseLoc = this.getLocation(e);
|
||||||
|
|
||||||
|
this.moveLinePt(this.grabPtIdx,mouseLoc)
|
||||||
|
|
||||||
|
this.dispatchEvent({ type: 'change' })
|
||||||
|
}
|
||||||
|
|
||||||
|
moveLinePt(ptIdx, absPos) {
|
||||||
|
this.ptsArr[ptIdx].geometry.attributes.position.set(absPos);
|
||||||
|
this.ptsArr[ptIdx].geometry.attributes.position.needsUpdate = true;
|
||||||
|
|
||||||
|
const lineIdx = Math.floor(ptIdx / 2)
|
||||||
|
const endPtIdx = (ptIdx % 2) * 3
|
||||||
|
this.linesArr[lineIdx].geometry.attributes.position.set(absPos, endPtIdx)
|
||||||
|
this.linesArr[lineIdx].geometry.attributes.position.needsUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
grabEnd() {
|
||||||
|
this.domElement.removeEventListener('pointermove', this.grabbedMove)
|
||||||
|
this.domElement.removeEventListener('pointerup', this.grabEnd)
|
||||||
|
this.ptsArr[this.grabPtIdx].geometry.computeBoundingSphere()
|
||||||
|
// this.grabbedObject = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
addLine() {
|
||||||
|
this.domElement.addEventListener('pointerdown', this.pointStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
if (this.mode == "") return
|
||||||
|
|
||||||
|
this.domElement.removeEventListener('pointerdown', this.pointStart)
|
||||||
|
this.domElement.removeEventListener('pointermove', this.move);
|
||||||
|
this.domElement.removeEventListener('pointerdown', this.pointEnd);
|
||||||
|
this.domElement.removeEventListener('pointerdown', this.pointEnd);
|
||||||
|
|
||||||
|
const lastLine = this.linesArr[this.linesArr.length - 1]
|
||||||
|
this.linesGroup.remove(lastLine)
|
||||||
|
lastLine.geometry.dispose()
|
||||||
|
|
||||||
|
const lastPoints = this.ptsArr.slice(this.ptsArr.length - 2)
|
||||||
|
this.pointsGroup.remove(...lastPoints)
|
||||||
|
lastPoints.forEach(obj => obj.geometry.dispose())
|
||||||
|
|
||||||
|
this.dispatchEvent({ type: 'change' })
|
||||||
|
}
|
||||||
|
|
||||||
|
getLocation(e) {
|
||||||
|
this.raycaster.setFromCamera(
|
||||||
|
new THREE.Vector2(
|
||||||
|
(e.clientX / window.innerWidth) * 2 - 1,
|
||||||
|
- (e.clientY / window.innerHeight) * 2 + 1
|
||||||
|
),
|
||||||
|
this.camera
|
||||||
|
);
|
||||||
|
// return this.worldToLocal(this.raycaster.ray.intersectPlane(this.plane)).toArray()
|
||||||
|
return this.raycaster.ray.intersectPlane(this.plane).applyMatrix4(this.inverse).toArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
pointStart(e) {
|
||||||
|
if (e.buttons !== 1) return
|
||||||
|
const mouseLoc = this.getLocation(e);
|
||||||
|
|
||||||
|
this.lineGeom = new THREE.BufferGeometry()
|
||||||
|
this.lineGeom.setAttribute('position',
|
||||||
|
new THREE.BufferAttribute(
|
||||||
|
new Float32Array(6), 3
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.lineGeom.attributes.position.set(mouseLoc)
|
||||||
|
this.line = new THREE.LineSegments(this.lineGeom, this.lineMaterial);
|
||||||
|
this.line.frustumCulled = false;
|
||||||
|
this.linesGroup.add(this.line)
|
||||||
|
|
||||||
|
this.p1Geom = new THREE.BufferGeometry()
|
||||||
|
this.p1Geom.setAttribute('position',
|
||||||
|
new THREE.BufferAttribute(
|
||||||
|
new Float32Array(3), 3
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.p1Geom.attributes.position.set(mouseLoc)
|
||||||
|
this.p1 = new THREE.Points(this.p1Geom, this.pointMaterial);
|
||||||
|
this.pointsGroup.add(this.p1)
|
||||||
|
|
||||||
|
this.p2Geom = new THREE.BufferGeometry()
|
||||||
|
this.p2Geom.setAttribute('position',
|
||||||
|
new THREE.BufferAttribute(
|
||||||
|
new Float32Array(3), 3
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.p2 = new THREE.Points(this.p2Geom, this.pointMaterial);
|
||||||
|
this.pointsGroup.add(this.p2)
|
||||||
|
|
||||||
|
this.domElement.removeEventListener('pointerdown', this.pointStart)
|
||||||
|
this.domElement.addEventListener('pointermove', this.move)
|
||||||
|
this.domElement.addEventListener('pointerdown', this.pointEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
move(e) {
|
||||||
|
const mouseLoc = this.getLocation(e);
|
||||||
|
this.lineGeom.attributes.position.set(mouseLoc, 3)
|
||||||
|
this.lineGeom.attributes.position.needsUpdate = true;
|
||||||
|
this.p2Geom.attributes.position.set(mouseLoc);
|
||||||
|
this.p2Geom.attributes.position.needsUpdate = true;
|
||||||
|
this.p2Geom.computeBoundingSphere();
|
||||||
|
this.dispatchEvent({ type: 'change' })
|
||||||
|
}
|
||||||
|
|
||||||
|
pointEnd(e) {
|
||||||
|
if (e.buttons !== 1) return;
|
||||||
|
this.domElement.removeEventListener('pointermove', this.move);
|
||||||
|
this.domElement.removeEventListener('pointerdown', this.pointEnd);
|
||||||
|
|
||||||
|
|
||||||
|
this.pointStart(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
// class MinMaxGUIHelper {
|
||||||
|
// constructor(obj, minProp, maxProp, minDif) {
|
||||||
|
// this.obj = obj;
|
||||||
|
// this.minProp = minProp;
|
||||||
|
// this.maxProp = maxProp;
|
||||||
|
// this.minDif = minDif;
|
||||||
|
// }
|
||||||
|
// get min() {
|
||||||
|
// return this.obj[this.minProp];
|
||||||
|
// }
|
||||||
|
// set min(v) {
|
||||||
|
// this.obj[this.minProp] = v;
|
||||||
|
// this.obj[this.maxProp] = Math.max(this.obj[this.maxProp], v + this.minDif);
|
||||||
|
// }
|
||||||
|
// get max() {
|
||||||
|
// return this.obj[this.maxProp];
|
||||||
|
// }
|
||||||
|
// set max(v) {
|
||||||
|
// this.obj[this.maxProp] = v;
|
||||||
|
// this.min = this.min; // this will call the min setter
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const gui = new GUI();
|
||||||
|
// gui.add(camera, 'zoom', 0.01, 1, 0.01).listen();
|
||||||
|
// const minMaxGUIHelper = new MinMaxGUIHelper(camera, 'near', 'far', 0.1);
|
||||||
|
// gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near');
|
||||||
|
// gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far');
|
||||||
|
|
||||||
|
import * as THREE from '../node_modules/three/src/Three';
|
||||||
|
import { OrbitControls } from './OrbitControls'
|
||||||
|
import { TrackballControls } from './trackball'
|
||||||
|
import { Sketcher } from './Sketcher'
|
||||||
|
import GUI from '../node_modules/dat.gui/src/dat/gui/GUI.js'
|
||||||
|
import Stats from '../node_modules/three/examples/jsm/libs/stats.module.js';
|
||||||
|
|
||||||
|
import { KeyboardController } from './utils'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
|
||||||
|
const Controller = new KeyboardController()
|
||||||
|
|
||||||
|
var stats = new Stats();
|
||||||
|
stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
|
||||||
|
document.body.appendChild(stats.dom);
|
||||||
|
|
||||||
|
const canvas = document.querySelector('#c');
|
||||||
|
const view1Elem = document.querySelector('#view1');
|
||||||
|
const view2Elem = document.querySelector('#view2');
|
||||||
|
const renderer = new THREE.WebGLRenderer({ canvas });
|
||||||
|
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
window.scene = scene;
|
||||||
|
scene.background = new THREE.Color('pink');
|
||||||
|
|
||||||
|
const helpersGroup = new THREE.Group();
|
||||||
|
scene.add(helpersGroup);
|
||||||
|
|
||||||
|
|
||||||
|
const size = 1;
|
||||||
|
const near = 5;
|
||||||
|
const far = 50;
|
||||||
|
const camera = new THREE.OrthographicCamera(-size, size, size, -size, near, far);
|
||||||
|
camera.zoom = 0.1;
|
||||||
|
camera.position.set(0, 0, 20);
|
||||||
|
const controls = new OrbitControls(camera, view1Elem);
|
||||||
|
// const controls = new TrackballControls(camera, view1Elem);
|
||||||
|
controls.target.set(0, 0, 0);
|
||||||
|
controls.update()
|
||||||
|
const cameraHelper = new THREE.CameraHelper(camera);
|
||||||
|
helpersGroup.add(cameraHelper);
|
||||||
|
|
||||||
|
|
||||||
|
const camera2 = new THREE.PerspectiveCamera(
|
||||||
|
60, // fov
|
||||||
|
2, // aspect
|
||||||
|
0.1, // near
|
||||||
|
500, // far
|
||||||
|
);
|
||||||
|
camera2.position.set(16, 28, 40);
|
||||||
|
camera2.lookAt(0, 5, 0);
|
||||||
|
const controls2 = new OrbitControls(camera2, view2Elem);
|
||||||
|
controls2.target.set(0, 5, 0);
|
||||||
|
controls2.update();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const axesHelper = new THREE.AxesHelper(5);
|
||||||
|
helpersGroup.add(axesHelper);
|
||||||
|
|
||||||
|
const sketchPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
|
||||||
|
const sketcher = new Sketcher(camera, view1Elem, sketchPlane)
|
||||||
|
scene.add(sketcher)
|
||||||
|
|
||||||
|
{
|
||||||
|
const color = 0xFFFFFF;
|
||||||
|
const intensity = 1;
|
||||||
|
const light = new THREE.DirectionalLight(color, intensity);
|
||||||
|
light.position.set(0, 10, 0);
|
||||||
|
light.target.position.set(-5, 0, 0);
|
||||||
|
scene.add(light);
|
||||||
|
scene.add(light.target);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function resizeRendererToDisplaySize(renderer) {
|
||||||
|
const canvas = renderer.domElement;
|
||||||
|
const width = canvas.clientWidth;
|
||||||
|
const height = canvas.clientHeight;
|
||||||
|
const needResize = canvas.width !== width || canvas.height !== height;
|
||||||
|
if (needResize) {
|
||||||
|
renderer.setSize(width, height, false);
|
||||||
|
}
|
||||||
|
return needResize;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setScissorForElement(elem) {
|
||||||
|
const canvasRect = canvas.getBoundingClientRect();
|
||||||
|
const elemRect = elem.getBoundingClientRect();
|
||||||
|
|
||||||
|
// compute a canvas relative rectangle
|
||||||
|
const right = Math.min(elemRect.right, canvasRect.right) - canvasRect.left;
|
||||||
|
const left = Math.max(0, elemRect.left - canvasRect.left);
|
||||||
|
const bottom = Math.min(elemRect.bottom, canvasRect.bottom) - canvasRect.top;
|
||||||
|
const top = Math.max(0, elemRect.top - canvasRect.top);
|
||||||
|
|
||||||
|
const width = Math.min(canvasRect.width, right - left);
|
||||||
|
const height = Math.min(canvasRect.height, bottom - top);
|
||||||
|
|
||||||
|
// setup the scissor to only render to that part of the canvas
|
||||||
|
const positiveYUpBottom = canvasRect.height - bottom;
|
||||||
|
renderer.setScissor(left, positiveYUpBottom, width, height);
|
||||||
|
renderer.setViewport(left, positiveYUpBottom, width, height);
|
||||||
|
|
||||||
|
// return the aspect
|
||||||
|
return width / height;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
stats.begin();
|
||||||
|
resizeRendererToDisplaySize(renderer);
|
||||||
|
|
||||||
|
renderer.setScissorTest(true);
|
||||||
|
{
|
||||||
|
const aspect = setScissorForElement(view1Elem);
|
||||||
|
camera.left = -aspect;
|
||||||
|
camera.right = aspect;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
cameraHelper.update();
|
||||||
|
cameraHelper.visible = false;
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const aspect = setScissorForElement(view2Elem);
|
||||||
|
camera2.aspect = aspect;
|
||||||
|
camera2.updateProjectionMatrix();
|
||||||
|
cameraHelper.visible = true;
|
||||||
|
renderer.render(scene, camera2);
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.end();
|
||||||
|
|
||||||
|
// requestAnimationFrame(render);
|
||||||
|
}
|
||||||
|
// requestAnimationFrame(render);
|
||||||
|
|
||||||
|
|
||||||
|
controls.addEventListener('change', render);
|
||||||
|
controls.addEventListener('start', render);
|
||||||
|
controls2.addEventListener('change', render);
|
||||||
|
sketcher.addEventListener('change', render);
|
||||||
|
window.addEventListener('resize', render);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
|
@ -0,0 +1,754 @@
|
||||||
|
import {
|
||||||
|
EventDispatcher,
|
||||||
|
MOUSE,
|
||||||
|
Quaternion,
|
||||||
|
Vector2,
|
||||||
|
Vector3
|
||||||
|
} from '../node_modules/three/src/Three';
|
||||||
|
|
||||||
|
var TrackballControls = function ( object, domElement ) {
|
||||||
|
|
||||||
|
if ( domElement === undefined ) console.warn( 'THREE.TrackballControls: The second parameter "domElement" is now mandatory.' );
|
||||||
|
if ( domElement === document ) console.error( 'THREE.TrackballControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
|
||||||
|
|
||||||
|
var scope = this;
|
||||||
|
var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
|
||||||
|
|
||||||
|
this.object = object;
|
||||||
|
this.domElement = domElement;
|
||||||
|
|
||||||
|
// API
|
||||||
|
|
||||||
|
this.enabled = true;
|
||||||
|
|
||||||
|
this.screen = { left: 0, top: 0, width: 0, height: 0 };
|
||||||
|
|
||||||
|
this.rotateSpeed = 3.0;
|
||||||
|
this.zoomSpeed = 1.2;
|
||||||
|
this.panSpeed = 89.5;
|
||||||
|
|
||||||
|
this.noRotate = false;
|
||||||
|
this.noZoom = false;
|
||||||
|
this.noPan = false;
|
||||||
|
|
||||||
|
this.staticMoving = true;
|
||||||
|
this.dynamicDampingFactor = 0.2;
|
||||||
|
|
||||||
|
this.minDistance = 0;
|
||||||
|
this.maxDistance = Infinity;
|
||||||
|
|
||||||
|
this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
|
||||||
|
|
||||||
|
this.mouseButtons = { LEFT: null, MIDDLE: MOUSE.ROTATE, RIGHT: MOUSE.PAN };
|
||||||
|
|
||||||
|
// internals
|
||||||
|
|
||||||
|
this.target = new Vector3();
|
||||||
|
|
||||||
|
var EPS = 0.000001;
|
||||||
|
|
||||||
|
var lastPosition = new Vector3();
|
||||||
|
var lastZoom = 1;
|
||||||
|
|
||||||
|
var _state = STATE.NONE,
|
||||||
|
_keyState = STATE.NONE,
|
||||||
|
|
||||||
|
_eye = new Vector3(),
|
||||||
|
|
||||||
|
_movePrev = new Vector2(),
|
||||||
|
_moveCurr = new Vector2(),
|
||||||
|
|
||||||
|
_lastAxis = new Vector3(),
|
||||||
|
_lastAngle = 0,
|
||||||
|
|
||||||
|
_zoomStart = new Vector2(),
|
||||||
|
_zoomEnd = new Vector2(),
|
||||||
|
|
||||||
|
_touchZoomDistanceStart = 0,
|
||||||
|
_touchZoomDistanceEnd = 0,
|
||||||
|
|
||||||
|
_panStart = new Vector2(),
|
||||||
|
_panEnd = new Vector2();
|
||||||
|
|
||||||
|
// for reset
|
||||||
|
|
||||||
|
this.target0 = this.target.clone();
|
||||||
|
this.position0 = this.object.position.clone();
|
||||||
|
this.up0 = this.object.up.clone();
|
||||||
|
this.zoom0 = this.object.zoom;
|
||||||
|
|
||||||
|
// events
|
||||||
|
|
||||||
|
var changeEvent = { type: 'change' };
|
||||||
|
var startEvent = { type: 'start' };
|
||||||
|
var endEvent = { type: 'end' };
|
||||||
|
|
||||||
|
|
||||||
|
// methods
|
||||||
|
|
||||||
|
this.handleResize = function () {
|
||||||
|
|
||||||
|
var box = scope.domElement.getBoundingClientRect();
|
||||||
|
// adjustments come from similar code in the jquery offset() function
|
||||||
|
var d = scope.domElement.ownerDocument.documentElement;
|
||||||
|
scope.screen.left = box.left + window.pageXOffset - d.clientLeft;
|
||||||
|
scope.screen.top = box.top + window.pageYOffset - d.clientTop;
|
||||||
|
scope.screen.width = box.width;
|
||||||
|
scope.screen.height = box.height;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
var getMouseOnScreen = ( function () {
|
||||||
|
|
||||||
|
var vector = new Vector2();
|
||||||
|
|
||||||
|
return function getMouseOnScreen( pageX, pageY ) {
|
||||||
|
|
||||||
|
vector.set(
|
||||||
|
( pageX - scope.screen.left ) / scope.screen.width,
|
||||||
|
( pageY - scope.screen.top ) / scope.screen.height
|
||||||
|
);
|
||||||
|
|
||||||
|
return vector;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}() );
|
||||||
|
|
||||||
|
var getMouseOnCircle = ( function () {
|
||||||
|
|
||||||
|
var vector = new Vector2();
|
||||||
|
|
||||||
|
return function getMouseOnCircle( pageX, pageY ) {
|
||||||
|
|
||||||
|
vector.set(
|
||||||
|
( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ) ),
|
||||||
|
( ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width ) // screen.width intentional
|
||||||
|
);
|
||||||
|
|
||||||
|
return vector;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}() );
|
||||||
|
|
||||||
|
this.rotateCamera = ( function () {
|
||||||
|
|
||||||
|
var axis = new Vector3(),
|
||||||
|
quaternion = new Quaternion(),
|
||||||
|
eyeDirection = new Vector3(),
|
||||||
|
objectUpDirection = new Vector3(),
|
||||||
|
objectSidewaysDirection = new Vector3(),
|
||||||
|
moveDirection = new Vector3(),
|
||||||
|
angle;
|
||||||
|
|
||||||
|
return function rotateCamera() {
|
||||||
|
|
||||||
|
moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
|
||||||
|
angle = moveDirection.length();
|
||||||
|
|
||||||
|
if ( angle ) {
|
||||||
|
|
||||||
|
_eye.copy( scope.object.position ).sub( scope.target );
|
||||||
|
|
||||||
|
eyeDirection.copy( _eye ).normalize();
|
||||||
|
objectUpDirection.copy( scope.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 *= scope.rotateSpeed;
|
||||||
|
quaternion.setFromAxisAngle( axis, angle );
|
||||||
|
|
||||||
|
_eye.applyQuaternion( quaternion );
|
||||||
|
scope.object.up.applyQuaternion( quaternion );
|
||||||
|
|
||||||
|
_lastAxis.copy( axis );
|
||||||
|
_lastAngle = angle;
|
||||||
|
|
||||||
|
} else if ( ! scope.staticMoving && _lastAngle ) {
|
||||||
|
|
||||||
|
_lastAngle *= Math.sqrt( 1.0 - scope.dynamicDampingFactor );
|
||||||
|
_eye.copy( scope.object.position ).sub( scope.target );
|
||||||
|
quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
|
||||||
|
_eye.applyQuaternion( quaternion );
|
||||||
|
scope.object.up.applyQuaternion( quaternion );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_movePrev.copy( _moveCurr );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}() );
|
||||||
|
|
||||||
|
|
||||||
|
this.zoomCamera = function () {
|
||||||
|
|
||||||
|
var factor;
|
||||||
|
|
||||||
|
if ( _state === STATE.TOUCH_ZOOM_PAN ) {
|
||||||
|
|
||||||
|
factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
|
||||||
|
_touchZoomDistanceStart = _touchZoomDistanceEnd;
|
||||||
|
|
||||||
|
if ( scope.object.isPerspectiveCamera ) {
|
||||||
|
|
||||||
|
_eye.multiplyScalar( factor );
|
||||||
|
|
||||||
|
} else if ( scope.object.isOrthographicCamera ) {
|
||||||
|
|
||||||
|
scope.object.zoom *= factor;
|
||||||
|
scope.object.updateProjectionMatrix();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed;
|
||||||
|
|
||||||
|
if ( factor !== 1.0 && factor > 0.0 ) {
|
||||||
|
|
||||||
|
if ( scope.object.isPerspectiveCamera ) {
|
||||||
|
|
||||||
|
_eye.multiplyScalar( factor );
|
||||||
|
|
||||||
|
} else if ( scope.object.isOrthographicCamera ) {
|
||||||
|
|
||||||
|
scope.object.zoom /= factor;
|
||||||
|
scope.object.updateProjectionMatrix();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( scope.staticMoving ) {
|
||||||
|
|
||||||
|
_zoomStart.copy( _zoomEnd );
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.panCamera = ( function () {
|
||||||
|
|
||||||
|
var mouseChange = new Vector2(),
|
||||||
|
objectUp = new Vector3(),
|
||||||
|
pan = new Vector3();
|
||||||
|
|
||||||
|
return function panCamera() {
|
||||||
|
|
||||||
|
mouseChange.copy( _panEnd ).sub( _panStart );
|
||||||
|
|
||||||
|
if ( mouseChange.lengthSq() ) {
|
||||||
|
|
||||||
|
if ( scope.object.isOrthographicCamera ) {
|
||||||
|
|
||||||
|
var scale_x = ( scope.object.right - scope.object.left ) / scope.object.zoom / scope.domElement.clientWidth;
|
||||||
|
var scale_y = ( scope.object.top - scope.object.bottom ) / scope.object.zoom / scope.domElement.clientWidth;
|
||||||
|
|
||||||
|
mouseChange.x *= scale_x;
|
||||||
|
mouseChange.y *= scale_y;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseChange.multiplyScalar( _eye.length() * scope.panSpeed );
|
||||||
|
|
||||||
|
pan.copy( _eye ).cross( scope.object.up ).setLength( mouseChange.x );
|
||||||
|
pan.add( objectUp.copy( scope.object.up ).setLength( mouseChange.y ) );
|
||||||
|
|
||||||
|
scope.object.position.add( pan );
|
||||||
|
scope.target.add( pan );
|
||||||
|
|
||||||
|
if ( scope.staticMoving ) {
|
||||||
|
|
||||||
|
_panStart.copy( _panEnd );
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}() );
|
||||||
|
|
||||||
|
this.checkDistances = function () {
|
||||||
|
|
||||||
|
if ( ! scope.noZoom || ! scope.noPan ) {
|
||||||
|
|
||||||
|
if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) {
|
||||||
|
|
||||||
|
scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) );
|
||||||
|
_zoomStart.copy( _zoomEnd );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) {
|
||||||
|
|
||||||
|
scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) );
|
||||||
|
_zoomStart.copy( _zoomEnd );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.update = function () {
|
||||||
|
|
||||||
|
_eye.subVectors( scope.object.position, scope.target );
|
||||||
|
|
||||||
|
if ( ! scope.noRotate ) {
|
||||||
|
|
||||||
|
scope.rotateCamera();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! scope.noZoom ) {
|
||||||
|
|
||||||
|
scope.zoomCamera();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! scope.noPan ) {
|
||||||
|
|
||||||
|
scope.panCamera();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.object.position.addVectors( scope.target, _eye );
|
||||||
|
|
||||||
|
if ( scope.object.isPerspectiveCamera ) {
|
||||||
|
|
||||||
|
scope.checkDistances();
|
||||||
|
|
||||||
|
scope.object.lookAt( scope.target );
|
||||||
|
|
||||||
|
if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) {
|
||||||
|
|
||||||
|
scope.dispatchEvent( changeEvent );
|
||||||
|
|
||||||
|
lastPosition.copy( scope.object.position );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if ( scope.object.isOrthographicCamera ) {
|
||||||
|
|
||||||
|
scope.object.lookAt( scope.target );
|
||||||
|
|
||||||
|
if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) {
|
||||||
|
|
||||||
|
scope.dispatchEvent( changeEvent );
|
||||||
|
|
||||||
|
lastPosition.copy( scope.object.position );
|
||||||
|
lastZoom = scope.object.zoom;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.reset = function () {
|
||||||
|
|
||||||
|
_state = STATE.NONE;
|
||||||
|
_keyState = STATE.NONE;
|
||||||
|
|
||||||
|
scope.target.copy( scope.target0 );
|
||||||
|
scope.object.position.copy( scope.position0 );
|
||||||
|
scope.object.up.copy( scope.up0 );
|
||||||
|
scope.object.zoom = scope.zoom0;
|
||||||
|
|
||||||
|
scope.object.updateProjectionMatrix();
|
||||||
|
|
||||||
|
_eye.subVectors( scope.object.position, scope.target );
|
||||||
|
|
||||||
|
scope.object.lookAt( scope.target );
|
||||||
|
|
||||||
|
scope.dispatchEvent( changeEvent );
|
||||||
|
|
||||||
|
lastPosition.copy( scope.object.position );
|
||||||
|
lastZoom = scope.object.zoom;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// listeners
|
||||||
|
|
||||||
|
function onPointerDown( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
switch ( event.pointerType ) {
|
||||||
|
|
||||||
|
case 'mouse':
|
||||||
|
case 'pen':
|
||||||
|
onMouseDown( event );
|
||||||
|
break;
|
||||||
|
|
||||||
|
// TODO touch
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPointerMove( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
switch ( event.pointerType ) {
|
||||||
|
|
||||||
|
case 'mouse':
|
||||||
|
case 'pen':
|
||||||
|
onMouseMove( event );
|
||||||
|
break;
|
||||||
|
|
||||||
|
// TODO touch
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPointerUp( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
switch ( event.pointerType ) {
|
||||||
|
|
||||||
|
case 'mouse':
|
||||||
|
case 'pen':
|
||||||
|
onMouseUp( event );
|
||||||
|
break;
|
||||||
|
|
||||||
|
// TODO touch
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function keydown( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
window.removeEventListener( 'keydown', keydown );
|
||||||
|
|
||||||
|
if ( _keyState !== STATE.NONE ) {
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
} else if ( event.keyCode === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) {
|
||||||
|
|
||||||
|
_keyState = STATE.ROTATE;
|
||||||
|
|
||||||
|
} else if ( event.keyCode === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) {
|
||||||
|
|
||||||
|
_keyState = STATE.ZOOM;
|
||||||
|
|
||||||
|
} else if ( event.keyCode === scope.keys[ STATE.PAN ] && ! scope.noPan ) {
|
||||||
|
|
||||||
|
_keyState = STATE.PAN;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyup() {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
_keyState = STATE.NONE;
|
||||||
|
|
||||||
|
window.addEventListener( 'keydown', keydown );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseDown( event ) {
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if ( _state === STATE.NONE ) {
|
||||||
|
|
||||||
|
switch ( event.button ) {
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
_state = scope.mouseButtons.LEFT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
_state = scope.mouseButtons.MIDDLE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
_state = scope.mouseButtons.RIGHT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
_state = STATE.NONE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
|
||||||
|
|
||||||
|
if ( state === STATE.ROTATE && ! scope.noRotate ) {
|
||||||
|
|
||||||
|
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
|
||||||
|
_movePrev.copy( _moveCurr );
|
||||||
|
|
||||||
|
} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
|
||||||
|
|
||||||
|
_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
||||||
|
_zoomEnd.copy( _zoomStart );
|
||||||
|
|
||||||
|
} else if ( state === STATE.PAN && ! scope.noPan ) {
|
||||||
|
|
||||||
|
_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
||||||
|
_panEnd.copy( _panStart );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
|
||||||
|
scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
|
||||||
|
|
||||||
|
scope.dispatchEvent( startEvent );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseMove( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
var state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
|
||||||
|
|
||||||
|
if ( state === STATE.ROTATE && ! scope.noRotate ) {
|
||||||
|
|
||||||
|
_movePrev.copy( _moveCurr );
|
||||||
|
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
|
||||||
|
|
||||||
|
} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
|
||||||
|
|
||||||
|
_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
||||||
|
|
||||||
|
} else if ( state === STATE.PAN && ! scope.noPan ) {
|
||||||
|
|
||||||
|
_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
||||||
|
|
||||||
|
}
|
||||||
|
scope.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseUp( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
_state = STATE.NONE;
|
||||||
|
|
||||||
|
scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
|
||||||
|
scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
|
||||||
|
|
||||||
|
scope.dispatchEvent( endEvent );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function mousewheel( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
if ( scope.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;
|
||||||
|
|
||||||
|
}
|
||||||
|
scope.update()
|
||||||
|
scope.dispatchEvent( startEvent );
|
||||||
|
scope.dispatchEvent( endEvent );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchstart( event ) {
|
||||||
|
|
||||||
|
if ( scope.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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.dispatchEvent( startEvent );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchmove( event ) {
|
||||||
|
|
||||||
|
if ( scope.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 ( scope.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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.dispatchEvent( endEvent );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function contextmenu( event ) {
|
||||||
|
|
||||||
|
if ( scope.enabled === false ) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispose = function () {
|
||||||
|
|
||||||
|
scope.domElement.removeEventListener( 'contextmenu', contextmenu );
|
||||||
|
|
||||||
|
scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
|
||||||
|
scope.domElement.removeEventListener( 'wheel', mousewheel );
|
||||||
|
|
||||||
|
scope.domElement.removeEventListener( 'touchstart', touchstart );
|
||||||
|
scope.domElement.removeEventListener( 'touchend', touchend );
|
||||||
|
scope.domElement.removeEventListener( 'touchmove', touchmove );
|
||||||
|
|
||||||
|
scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
|
||||||
|
scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
|
||||||
|
|
||||||
|
window.removeEventListener( 'keydown', keydown );
|
||||||
|
window.removeEventListener( 'keyup', keyup );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.domElement.addEventListener( 'contextmenu', contextmenu );
|
||||||
|
|
||||||
|
this.domElement.addEventListener( 'pointerdown', onPointerDown );
|
||||||
|
this.domElement.addEventListener( 'wheel', mousewheel );
|
||||||
|
|
||||||
|
this.domElement.addEventListener( 'touchstart', touchstart );
|
||||||
|
this.domElement.addEventListener( 'touchend', touchend );
|
||||||
|
this.domElement.addEventListener( 'touchmove', touchmove );
|
||||||
|
|
||||||
|
this.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
|
||||||
|
this.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
|
||||||
|
|
||||||
|
window.addEventListener( 'keydown', keydown );
|
||||||
|
window.addEventListener( 'keyup', keyup );
|
||||||
|
|
||||||
|
this.handleResize();
|
||||||
|
|
||||||
|
// force an update at start
|
||||||
|
this.update();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
TrackballControls.prototype = Object.create( EventDispatcher.prototype );
|
||||||
|
TrackballControls.prototype.constructor = TrackballControls;
|
||||||
|
|
||||||
|
export { TrackballControls };
|
|
@ -0,0 +1,10 @@
|
||||||
|
export function KeyboardController() {
|
||||||
|
this.state="";
|
||||||
|
window.addEventListener('keydown', (e)=> {
|
||||||
|
if (e.key == "Escape"){
|
||||||
|
this.state = ""
|
||||||
|
} else {
|
||||||
|
this.state = e.key
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: './src/index.js',
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
],
|
||||||
|
|
||||||
|
output: {
|
||||||
|
filename: 'bundle.js',
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
// clean: true,
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.s[ac]ss$/i,
|
||||||
|
use: [
|
||||||
|
// Creates `style` nodes from JS strings
|
||||||
|
"style-loader",
|
||||||
|
// Translates CSS into CommonJS
|
||||||
|
"css-loader",
|
||||||
|
// Compiles Sass to CSS
|
||||||
|
"sass-loader",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
const { merge } = require('webpack-merge');
|
||||||
|
|
||||||
|
const common = require('./webpack.common.js');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = merge(common, {
|
||||||
|
|
||||||
|
mode: 'development',
|
||||||
|
|
||||||
|
// devtool: 'inline-source-map',
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,10 @@
|
||||||
|
const { merge } = require('webpack-merge');
|
||||||
|
|
||||||
|
const common = require('./webpack.common.js');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = merge(common, {
|
||||||
|
|
||||||
|
mode: 'production',
|
||||||
|
|
||||||
|
});
|