Toggle 3D cameras

pull/863/head
Piero Toffanin 2020-05-21 09:58:50 -04:00
rodzic 24b354fdaa
commit eb976672f7
4 zmienionych plików z 167 dodań i 29 usunięć

Wyświetl plik

@ -5,19 +5,11 @@ import SwitchModeButton from './components/SwitchModeButton';
import AssetDownloadButtons from './components/AssetDownloadButtons';
import Standby from './components/Standby';
import ShareButton from './components/ShareButton';
import ImagePopup from './components/ImagePopup';
import PropTypes from 'prop-types';
import epsg from 'epsg';
import $ from 'jquery';
// Add more proj definitions
const defs = [];
for (let k in epsg){
if (epsg[k]){
defs.push([k, epsg[k]]);
}
}
window.proj4.defs(defs);
require('./vendor/OBJLoader');
require('./vendor/MTLLoader');
require('./vendor/ColladaLoader');
@ -49,6 +41,33 @@ class TexturedModelMenu extends React.Component{
}
}
class CamerasMenu extends React.Component{
static propTypes = {
toggleCameras: PropTypes.func.isRequired
}
constructor(props){
super(props);
this.state = {
showCameras: false
}
}
handleClick = (e) => {
this.setState({showCameras: e.target.checked});
this.props.toggleCameras(e);
}
render(){
return (<label><input
type="checkbox"
checked={this.state.showCameras}
onChange={this.handleClick}
/> Show Cameras</label>);
}
}
class ModelView extends React.Component {
static defaultProps = {
task: null,
@ -66,13 +85,16 @@ class ModelView extends React.Component {
this.state = {
error: "",
showTexturedModel: false,
initializingModel: false
initializingModel: false,
selectedCamera: null,
};
this.pointCloud = null;
this.modelReference = null;
this.toggleTexturedModel = this.toggleTexturedModel.bind(this);
this.toggleCameras = this.toggleCameras.bind(this);
this.cameraMeshes = [];
}
@ -118,6 +140,10 @@ class ModelView extends React.Component {
return this.props.task.available_assets.indexOf('textured_model.zip') !== -1;
}
hasCameras(){
return this.props.task.available_assets.indexOf('shots.geojson') !== -1;
}
objFilePath(cb){
const geoUrl = this.texturedModelDirectoryPath() + 'odm_textured_model_geo.obj';
const nongeoUrl = this.texturedModelDirectoryPath() + 'odm_textured_model.obj';
@ -158,6 +184,13 @@ class ModelView extends React.Component {
$("#textured_model").hide();
$("#textured_model_container").hide();
}
if (this.hasCameras()){
window.ReactDOM.render(<CamerasMenu toggleCameras={this.toggleCameras}/>, $("#cameras_button").get(0));
}else{
$("#cameras").hide();
$("#cameras_container").hide();
}
});
viewer.scene.scene.add( new THREE.AmbientLight( 0x404040, 2.0 ) ); // soft white light );
@ -167,7 +200,6 @@ class ModelView extends React.Component {
directional.position.z = 99999999999;
viewer.scene.scene.add( directional );
this.pointCloudFilePath(pointCloudPath => {
Potree.loadPointCloud(pointCloudPath, "Point Cloud", e => {
if (e.type == "loading_failed"){
@ -182,22 +214,22 @@ class ModelView extends React.Component {
let material = e.pointcloud.material;
material.size = 1;
this.loadCameras();
window.scene = viewer.scene.scene;
viewer.fitToScreen();
});
});
viewer.renderer.domElement.addEventListener( 'mousedown', this.handleRenderClick );
viewer.renderer.domElement.addEventListener( 'mousedown', this.handleRenderMouseClick );
viewer.renderer.domElement.addEventListener( 'mousemove', this.handleRenderMouseMove );
}
componentWillUnmount(){
viewer.renderer.domElement.removeEventListener( 'mousedown', this.handleRenderClick );
viewer.renderer.domElement.removeEventListener( 'mousedown', this.handleRenderMouseClick );
viewer.renderer.domElement.removeEventListener( 'mousemove', this.handleRenderMouseMove );
}
handleRenderClick = (evt) => {
getCameraUnderCursor = (evt) => {
const raycaster = new THREE.Raycaster();
const rect = viewer.renderer.domElement.getBoundingClientRect();
const [x, y] = [evt.clientX, evt.clientY];
@ -214,12 +246,50 @@ class ModelView extends React.Component {
const intersects = raycaster.intersectObjects( this.cameraMeshes );
if ( intersects.length > 0){
//console.log(intersects);
const intersection = intersects[0];
console.log(intersection);
return intersection.object;
}
}
setCameraOpacity(camera, opacity){
camera.material.forEach(m => {
m.opacity = opacity;
});
}
handleRenderMouseMove = (evt) => {
if (this._prevCamera && this._prevCamera !== this.state.selectedCamera) {
this.setCameraOpacity(this._prevCamera, 0.7);
}
const camera = this.getCameraUnderCursor(evt);
if (camera){
viewer.renderer.domElement.classList.add("pointer-cursor");
this.setCameraOpacity(camera, 1);
}else{
viewer.renderer.domElement.classList.remove("pointer-cursor");
}
this._prevCamera = camera;
}
handleRenderMouseClick = (evt) => {
let camera = this.getCameraUnderCursor(evt);
// Deselect
if (camera === this.state.selectedCamera){
this.setState({selectedCamera: null});
}else if (camera){
if (this.state.selectedCamera){
this.setCameraOpacity(this.state.selectedCamera, 0.7);
}
this.setState({selectedCamera: camera});
}
}
closeThumb = (e) => {
e.stopPropagation();
this.setState({selectedCamera: null});
}
loadCameras(){
const { task } = this.props;
@ -234,7 +304,7 @@ class ModelView extends React.Component {
return matrix.transpose();
}
if (task.available_assets.indexOf('shots.geojson') !== -1){
if (this.hasCameras()){
const colladaLoader = new THREE.ColladaLoader();
const fileloader = new THREE.FileLoader();
@ -254,13 +324,17 @@ class ModelView extends React.Component {
const toScene = proj4(gjproj, pcproj);
const cameraObj = dae.children[0];
cameraObj.material.forEach(m => {
m.transparent = true;
m.opacity = 0.7;
});
// const cameraObj = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshNormalMaterial());
// TODO: instancing doesn't seem to work :/
// const cameraMeshes = new THREE.InstancedMesh( cameraObj.geometry, cameraObj.material, geojson.features.length );
// const dummy = new THREE.Object3D();
let i = 0;
geojson.features.forEach(feat => {
const coords = feat.geometry.coordinates;
@ -268,7 +342,8 @@ class ModelView extends React.Component {
const utm = toScene.forward([coords[0], coords[1]]);
utm.push(coords[2]); // z in meters doesn't change
const cameraMesh = new THREE.Mesh(cameraObj.geometry, cameraObj.material);
const material = cameraObj.material.map(m => m.clone());
const cameraMesh = new THREE.Mesh(cameraObj.geometry, material);
cameraMesh.matrixAutoUpdate = false;
cameraMesh.matrix.set(...getMatrix(utm, feat.properties.rotation).elements);
viewer.scene.scene.add(cameraMesh);
@ -289,6 +364,16 @@ class ModelView extends React.Component {
}
}
toggleCameras(e){
if (this.cameraMeshes.length === 0){
this.loadCameras();
if (this.cameraMeshes.length === 0) return;
}
const isVisible = this.cameraMeshes[0].visible;
this.cameraMeshes.forEach(cam => cam.visible = !isVisible);
}
toggleTexturedModel(e){
const value = e.target.checked;
@ -353,6 +438,9 @@ class ModelView extends React.Component {
// React render
render(){
const { selectedCamera } = this.state;
const { task } = this.props;
return (<div className="model-view">
<ErrorMessage bind={[this, "error"]} />
<div className="container potree_container"
@ -383,6 +471,11 @@ class ModelView extends React.Component {
type="modelToMap" />
</div>
{selectedCamera ? <div className="thumbnail">
<a className="close-thumb" href="javascript:void(0)" onClick={this.closeThumb}><i className="fa fa-window-close"></i></a>
<ImagePopup feature={selectedCamera._feat} task={task} />
</div> : ""}
<Standby
message="Loading textured model..."
show={this.state.initializingModel}
@ -392,6 +485,15 @@ class ModelView extends React.Component {
}
$(function(){
// Add more proj definitions
const defs = [];
for (let k in epsg){
if (epsg[k]){
defs.push([k, epsg[k]]);
}
}
window.proj4.defs(defs);
$("[data-modelview]").each(function(){
let props = $(this).data();
delete(props.modelview);

Wyświetl plik

@ -17,11 +17,15 @@ class ImagePopup extends React.Component {
loading: true,
expandThumb: false,
}
}
const { feature, task } = props;
const imageUrl = `/api/projects/${task.project}/tasks/${task.id}/images/thumbnail/${feature.properties.filename}`;
this.thumbUrl = `${imageUrl}?size=320`;
this.highresUrl = `${imageUrl}?size=9999999`;
getImageUrl(){
const { feature, task } = this.props;
return `/api/projects/${task.project}/tasks/${task.id}/images/thumbnail/${feature.properties.filename}`;
}
getThumbUrl(size){
return `${this.getImageUrl()}?size=${size}`;
}
componentDidMount(){
@ -64,11 +68,11 @@ class ImagePopup extends React.Component {
const downloadImageLink = `/api/projects/${task.project}/tasks/${task.id}/images/download/${feature.properties.filename}`;
const downloadShotsLink = `/api/projects/${task.project}/tasks/${task.id}/download/shots.geojson`;
const imageUrl = expandThumb ? this.highresUrl : this.thumbUrl;
const imageUrl = expandThumb ? this.getThumbUrl(999999999) : this.getThumbUrl(320);
const assetDownload = AssetDownloads.only(["shots.geojson"])[0];
return (<div className="image-popup">
<strong>{feature.properties.filename}</strong>
<div className="title" title={feature.properties.filename}>{feature.properties.filename}</div>
{loading ? <div><i className="fa fa-circle-notch fa-spin fa-fw"></i></div>
: ""}
{error !== "" ? <div style={{marginTop: "8px"}}>{error}</div>

Wyświetl plik

@ -3,6 +3,15 @@
margin-top: 8px;
}
div.title{
margin-top: 0;
font-weight: bold;
max-width: 300px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
div.image{
a{
cursor: zoom-in;

Wyświetl plik

@ -5,6 +5,10 @@
#potree_render_area > canvas{
width: 100% !important;
height: 100% !important;
&.pointer-cursor{
cursor: pointer;
}
}
.container{
@ -180,6 +184,25 @@
#potree_download_profile_ortho_link, #potree_download_profile_link{
color: black;
}
.thumbnail{
position: absolute;
top: 8px;
right: 8px;
z-index: 10;
background: #19282c;
border-color: #a6a9aa;
color: #ccccff;
.close-thumb{
opacity: 0.8;
position: absolute;
top: 4px;
right: 8px;
&:hover{
opacity: 1;
}
}
}
}
#profile_window{