mirror of
https://github.com/Doodle3D/Doodle3D-Core.git
synced 2024-12-22 19:13:49 +01:00
add components
This commit is contained in:
parent
28c7d06dfd
commit
6b56224b93
34
package-lock.json
generated
34
package-lock.json
generated
@ -1415,6 +1415,16 @@
|
|||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||||
},
|
},
|
||||||
|
"create-react-class": {
|
||||||
|
"version": "15.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.2.tgz",
|
||||||
|
"integrity": "sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co=",
|
||||||
|
"requires": {
|
||||||
|
"fbjs": "0.8.16",
|
||||||
|
"loose-envify": "1.3.1",
|
||||||
|
"object-assign": "4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cryptiles": {
|
"cryptiles": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
|
||||||
@ -4516,6 +4526,25 @@
|
|||||||
"theming": "1.2.1"
|
"theming": "1.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-notification-system": {
|
||||||
|
"version": "0.2.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-notification-system/-/react-notification-system-0.2.16.tgz",
|
||||||
|
"integrity": "sha1-m52iCw00eGtgBXxStCUW6hKVN0o=",
|
||||||
|
"requires": {
|
||||||
|
"create-react-class": "15.6.2",
|
||||||
|
"object-assign": "4.1.1",
|
||||||
|
"prop-types": "15.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-notification-system-redux": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-notification-system-redux/-/react-notification-system-redux-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-FPsJFccuTBLZmD/+ByVOLNGexd8=",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "15.6.0",
|
||||||
|
"react-notification-system": "0.2.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-redux": {
|
"react-redux": {
|
||||||
"version": "5.0.6",
|
"version": "5.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz",
|
||||||
@ -4734,6 +4763,11 @@
|
|||||||
"x-path": "0.0.2"
|
"x-path": "0.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"reselect": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-3.0.1.tgz",
|
||||||
|
"integrity": "sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc="
|
||||||
|
},
|
||||||
"rimraf": {
|
"rimraf": {
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
|
||||||
|
@ -33,11 +33,13 @@
|
|||||||
"react": "^16.0.0",
|
"react": "^16.0.0",
|
||||||
"react-addons-update": "^15.6.2",
|
"react-addons-update": "^15.6.2",
|
||||||
"react-jss": "^8.0.0",
|
"react-jss": "^8.0.0",
|
||||||
|
"react-notification-system-redux": "^1.2.0",
|
||||||
"react-redux": "^5.0.6",
|
"react-redux": "^5.0.6",
|
||||||
"react-resize-detector": "^1.1.0",
|
"react-resize-detector": "^1.1.0",
|
||||||
"redux-form": "^7.1.2",
|
"redux-form": "^7.1.2",
|
||||||
"redux-undo": "^1.0.0-beta9-9-7",
|
"redux-undo": "^1.0.0-beta9-9-7",
|
||||||
"regenerator-runtime": "^0.11.0",
|
"regenerator-runtime": "^0.11.0",
|
||||||
|
"reselect": "^3.0.1",
|
||||||
"semver": "^5.4.1",
|
"semver": "^5.4.1",
|
||||||
"shortid": "^2.2.8",
|
"shortid": "^2.2.8",
|
||||||
"three": "^0.83.0",
|
"three": "^0.83.0",
|
||||||
|
87
src/components/App.js
Normal file
87
src/components/App.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'proptypes';
|
||||||
|
import injectSheet from 'react-jss';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import * as actions from 'doodle3d-core/actions';
|
||||||
|
import Logo from './Logo.js';
|
||||||
|
import D2Panel from './D2Panel.js';
|
||||||
|
import D3Panel from './D3Panel.js';
|
||||||
|
import SketcherToolbars from './SketcherToolbars.js';
|
||||||
|
import vlineImageURL from 'img/vline.png';
|
||||||
|
import btnUndoImageURL from 'img/mainmenu/btnUndo.png';
|
||||||
|
import btnRedoImageURL from 'img/mainmenu/btnRedo.png';
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
container: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'stretch',
|
||||||
|
height: '100%'
|
||||||
|
},
|
||||||
|
appContainer: {
|
||||||
|
flexGrow: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'stretch',
|
||||||
|
overflow: 'hidden'
|
||||||
|
},
|
||||||
|
vLine: {
|
||||||
|
backgroundSize: '14px auto',
|
||||||
|
backgroundImage: `url('${vlineImageURL}')`,
|
||||||
|
width: '28px'
|
||||||
|
},
|
||||||
|
undoMenu: {
|
||||||
|
display: 'flex',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: '50%',
|
||||||
|
marginLeft: '-68px'
|
||||||
|
},
|
||||||
|
undo: {
|
||||||
|
cursor: 'pointer',
|
||||||
|
backgroundSize: '68px auto',
|
||||||
|
width: '68px',
|
||||||
|
height: '58px',
|
||||||
|
backgroundImage: `url('${btnUndoImageURL}')`
|
||||||
|
},
|
||||||
|
redo: {
|
||||||
|
cursor: 'pointer',
|
||||||
|
backgroundSize: '71px auto',
|
||||||
|
width: '71px',
|
||||||
|
height: '58px',
|
||||||
|
backgroundImage: `url('${btnRedoImageURL}')`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
undo: PropTypes.func.isRequired,
|
||||||
|
redo: PropTypes.func.isRequired,
|
||||||
|
classes: PropTypes.objectOf(PropTypes.string)
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes, undo, redo } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
<div className={classes.appContainer}>
|
||||||
|
<D2Panel />
|
||||||
|
<div className={classes.vLine} />
|
||||||
|
<D3Panel />
|
||||||
|
</div>
|
||||||
|
<Logo />
|
||||||
|
<SketcherToolbars />
|
||||||
|
<div className={classes.undoMenu}>
|
||||||
|
<div type="button" onTouchTap={undo} className={classes.undo} />
|
||||||
|
<div type="button" onTouchTap={redo} className={classes.redo} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default injectSheet(styles)(connect(null, {
|
||||||
|
undo: actions.undo.undo,
|
||||||
|
redo: actions.undo.redo
|
||||||
|
})(App));
|
237
src/components/D2Panel.js
Normal file
237
src/components/D2Panel.js
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import injectSheet from 'react-jss';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import * as CAL from 'cal';
|
||||||
|
import * as toolNames from 'doodle3d-core/constants/d2Tools';
|
||||||
|
import { CANVAS_SIZE } from 'doodle3d-core/constants/d2Constants';
|
||||||
|
import createRAFOnce from 'src/js/utils/rafOnce.js';
|
||||||
|
import Grid from 'src/js/design/Grid.js';
|
||||||
|
import BaseTool from 'src/js/design/tools/BaseTool.js';
|
||||||
|
import TransformTool from 'src/js/design/tools/TransformTool.js';
|
||||||
|
import EraserTool from 'src/js/design/tools/EraserTool.js';
|
||||||
|
import BrushTool from 'src/js/design/tools/BrushTool.js';
|
||||||
|
import PolygonTool from 'src/js/design/tools/PolygonTool.js';
|
||||||
|
import CircleTool from 'src/js/design/tools/CircleTool.js';
|
||||||
|
import TextTool from 'src/js/design/tools/TextTool.js';
|
||||||
|
import BucketTool from 'src/js/design/tools/BucketTool.js';
|
||||||
|
import PhotoGuideTool from 'src/js/design/tools/PhotoGuideTool.js';
|
||||||
|
import { PIXEL_RATIO } from 'doodle3d-core/constants/general';
|
||||||
|
import ShapesManager from 'src/js/design/ShapesManager.js';
|
||||||
|
import EventGroup from 'src/js/design/EventGroup.js';
|
||||||
|
// import createDebug from 'debug';
|
||||||
|
// const debug = createDebug('d3d:d2');
|
||||||
|
|
||||||
|
const rafOnce = createRAFOnce();
|
||||||
|
|
||||||
|
const CANVAS_WIDTH = CANVAS_SIZE * 2;
|
||||||
|
const CANVAS_HEIGHT = CANVAS_SIZE * 2;
|
||||||
|
|
||||||
|
const tools = {
|
||||||
|
[toolNames.TRANSFORM]: TransformTool,
|
||||||
|
[toolNames.ERASER]: EraserTool,
|
||||||
|
[toolNames.TEXT]: TextTool,
|
||||||
|
[toolNames.BUCKET]: BucketTool,
|
||||||
|
[toolNames.PHOTO_GUIDE]: PhotoGuideTool,
|
||||||
|
[toolNames.BRUSH]: BrushTool,
|
||||||
|
[toolNames.POLYGON]: PolygonTool,
|
||||||
|
[toolNames.CIRCLE]: CircleTool
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO the same as 3d
|
||||||
|
const styles = {
|
||||||
|
container: {
|
||||||
|
flexGrow: 1,
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'stretch',
|
||||||
|
overflow: 'hidden'
|
||||||
|
},
|
||||||
|
canvas: {
|
||||||
|
flexGrow: 1,
|
||||||
|
margin: '0px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
'& canvas': {
|
||||||
|
position: 'absolute'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class D2Panel extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
state: PropTypes.object.isRequired,
|
||||||
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
classes: PropTypes.objectOf(PropTypes.string)
|
||||||
|
};
|
||||||
|
activeNeedRender = false;
|
||||||
|
inactiveNeedRender = false;
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
// Scene space
|
||||||
|
this.sceneActive = new EventGroup({
|
||||||
|
autoClearCanvas: true,
|
||||||
|
autoDrawCanvas: true,
|
||||||
|
drawWhenBlurred: true
|
||||||
|
});
|
||||||
|
this.sceneInactive = new CAL.Group({
|
||||||
|
autoClearCanvas: true,
|
||||||
|
autoDrawCanvas: true,
|
||||||
|
drawWhenBlurred: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Objects Container Space
|
||||||
|
this.objectContainerActive = new EventGroup();
|
||||||
|
this.sceneActive.add(this.objectContainerActive);
|
||||||
|
this.objectContainerInactive = new CAL.Group();
|
||||||
|
this.sceneInactive.add(this.objectContainerInactive);
|
||||||
|
|
||||||
|
// Grid
|
||||||
|
this.objectContainerInactive.add(new Grid(new CAL.Color(0xdddddd)));
|
||||||
|
|
||||||
|
this.shapesManager = new ShapesManager(this.objectContainerActive, this.objectContainerInactive);
|
||||||
|
|
||||||
|
window.addEventListener('resize', this.resizeHandler);
|
||||||
|
|
||||||
|
this.DOM = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { canvasContainer, activeScene, inactiveScene } = this.refs;
|
||||||
|
console.log('canvasContainer: ', canvasContainer);
|
||||||
|
this.container = canvasContainer;
|
||||||
|
|
||||||
|
this.sceneActive.setCanvas(activeScene);
|
||||||
|
this.sceneInactive.setCanvas(inactiveScene);
|
||||||
|
this.resizeHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener('resize', this.resizeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTool(newState) {
|
||||||
|
this.switchTool(newState.d2.tool);
|
||||||
|
if (this.tool) {
|
||||||
|
const toolNeedRender = this.tool.update(newState);
|
||||||
|
if (toolNeedRender) this.activeNeedRender = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchTool(toolName) {
|
||||||
|
if (this.state && toolName === this.state.d2.tool) return;
|
||||||
|
// cleanup & remove previous tool
|
||||||
|
if (this.tool) {
|
||||||
|
this.tool.destroy();
|
||||||
|
this.objectContainerActive.remove(this.tool);
|
||||||
|
}
|
||||||
|
// initialize new tool
|
||||||
|
// The tool itself is added to the active object container (Objects Container Space), which is
|
||||||
|
// influenced by zooming and panning, but the tool also needs to add objects to the container that
|
||||||
|
// isn't; the active scene (Scene Space). For example the wheelContainer, because it needs a
|
||||||
|
// mouse position, regardless of zoom level.
|
||||||
|
// Each tool also get's the renderRequest callback to request re-renders,
|
||||||
|
// even when it won't change the state
|
||||||
|
const ToolClass = tools[toolName] ? tools[toolName] : BaseTool;
|
||||||
|
this.tool = new ToolClass(this.props.dispatch, this.sceneActive, this.renderRequest);
|
||||||
|
this.objectContainerActive.add(this.tool);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(newState) {
|
||||||
|
if (this.state === newState) return;
|
||||||
|
|
||||||
|
this.updateTool(newState);
|
||||||
|
|
||||||
|
const shapesNeedRender = this.shapesManager.update(newState);
|
||||||
|
if (shapesNeedRender.active) this.activeNeedRender = true;
|
||||||
|
if (shapesNeedRender.inactive) this.inactiveNeedRender = true;
|
||||||
|
|
||||||
|
// Update Objects Container Space with zoom & panning
|
||||||
|
const newCanvasMatrix = newState.d2.canvasMatrix;
|
||||||
|
if (this.state && newCanvasMatrix !== this.state.d2.canvasMatrix) {
|
||||||
|
this.objectContainerActive.copyMatrix(newCanvasMatrix);
|
||||||
|
this.objectContainerInactive.copyMatrix(newCanvasMatrix);
|
||||||
|
|
||||||
|
this.activeNeedRender = true;
|
||||||
|
this.inactiveNeedRender = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = (this.state) ? this.state.selection : null;
|
||||||
|
const newSelection = newState.selection;
|
||||||
|
if (!this.state || newSelection !== selection) {
|
||||||
|
const newSelectedObjects = newSelection.objects;
|
||||||
|
if (!selection || selection.objects !== newSelectedObjects) {
|
||||||
|
const selected = newSelectedObjects.map((object) => object.id);
|
||||||
|
this.shapesManager.updateTransparent(selected);
|
||||||
|
|
||||||
|
this.activeNeedRender = true;
|
||||||
|
this.inactiveNeedRender = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dragSelect = (this.state) ? this.state.d2.transform.dragSelect : null;
|
||||||
|
const newDragSelect = newState.d2.transform.dragSelect;
|
||||||
|
if (!dragSelect || dragSelect !== newDragSelect) {
|
||||||
|
this.activeNeedRender = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeHandler = () => {
|
||||||
|
const { offsetWidth: width, offsetHeight: height } = this.container;
|
||||||
|
this.sceneActive.setSize(width, height, PIXEL_RATIO);
|
||||||
|
this.sceneInactive.setSize(width, height, PIXEL_RATIO);
|
||||||
|
|
||||||
|
this.sceneInactive.x = this.sceneActive.x = Math.round(width / 2 * PIXEL_RATIO);
|
||||||
|
this.sceneInactive.y = this.sceneActive.y = Math.round(height / 2 * PIXEL_RATIO);
|
||||||
|
|
||||||
|
const scale = Math.min(width * PIXEL_RATIO / CANVAS_WIDTH, height * PIXEL_RATIO / CANVAS_HEIGHT);
|
||||||
|
|
||||||
|
this.sceneInactive.scale = this.sceneActive.scale = scale;
|
||||||
|
|
||||||
|
this.inactiveNeedRender = this.activeNeedRender = true;
|
||||||
|
this.renderRequest();
|
||||||
|
};
|
||||||
|
|
||||||
|
renderRequest = () => {
|
||||||
|
this.activeNeedRender = true;
|
||||||
|
rafOnce(this.renderCanvas);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderCanvas = () => {
|
||||||
|
if (this.activeNeedRender) {
|
||||||
|
this.sceneActive.cycle();
|
||||||
|
this.activeNeedRender = false;
|
||||||
|
}
|
||||||
|
if (this.inactiveNeedRender) {
|
||||||
|
this.sceneInactive.cycle();
|
||||||
|
this.inactiveNeedRender = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
stopPropagation(event) {
|
||||||
|
// stop propagation to prefent popup to close immidiatly
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// debug('this.props.state: ', this.props.state);
|
||||||
|
const { state, classes } = this.props;
|
||||||
|
this.update(state);
|
||||||
|
this.renderCanvas();
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
<div className={classes.canvas} ref="canvasContainer">
|
||||||
|
<canvas ref="inactiveScene" />
|
||||||
|
<canvas onClick={this.stopPropagation} ref="activeScene" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the component to inject dispatch and state into it
|
||||||
|
export default injectSheet(styles)(connect(state => ({
|
||||||
|
state: state.sketcher.present
|
||||||
|
}))(D2Panel));
|
264
src/components/D3Panel.js
Normal file
264
src/components/D3Panel.js
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import injectSheet from 'react-jss';
|
||||||
|
import { CANVAS_SIZE } from 'doodle3d-core/constants/d2Constants';
|
||||||
|
import ShapesManager from 'doodle3d-core/d3/ShapesManager';
|
||||||
|
import { getSelectedObjectsSelector, getBoundingBox } from 'doodle3d-core/utils/selectionUtils';
|
||||||
|
import createRAFOnce from 'src/js/utils/rafOnce.js';
|
||||||
|
import { hasExtensionsFor } from 'doodle3d-core/utils/WebGLSupport.js';
|
||||||
|
import { PIXEL_RATIO } from 'doodle3d-core/constants/general.js';
|
||||||
|
import * as toolsNames from 'doodle3d-core/constants/d3Tools.js';
|
||||||
|
import { EventScene, EventObject3D } from 'src/js/preview/EventScene.js';
|
||||||
|
import HeightTransformer from 'src/js/preview/transformers/HeightTransformer.js';
|
||||||
|
import TwistTransformer from 'src/js/preview/transformers/TwistTransformer.js';
|
||||||
|
import SculptTransformer from 'src/js/preview/transformers/SculptTransformer.js';
|
||||||
|
import StampTransformer from 'src/js/preview/transformers/StampTransformer.js';
|
||||||
|
import SelectionBox from 'src/js/preview/SelectionBox.js';
|
||||||
|
import ToonShaderRenderChain from 'doodle3d-core/d3/ToonShaderRenderChain';
|
||||||
|
import RenderChain from 'doodle3d-core/d3/RenderChain';
|
||||||
|
import BaseTransformer from 'src/js/preview/transformers/BaseTransformer.js';
|
||||||
|
import Camera from 'src/js/preview/Camera.js';
|
||||||
|
// import createDebug from 'debug';
|
||||||
|
// const debug = createDebug('d3d:d3');
|
||||||
|
|
||||||
|
const rafOnce = createRAFOnce();
|
||||||
|
|
||||||
|
const CANVAS_WIDTH = CANVAS_SIZE * 2;
|
||||||
|
const CANVAS_HEIGHT = CANVAS_SIZE * 2;
|
||||||
|
|
||||||
|
const tools = {
|
||||||
|
[toolsNames.HEIGHT]: HeightTransformer,
|
||||||
|
[toolsNames.SCULPT]: SculptTransformer,
|
||||||
|
[toolsNames.TWIST]: TwistTransformer,
|
||||||
|
[toolsNames.STAMP]: StampTransformer
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO the same as 3d
|
||||||
|
const styles = {
|
||||||
|
container: {
|
||||||
|
flexGrow: 1,
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'stretch',
|
||||||
|
overflow: 'hidden'
|
||||||
|
},
|
||||||
|
canvas: {
|
||||||
|
flexGrow: 1,
|
||||||
|
margin: '0px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
'& canvas': {
|
||||||
|
position: 'absolute'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class D3Panel extends React.Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
state: PropTypes.object.isRequired,
|
||||||
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
classes: PropTypes.objectOf(PropTypes.string)
|
||||||
|
};
|
||||||
|
needRender = false;
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.createScene();
|
||||||
|
|
||||||
|
if (hasExtensionsFor.toonShaderPreview) {
|
||||||
|
this.renderChain = new ToonShaderRenderChain(this.renderer, this.scene, this.camera, {
|
||||||
|
UI: this.UIContainer,
|
||||||
|
shapes: this.shapesManager,
|
||||||
|
boundingBox: this.selectionBox,
|
||||||
|
plane: this.plane
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.renderer.setClearColor(0xffffff);
|
||||||
|
this.renderChain = new RenderChain(this.renderer, this.scene, this.camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', this.resizeHandler);
|
||||||
|
|
||||||
|
this.DOM = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.container = this.refs.canvasContainer;
|
||||||
|
this.resizeHandler();
|
||||||
|
this.container.appendChild(this.renderer.domElement);
|
||||||
|
|
||||||
|
this.renderScene(); // immidiatly render because when THREE.JS inits, a black screen is generated
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener('resize', this.resizeHandler);
|
||||||
|
this.scene.setCanvas(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
createScene() {
|
||||||
|
this.renderer = new THREE.WebGLRenderer();
|
||||||
|
|
||||||
|
this.scene = new EventScene(this.renderer.domElement);
|
||||||
|
|
||||||
|
this.camera = new Camera(50, 1, 1, 10000);
|
||||||
|
|
||||||
|
this.scene.add(this.camera);
|
||||||
|
|
||||||
|
// this.grid = new THREE.GridHelper(200, 20);
|
||||||
|
// this.grid.setColors(0xe0e0e0, 0xeeeeee);
|
||||||
|
// this.scene.add(this.grid);
|
||||||
|
const geometryPlane = new THREE.PlaneGeometry(CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||||
|
const materialPlane = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0xcccccc,
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.5
|
||||||
|
});
|
||||||
|
this.plane = new THREE.Mesh(geometryPlane, materialPlane);
|
||||||
|
this.plane.rotation.x = Math.PI / 2;
|
||||||
|
this.plane.position.y = -0.01;
|
||||||
|
this.plane.name = 'bed-plane';
|
||||||
|
this.scene.add(this.plane);
|
||||||
|
|
||||||
|
const directionalLight = new THREE.PointLight(0xffffff, 0.6);
|
||||||
|
this.camera.add(directionalLight);
|
||||||
|
|
||||||
|
const ambientLight = new THREE.AmbientLight(0x505050);
|
||||||
|
this.scene.add(ambientLight);
|
||||||
|
|
||||||
|
this.shapesManager = new ShapesManager({ toonShader: hasExtensionsFor.toonShaderPreview });
|
||||||
|
|
||||||
|
this.UIContainer = new EventObject3D();
|
||||||
|
this.UIContainer.matrixAutoUpdate = false;
|
||||||
|
this.UIContainer.name = 'ui-container';
|
||||||
|
|
||||||
|
this.scene.add(this.shapesManager, this.UIContainer);
|
||||||
|
|
||||||
|
this.selectionBox = new SelectionBox();
|
||||||
|
this.scene.add(this.selectionBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTool(newState) {
|
||||||
|
this.switchTool(newState.d3.tool);
|
||||||
|
if (this.tool) {
|
||||||
|
const toolNeedRender = this.tool.update(newState);
|
||||||
|
if (toolNeedRender) this.activeNeedRender = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchTool(toolName) {
|
||||||
|
if (this.state && toolName === this.state.d3.tool) return;
|
||||||
|
// cleanup & remove previous tool
|
||||||
|
if (this.tool) {
|
||||||
|
this.tool.destroy();
|
||||||
|
this.UIContainer.remove(this.tool);
|
||||||
|
}
|
||||||
|
// initialize new tool
|
||||||
|
// The tool itself is added to the active object container (Objects Container Space), which is
|
||||||
|
// influenced by zooming and panning, but the tool also needs to add objects to the container that
|
||||||
|
// isn't; the active scene (Scene Space). For example the wheelContainer, because it needs a
|
||||||
|
// mouse position, regardless of zoom level.
|
||||||
|
// Each tool also get's the renderRequest callback to request re-renders,
|
||||||
|
// even when it won't change the state
|
||||||
|
const ToolClass = tools[toolName] ? tools[toolName] : BaseTransformer;
|
||||||
|
this.tool = new ToolClass(this.props.dispatch, this.scene, this.camera, this.renderer.domElement);
|
||||||
|
this.UIContainer.add(this.tool);
|
||||||
|
|
||||||
|
this.needRender = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(newState) {
|
||||||
|
if (this.state === newState) return;
|
||||||
|
|
||||||
|
if (!this.state || newState.activeSpace !== this.state.activeSpace) {
|
||||||
|
const spaceMatrix = newState.spaces[newState.activeSpace].matrix;
|
||||||
|
this.UIContainer.matrix.copy(spaceMatrix);
|
||||||
|
this.UIContainer.updateMatrixWorld(true);
|
||||||
|
this.selectionBox.matrix.copy(spaceMatrix);
|
||||||
|
this.selectionBox.updateMatrixWorld(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = (this.state) ? this.state.selection : null;
|
||||||
|
const newSelection = newState.selection;
|
||||||
|
|
||||||
|
this.updateTool(newState);
|
||||||
|
|
||||||
|
const meshNeedRender = this.shapesManager.update(newState);
|
||||||
|
if (meshNeedRender) this.needRender = true;
|
||||||
|
|
||||||
|
const hasSelections = newSelection.objects.length > 0;
|
||||||
|
if (!this.state || newSelection !== selection) {
|
||||||
|
const newSelectedObjects = newSelection.objects;
|
||||||
|
if (!selection || selection.objects !== newSelectedObjects) {
|
||||||
|
const selected = newSelectedObjects.map((object) => object.id);
|
||||||
|
this.shapesManager.updateTransparent(selected);
|
||||||
|
|
||||||
|
this.needRender = true;
|
||||||
|
|
||||||
|
this.selectionBox.visible = hasSelections;
|
||||||
|
}
|
||||||
|
if (hasSelections) {
|
||||||
|
this.selectionBox.updateTransform(newSelection.transform);
|
||||||
|
|
||||||
|
this.needRender = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.state || this.state.d3.camera !== newState.d3.camera) {
|
||||||
|
this.camera.update(newState.d3.camera);
|
||||||
|
|
||||||
|
this.needRender = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: if there is a selection, any state change will trigger this:
|
||||||
|
if (hasSelections) {
|
||||||
|
const objectsById = newState.objectsById;
|
||||||
|
const selectedShapeDatas = getSelectedObjectsSelector(newSelection.objects, objectsById);
|
||||||
|
const boundingBox = getBoundingBox(selectedShapeDatas, newSelection.transform);
|
||||||
|
// TODO: add selected shape datas and boundingbox diff checking
|
||||||
|
this.selectionBox.updateBoundingBox(boundingBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeHandler = () => {
|
||||||
|
const { offsetWidth: width, offsetHeight: height } = this.container;
|
||||||
|
|
||||||
|
// set renderer size
|
||||||
|
this.renderChain.setSize(width, height, PIXEL_RATIO);
|
||||||
|
|
||||||
|
this.renderRequest();
|
||||||
|
};
|
||||||
|
|
||||||
|
renderRequest = () => {
|
||||||
|
this.needRender = true;
|
||||||
|
rafOnce(this.renderScene);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { state, classes } = this.props;
|
||||||
|
this.update(state);
|
||||||
|
this.renderScene();
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
<div className={classes.canvas} ref="canvasContainer"/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderScene = () => {
|
||||||
|
if (this.needRender) {
|
||||||
|
this.scene.updateMatrixWorld();
|
||||||
|
this.renderChain.render(true);
|
||||||
|
this.needRender = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the component to inject dispatch and state into it
|
||||||
|
export default injectSheet(styles)(connect(state => ({
|
||||||
|
state: state.sketcher.present
|
||||||
|
}))(D3Panel));
|
29
src/components/Logo.js
Normal file
29
src/components/Logo.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import doodleSignImageURL from 'img/doodle3d-sign.png';
|
||||||
|
import injectSheet from 'react-jss';
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
container: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: '0px',
|
||||||
|
right: '15%',
|
||||||
|
width: '19%',
|
||||||
|
pointerEvents: 'none', // enable clicking through logo
|
||||||
|
'& img': {
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '290px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'@media (max-width: 555px)': {
|
||||||
|
container: {
|
||||||
|
display: 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Logo = ({ classes }) => (
|
||||||
|
<div className={classes.container}>
|
||||||
|
<img src={doodleSignImageURL} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
export default injectSheet(styles)(Logo);
|
200
src/components/SketcherToolbars.js
Normal file
200
src/components/SketcherToolbars.js
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import * as actions from 'doodle3d-core/actions/index.js';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import * as contextTools from 'doodle3d-core/constants/contextTools';
|
||||||
|
import * as d2Tools from 'doodle3d-core/constants/d2Tools';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import Menu from 'src/js/components/Menu.js';
|
||||||
|
import SubMenu from 'src/js/components/SubMenu.js';
|
||||||
|
import MenuItem from 'src/js/components/MenuItem.js';
|
||||||
|
// import createDebug from 'debug';
|
||||||
|
// const debug = createDebug('d3d:sketchertoolbars');
|
||||||
|
|
||||||
|
const TOOLBAR2D = 'toolbar2d';
|
||||||
|
const TOOLBAR3D = 'toolbar3d';
|
||||||
|
const CONTEXT = 'context';
|
||||||
|
|
||||||
|
class SketcherToolbars extends React.Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
toolbar2d: PropTypes.object, // TODO: specify further
|
||||||
|
toolbar3d: PropTypes.object, // TODO: specify further
|
||||||
|
context: PropTypes.object // TODO: specify further
|
||||||
|
};
|
||||||
|
onSelect2D = ({ value }) => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
dispatch(actions.d2ChangeTool(value));
|
||||||
|
};
|
||||||
|
onSelect3D = ({ value }) => {
|
||||||
|
this.props.dispatch(actions.d3ChangeTool(value));
|
||||||
|
};
|
||||||
|
onSelectContext = ({ value }) => {
|
||||||
|
switch (value) {
|
||||||
|
case contextTools.DUPLICATE:
|
||||||
|
this.props.dispatch(actions.duplicateSelection(value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case contextTools.DELETE:
|
||||||
|
this.props.dispatch(actions.deleteSelection(value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case contextTools.UNION:
|
||||||
|
this.props.dispatch(actions.union(value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case contextTools.INTERSECT:
|
||||||
|
this.props.dispatch(actions.intersect(value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
dispatch(actions.contextChangeTool(value));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onOpen = ({ menuValue }) => {
|
||||||
|
this.props.dispatch(actions.menuOpen(menuValue));
|
||||||
|
};
|
||||||
|
onClose = ({ menuValue }) => {
|
||||||
|
this.props.dispatch(actions.menuClose(menuValue));
|
||||||
|
};
|
||||||
|
renderToolbar = (value, onSelect, data) => {
|
||||||
|
return (
|
||||||
|
<div id={`${value}-container`}>
|
||||||
|
<Menu
|
||||||
|
id={value}
|
||||||
|
value={value}
|
||||||
|
className="toolbar"
|
||||||
|
onSelect={onSelect}
|
||||||
|
onOpen={this.onOpen}
|
||||||
|
onClose={this.onClose}
|
||||||
|
selectedValue={data.selected}
|
||||||
|
>
|
||||||
|
{renderChildren(data.children)}
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render() {
|
||||||
|
const { toolbar2d, toolbar3d, context } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div id="sketcher-toolbars">
|
||||||
|
{this.renderToolbar(TOOLBAR2D, this.onSelect2D, toolbar2d)}
|
||||||
|
{this.renderToolbar(TOOLBAR3D, this.onSelect3D, toolbar3d)}
|
||||||
|
</div>
|
||||||
|
{context.children.length > 0 && <div className="centerer">
|
||||||
|
{this.renderToolbar(CONTEXT, this.onSelectContext, context)}
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderChildren(children) {
|
||||||
|
const components = [];
|
||||||
|
|
||||||
|
for (const child of children) {
|
||||||
|
let component;
|
||||||
|
if (child.children.length > 0) {
|
||||||
|
component = (
|
||||||
|
<SubMenu
|
||||||
|
id={child.value}
|
||||||
|
value={child.value}
|
||||||
|
key={child.value}
|
||||||
|
selectedValue={child.selected}
|
||||||
|
open={child.open}
|
||||||
|
svg={child.svg}
|
||||||
|
selectOnOpen={child.selectOnOpen}
|
||||||
|
toggleBehavior={child.toggleBehavior}
|
||||||
|
>
|
||||||
|
{renderChildren(child.children)}
|
||||||
|
</SubMenu>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
component = (
|
||||||
|
<MenuItem
|
||||||
|
id={child.value}
|
||||||
|
value={child.value}
|
||||||
|
key={child.value}
|
||||||
|
svg={child.svg}
|
||||||
|
disabled={child.disabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
components.push(component);
|
||||||
|
}
|
||||||
|
return components;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterMenus(numSelectedObjects, allObjectsAreFilled, activeTool, menus) {
|
||||||
|
const showUnion = activeTool === d2Tools.TRANSFORM && allObjectsAreFilled && numSelectedObjects >= 2;
|
||||||
|
const showIntersect = activeTool === d2Tools.TRANSFORM && numSelectedObjects >= 1;
|
||||||
|
return {
|
||||||
|
...menus,
|
||||||
|
children: menus.children.filter(({ value }) => {
|
||||||
|
switch (value) {
|
||||||
|
case contextTools.ADVANCED:
|
||||||
|
return showUnion || showIntersect;
|
||||||
|
|
||||||
|
case contextTools.UNION:
|
||||||
|
return showUnion;
|
||||||
|
|
||||||
|
case contextTools.INTERSECT:
|
||||||
|
return showIntersect;
|
||||||
|
|
||||||
|
case contextTools.DUPLICATE:
|
||||||
|
case contextTools.DELETE:
|
||||||
|
case contextTools.FILL_TOGGLE:
|
||||||
|
return numSelectedObjects > 0;
|
||||||
|
|
||||||
|
case contextTools.ALIGN:
|
||||||
|
return numSelectedObjects > 1;
|
||||||
|
|
||||||
|
case contextTools.COLOR_PICKER:
|
||||||
|
if (activeTool === d2Tools.ERASER) return false;
|
||||||
|
if (activeTool === d2Tools.TRANSFORM && numSelectedObjects === 0) return false;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case contextTools.ERASER_SIZE:
|
||||||
|
return activeTool === d2Tools.ERASER;
|
||||||
|
|
||||||
|
case contextTools.BRUSH_SIZE:
|
||||||
|
return activeTool === d2Tools.BRUSH;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}).map(child => {
|
||||||
|
return filterMenus(numSelectedObjects, allObjectsAreFilled, activeTool, child);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function nestChildren(menus, target) {
|
||||||
|
// const targetStr = JSON.stringify(target);
|
||||||
|
const children = target.children.map(value => nestChildren(menus, menus[value]));
|
||||||
|
const { selected } = target;
|
||||||
|
return {
|
||||||
|
svg: selected && children.find(({ value }) => value === selected).svg,
|
||||||
|
...target,
|
||||||
|
selected,
|
||||||
|
children: children.filter(({ hide }) => !hide)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMenus = createSelector([
|
||||||
|
state => state.sketcher.present.menus,
|
||||||
|
state => state.sketcher.present.selection.objects.length,
|
||||||
|
state => state.sketcher.present.selection.objects.every(({ id }) => state.sketcher.present.objectsById[id].fill),
|
||||||
|
state => state.sketcher.present.d2.tool
|
||||||
|
], (menus, numSelectedObjects, allObjectsAreFilled, activeTool) => ({
|
||||||
|
toolbar2d: filterMenus(numSelectedObjects, allObjectsAreFilled, activeTool, nestChildren(menus, menus[TOOLBAR2D])),
|
||||||
|
toolbar3d: filterMenus(numSelectedObjects, allObjectsAreFilled, activeTool, nestChildren(menus, menus[TOOLBAR3D])),
|
||||||
|
context: filterMenus(numSelectedObjects, allObjectsAreFilled, activeTool, nestChildren(menus, menus[CONTEXT]))
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default connect(getMenus)(SketcherToolbars);
|
Loading…
Reference in New Issue
Block a user