Merge branch 'master' into feature-cordova

This commit is contained in:
casperlamboo 2018-01-10 12:32:55 +01:00
commit e8243d84e3
50 changed files with 1855 additions and 698 deletions

25
.babelrc Normal file
View File

@ -0,0 +1,25 @@
{
"env": {
"module": {
"presets": [
["env", {
"targets": { "node": "6" },
"modules": false
}],
"stage-0",
"react"
]
},
"main": {
"presets": ["env", "stage-0", "react"]
}
},
"plugins": [
"babel-plugin-transform-regenerator",
"babel-plugin-transform-object-rest-spread",
"babel-plugin-inline-import",
"babel-plugin-transform-class-properties",
"babel-plugin-transform-es2015-classes",
"babel-plugin-syntax-dynamic-import"
]
}

33
.eslintrc Normal file
View File

@ -0,0 +1,33 @@
{
"extends": "eslint-config-airbnb",
"parser": "babel-eslint",
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"modules": true,
"jsx": true
},
"rules": {
"comma-dangle": [1, "never"],
"no-else-return": 0,
"no-use-before-define": [2, "nofunc"],
"no-param-reassign": 0,
"no-var": 1,
"no-labels": 0,
"guard-for-in": 0,
"prefer-const": 0,
"no-unused-vars": 1,
"key-spacing": [1, {"beforeColon": false, "afterColon": true, "mode": "minimum"}],
"no-loop-func": 1,
"react/sort-comp": [0],
"max-len": [1, 110, 4],
"camelcase": 1,
"new-cap": 0
},
"env": {
"browser": true,
"es6": true
},
"globals": {
"THREE": false
}
}

View File

@ -15,24 +15,43 @@ const reducer = combineReducers({ sketcher: sketcherReducer });
const enhancer = compose(applyMiddleware(thunkMiddleware, promiseMiddleware(), createLogger({ collapsed: true }))); const enhancer = compose(applyMiddleware(thunkMiddleware, promiseMiddleware(), createLogger({ collapsed: true })));
const store = createStore(reducer, enhancer); const store = createStore(reducer, enhancer);
// prepare html (SHOULDN'T BE DONE LIKE THIS) // add actions to window
document.body.style.margin = 0;
document.body.style.padding = 0;
document.body.style.height = '100%';
document.documentElement.style.height = '100%';
document.documentElement.style.overflow = 'hidden';
document.getElementById('app').style.height = '100%';
import actionWrapper from 'redux-action-wrapper'; import actionWrapper from 'redux-action-wrapper';
import * as actions from './src/actions/index.js'; import * as actions from './src/actions/index.js';
window.actions = actionWrapper(actions, store.dispatch); window.actions = actionWrapper(actions, store.dispatch);
import { saveAs as saveAsLib } from 'file-saver';
import modelData from './models/noodlebot.d3sketch'; // download file
import { createFile } from './src/utils/exportUtils.js';
window.downloadStl = () => {
store.dispatch(async (dispatch, getState) => {
const state = getState();
const blob = await createFile(state.sketcher.present, 'stl-blob');
saveAsLib(blob, 'doodle.stl');
});
};
// add model to store
import modelData from './models/circle_error.d3sketch';
import JSONToSketchData from './src/shape/JSONToSketchData.js'; import JSONToSketchData from './src/shape/JSONToSketchData.js';
(async () => { JSONToSketchData(JSON.parse(modelData)).then(data => {
const data = await JSONToSketchData(JSON.parse(modelData));
store.dispatch(actions.openSketch({ data })); store.dispatch(actions.openSketch({ data }));
})(); });
// default css
import jss from 'jss';
import preset from 'jss-preset-default';
import normalize from 'normalize-jss';
jss.setup(preset());
jss.createStyleSheet(normalize).attach();
jss.createStyleSheet({
'@global': {
'*': { margin: 0, padding: 0 },
'#app, body, html': { height: '100%', fontFamily: 'sans-serif' },
body: { overflow: 'auto' },
html: { overflow: 'hidden' }
}
}).attach();
// render dom // render dom
import React from 'react'; import React from 'react';

1
models/Doodle.d3sketch Normal file
View File

@ -0,0 +1 @@
{"data":"{\"spaces\":[{\"matrix\":{\"metadata\":{\"type\":\"Matrix4\",\"library\":\"three.js\"},\"elements\":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]},\"objects\":[{\"height\":20,\"transform\":{\"metadata\":{\"library\":\"CAL\",\"type\":\"Matrix\"},\"matrix\":[-0.181725074441806,0,77.79195055196584,0,0.1712971976655113,-50.224936115703166]},\"z\":0,\"sculpt\":[{\"pos\":0,\"scale\":1},{\"pos\":1,\"scale\":1}],\"twist\":0,\"fill\":true,\"solid\":true,\"points\":{\"metadata\":{\"type\":\"VectorArray\",\"size\":\"Float32\"},\"data\":\"PUrTQ3sUzENmxklErkeMQ3tU0EPXI7FDAIDQQwAAskMAANBDAAC2QwCAzkMAgLlDAADMQwCAvEMAAMlDAAC/QwCAxUMAgMBDAADCQwAAwUMAAL5DAIDAQwCAukMAAL9DAAC3QwAAvUOFS7ZD7PG7QwAAtUMAgL1DAACyQwAAwEMAgK5DAIDBQwAAq0MAAMJDAACnQwCAwUMAgKNDAADAQwAAoEMAAL5DAACeQwAAu0MAAJxDAIC3QwCAm0MAgLNDM9ObQwo3sUNSOJ/CrkeMQ67HhEPNjMVDrueFQ1J4xENcb4pDPWrCQwr3jkOPYsFD1yOUQ7jewEPNrJhDZubBQ+wxnUOkcMNDwxWhQ9cDxkOkUKRDmhnJQ+GapUNSOM1DSEGmQ+zR0EPhmqVDXO/UQx8Fo0M9ithDXG+gQwCg20Ou55tDXK/dQwBgl0OaOd9DMzOSQ3G930OFq41DmjnfQxRujEMUzt5D4XqzQnvELURSmKxDUjjlQ3E9q0PN7ONDUripQ+Ea4UPDNalDj+LdQ5o5qkMp3NlDuL6rQz0K10NmRq5DAKDUQ+zRsUPXA9NDcV21Q8M10kM9arlDFM7RQ3v0vENxndJDAIDAQ+zR00M9isNDw9XVQ+wRxkMAQNhDexTHQ1J420PDlcdD9kjeQ3sUx0OPguFDXA/FQzNT5EOuB8NDuL7mQ2aGwUPNbOdDAHAfRHvELUT2iNRD4brQQ6Qw1EMKN9FDSIHTQ64H0kNcT9JDM5PSQ3Ed0UPh+tJDAMDPQ7ge00Ncj85D4frSQylczUMzk9JDw1XMQ2bm0UMpfMtDwxXRQ5r5ykOPItBDXM/KQ80Mz0NmJstDpLDNQ66ny0O4vsxDj4LMQxTuy0N7tM1Dj2LLQ2bmzkNxHctD10PQQ5r5ykN7dNFDSEHLQ66n0kP2qMtDPUrTQ3sUzEM=\"},\"holes\":[],\"color\":6939133,\"star\":{\"rays\":5,\"innerRadius\":7.898894154818322,\"outerRadius\":63.507109004739334},\"type\":\"COMPOUND_PATH\"}]}]}","appVersion":"0.17.4"}

View File

@ -0,0 +1 @@
{"data":"{\"spaces\":[{\"matrix\":{\"metadata\":{\"type\":\"Matrix4\",\"library\":\"three.js\"},\"elements\":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]},\"objects\":[{\"height\":20,\"transform\":{\"metadata\":{\"library\":\"CAL\",\"type\":\"Matrix\"},\"matrix\":[1,0,-16.42969984202213,0,1,-10.74249605055293]},\"z\":0,\"sculpt\":[{\"pos\":0,\"scale\":1},{\"pos\":1,\"scale\":1}],\"twist\":0,\"fill\":true,\"solid\":true,\"circle\":{\"radius\":31.061842611644483,\"segment\":6.283185307179586},\"color\":6873597,\"type\":\"CIRCLE\"},{\"height\":20,\"transform\":{\"metadata\":{\"library\":\"CAL\",\"type\":\"Matrix\"},\"matrix\":[1,0,-3.1595576619273373,0,1,-2.5276461295418784]},\"z\":0,\"sculpt\":[{\"pos\":0,\"scale\":1},{\"pos\":1,\"scale\":1}],\"twist\":0,\"fill\":true,\"solid\":false,\"circle\":{\"radius\":30.41228120273779,\"segment\":6.283185307179586},\"color\":6873597,\"type\":\"CIRCLE\"}]}]}","appVersion":"0.17.4"}

View File

@ -0,0 +1 @@
{"data":"{\"spaces\":[{\"matrix\":{\"metadata\":{\"type\":\"Matrix4\",\"library\":\"three.js\"},\"elements\":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]},\"objects\":[{\"height\":21.005096748926302,\"transform\":{\"metadata\":{\"library\":\"CAL\",\"type\":\"Matrix\"},\"matrix\":[1,0,8.214849921011051,0,1,-3.7914691943127927]},\"z\":7.243224684956081,\"sculpt\":[{\"pos\":0,\"scale\":1},{\"pos\":1,\"scale\":1}],\"twist\":0,\"fill\":true,\"solid\":false,\"star\":{\"rays\":5,\"innerRadius\":18.00947867298578,\"outerRadius\":38.54660347551342},\"color\":6873597,\"type\":\"STAR\"},{\"height\":20,\"transform\":{\"metadata\":{\"library\":\"CAL\",\"type\":\"Matrix\"},\"matrix\":[1,0,-14.533965244865726,0,1,7.266982622432863]},\"z\":0,\"sculpt\":[{\"pos\":0,\"scale\":1},{\"pos\":1,\"scale\":1}],\"twist\":0,\"fill\":true,\"solid\":true,\"star\":{\"rays\":5,\"innerRadius\":18.00947867298578,\"outerRadius\":38.54660347551342},\"color\":6873597,\"type\":\"STAR\"}]}]}","appVersion":"0.17.4"}

1549
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@doodle3d/doodle3d-core", "name": "@doodle3d/doodle3d-core",
"version": "0.17.4", "version": "0.17.5",
"description": "Core functions of Doodle3D Transform", "description": "Core functions of Doodle3D Transform",
"main": "lib", "main": "lib",
"module": "module", "module": "module",
@ -9,6 +9,7 @@
"start": "webpack-dev-server -w", "start": "webpack-dev-server -w",
"ios": "TARGET=app webpack -p && cordova run ios", "ios": "TARGET=app webpack -p && cordova run ios",
"prepare": "npm run build", "prepare": "npm run build",
"lint": "eslint src",
"build": "npm run build:main && npm run build:module ", "build": "npm run build:main && npm run build:module ",
"build:main": "BABEL_ENV=main babel src -s -d lib", "build:main": "BABEL_ENV=main babel src -s -d lib",
"build:module": "BABEL_ENV=module babel src -s -d module" "build:module": "BABEL_ENV=module babel src -s -d module"
@ -27,6 +28,9 @@
"bowser": "^1.8.1", "bowser": "^1.8.1",
"fit-curve": "^0.1.6", "fit-curve": "^0.1.6",
"imports-loader": "^0.7.1", "imports-loader": "^0.7.1",
"jss": "^9.4.0",
"keycode": "^2.1.9",
"lodash": "^4.17.4",
"memoizee": "^0.3.9", "memoizee": "^0.3.9",
"pouchdb": "^6.3.4", "pouchdb": "^6.3.4",
"proptypes": "^1.1.0", "proptypes": "^1.1.0",
@ -38,6 +42,7 @@
"react-notification-system-redux": "^1.2.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",
"react-svg-inline": "^2.0.1",
"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",
"reselect": "^3.0.1", "reselect": "^3.0.1",
@ -50,6 +55,7 @@
"devDependencies": { "devDependencies": {
"babel-cli": "6.24.1", "babel-cli": "6.24.1",
"babel-core": "6.24.1", "babel-core": "6.24.1",
"babel-eslint": "^5.0.4",
"babel-loader": "^7.0.0", "babel-loader": "^7.0.0",
"babel-plugin-add-module-exports": "0.2.1", "babel-plugin-add-module-exports": "0.2.1",
"babel-plugin-inline-import": "^2.0.6", "babel-plugin-inline-import": "^2.0.6",
@ -66,12 +72,17 @@
"babel-preset-stage-0": "^6.24.1", "babel-preset-stage-0": "^6.24.1",
"cordova": "^7.1.0", "cordova": "^7.1.0",
"css-loader": "^0.28.7", "css-loader": "^0.28.7",
"eslint": "^1.10.3",
"eslint-config-airbnb": "^3.1.0",
"eslint-plugin-react": "^3.16.1",
"file-saver": "^1.3.3",
"html-webpack-plugin": "^2.30.1", "html-webpack-plugin": "^2.30.1",
"html-webpack-template": "^6.0.2", "html-webpack-template": "^6.0.2",
"jss-preset-default": "^4.0.1",
"normalize-jss": "^4.0.0",
"raw-loader": "^0.5.1", "raw-loader": "^0.5.1",
"react-dom": "^16.1.1", "react-dom": "^16.1.1",
"react-router-redux": "^4.0.8", "react-router-redux": "^4.0.8",
"react-svg-inline": "^2.0.1",
"react-tap-event-plugin": "^3.0.2", "react-tap-event-plugin": "^3.0.2",
"redux": "^3.7.2", "redux": "^3.7.2",
"redux-action-wrapper": "^1.0.1", "redux-action-wrapper": "^1.0.1",

View File

@ -0,0 +1,36 @@
uniform sampler2D mapLeft;
uniform sampler2D mapRight;
varying vec2 vUv;
uniform mat3 colorMatrixLeft;
uniform mat3 colorMatrixRight;
float lin( float c ) {
return c <= 0.04045 ? c * 0.0773993808 :
pow( c * 0.9478672986 + 0.0521327014, 2.4 );
}
vec4 lin( vec4 c ) {
return vec4( lin( c.r ), lin( c.g ), lin( c.b ), c.a );
}
float dev( float c ) {
return c <= 0.0031308 ? c * 12.92 : pow( c, 0.41666 ) * 1.055 - 0.055;
}
void main() {
vec2 uv = vUv;
vec4 colorL = lin( texture2D( mapLeft, uv ) );
vec4 colorR = lin( texture2D( mapRight, uv ) );
vec3 color = clamp(
colorMatrixLeft * colorL.rgb +
colorMatrixRight * colorR.rgb, 0., 1. );
gl_FragColor = vec4(
dev( color.r ), dev( color.g ), dev( color.b ),
max( colorL.a, colorR.a ) );
}

View File

@ -0,0 +1,5 @@
varying vec2 vUv;
void main() {
vUv = vec2( uv.x, uv.y );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}

View File

@ -228,13 +228,13 @@ export function d3ChangeTool(tool) {
export function contextChangeTool(tool) { export function contextChangeTool(tool) {
return { type: CONTEXT_CHANGE_TOOL, tool }; return { type: CONTEXT_CHANGE_TOOL, tool };
} }
export function heightStart(handle) { export function changeHeightStart(handle) {
return { type: HEIGHT_START, handle }; return { type: HEIGHT_START, handle };
} }
export function height(delta) { export function changeHeight(delta) {
return { type: HEIGHT, delta, log: false }; return { type: HEIGHT, delta, log: false };
} }
export function heightEnd() { export function changeHeightEnd() {
return { type: HEIGHT_END }; return { type: HEIGHT_END };
} }
export function twistStart() { export function twistStart() {
@ -378,7 +378,10 @@ export function addImage(file) {
}).catch(error => { }).catch(error => {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
dispatch(notification.error({ position: 'tc', title: 'Error loading image, please try again with another image' })); dispatch(notification.error({
position: 'tc',
title: 'Error loading image, please try again with another image'
}));
throw error; // rethrow for other listeners throw error; // rethrow for other listeners
}); });

View File

@ -13,6 +13,9 @@ import btnUndoImageURL from '../../img/mainmenu/btnUndo.png';
import btnRedoImageURL from '../../img/mainmenu/btnRedo.png'; import btnRedoImageURL from '../../img/mainmenu/btnRedo.png';
import InlineIconsLoader from './InlineIconsLoader.js'; import InlineIconsLoader from './InlineIconsLoader.js';
import JSONToSketchData from '../shape/JSONToSketchData.js'; import JSONToSketchData from '../shape/JSONToSketchData.js';
import keycode from 'keycode';
import bowser from 'bowser';
import * as d2Tools from '../constants/d2Tools.js';
const styles = { const styles = {
container: { container: {
@ -63,7 +66,12 @@ class App extends React.Component {
addImage: PropTypes.func.isRequired, addImage: PropTypes.func.isRequired,
undo: PropTypes.func.isRequired, undo: PropTypes.func.isRequired,
redo: PropTypes.func.isRequired, redo: PropTypes.func.isRequired,
classes: PropTypes.objectOf(PropTypes.string) classes: PropTypes.objectOf(PropTypes.string),
deleteSelection: PropTypes.func.isRequired,
selectAll: PropTypes.func.isRequired,
d2ChangeTool: PropTypes.func.isRequired,
moveSelection: PropTypes.func.isRequired,
selectedPen: PropTypes.string.isRequired
}; };
componentDidMount() { componentDidMount() {
@ -71,6 +79,13 @@ class App extends React.Component {
container.addEventListener('dragover', event => event.preventDefault()); container.addEventListener('dragover', event => event.preventDefault());
container.addEventListener('drop', this.onDrop); container.addEventListener('drop', this.onDrop);
window.addEventListener('keydown', this.onKeyDown);
}
componentWillUnmount() {
const { container } = this.refs;
container.removeEventListener('drop', this.onDrop);
window.removeEventListener('keydown', this.onKeyDown);
} }
onDrop = async event => { onDrop = async event => {
@ -78,9 +93,9 @@ class App extends React.Component {
event.preventDefault(); event.preventDefault();
for (const file of event.dataTransfer.files) { for (const file of event.dataTransfer.files) {
const [name, ...extentions] = file.name.split('.'); const extentions = file.name.split('.').pop();
switch (extentions.pop().toUpperCase()) { switch (extentions.toUpperCase()) {
case 'D3SKETCH': case 'D3SKETCH':
case 'JSON': case 'JSON':
const url = URL.createObjectURL(file); const url = URL.createObjectURL(file);
@ -100,8 +115,65 @@ class App extends React.Component {
} }
}; };
componentWillUnmount() { onKeyDown = (event) => {
container.removeEventListener('drop', this.onDrop); const { undo, redo, deleteSelection, selectAll, d2ChangeTool, moveSelection, selectedPen } = this.props;
const { metaKey, ctrlKey, shiftKey } = event;
const key = keycode(event);
const commandKey = bowser.mac ? metaKey : ctrlKey;
const targetTag = event.target.tagName.toLowerCase();
if (targetTag === 'input' || targetTag === 'textarea') return;
switch (key) {
case 'backspace':
case 'delete':
event.preventDefault();
deleteSelection();
break;
case 'a':
if (commandKey) selectAll();
break;
case 'z':
if (commandKey) {
if (shiftKey) {
redo();
} else {
undo();
}
}
break;
case 't': {
if (!commandKey) d2ChangeTool(d2Tools.TEXT);
break;
}
case 'b': {
if (!commandKey) d2ChangeTool(selectedPen);
break;
}
case 'left':
case 'right':
case 'up':
case 'down': {
const delta = shiftKey ? 10 : 1;
const deltas = {
left: { deltaX: -delta, deltaY: 0 },
right: { deltaX: delta, deltaY: 0 },
up: { deltaX: 0, deltaY: -delta },
down: { deltaX: 0, deltaY: delta }
};
const { deltaX, deltaY } = deltas[key];
moveSelection(deltaX, deltaY);
}
default:
break;
}
} }
render() { render() {
@ -125,9 +197,15 @@ class App extends React.Component {
} }
} }
export default injectSheet(styles)(connect(null, { export default injectSheet(styles)(connect(state => ({
selectedPen: state.sketcher.present.menus['pen-tools'].selected
}), {
undo: actions.undo.undo, undo: actions.undo.undo,
redo: actions.undo.redo, redo: actions.undo.redo,
openSketch: actions.openSketch, openSketch: actions.openSketch,
addImage: actions.addImage, addImage: actions.addImage,
deleteSelection: actions.deleteSelection,
selectAll: actions.selectAll,
d2ChangeTool: actions.d2ChangeTool,
moveSelection: actions.moveSelection
})(App)); })(App));

View File

@ -16,7 +16,7 @@ import TwistTransformer from '../d3/transformers/TwistTransformer.js';
import SculptTransformer from '../d3/transformers/SculptTransformer.js'; import SculptTransformer from '../d3/transformers/SculptTransformer.js';
import StampTransformer from '../d3/transformers/StampTransformer.js'; import StampTransformer from '../d3/transformers/StampTransformer.js';
import SelectionBox from '../d3/SelectionBox.js'; import SelectionBox from '../d3/SelectionBox.js';
import RenderChain from '../d3/RenderChain'; import RenderChain, { TOONSHADER_OUTLINE, TOONSHADER } from '../d3/RenderChain';
import BaseTransformer from '../d3/transformers/BaseTransformer.js'; import BaseTransformer from '../d3/transformers/BaseTransformer.js';
import Camera from '../d3/Camera.js'; import Camera from '../d3/Camera.js';
import ReactResizeDetector from 'react-resize-detector'; import ReactResizeDetector from 'react-resize-detector';
@ -62,7 +62,8 @@ class D3Panel extends React.Component {
componentWillMount() { componentWillMount() {
this.createScene(); this.createScene();
this.renderChain = new RenderChain(this.renderer, this.scene, this.camera, hasExtensionsFor.toonShaderPreview, { const shader = hasExtensionsFor.toonShaderPreview ? TOONSHADER_OUTLINE : TOONSHADER;
this.renderChain = new RenderChain(this.renderer, this.scene, this.camera, shader, {
UI: this.UIContainer, UI: this.UIContainer,
shapes: this.shapesManager, shapes: this.shapesManager,
boundingBox: this.selectionBox, boundingBox: this.selectionBox,

View File

@ -51,7 +51,6 @@ class DoodlePreview extends React.Component {
if (docData) sketchData = await JSONToSketchData(this.props.docData); if (docData) sketchData = await JSONToSketchData(this.props.docData);
const { canvas } = this.refs; const { canvas } = this.refs;
const { pixelRatio } = this.props
const sceneData = createSceneData(sketchData); const sceneData = createSceneData(sketchData);
@ -70,7 +69,7 @@ class DoodlePreview extends React.Component {
resizeHandler = (width, height) => { resizeHandler = (width, height) => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
const { setSize, render } = this.state; const { setSize } = this.state;
const { pixelRatio } = this.props; const { pixelRatio } = this.props;
setSize(width, height, pixelRatio); setSize(width, height, pixelRatio);
}); });

View File

@ -33,7 +33,7 @@ const styles = {
pointerEvents: 'visible' /* enable clicking in children */ pointerEvents: 'visible' /* enable clicking in children */
} }
} }
} };
class SketcherToolbars extends React.Component { class SketcherToolbars extends React.Component {
@ -41,7 +41,8 @@ class SketcherToolbars extends React.Component {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
toolbar2d: PropTypes.object, // TODO: specify further toolbar2d: PropTypes.object, // TODO: specify further
toolbar3d: PropTypes.object, // TODO: specify further toolbar3d: PropTypes.object, // TODO: specify further
context: PropTypes.object // TODO: specify further context: PropTypes.object, // TODO: specify further
classes: PropTypes.objectOf(PropTypes.string)
}; };
onSelect2D = ({ value }) => { onSelect2D = ({ value }) => {
const { dispatch } = this.props; const { dispatch } = this.props;

View File

@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Button from './Button.js'; import Button from './Button.js';
import Menu from './Menu.js'; import Menu from './Menu.js';
import bowser from 'bowser';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { hexToStyle } from '../utils/colorUtils.js'; import { hexToStyle } from '../utils/colorUtils.js';
// import createDebug from 'debug'; // import createDebug from 'debug';
@ -21,7 +20,9 @@ class SubMenu extends React.Component {
svg: PropTypes.string, svg: PropTypes.string,
children: PropTypes.node, children: PropTypes.node,
selectOnOpen: PropTypes.bool, selectOnOpen: PropTypes.bool,
toggleBehavior: PropTypes.bool toggleBehavior: PropTypes.bool,
color: PropTypes.number,
solid: PropTypes.bool
}; };
componentWillMount = () => { componentWillMount = () => {
// Listeners to close the submenu when anywhere else is clicked // Listeners to close the submenu when anywhere else is clicked

View File

@ -1,4 +0,0 @@
import DoodlePreview from './DoodlePreview.js';
import AuthImage from './AuthImage.js';
export { DoodlePreview, AuthImage };

View File

@ -36,33 +36,6 @@ export const BLACK_C = 'color-black-c';
export const HOLE_MATERIAL = 'color-hole-material'; export const HOLE_MATERIAL = 'color-hole-material';
export const PIPETTE = 'pipette-tool'; export const PIPETTE = 'pipette-tool';
export const COLORS = [
LIGHT_BLUE_A,
LIGHT_BLUE_B,
LIGHT_BLUE_C,
DARK_BLUE_A,
DARK_BLUE_B,
DARK_BLUE_C,
PURPLE_A,
PURPLE_B,
PURPLE_C,
PINK_A,
PINK_B,
PINK_C,
RED_A,
RED_B,
RED_C,
YELLOW_A,
YELLOW_B,
YELLOW_C,
GREEN_A,
GREEN_B,
GREEN_C,
BLACK_A,
BLACK_B,
BLACK_C
];
export const ERASER_SIZE_SMALL = 'eraser-size-small'; export const ERASER_SIZE_SMALL = 'eraser-size-small';
export const ERASER_SIZE_MEDIUM = 'eraser-size-medium'; export const ERASER_SIZE_MEDIUM = 'eraser-size-medium';
export const ERASER_SIZE_LARGE = 'eraser-size-large'; export const ERASER_SIZE_LARGE = 'eraser-size-large';

View File

@ -5,28 +5,35 @@ export const SHAPE_CACHE_LIMIT = 50;
export const PIXEL_RATIO = 1.0; export const PIXEL_RATIO = 1.0;
export const COLOR_STRING_TO_HEX = { export const COLOR_STRING_TO_HEX = {
[contextTools.LIGHT_BLUE_A]: 0xbcffff, [contextTools.LIGHT_BLUE_A]: 0xBCFFFF,
[contextTools.LIGHT_BLUE_B]: 0x69e1fd, [contextTools.LIGHT_BLUE_B]: 0x68E1FD,
[contextTools.LIGHT_BLUE_C]: 0x00b8ff, [contextTools.LIGHT_BLUE_C]: 0x01B8FF,
[contextTools.DARK_BLUE_A]: 0xc8e2ff,
[contextTools.DARK_BLUE_B]: 0x7dacfc, [contextTools.DARK_BLUE_A]: 0xC8E3FF,
[contextTools.DARK_BLUE_C]: 0x0357ff, [contextTools.DARK_BLUE_B]: 0x7DACFC,
[contextTools.PURPLE_A]: 0xefc9ff, [contextTools.DARK_BLUE_C]: 0x0256FF,
[contextTools.PURPLE_B]: 0xc57efc,
[contextTools.PURPLE_C]: 0x820ef9, [contextTools.PURPLE_A]: 0xEFC9FF,
[contextTools.PINK_A]: 0xffc7ee, [contextTools.PURPLE_B]: 0xC57EFC,
[contextTools.PINK_B]: 0xfd7cc1, [contextTools.PURPLE_C]: 0x820FF9,
[contextTools.PINK_C]: 0xfa047b,
[contextTools.RED_A]: 0xffcdce, [contextTools.PINK_A]: 0xFFC7EE,
[contextTools.RED_B]: 0xfd898a, [contextTools.PINK_B]: 0xFD7BC1,
[contextTools.RED_C]: 0xfd898a, [contextTools.PINK_C]: 0xFA047B,
[contextTools.YELLOW_A]: 0xfffea0,
[contextTools.YELLOW_B]: 0xfffb39, [contextTools.RED_A]: 0xFFCDCE,
[contextTools.YELLOW_C]: 0xfdac05, [contextTools.RED_B]: 0xFD898A,
[contextTools.GREEN_A]: 0xdaffd4, [contextTools.RED_C]: 0xFF2600,
[contextTools.GREEN_B]: 0x97f194,
[contextTools.GREEN_C]: 0x31d22d, [contextTools.YELLOW_A]: 0xFFF76B,
[contextTools.BLACK_A]: 0xf4f4f4, [contextTools.YELLOW_B]: 0xFF9201,
[contextTools.BLACK_B]: 0x7f7f7f, [contextTools.YELLOW_C]: 0xAA7942,
[contextTools.BLACK_C]: 0x1f1f1f
[contextTools.GREEN_A]: 0xDAFFD5,
[contextTools.GREEN_B]: 0x97F294,
[contextTools.GREEN_C]: 0x00EA01,
[contextTools.BLACK_A]: 0xF4F4F4,
[contextTools.BLACK_B]: 0xAAAAAA,
[contextTools.BLACK_C]: 0x444444
}; };

View File

@ -1,25 +0,0 @@
import * as contextTools from './contextTools.js';
import * as d2Constants from './d2Constants.js';
import * as d2Tools from './d2Tools.js';
import * as d3Constants from './d3Constants.js';
import * as d3Tools from './d3Tools.js';
import * as exportConstants from './exportConstants.js';
import * as general from './general.js';
import * as saveConstants from './saveConstants.js';
import * as shapeTypeProperties from './shapeTypeProperties.js';
import * as mainMenuItems from './mainMenuItems.js';
import * as menu from './menu.js';
export {
contextTool,
d2Constant,
d2Tool,
d3Constant,
d3Tool,
exportConstant,
genera,
saveConstant,
shapeTypePropertie,
mainMenuItems,
menu
};

View File

@ -46,7 +46,7 @@ const toolbar3d = {
children: [ children: [
{ value: d3Tools.HEIGHT }, { value: d3Tools.HEIGHT },
{ value: d3Tools.SCULPT }, { value: d3Tools.SCULPT },
{ value: d3Tools.TWIST }, { value: d3Tools.TWIST }
// { value: d3Tools.STAMP } // { value: d3Tools.STAMP }
] ]
}; };

View File

@ -1,5 +1,5 @@
import { shapeDataToShape, determineActiveShape2d } from '../shape/shapeDataUtils.js'; import { shapeDataToShape, determineActiveShape2d } from '../shape/shapeDataUtils.js';
// import R from 'ramda'; import _ from 'lodash';
export default class ShapesManager { export default class ShapesManager {
constructor(objectContainerActive, objectContainerInactive) { constructor(objectContainerActive, objectContainerInactive) {
@ -21,11 +21,9 @@ export default class ShapesManager {
if ( if (
this._objectsById === objectsById && this._objectsById === objectsById &&
true && _.isEqual(activeShapes, this._activeShapes) &&
state.activeSpace === this._activeSpace state.activeSpace === this._activeSpace
) { ) return needRender;
return needRender;
}
// object ids that are in the current space // object ids that are in the current space
const spaceObjectIds = state.spaces[state.activeSpace].objectIds; const spaceObjectIds = state.spaces[state.activeSpace].objectIds;

View File

@ -12,9 +12,9 @@ export default class MatcapMaterial extends THREE.ShaderMaterial {
constructor({ color = new THREE.Color(), opacity = 1 }) { constructor({ color = new THREE.Color(), opacity = 1 }) {
super({ super({
uniforms: { uniforms: {
"opacity": { type: 'f', value: opacity }, opacity: { type: 'f', value: opacity },
"tMatcap": { type: 't', value: matcapTexture }, tMatcap: { type: 't', value: matcapTexture },
"color": { type: 'vec3', value: new THREE.Vector3() } color: { type: 'vec3', value: new THREE.Vector3() }
}, },
vertexShader: matcapVert, vertexShader: matcapVert,
fragmentShader: matcapFrag fragmentShader: matcapFrag
@ -35,7 +35,8 @@ export default class MatcapMaterial extends THREE.ShaderMaterial {
set opacity(opacity) { set opacity(opacity) {
if (!this.uniforms) return opacity; if (!this.uniforms) return opacity;
return this.uniforms.opacity.value = opacity; this.uniforms.opacity.value = opacity;
return opacity;
} }
clone() { clone() {

39
src/d3/RenderChain.js vendored
View File

@ -1,16 +1,22 @@
import * as THREE from 'three'; import * as THREE from 'three';
import OutlinePass from './OutlinePass.js'; import OutlinePass from './effects/OutlinePass.js';
import RenderPass from './RenderPass.js'; import RenderPass from './effects/RenderPass.js';
import AnaglyphPass from './effects/AnaglyphPass.js';
import 'three/examples/js/shaders/CopyShader.js'; import 'three/examples/js/shaders/CopyShader.js';
import 'three/examples/js/postprocessing/EffectComposer.js'; import 'three/examples/js/postprocessing/EffectComposer.js';
import 'three/examples/js/postprocessing/ShaderPass.js'; import 'three/examples/js/postprocessing/ShaderPass.js';
export const TOONSHADER_OUTLINE = 'toonshader-outline';
export const ANAGLYPH = 'anaglyph';
export const TOONSHADER = 'toonshader';
export default class RenderChain extends THREE.EffectComposer { export default class RenderChain extends THREE.EffectComposer {
constructor(renderer, scene, camera, toonShader, groups) { constructor(renderer, scene, camera, shader, groups) {
super(renderer); super(renderer);
this._groups = groups; this._groups = groups;
if (toonShader) { switch (shader) {
case TOONSHADER_OUTLINE: {
const renderPass = new RenderPass(scene, camera, () => { const renderPass = new RenderPass(scene, camera, () => {
this._setVisible(this._initalValues, [groups.shapes, groups.plane, groups.boundingBox]); this._setVisible(this._initalValues, [groups.shapes, groups.plane, groups.boundingBox]);
}); });
@ -28,16 +34,29 @@ export default class RenderChain extends THREE.EffectComposer {
renderPassUI.clear = false; renderPassUI.clear = false;
renderPassUI.renderToScreen = true; renderPassUI.renderToScreen = true;
this.addPass(renderPassUI); this.addPass(renderPassUI);
} else { break;
}
case ANAGLYPH: {
const anaglyphPass = new AnaglyphPass(scene, camera);
anaglyphPass.renderToScreen = true;
this.addPass(anaglyphPass);
break;
}
case TOONSHADER:
default: {
const renderPass = new RenderPass(scene, camera); const renderPass = new RenderPass(scene, camera);
renderPass.renderToScreen = true; renderPass.renderToScreen = true;
this.addPass(renderPass); this.addPass(renderPass);
break;
}
} }
this._renderer = renderer; this._renderer = renderer;
this._camera = camera; this._camera = camera;
this._scene = scene; this._scene = scene;
this._toonShader = toonShader; this._shader = shader;
} }
_getCurrentVisibleValues() { _getCurrentVisibleValues() {
@ -62,7 +81,7 @@ export default class RenderChain extends THREE.EffectComposer {
} }
} }
setSize(width, height, pixelRatio, rerender = true) { setSize(width, height, pixelRatio, render = true) {
this._renderer.setPixelRatio(pixelRatio); this._renderer.setPixelRatio(pixelRatio);
this._renderer.setSize(width, height); this._renderer.setSize(width, height);
super.setSize(width * pixelRatio, height * pixelRatio); super.setSize(width * pixelRatio, height * pixelRatio);
@ -70,17 +89,17 @@ export default class RenderChain extends THREE.EffectComposer {
this._camera.aspect = width / height; this._camera.aspect = width / height;
this._camera.updateProjectionMatrix(); this._camera.updateProjectionMatrix();
if (rerender) this.render(); if (render) this.render();
} }
render() { render() {
if (this._toonShader) { if (this._shader === TOONSHADER_OUTLINE) {
this._initalValues = this._getCurrentVisibleValues(); this._initalValues = this._getCurrentVisibleValues();
} }
super.render(); super.render();
if (this._toonShader) { if (this._shader === TOONSHADER_OUTLINE) {
const { shapes, UI, plane, boundingBox } = this._groups; const { shapes, UI, plane, boundingBox } = this._groups;
this._setVisible(this._initalValues, [shapes, UI, plane, boundingBox]); this._setVisible(this._initalValues, [shapes, UI, plane, boundingBox]);
} }

61
src/d3/ShapeMesh.js vendored
View File

@ -37,6 +37,7 @@ class ShapeMesh extends THREE.Object3D {
this._holeMesh.name = shapeData.UID; this._holeMesh.name = shapeData.UID;
this._holeMesh.isShapeMesh = true; this._holeMesh.isShapeMesh = true;
this._reverse = shapeData.transform.sx > 0 !== shapeData.transform.sy > 0;
this._sculpt = sculpt; this._sculpt = sculpt;
this._rotate = rotate; this._rotate = rotate;
this._twist = twist; this._twist = twist;
@ -47,12 +48,8 @@ class ShapeMesh extends THREE.Object3D {
this._color = color; this._color = color;
this.updateSolid(solid, active); this.updateSolid(solid, active);
this.updatePoints(shapeData); this.updatePoints(shapeData);
}
_isReverse() { this._shapeData = shapeData;
const sx = this._transform.sx > 0;
const sy = this._transform.sy > 0;
return sx !== sy;
} }
add(object) { add(object) {
@ -66,22 +63,34 @@ class ShapeMesh extends THREE.Object3D {
if (!this._solid) return false; if (!this._solid) return false;
if (holes === this._holes && !this._changedGeometry) return false; if (holes === this._holes && !this._changedGeometry) return false;
this._holeMesh.geometry.dispose(); const fill = this._shapeData.type === 'EXPORT_SHAPE' ? !this._shapeData.originalFill : !this._fill;
if (holes.length === 0 || fill) {
if (this._holeMeshIsOriginal && !this._changedGeometry) return false;
if (holes === null || !this._fill || this._type === 'EXPORT_SHAPE') { this._holeMesh.geometry.dispose();
this._holeMesh.geometry = new THREE.Geometry().fromBufferGeometry(this._mesh.geometry); this._holeMesh.geometry = new THREE.Geometry().fromBufferGeometry(this._mesh.geometry);
this._holeMeshIsOriginal = true;
this._changedGeometry = false;
return true; return true;
} }
const objectGeometry = new THREE.Geometry().fromBufferGeometry(this._mesh.geometry); const objectGeometry = new THREE.Geometry().fromBufferGeometry(this._mesh.geometry);
objectGeometry.mergeVertices(); objectGeometry.mergeVertices();
let objectBSP = new THREE_BSP(objectGeometry); const box = new THREE.Box3().setFromPoints(objectGeometry.vertices);
let bsp = new THREE_BSP(objectGeometry);
objectGeometry.dispose(); objectGeometry.dispose();
objectBSP = objectBSP.subtract(holes);
this._holeMesh.geometry = objectBSP.toMesh().geometry; for (const hole of holes) {
if (hole.box.intersectsBox(box)) {
bsp = bsp.subtract(hole.bsp);
}
}
this._holeMesh.geometry = bsp.toMesh().geometry;
this._holes = holes; this._holes = holes;
this._changedGeometry = false; this._changedGeometry = false;
this._holeMeshIsOriginal = false;
return true; return true;
} }
@ -194,6 +203,12 @@ class ShapeMesh extends THREE.Object3D {
throw new Error(`Cannot update object ${this.name}: transform contains invalid values.`); throw new Error(`Cannot update object ${this.name}: transform contains invalid values.`);
} }
const reverse = this._transform.sx > 0 !== this._transform.sy > 0;
if (reverse !== this._reverse) {
this._reverse = reverse;
this._updateFaces();
}
this._transform = transform; this._transform = transform;
this._z = z; this._z = z;
this._updateVertices(); this._updateVertices();
@ -258,7 +273,6 @@ class ShapeMesh extends THREE.Object3D {
_updateVerticesHorizontal(heightStep, paths, center, indexCounter) { _updateVerticesHorizontal(heightStep, paths, center, indexCounter) {
for (let pathindex = 0; pathindex < paths.length; pathindex ++) { for (let pathindex = 0; pathindex < paths.length; pathindex ++) {
const path = applyMatrixOnPath(paths[pathindex], this._transform); const path = applyMatrixOnPath(paths[pathindex], this._transform);
if (this._isReverse()) path.reverse();
for (let pathIndex = 0; pathIndex < path.length; pathIndex ++) { for (let pathIndex = 0; pathIndex < path.length; pathIndex ++) {
let point = path[pathIndex]; let point = path[pathIndex];
@ -291,7 +305,6 @@ class ShapeMesh extends THREE.Object3D {
for (let pathsIndex = 0; pathsIndex < paths.length; pathsIndex ++) { for (let pathsIndex = 0; pathsIndex < paths.length; pathsIndex ++) {
const path = applyMatrixOnPath(paths[pathsIndex], this._transform); const path = applyMatrixOnPath(paths[pathsIndex], this._transform);
if (this._isReverse()) path.reverse();
for (let pathIndex = 0; pathIndex < path.length; pathIndex ++) { for (let pathIndex = 0; pathIndex < path.length; pathIndex ++) {
const point = path[pathIndex]; const point = path[pathIndex];
@ -405,11 +418,6 @@ class ShapeMesh extends THREE.Object3D {
}) })
.map(path => path.map(({ x, y }) => new THREE.Vector2(x, y))); .map(path => path.map(({ x, y }) => new THREE.Vector2(x, y)));
if (this._isReverse()) {
points.reverse();
holes.map(hole => hole.reverse());
}
// triangulate // triangulate
const triangulatedTop = THREE.ShapeUtils.triangulateShape(points, holes) const triangulatedTop = THREE.ShapeUtils.triangulateShape(points, holes)
.reduce((a, b) => a.concat(b), []) .reduce((a, b) => a.concat(b), [])
@ -419,12 +427,7 @@ class ShapeMesh extends THREE.Object3D {
// reverse index order for bottom so faces are flipped // reverse index order for bottom so faces are flipped
const triangulatedBottom = triangulatedTop const triangulatedBottom = triangulatedTop
.map(value => value + numPoints) .map(value => value + numPoints)
.reverse();
if (this._isReverse()) {
triangulatedTop.reverse();
} else {
triangulatedBottom.reverse();
}
triangulatedIndexes.push(triangulatedBottom.concat(triangulatedTop)); triangulatedIndexes.push(triangulatedBottom.concat(triangulatedTop));
@ -453,6 +456,7 @@ class ShapeMesh extends THREE.Object3D {
const { shape } = this._shapes[i]; const { shape } = this._shapes[i];
if (this._fill) { if (this._fill) {
if (this._reverse) triangulatedIndexes[i].reverse();
for (let j = 0; j < triangulatedIndexes[i].length; j ++) { for (let j = 0; j < triangulatedIndexes[i].length; j ++) {
indexes[indexCounter ++] = triangulatedIndexes[i][j]; indexes[indexCounter ++] = triangulatedIndexes[i][j];
} }
@ -466,6 +470,15 @@ class ShapeMesh extends THREE.Object3D {
let base = (pointIndexOffset + pointIndex) * numHeightSteps + vertexOffsets[i]; let base = (pointIndexOffset + pointIndex) * numHeightSteps + vertexOffsets[i];
for (let heightStep = 0; heightStep < (numHeightSteps - 1); heightStep ++) { for (let heightStep = 0; heightStep < (numHeightSteps - 1); heightStep ++) {
if (this._reverse) {
indexes[indexCounter ++] = base + 1;
indexes[indexCounter ++] = base + numHeightSteps;
indexes[indexCounter ++] = base;
indexes[indexCounter ++] = base + numHeightSteps + 1;
indexes[indexCounter ++] = base + numHeightSteps;
indexes[indexCounter ++] = base + 1;
} else {
indexes[indexCounter ++] = base; indexes[indexCounter ++] = base;
indexes[indexCounter ++] = base + numHeightSteps; indexes[indexCounter ++] = base + numHeightSteps;
indexes[indexCounter ++] = base + 1; indexes[indexCounter ++] = base + 1;
@ -473,7 +486,7 @@ class ShapeMesh extends THREE.Object3D {
indexes[indexCounter ++] = base + 1; indexes[indexCounter ++] = base + 1;
indexes[indexCounter ++] = base + numHeightSteps; indexes[indexCounter ++] = base + numHeightSteps;
indexes[indexCounter ++] = base + numHeightSteps + 1; indexes[indexCounter ++] = base + numHeightSteps + 1;
}
base ++; base ++;
} }
} }

View File

@ -14,7 +14,7 @@ export default class ShapesManager extends THREE.Object3D {
this._spaces = {}; this._spaces = {};
this.name = 'shapes-manager'; this.name = 'shapes-manager';
this._holes = null; this._holes = [];
// this._edges = {}; // this._edges = {};
} }
@ -77,23 +77,33 @@ export default class ShapesManager extends THREE.Object3D {
} }
if (holesChanged) { if (holesChanged) {
this._holes = null; this._holes = [];
for (let i = 0; i < ids.length; i ++) { for (let i = 0; i < ids.length; i ++) {
const id = ids[i]; const id = ids[i];
if (activeShapes[id]) continue;
const { solid, type } = state.objectsById[id]; const { solid, type } = state.objectsById[id];
const d3Visible = SHAPE_TYPE_PROPERTIES[type].D3Visible; const d3Visible = SHAPE_TYPE_PROPERTIES[type].D3Visible;
if (!solid && d3Visible) { if (solid || !d3Visible) continue;
const hole = this._meshes[id].mesh._mesh;
const holeGeometry = new THREE.Geometry().fromBufferGeometry(hole.geometry); const holeMesh = this._meshes[id].mesh._mesh;
if (holeGeometry.vertices.length === 0) continue; const geometry = new THREE.Geometry().fromBufferGeometry(holeMesh.geometry);
const holeBSP = new THREE_BSP(holeGeometry); if (geometry.vertices.length === 0) continue;
if (!this._holes) {
this._holes = holeBSP; const box = new THREE.Box3().setFromPoints(geometry.vertices);
const intersectHole = this._holes.find(hole => hole.box.intersectsBox(box));
const bsp = new THREE_BSP(geometry);
if (intersectHole) {
intersectHole.bsp = intersectHole.bsp.union(bsp);
const min = box.min.min(intersectHole.box.min);
const max = box.max.max(intersectHole.box.max);
intersectHole.box = new THREE.Box3(min, max);
} else { } else {
this._holes = this._holes.union(holeBSP); this._holes.push({ bsp, box });
}
holeGeometry.dispose();
} }
geometry.dispose();
} }
} }
@ -104,9 +114,7 @@ export default class ShapesManager extends THREE.Object3D {
const d3Visible = SHAPE_TYPE_PROPERTIES[type].D3Visible; const d3Visible = SHAPE_TYPE_PROPERTIES[type].D3Visible;
if (!active && solid && d3Visible) { if (!active && solid && d3Visible) {
const shape = this._meshes[id].mesh; const shape = this._meshes[id].mesh;
if (shape.updateHoleGeometry(this._holes)) { if (shape.updateHoleGeometry(this._holes)) render = true;
render = true;
}
} }
} }

View File

@ -1,6 +1,6 @@
import * as THREE from 'three'; import * as THREE from 'three';
import ShapesManager from './ShapesManager.js'; import ShapesManager from './ShapesManager.js';
import RenderChain from './RenderChain.js'; import RenderChain, { TOONSHADER, TOONSHADER_OUTLINE } from './RenderChain.js';
import { hasExtensionsFor } from '../utils/webGLSupport.js'; import { hasExtensionsFor } from '../utils/webGLSupport.js';
import { CANVAS_SIZE } from '../constants/d2Constants.js'; import { CANVAS_SIZE } from '../constants/d2Constants.js';
@ -17,7 +17,7 @@ export default function createScene(state, canvas) {
scene.add(camera); scene.add(camera);
const shapesManager = new ShapesManager({ toonShader: hasExtensionsFor.toonShaderThumbnail }); const shapesManager = new ShapesManager();
shapesManager.update(state); shapesManager.update(state);
scene.add(shapesManager); scene.add(shapesManager);
@ -37,7 +37,8 @@ export default function createScene(state, canvas) {
const renderer = new THREE.WebGLRenderer({ canvas, alpha: true }); const renderer = new THREE.WebGLRenderer({ canvas, alpha: true });
const renderChain = new RenderChain(renderer, scene, camera, hasExtensionsFor.toonShaderThumbnail, { const shader = hasExtensionsFor.toonShaderThumbnail ? TOONSHADER_OUTLINE : TOONSHADER;
const renderChain = new RenderChain(renderer, scene, camera, shader, {
plane, plane,
UI: new THREE.Object3D(), UI: new THREE.Object3D(),
shapes: shapesManager, shapes: shapesManager,

66
src/d3/effects/AnaglyphPass.js vendored Normal file
View File

@ -0,0 +1,66 @@
import * as THREE from 'three';
import anaglyphVert from '../../../shaders/anaglyph_vert.glsl';
import anaglyphFrag from '../../../shaders/anaglyph_frag.glsl';
const COLOR_MATRIX_LEFT = new THREE.Matrix3().fromArray([
1.0671679973602295, -0.0016435992438346148, 0.0001777536963345483,
-0.028107794001698494, -0.00019593400065787137, -0.0002875397040043026,
-0.04279090091586113, 0.000015809757314855233, -0.00024287120322696865
]);
const COLOR_MATRIX_RIGHT = new THREE.Matrix3().fromArray([
-0.0355340838432312, -0.06440307199954987, 0.018319187685847282,
-0.10269022732973099, 0.8079727292060852, -0.04835830628871918,
0.0001224992738571018, -0.009558862075209618, 0.567823588848114
]);
export default class AnaglyphPass {
constructor(scene, camera) {
this.scene = scene;
this.camera = camera;
this.clear = true;
this.renderToScreen = false;
const params = {
minFilter: THREE.LinearFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat
};
this._stereo = new THREE.StereoCamera();
this._renderTargetL = new THREE.WebGLRenderTarget(1, 1, params);
this._renderTargetR = new THREE.WebGLRenderTarget(1, 1, params);
this._material = new THREE.ShaderMaterial({
uniforms: {
mapLeft: { value: this._renderTargetL.texture },
mapRight: { value: this._renderTargetR.texture },
colorMatrixLeft: { value: COLOR_MATRIX_LEFT },
colorMatrixRight: { value: COLOR_MATRIX_RIGHT }
},
vertexShader: anaglyphVert,
fragmentShader: anaglyphFrag
});
this._camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
this._scene = new THREE.Scene();
this._quad = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), this._material);
this._quad.frustumCulled = false;
this._scene.add(this._quad);
}
setSize(width, height, pixelRatio = 1) {
this._renderTargetL.setSize(width * pixelRatio, height * pixelRatio);
this._renderTargetR.setSize(width * pixelRatio, height * pixelRatio);
}
render(renderer, writeBuffer, readBuffer, delta, maskActive) {
this.scene.updateMatrixWorld();
this._stereo.update(this.camera);
renderer.render(this.scene, this._stereo.cameraL, this._renderTargetL, true);
renderer.render(this.scene, this._stereo.cameraR, this._renderTargetR, true);
renderer.render(this._scene, this._camera, this.renderToScreen ? null : readBuffer, this.clear);
}
}

View File

@ -1,10 +1,10 @@
import * as THREE from 'three'; import * as THREE from 'three';
import normalDepthVert from '../../shaders/normal_depth_vert.glsl'; import normalDepthVert from '../../../shaders/normal_depth_vert.glsl';
import normalDepthFrag from '../../shaders/normal_depth_frag.glsl'; import normalDepthFrag from '../../../shaders/normal_depth_frag.glsl';
import edgeVert from '../../shaders/edge_vert.glsl'; import edgeVert from '../../../shaders/edge_vert.glsl';
import edgeFrag from '../../shaders/edge_frag.glsl'; import edgeFrag from '../../../shaders/edge_frag.glsl';
import combineVert from '../../shaders/combine_vert.glsl'; import combineVert from '../../../shaders/combine_vert.glsl';
import combineFrag from '../../shaders/combine_frag.glsl'; import combineFrag from '../../../shaders/combine_frag.glsl';
export default class OutlinePass { export default class OutlinePass {
constructor(scene, camera, callbackBeforeRender) { constructor(scene, camera, callbackBeforeRender) {
@ -26,8 +26,8 @@ export default class OutlinePass {
this._edgeMaterial = new THREE.ShaderMaterial({ this._edgeMaterial = new THREE.ShaderMaterial({
uniforms: { uniforms: {
"tDiffuse": { type: 't', value: this._depthNormalRenderTarget.texture }, tDiffuse: { type: 't', value: this._depthNormalRenderTarget.texture },
"resolution": { type: 'v2', value: new THREE.Vector2() } resolution: { type: 'v2', value: new THREE.Vector2() }
}, },
vertexShader: edgeVert, vertexShader: edgeVert,
fragmentShader: edgeFrag fragmentShader: edgeFrag
@ -35,12 +35,12 @@ export default class OutlinePass {
this._copyEdge = new THREE.ShaderMaterial({ this._copyEdge = new THREE.ShaderMaterial({
uniforms: { uniforms: {
"tDiffuse": { type: 't', value: null }, tDiffuse: { type: 't', value: null },
"uTexArray" : { type: 'tv', value: [this._edgeRenderTarget.texture] } uTexArray : { type: 'tv', value: [this._edgeRenderTarget.texture] }
}, },
vertexShader: combineVert, vertexShader: combineVert,
fragmentShader: combineFrag fragmentShader: combineFrag
}) });
this._camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); this._camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
this._scene = new THREE.Scene(); this._scene = new THREE.Scene();
@ -55,7 +55,7 @@ export default class OutlinePass {
this._edgeMaterial.uniforms.resolution.value.set(width, height); this._edgeMaterial.uniforms.resolution.value.set(width, height);
} }
render(renderer, writeBuffer, readBuffer, delta, maskActive) { render(renderer, writeBuffer, readBuffer) {
if (this._callbackBeforeRender) this._callbackBeforeRender(); if (this._callbackBeforeRender) this._callbackBeforeRender();
this._copyEdge.uniforms.tDiffuse.value = readBuffer.texture; this._copyEdge.uniforms.tDiffuse.value = readBuffer.texture;

7
src/d3/index.js vendored
View File

@ -1,7 +0,0 @@
import createSceneData from './createSceneData.js';
import createScene from './createScene.js';
import RenderChain from './RenderChain.js';
import ShapeMesh from './ShapeMesh.js';
import ShapesManager from './ShapesManager.js';
export { createSceneData, createScene, RenderChain, ToonShaderRenderChain, ShapeMesh, ShapesManager };

View File

@ -38,7 +38,7 @@ export default class HeightTransformer extends BaseTransformer {
dragStart(event) { dragStart(event) {
const handle = this.includesHandle(event.intersections); const handle = this.includesHandle(event.intersections);
if (handle) { if (handle) {
this.dispatch(actions.heightStart(handle)); this.dispatch(actions.changeHeightStart(handle));
} else { } else {
super.dragStart(event); super.dragStart(event);
} }
@ -47,7 +47,7 @@ export default class HeightTransformer extends BaseTransformer {
drag(event) { drag(event) {
if (this._active) { if (this._active) {
const delta = event.position.subtract(event.previousPosition); const delta = event.position.subtract(event.previousPosition);
this.dispatch(actions.height(delta)); this.dispatch(actions.changeHeight(delta));
} else { } else {
super.drag(event); super.drag(event);
} }
@ -55,7 +55,7 @@ export default class HeightTransformer extends BaseTransformer {
dragEnd(event) { dragEnd(event) {
if (this._active) { if (this._active) {
this.dispatch(actions.heightEnd()); this.dispatch(actions.changeHeightEnd());
} else { } else {
super.dragEnd(event); super.dragEnd(event);
} }

View File

@ -1,9 +0,0 @@
import * as shape from './shape/index.js';
import * as utils from './utils/index.js';
import * as d3 from './d3/index.js';
import * as components from './components/index.js';
import * as constants from './constants/index.js';
import * as actions from './actions/index.js';
import * as reducer from './reducer/index.js';
export { shape, utils, d3, components, constants, actions, reducer };

View File

@ -1,6 +1,6 @@
import update from 'react-addons-update'; import update from 'react-addons-update';
import * as contextTools from '../constants/contextTools.js'; import * as contextTools from '../constants/contextTools.js';
import { COLOR_STRING_TO_HEX, COLOR_HEX_TO_STRING } from '../constants/general.js'; import { COLOR_STRING_TO_HEX } from '../constants/general.js';
import { ERASER_SIZES, BRUSH_SIZES } from '../constants/d2Constants.js'; import { ERASER_SIZES, BRUSH_SIZES } from '../constants/d2Constants.js';
import { SHAPE_TYPE_PROPERTIES } from '../constants/shapeTypeProperties.js'; import { SHAPE_TYPE_PROPERTIES } from '../constants/shapeTypeProperties.js';
import * as actions from '../actions/index.js'; import * as actions from '../actions/index.js';

View File

@ -139,6 +139,16 @@ export default function penReducer(state, action) {
.filter(({ distance }) => distance < snappingDistance) .filter(({ distance }) => distance < snappingDistance)
.sort((a, b) => a.distance - b.distance); .sort((a, b) => a.distance - b.distance);
const hits = snappingPoints.map(snappingPoint => snappingPoint.hit);
if (hits.includes('start-end') && hits.includes('end-end')) {
const index = Math.max(hits.indexOf('start-end') && hits.indexOf('end-end'));
snappingPoints.splice(index, 1);
}
if (hits.includes('start-start') && hits.includes('end-start')) {
const index = Math.max(hits.indexOf('start-start') && hits.indexOf('end-start'));
snappingPoints.splice(index, 1);
}
// the active shape's start and end points can only be connected to one other shape, // the active shape's start and end points can only be connected to one other shape,
// this variable can be used to check if the start or end point is already been snapped to // this variable can be used to check if the start or end point is already been snapped to
// when the point has a connection it stores the shape UID of the connected shape // when the point has a connection it stores the shape UID of the connected shape

View File

@ -8,7 +8,7 @@ import { COLOR_STRING_TO_HEX } from '../constants/general.js';
import * as contextTools from '../constants/contextTools.js'; import * as contextTools from '../constants/contextTools.js';
import { ERASER_SIZES, BRUSH_SIZES } from '../constants/d2Constants.js'; import { ERASER_SIZES, BRUSH_SIZES } from '../constants/d2Constants.js';
import update from 'react-addons-update'; import update from 'react-addons-update';
import { defaultCamera, cameraReducer } from './d3/tools/cameraReducer.js'; import { defaultCamera } from './d3/tools/cameraReducer.js';
import d2AddImageReducer from './d2/addImageReducer.js'; import d2AddImageReducer from './d2/addImageReducer.js';
import d2ToolReducer from './d2/toolReducer.js'; import d2ToolReducer from './d2/toolReducer.js';
import d3ToolReducer from './d3/toolReducer.js'; import d3ToolReducer from './d3/toolReducer.js';
@ -19,9 +19,7 @@ import selectionReducer from './selectionReducer.js';
import selectionOperationReducer from './selectionOperationReducer.js'; import selectionOperationReducer from './selectionOperationReducer.js';
import contextReducer from './contextReducer.js'; import contextReducer from './contextReducer.js';
import { Matrix, Vector } from 'cal'; import { Matrix, Vector } from 'cal';
import { import { setActiveSpace, addSpaceActive, setActive2D, getActive2D, addObject } from './objectReducers.js';
setActiveSpace, addSpaceActive, setActive2D, removeAllObjects, getActive2D, addObject
} from './objectReducers.js';
import menusReducer from './menusReducer.js'; import menusReducer from './menusReducer.js';
// import createDebug from 'debug'; // import createDebug from 'debug';
// const debug = createDebug('d3d:reducer:sketcher'); // const debug = createDebug('d3d:reducer:sketcher');

View File

@ -1,7 +0,0 @@
import docToFile from './docToFile.js';
import * as shapeToPoints from './shapeToPoints.js';
import JSONToSketchData from './JSONToSketchData.js';
import SketchDataToJSON from './SketchDataToJSON.js';
import * as shapeDataUtils from './shapeDataUtils.js';
export { docToFile, shapeToPoints, JSONToSketchData, SketchDataToJSON, shapeDataUtils };

View File

@ -96,7 +96,7 @@ export const determineActiveShape3d = (state) => {
state.d3.sculpt.activeHandle !== null || state.d3.sculpt.activeHandle !== null ||
state.d3.twist.active; state.d3.twist.active;
const selectedObjects = state.selection.objects.map(({ id }) => id); // const selectedObjects = state.selection.objects.map(({ id }) => id);
const activeShapes = {}; const activeShapes = {};
for (const id in state.objectsById) { for (const id in state.objectsById) {
activeShapes[id] = activeTransformer || state.d2.activeShape === id; activeShapes[id] = activeTransformer || state.d2.activeShape === id;

View File

@ -83,8 +83,8 @@ function shapeToPointsRaw(shapeData) {
const { radius, segment } = shapeData.circle; const { radius, segment } = shapeData.circle;
const points = []; const points = [];
const circumference = 2 * radius * Math.PI; const circumference = 2 * radius * Math.PI;
const numSegments = Math.min(circumference * 2, 64); const numSegments = Math.max(3, Math.min(circumference * 2, 32));
for (let rad = 0; rad <= segment; rad += Math.PI * 2 / numSegments) { for (let rad = 0; rad < segment; rad += Math.PI * 2 / numSegments) {
const x = Math.sin(rad) * radius; const x = Math.sin(rad) * radius;
const y = -Math.cos(rad) * radius; const y = -Math.cos(rad) * radius;
points.push(new Vector(x, y)); points.push(new Vector(x, y));

View File

@ -52,13 +52,13 @@ export function createThrottle() {
if (!startLoop) return null; if (!startLoop) return null;
return function loop() { return (function loop() {
const promise = next().then(() => { const promise = next().then(() => {
if (typeof next === 'function') return loop(); if (typeof next === 'function') return loop();
}); });
next = true; next = true;
return promise; return promise;
}().then(() => { })().then(() => {
next = null; next = null;
}); });
}; };

View File

@ -20,7 +20,7 @@ export function getDbUrl(db) {
} else { } else {
return `${db.protocol}${db.host}`; return `${db.protocol}${db.host}`;
} }
}; }
const dbs = {}; const dbs = {};
export function getDb(dbUrl) { export function getDb(dbUrl) {

View File

@ -1,4 +1,3 @@
import 'blueimp-canvas-to-blob'; // canvas toBlob polyfill
import { Matrix } from '@doodle3d/cal'; import { Matrix } from '@doodle3d/cal';
import * as exportSTL from '@doodle3d/threejs-export-stl'; import * as exportSTL from '@doodle3d/threejs-export-stl';
import * as exportOBJ from '@doodle3d/threejs-export-obj'; import * as exportOBJ from '@doodle3d/threejs-export-obj';
@ -11,8 +10,6 @@ import { shapeToPoints } from '../shape/shapeToPoints.js';
import { SHAPE_TYPE_PROPERTIES } from '../constants/shapeTypeProperties.js'; import { SHAPE_TYPE_PROPERTIES } from '../constants/shapeTypeProperties.js';
import { LINE_WIDTH } from '../constants/exportConstants.js'; import { LINE_WIDTH } from '../constants/exportConstants.js';
import { bufferToBase64 } from '../utils/binaryUtils.js'; import { bufferToBase64 } from '../utils/binaryUtils.js';
import { IMAGE_TYPE, IMAGE_QUALITY } from '../constants/saveConstants.js';
import createScene from '../d3/createScene.js';
const THREE_BSP = ThreeBSP(THREE); const THREE_BSP = ThreeBSP(THREE);
@ -60,6 +57,7 @@ function createExportShapeData(shapeData, offsetSingleWalls, lineWidth) {
...shapeData, ...shapeData,
transform: new Matrix(), transform: new Matrix(),
type: 'EXPORT_SHAPE', type: 'EXPORT_SHAPE',
originalFill: shapeData.fill,
fill, fill,
shapes shapes
}; };
@ -79,38 +77,42 @@ export function generateExportMesh(state, options = {}) {
}; };
for (const id in state.objectsById) { for (const id in state.objectsById) {
exportState.objectsById[id] = createExportShapeData(state.objectsById[id], offsetSingleWalls || unionGeometry, lineWidth); const shapeData = state.objectsById[id];
if (!SHAPE_TYPE_PROPERTIES[shapeData.type].D3Visible) continue;
const exportShapeData = createExportShapeData(shapeData, offsetSingleWalls || unionGeometry, lineWidth);
exportState.objectsById[id] = exportShapeData;
}
if (Object.keys(exportState.objectsById).length === 0) {
throw new Error('sketch is empty');
} }
const shapesManager = new ShapesManager({ toonShader: false }); const shapesManager = new ShapesManager({ toonShader: false });
shapesManager.update(exportState); shapesManager.update(exportState);
const materials = []; const materials = [];
const objectMatrix = new THREE.Matrix4();
let exportGeometry; let exportGeometry;
shapesManager.traverse(mesh => { shapesManager.traverse(mesh => {
const shapeData = exportState.objectsById[mesh.name]; const shapeData = exportState.objectsById[mesh.name];
if (mesh instanceof THREE.Mesh && shapeData.solid) { if (mesh instanceof THREE.Mesh && shapeData.solid) {
const { geometry, material } = mesh; const { geometry, material } = mesh;
const objectMatrix = state.spaces[shapeData.space].matrix;
let objectGeometry = geometry.clone(); let objectGeometry = geometry.clone();
objectGeometry.mergeVertices(); objectGeometry.mergeVertices();
objectGeometry.applyMatrix(objectMatrix.multiplyMatrices(state.spaces[shapeData.space].matrix, matrix)); objectGeometry.applyMatrix(new THREE.Matrix4().multiplyMatrices(objectMatrix, matrix));
const colorHex = material.color.getHex(); const materialIndex = materials.length;
let materialIndex = materials.findIndex(exportMaterial => exportMaterial.color.getHex() === colorHex); const exportMaterial = new THREE.MeshBasicMaterial({ color: material.color.getHex() });
if (materialIndex === -1) { exportMaterial.side = shapeData.fill ? THREE.FrontSide : THREE.DoubleSide;
materialIndex = materials.length; materials.push(exportMaterial);
materials.push(material);
}
if (unionGeometry) objectGeometry = new THREE_BSP(objectGeometry, materialIndex);
if (unionGeometry) { if (unionGeometry) {
if (!exportGeometry) { objectGeometry = new THREE_BSP(objectGeometry, materialIndex);
exportGeometry = objectGeometry; if (exportGeometry) {
} else {
exportGeometry = exportGeometry.union(objectGeometry); exportGeometry = exportGeometry.union(objectGeometry);
} else {
exportGeometry = objectGeometry;
} }
} else { } else {
if (!exportGeometry) exportGeometry = new THREE.Geometry(); if (!exportGeometry) exportGeometry = new THREE.Geometry();
@ -151,7 +153,7 @@ export async function createFile(state, type, options) {
} }
case 'stl-blob': { case 'stl-blob': {
const buffer = exportSTL.fromMesh(exportMesh, true); const buffer = exportSTL.fromMesh(exportMesh, true);
return new Blob([buffer], { type: 'application/vnd.ms-pki.stl' }) return new Blob([buffer], { type: 'application/vnd.ms-pki.stl' });
} }
case 'obj-blob': { case 'obj-blob': {
const buffer = await exportOBJ.fromMesh(exportMesh); const buffer = await exportOBJ.fromMesh(exportMesh);
@ -166,34 +168,3 @@ export async function createFile(state, type, options) {
throw new Error(`did not regonize type ${type}`); throw new Error(`did not regonize type ${type}`);
} }
} }
export function generateThumb(state, width, height, responseType = 'blob') {
return new Promise((resolve) => {
const { render, renderer, setSize } = createScene(state);
setSize(width, height, 1.0);
render();
// possible to add encoder options for smaller file setSize
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
switch (responseType) {
case 'base64':
const base64 = renderer.domElement.toDataURL(IMAGE_TYPE, IMAGE_QUALITY);
resolve(base64);
break;
case 'objectURL':
renderer.domElement.toCanvas((blob) => {
const objectURL = URL.createObjectURL(blob);
resolve(objectURL);
}, IMAGE_TYPE, IMAGE_QUALITY);
break;
default:
renderer.domElement.toBlob((blob) => {
resolve(blob);
}, IMAGE_TYPE, IMAGE_QUALITY);
break;
}
});
}

View File

@ -0,0 +1,34 @@
import 'blueimp-canvas-to-blob'; // canvas toBlob polyfill
import createScene from '../d3/createScene.js';
import { IMAGE_TYPE, IMAGE_QUALITY } from '../constants/saveConstants.js';
export function generateThumb(state, width, height, responseType = 'blob') {
return new Promise((resolve) => {
const { render, renderer, setSize } = createScene(state);
setSize(width, height, 1.0);
render();
// possible to add encoder options for smaller file setSize
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
switch (responseType) {
case 'base64':
const base64 = renderer.domElement.toDataURL(IMAGE_TYPE, IMAGE_QUALITY);
resolve(base64);
break;
case 'objectURL':
renderer.domElement.toCanvas((blob) => {
const objectURL = URL.createObjectURL(blob);
resolve(objectURL);
}, IMAGE_TYPE, IMAGE_QUALITY);
break;
default:
renderer.domElement.toBlob((blob) => {
resolve(blob);
}, IMAGE_TYPE, IMAGE_QUALITY);
break;
}
});
}

View File

@ -1,11 +0,0 @@
import * as asyncUtils from './async.js';
import * as binaryUtils from './async.js';
import * as dbUtils from './dbUtils.js';
import * as imageUtils from './imageUtils.js';
import * as exportUtils from './exportUtils.js';
import * as webGLSupport from './webGLSupport.js';
import * as vectorUtils from './vectorUtils.js';
import * as textUtils from './textUtils.js';
import * as reactUtils from './reactUtils.js';
export { dbUtils, asyncUtils, imageUtils, exportUtils, webGLSupport, binaryUtils, vectorUtils, textUtils, reactUtils };

View File

@ -45,5 +45,5 @@ export function asyncValidateForm(dispatch, form, asyncValidate, formData) {
dispatch(stopAsyncValidation(form)); dispatch(stopAsyncValidation(form));
}).catch(error => { }).catch(error => {
dispatch(stopAsyncValidation(form, error)); dispatch(stopAsyncValidation(form, error));
}) });
} }

View File

@ -1,13 +1,13 @@
import * as THREE from 'three'; import * as THREE from 'three';
import { shapeToPoints } from '../shape/shapeToPoints.js' import { shapeToPoints } from '../shape/shapeToPoints.js';
import { getPointsBounds } from '../shape/shapeDataUtils.js' import { getPointsBounds } from '../shape/shapeDataUtils.js';
import { Vector } from 'cal'; import { Vector } from 'cal';
import arrayMemoizer from './arrayMemoizer.js'; import arrayMemoizer from './arrayMemoizer.js';
import memoize from 'memoizee'; import memoize from 'memoizee';
// import createDebug from 'debug'; // import createDebug from 'debug';
// const debug = createDebug('d3d:util:selection'); // const debug = createDebug('d3d:util:selection');
// Memoized selector that returns the same array of shapeSata's when // Memoized selector that returns the same array of shapeData's when
// - the selection array didn't change // - the selection array didn't change
// - the objects in the resulting array didn't change // - the objects in the resulting array didn't change
// enables memoization of utils that use this array // enables memoization of utils that use this array

View File

@ -1,5 +1,5 @@
import * as THREE from 'three'; import * as THREE from 'three';
import { loadImage } from './imageUtils.js' import { loadImage } from './imageUtils.js';
import createDebug from 'debug'; import createDebug from 'debug';
const debug = createDebug('d3d:threeUtils'); const debug = createDebug('d3d:threeUtils');

View File

@ -249,78 +249,37 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
#color-light-blue-a { #color-light-blue-a { fill: #BCFFFF; }
fill: #bcffff; #color-light-blue-b { fill: #68E1FD; }
} #color-light-blue-c { fill: #01B8FF; }
#color-light-blue-b {
fill: #69e1fd; #color-dark-blue-a { fill: #C8E3FF; }
} #color-dark-blue-b { fill: #7DACFC; }
#color-light-blue-c { #color-dark-blue-c { fill: #0256FF; }
fill: #00b8ff;
} #color-purple-a { fill: #EFC9FF; }
#color-dark-blue-a { #color-purple-b { fill: #C57EFC; }
fill: #c8e2ff; #color-purple-c { fill: #820FF9; }
}
#color-dark-blue-b { #color-pink-a { fill: #FFC7EE; }
fill: #7dacfc; #color-pink-b { fill: #FD7BC1; }
} #color-pink-c { fill: #FA047B; }
#color-dark-blue-c {
fill: #0357ff; #color-red-a { fill: #FFCDCE; }
} #color-red-b { fill: #FD898A; }
#color-purple-a { #color-red-c { fill: #FF2600; }
fill: #efc9ff;
} #color-yellow-a { fill: #FFF76B; }
#color-purple-b { #color-yellow-b { fill: #FF9201; }
fill: #c57efc; #color-yellow-c { fill: #AA7942; }
}
#color-purple-c { #color-green-a { fill: #DAFFD5; }
fill: #820ef9; #color-green-b { fill: #97F294; }
} #color-green-c { fill: #00EA01; }
#color-pink-a {
fill: #ffc7ee; #color-black-a { fill: #F4F4F4; }
} #color-black-b { fill: #AAAAAA; }
#color-pink-b { #color-black-c { fill: #444444; }
fill: #fd7cc1;
}
#color-pink-c {
fill: #fa047b;
}
#color-red-a {
fill: #ffcdce;
}
#color-red-b {
fill: #fd898a;
}
#color-red-c {
fill: #fd898a;
}
#color-yellow-a {
fill: #fffea0;
}
#color-yellow-b {
fill: #fffb39;
}
#color-yellow-c {
fill: #fdac05;
}
#color-green-a {
fill: #daffd4;
}
#color-green-b {
fill: #97f194;
}
#color-green-c {
fill: #31d22d;
}
#color-black-a {
fill: #f4f4f4;
}
#color-black-b {
fill: #7f7f7f;
}
#color-black-c {
fill: #1f1f1f;
}
#color-hole-material { #color-hole-material {
fill: url(#holepattern); fill: url(#holepattern);

View File

@ -69,7 +69,7 @@ module.exports = {
} }
}, { }, {
test: /\.css$/, test: /\.css$/,
use: [ 'style-loader', 'css-loader' ] use: ['style-loader', 'css-loader']
} }
] ]
}, },

View File

@ -74,9 +74,7 @@ function floodFill(imageData, start, tolerance) {
for (let i = 0; i < neighbours.length; i ++) { for (let i = 0; i < neighbours.length; i ++) {
const neighbourIndex = neighbours[i]; const neighbourIndex = neighbours[i];
if (!done[neighbourIndex]) { if (!done[neighbourIndex]) stack.push(neighbourIndex);
stack.push(neighbourIndex);
}
done[neighbourIndex] = true; done[neighbourIndex] = true;
} }
} }