This commit is contained in:
Robert Long 2018-05-31 23:48:55 -07:00
Родитель 531c09e0a2
Коммит 29aa883e24
34 изменённых файлов: 2710 добавлений и 2916 удалений

Просмотреть файл

@ -26,6 +26,7 @@
"react-router-dom": "^4.2.2",
"react-select": "^1.2.1",
"react-ui-tree": "^3.1.0",
"signals": "^1.0.0",
"source-map-support": "^0.5.6",
"three": "^0.92.0"
},

Просмотреть файл

@ -4,10 +4,10 @@ import styles from "./Editor.scss";
import Modal from "react-modal";
import { Mosaic } from "react-mosaic-component";
export default function Editor({ initialPanels, renderPanel, openModal, onCloseModal }) {
export default function Editor({ initialPanels, renderPanel, openModal, onCloseModal, onPanelChange }) {
return (
<div className={styles.editor}>
<Mosaic className="mosaic-theme" renderTile={renderPanel} initialValue={initialPanels} />
<Mosaic className="mosaic-theme" renderTile={renderPanel} initialValue={initialPanels} onChange={onPanelChange} />
<Modal
isOpen={!!openModal}
onRequestClose={onCloseModal}
@ -25,5 +25,6 @@ Editor.propTypes = {
initialPanels: PropTypes.object.isRequired,
renderPanel: PropTypes.func.isRequired,
openModal: PropTypes.object,
onCloseModal: PropTypes.func
onCloseModal: PropTypes.func,
onPanelChange: PropTypes.func
};

Просмотреть файл

@ -5,7 +5,7 @@ import PanelToolbar from "./PanelToolbar";
export default function Panel({ title, path, toolbarControls, children }) {
return (
<MosaicWindow title={title} path={path} toolbarControls={toolbarControls}>
<MosaicWindow title={title} path={path} toolbarControls={toolbarControls} draggable>
{children}
</MosaicWindow>
);

Просмотреть файл

@ -109,6 +109,10 @@ export default class EditorContainer extends Component {
});
};
onPanelChange = e => {
console.log(e);
};
renderPanel = (panelId, path) => {
const PanelComponent = this.state.registeredPanels[panelId];
return <PanelComponent path={path} />;
@ -121,6 +125,7 @@ export default class EditorContainer extends Component {
renderPanel={this.renderPanel}
openModal={this.state.openModal}
onCloseModal={this.onCloseModal}
onPanelChange={this.onPanelChange}
/>
);
}

Просмотреть файл

@ -0,0 +1,11 @@
import React from "react";
import Editor from "../editor/Editor";
const editor = new Editor();
const EditorContext = React.createContext(editor);
export function withEditor(Component) {
return function EditorContextComponent(props) {
return <EditorContext.Consumer>{editor => <Component {...props} editor={editor} />}</EditorContext.Consumer>;
};
}

Просмотреть файл

@ -1,13 +1,87 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import Panel from "../components/Panel";
import { withEditor } from "./EditorContext";
import Tree from "react-ui-tree";
import "../vendor/react-ui-tree/index.scss";
import classNames from "classnames";
export default class HierarchyPanelContainer extends Component {
function createNodeHierarchy(object) {
return {
object,
collapsed: false,
children: object.children.map(createNodeHierarchy)
};
}
class HierarchyPanelContainer extends Component {
static propTypes = {
path: PropTypes.array
path: PropTypes.array,
editor: PropTypes.object
};
constructor(props) {
super(props);
this.state = {
tree: createNodeHierarchy(props.editor.scene)
};
this.clicked = null;
this.doubleClickTimeout = null;
const editor = this.props.editor;
editor.signals.editorCleared.add(this.rebuildNodeHierarchy);
editor.signals.sceneGraphChanged.add(this.rebuildNodeHierarchy);
editor.signals.objectChanged.add(this.rebuildNodeHierarchy);
editor.signals.objectSelected.add(this.rebuildNodeHierarchy);
}
onChange = node => {
this.ignoreObjectSelectedSignal = true;
this.props.editor.selectById(node.object.id);
this.ignoreObjectSelectedSignal = false;
};
onClickNode = node => {
if (this.clicked === node.object) {
this.props.editor.focusById(node.object.id);
}
this.clicked = node.object;
clearTimeout(this.doubleClickTimeout);
this.doubleClickTimeout = setTimeout(() => {
this.clicked = null;
}, 500);
};
rebuildNodeHierarchy = () => {
this.setState({
tree: createNodeHierarchy(this.props.editor.scene)
});
};
renderNode = node => {
return (
<span
className={classNames("node", {
"is-active": this.props.editor.selected && node.object.id === this.props.editor.selected.id
})}
onClick={() => this.onClickNode(node)}
>
{node.object.name}
</span>
);
};
render() {
return <Panel title="Hierarchy" path={this.props.path} />;
return (
<Panel title="Hierarchy" path={this.props.path}>
<Tree paddingLeft={8} tree={this.state.tree} renderNode={this.renderNode} onChange={this.onChange} />
</Panel>
);
}
}
export default withEditor(HierarchyPanelContainer);

Просмотреть файл

@ -1,88 +1,32 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
window.THREE = require("three");
require("three/examples/js/loaders/GLTFLoader");
import Viewport from "../components/Viewport";
import Panel from "../components/Panel";
import { withEditor } from "./EditorContext";
export default class ViewportPanelContainer extends Component {
class ViewportPanelContainer extends Component {
static propTypes = {
path: PropTypes.array,
gltfURI: PropTypes.string
editor: PropTypes.object
};
constructor(props) {
super(props);
this.canvasRef = React.createRef();
this.renderer = null;
this.editorScene = null;
this.editorCamera = null;
this.scene = null;
}
componentDidUpdate(prevProps) {
if (prevProps.gltfURI !== this.props.gltfURI) {
this.gltfLoader.load(this.props.gltfURI, this.onGLTFLoad, undefined, this.onGLTFLoadError);
}
}
componentDidMount() {
this.initializeThreeScene();
this.renderThreeScene();
window.addEventListener("resize", this.onResize);
}
onResize = () => {
const canvas = this.canvasRef.current;
this.editorCamera.aspect = canvas.offsetWidth / canvas.offsetHeight;
this.editorCamera.updateProjectionMatrix();
this.renderer.setSize(canvas.offsetWidth, canvas.offsetHeight);
this.renderThreeScene();
};
initializeThreeScene() {
this.renderer = new THREE.WebGLRenderer({
canvas: this.canvasRef.current
});
this.editorScene = new THREE.Scene();
this.editorScene.name = "Editor Scene";
this.editorScene = this.editorScene;
window.editorScene = this.editorScene;
this.editorCamera = new THREE.PerspectiveCamera();
this.editorCamera.name = "Editor Camera";
this.editorScene.add(this.editorCamera);
this.scene = new THREE.Scene();
this.gltfLoader = new THREE.GLTFLoader();
}
onGLTFLoad = ({ scene }) => {
this.editorScene.add(scene);
this.scene = scene;
};
onGLTFLoadError = error => {
console.log(error);
};
renderThreeScene() {
this.renderer.render(this.editorScene, this.editorCamera);
this.props.editor.createRenderer(this.canvasRef.current);
}
render() {
return (
<Panel title="Viewport" path={this.props.path}>
<Panel title="Viewport" path={this.props.path} toolbarControls={[]}>
<Viewport ref={this.canvasRef} />
</Panel>
);
}
}
export default withEditor(ViewportPanelContainer);

Просмотреть файл

@ -0,0 +1,31 @@
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
export default class Command {
constructor() {
this.id = -1;
this.inMemory = false;
this.updatable = false;
this.type = "";
this.name = "";
this.editor = Command.editor;
}
toJSON() {
const output = {};
output.type = this.type;
output.id = this.id;
output.name = this.name;
return output;
}
fromJSON(json) {
this.inMemory = true;
this.type = json.type;
this.id = json.id;
this.name = json.name;
}
}

Просмотреть файл

@ -0,0 +1,465 @@
import signals from "signals";
import THREE from "../vendor/three";
import History from "./History";
import Storage from "./Storage";
import Viewport from "./Viewport";
/**
* @author mrdoob / http://mrdoob.com/
*/
export default class Editor {
constructor() {
this.DEFAULT_CAMERA = new THREE.PerspectiveCamera(50, 1, 0.01, 1000);
this.DEFAULT_CAMERA.name = "Camera";
this.DEFAULT_CAMERA.position.set(0, 5, 10);
this.DEFAULT_CAMERA.lookAt(new THREE.Vector3());
const Signal = signals.Signal;
this.signals = {
// script
editScript: new Signal(),
// player
startPlayer: new Signal(),
stopPlayer: new Signal(),
// actions
showModal: new Signal(),
// notifications
editorCleared: new Signal(),
savingStarted: new Signal(),
savingFinished: new Signal(),
themeChanged: new Signal(),
transformModeChanged: new Signal(),
snapChanged: new Signal(),
spaceChanged: new Signal(),
rendererChanged: new Signal(),
sceneBackgroundChanged: new Signal(),
sceneFogChanged: new Signal(),
sceneGraphChanged: new Signal(),
cameraChanged: new Signal(),
geometryChanged: new Signal(),
objectSelected: new Signal(),
objectFocused: new Signal(),
objectAdded: new Signal(),
objectChanged: new Signal(),
objectRemoved: new Signal(),
helperAdded: new Signal(),
helperRemoved: new Signal(),
materialChanged: new Signal(),
scriptAdded: new Signal(),
scriptChanged: new Signal(),
scriptRemoved: new Signal(),
windowResize: new Signal(),
showGridChanged: new Signal(),
refreshSidebarObject3D: new Signal(),
historyChanged: new Signal()
};
this.history = new History(this);
this.storage = new Storage();
this.camera = this.DEFAULT_CAMERA.clone();
this.scene = new THREE.Scene();
this.scene.name = "Scene";
this.scene.background = new THREE.Color(0xaaaaaa);
this.sceneHelpers = new THREE.Scene();
this.object = {};
this.geometries = {};
this.materials = {};
this.textures = {};
this.scripts = {};
this.selected = null;
this.helpers = {};
this.viewport = null;
window.addEventListener("resize", this.onWindowResize, false);
this.onWindowResize();
}
onWindowResize = () => {
this.signals.windowResize.dispatch();
};
setTheme(value) {
document.getElementById("theme").href = value;
this.signals.themeChanged.dispatch(value);
}
createRenderer(canvas) {
this.canvas = canvas;
const renderer = new THREE.WebGLRenderer({
canvas
});
this.viewport = new Viewport(this);
this.signals.rendererChanged.dispatch(renderer);
}
//
setScene(scene) {
this.scene.uuid = scene.uuid;
this.scene.name = scene.name;
if (scene.background !== null) this.scene.background = scene.background.clone();
if (scene.fog !== null) this.scene.fog = scene.fog.clone();
this.scene.userData = JSON.parse(JSON.stringify(scene.userData));
// avoid render per object
this.signals.sceneGraphChanged.active = false;
while (scene.children.length > 0) {
this.addObject(scene.children[0]);
}
this.signals.sceneGraphChanged.active = true;
this.signals.sceneGraphChanged.dispatch();
}
//
addObject(object) {
const scope = this;
object.traverse(function(child) {
if (child.geometry !== undefined) scope.addGeometry(child.geometry);
if (child.material !== undefined) scope.addMaterial(child.material);
scope.addHelper(child);
});
this.scene.add(object);
this.signals.objectAdded.dispatch(object);
this.signals.sceneGraphChanged.dispatch();
}
moveObject(object, parent, before) {
if (parent === undefined) {
parent = this.scene;
}
parent.add(object);
// sort children array
if (before !== undefined) {
const index = parent.children.indexOf(before);
parent.children.splice(index, 0, object);
parent.children.pop();
}
this.signals.sceneGraphChanged.dispatch();
}
nameObject(object, name) {
object.name = name;
this.signals.sceneGraphChanged.dispatch();
}
removeObject(object) {
if (object.parent === null) return; // avoid deleting the camera or scene
const scope = this;
object.traverse(function(child) {
scope.removeHelper(child);
});
object.parent.remove(object);
this.signals.objectRemoved.dispatch(object);
this.signals.sceneGraphChanged.dispatch();
}
addGeometry(geometry) {
this.geometries[geometry.uuid] = geometry;
}
setGeometryName(geometry, name) {
geometry.name = name;
this.signals.sceneGraphChanged.dispatch();
}
addMaterial(material) {
this.materials[material.uuid] = material;
}
setMaterialName(material, name) {
material.name = name;
this.signals.sceneGraphChanged.dispatch();
}
addTexture(texture) {
this.textures[texture.uuid] = texture;
}
//
addHelper = (function() {
const geometry = new THREE.SphereBufferGeometry(2, 4, 2);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, visible: false });
return function(object) {
let helper;
if (object instanceof THREE.Camera) {
helper = new THREE.CameraHelper(object, 1);
} else if (object instanceof THREE.PointLight) {
helper = new THREE.PointLightHelper(object, 1);
} else if (object instanceof THREE.DirectionalLight) {
helper = new THREE.DirectionalLightHelper(object, 1);
} else if (object instanceof THREE.SpotLight) {
helper = new THREE.SpotLightHelper(object, 1);
} else if (object instanceof THREE.HemisphereLight) {
helper = new THREE.HemisphereLightHelper(object, 1);
} else if (object instanceof THREE.SkinnedMesh) {
helper = new THREE.SkeletonHelper(object);
} else {
// no helper for this object type
return;
}
const picker = new THREE.Mesh(geometry, material);
picker.name = "picker";
picker.userData.object = object;
helper.add(picker);
this.sceneHelpers.add(helper);
this.helpers[object.id] = helper;
this.signals.helperAdded.dispatch(helper);
};
})();
removeHelper(object) {
if (this.helpers[object.id] !== undefined) {
const helper = this.helpers[object.id];
helper.parent.remove(helper);
delete this.helpers[object.id];
this.signals.helperRemoved.dispatch(helper);
}
}
//
addScript(object, script) {
if (this.scripts[object.uuid] === undefined) {
this.scripts[object.uuid] = [];
}
this.scripts[object.uuid].push(script);
this.signals.scriptAdded.dispatch(script);
}
removeScript(object, script) {
if (this.scripts[object.uuid] === undefined) return;
const index = this.scripts[object.uuid].indexOf(script);
if (index !== -1) {
this.scripts[object.uuid].splice(index, 1);
}
this.signals.scriptRemoved.dispatch(script);
}
getObjectMaterial(object, slot) {
let material = object.material;
if (Array.isArray(material)) {
material = material[slot];
}
return material;
}
setObjectMaterial(object, slot, newMaterial) {
if (Array.isArray(object.material)) {
object.material[slot] = newMaterial;
} else {
object.material = newMaterial;
}
}
//
select(object) {
if (this.selected === object) return;
let uuid = null;
if (object !== null) {
uuid = object.uuid;
}
this.selected = object;
this.signals.objectSelected.dispatch(object);
}
selectById(id) {
if (id === this.scene.id) {
this.select(this.scene);
return;
}
if (id === this.camera.id) {
this.select(this.camera);
return;
}
this.select(this.scene.getObjectById(id, true));
}
selectByUuid(uuid) {
const scope = this;
this.scene.traverse(function(child) {
if (child.uuid === uuid) {
scope.select(child);
}
});
}
deselect() {
this.select(null);
}
focus(object) {
this.signals.objectFocused.dispatch(object);
}
focusById(id) {
this.focus(this.scene.getObjectById(id, true));
}
clear() {
this.history.clear();
this.storage.clear();
this.camera.copy(this.DEFAULT_CAMERA);
this.scene.background.setHex(0xaaaaaa);
this.scene.fog = null;
const objects = this.scene.children;
while (objects.length > 0) {
this.removeObject(objects[0]);
}
this.geometries = {};
this.materials = {};
this.textures = {};
this.scripts = {};
this.deselect();
this.signals.editorCleared.dispatch();
}
//
fromJSON(json) {
const loader = new THREE.ObjectLoader();
// backwards
if (json.scene === undefined) {
this.setScene(loader.parse(json));
return;
}
const camera = loader.parse(json.camera);
this.camera.copy(camera);
this.camera.aspect = this.DEFAULT_CAMERA.aspect;
this.camera.updateProjectionMatrix();
this.history.fromJSON(json.history);
this.scripts = json.scripts;
this.setScene(loader.parse(json.scene));
}
toJSON() {
// scripts clean up
const scene = this.scene;
const scripts = this.scripts;
for (const key in scripts) {
const script = scripts[key];
if (script.length === 0 || scene.getObjectByProperty("uuid", key) === undefined) {
delete scripts[key];
}
}
//
return {
metadata: {},
project: {
gammaInput: this.config.getKey("project/renderer/gammaInput"),
gammaOutput: this.config.getKey("project/renderer/gammaOutput"),
shadows: this.config.getKey("project/renderer/shadows"),
vr: this.config.getKey("project/vr")
},
camera: this.camera.toJSON(),
scene: this.scene.toJSON(),
scripts: this.scripts,
history: this.history.toJSON()
};
}
objectByUuid(uuid) {
return this.scene.getObjectByProperty("uuid", uuid, true);
}
execute(cmd, optionalName) {
this.history.execute(cmd, optionalName);
}
undo() {
this.history.undo();
}
redo() {
this.history.redo();
}
}

Просмотреть файл

@ -0,0 +1,251 @@
import Command from "./Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
export default class History {
constructor(editor) {
this.editor = editor;
this.undos = [];
this.redos = [];
this.lastCmdTime = new Date();
this.idCounter = 0;
this.historyDisabled = false;
this.config = editor.config;
//Set editor-reference in Command
Command.editor = editor;
// signals
const scope = this;
this.editor.signals.startPlayer.add(function() {
scope.historyDisabled = true;
});
this.editor.signals.stopPlayer.add(function() {
scope.historyDisabled = false;
});
}
execute(cmd, optionalName) {
const lastCmd = this.undos[this.undos.length - 1];
const timeDifference = new Date().getTime() - this.lastCmdTime.getTime();
const isUpdatableCmd =
lastCmd &&
lastCmd.updatable &&
cmd.updatable &&
lastCmd.object === cmd.object &&
lastCmd.type === cmd.type &&
lastCmd.script === cmd.script &&
lastCmd.attributeName === cmd.attributeName;
if (isUpdatableCmd && cmd.type === "SetScriptValueCommand") {
// When the cmd.type is "SetScriptValueCommand" the timeDifference is ignored
lastCmd.update(cmd);
cmd = lastCmd;
} else if (isUpdatableCmd && timeDifference < 500) {
lastCmd.update(cmd);
cmd = lastCmd;
} else {
// the command is not updatable and is added as a new part of the history
this.undos.push(cmd);
cmd.id = ++this.idCounter;
}
cmd.name = optionalName !== undefined ? optionalName : cmd.name;
cmd.execute();
cmd.inMemory = true;
if (this.config.getKey("settings/history")) {
cmd.json = cmd.toJSON(); // serialize the cmd immediately after execution and append the json to the cmd
}
this.lastCmdTime = new Date();
// clearing all the redo-commands
this.redos = [];
this.editor.signals.historyChanged.dispatch(cmd);
}
undo() {
if (this.historyDisabled) {
alert("Undo/Redo disabled while scene is playing.");
return;
}
let cmd = undefined;
if (this.undos.length > 0) {
cmd = this.undos.pop();
if (cmd.inMemory === false) {
cmd.fromJSON(cmd.json);
}
}
if (cmd !== undefined) {
cmd.undo();
this.redos.push(cmd);
this.editor.signals.historyChanged.dispatch(cmd);
}
return cmd;
}
redo() {
if (this.historyDisabled) {
alert("Undo/Redo disabled while scene is playing.");
return;
}
let cmd = undefined;
if (this.redos.length > 0) {
cmd = this.redos.pop();
if (cmd.inMemory === false) {
cmd.fromJSON(cmd.json);
}
}
if (cmd !== undefined) {
cmd.execute();
this.undos.push(cmd);
this.editor.signals.historyChanged.dispatch(cmd);
}
return cmd;
}
toJSON() {
const history = {};
history.undos = [];
history.redos = [];
if (!this.config.getKey("settings/history")) {
return history;
}
// Append Undos to History
for (let i = 0; i < this.undos.length; i++) {
if (this.undos[i].hasOwnProperty("json")) {
history.undos.push(this.undos[i].json);
}
}
// Append Redos to History
for (let i = 0; i < this.redos.length; i++) {
if (this.redos[i].hasOwnProperty("json")) {
history.redos.push(this.redos[i].json);
}
}
return history;
}
fromJSON(json) {
if (json === undefined) return;
for (let i = 0; i < json.undos.length; i++) {
const cmdJSON = json.undos[i];
const cmd = new window[cmdJSON.type](); // creates a new object of type "json.type"
cmd.json = cmdJSON;
cmd.id = cmdJSON.id;
cmd.name = cmdJSON.name;
this.undos.push(cmd);
this.idCounter = cmdJSON.id > this.idCounter ? cmdJSON.id : this.idCounter; // set last used idCounter
}
for (let i = 0; i < json.redos.length; i++) {
const cmdJSON = json.redos[i];
const cmd = new window[cmdJSON.type](); // creates a new object of type "json.type"
cmd.json = cmdJSON;
cmd.id = cmdJSON.id;
cmd.name = cmdJSON.name;
this.redos.push(cmd);
this.idCounter = cmdJSON.id > this.idCounter ? cmdJSON.id : this.idCounter; // set last used idCounter
}
// Select the last executed undo-command
this.editor.signals.historyChanged.dispatch(this.undos[this.undos.length - 1]);
}
clear() {
this.undos = [];
this.redos = [];
this.idCounter = 0;
this.editor.signals.historyChanged.dispatch();
}
goToState(id) {
if (this.historyDisabled) {
alert("Undo/Redo disabled while scene is playing.");
return;
}
this.editor.signals.sceneGraphChanged.active = false;
this.editor.signals.historyChanged.active = false;
let cmd = this.undos.length > 0 ? this.undos[this.undos.length - 1] : undefined; // next cmd to pop
if (cmd === undefined || id > cmd.id) {
cmd = this.redo();
while (cmd !== undefined && id > cmd.id) {
cmd = this.redo();
}
} else {
// eslint-disable-next-line
while (true) {
cmd = this.undos[this.undos.length - 1]; // next cmd to pop
if (cmd === undefined || id === cmd.id) break;
this.undo();
}
}
this.editor.signals.sceneGraphChanged.active = true;
this.editor.signals.historyChanged.active = true;
this.editor.signals.sceneGraphChanged.dispatch();
this.editor.signals.historyChanged.dispatch(cmd);
}
enableSerialization(id) {
/**
* because there might be commands in this.undos and this.redos
* which have not been serialized with .toJSON() we go back
* to the oldest command and redo one command after the other
* while also calling .toJSON() on them.
*/
this.goToState(-1);
this.editor.signals.sceneGraphChanged.active = false;
this.editor.signals.historyChanged.active = false;
let cmd = this.redo();
while (cmd !== undefined) {
if (!cmd.hasOwnProperty("json")) {
cmd.json = cmd.toJSON();
}
cmd = this.redo();
}
this.editor.signals.sceneGraphChanged.active = true;
this.editor.signals.historyChanged.active = true;
this.goToState(id);
}
}

Просмотреть файл

@ -0,0 +1,74 @@
/**
* @author mrdoob / http://mrdoob.com/
*/
const Storage = function() {
const indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
if (indexedDB === undefined) {
console.warn("Storage: IndexedDB not available.");
return { init: function() {}, get: function() {}, set: function() {}, clear: function() {} };
}
const name = "threejs-editor";
const version = 1;
let database;
return {
init: function(callback) {
const request = indexedDB.open(name, version);
request.onupgradeneeded = function(event) {
const db = event.target.result;
if (db.objectStoreNames.contains("states") === false) {
db.createObjectStore("states");
}
};
request.onsuccess = function(event) {
database = event.target.result;
callback();
};
request.onerror = function(event) {
console.error("IndexedDB", event);
};
},
get: function(callback) {
const transaction = database.transaction(["states"], "readwrite");
const objectStore = transaction.objectStore("states");
const request = objectStore.get(0);
request.onsuccess = function(event) {
callback(event.target.result);
};
},
set: function(data) {
const start = performance.now();
const transaction = database.transaction(["states"], "readwrite");
const objectStore = transaction.objectStore("states");
const request = objectStore.put(data, 0);
request.onsuccess = function() {
console.log(
"[" + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + "]",
"Saved state to IndexedDB. " + (performance.now() - start).toFixed(2) + "ms"
);
};
},
clear: function() {
if (database === undefined) return;
const transaction = database.transaction(["states"], "readwrite");
const objectStore = transaction.objectStore("states");
const request = objectStore.clear();
request.onsuccess = function() {
console.log("[" + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + "]", "Cleared IndexedDB.");
};
}
};
};
export default Storage;

Просмотреть файл

@ -0,0 +1,398 @@
import THREE from "../vendor/three";
import SetPositionCommand from "./commands/SetPositionCommand";
import SetRotationCommand from "./commands/SetRotationCommand";
import SetScaleCommand from "./commands/SetScaleCommand";
/**
* @author mrdoob / http://mrdoob.com/
*/
export default class Viewport {
constructor(editor) {
const signals = editor.signals;
//
let renderer = null;
const canvas = editor.canvas;
const camera = editor.camera;
const scene = editor.scene;
const sceneHelpers = editor.sceneHelpers;
const objects = [];
// helpers
const grid = new THREE.GridHelper(30, 30, 0x444444, 0x888888);
sceneHelpers.add(grid);
const array = grid.geometry.attributes.color.array;
for (let i = 0; i < array.length; i += 60) {
for (let j = 0; j < 12; j++) {
array[i + j] = 0.26;
}
}
//
const box = new THREE.Box3();
const selectionBox = new THREE.BoxHelper();
selectionBox.material.depthTest = false;
selectionBox.material.transparent = true;
selectionBox.visible = false;
sceneHelpers.add(selectionBox);
let objectPositionOnDown = null;
let objectRotationOnDown = null;
let objectScaleOnDown = null;
function render() {
sceneHelpers.updateMatrixWorld();
scene.updateMatrixWorld();
renderer.render(scene, camera);
renderer.render(sceneHelpers, camera);
}
const transformControls = new THREE.TransformControls(camera, canvas);
transformControls.addEventListener("change", function() {
const object = transformControls.object;
if (object !== undefined) {
selectionBox.setFromObject(object);
if (editor.helpers[object.id] !== undefined) {
editor.helpers[object.id].update();
}
signals.refreshSidebarObject3D.dispatch(object);
}
render();
});
sceneHelpers.add(transformControls);
// object picking
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// events
function getIntersects(point, objects) {
mouse.set(point.x * 2 - 1, -(point.y * 2) + 1);
raycaster.setFromCamera(mouse, camera);
return raycaster.intersectObjects(objects);
}
const onDownPosition = new THREE.Vector2();
const onUpPosition = new THREE.Vector2();
const onDoubleClickPosition = new THREE.Vector2();
function getMousePosition(dom, x, y) {
const rect = dom.getBoundingClientRect();
return [(x - rect.left) / rect.width, (y - rect.top) / rect.height];
}
function handleClick() {
if (onDownPosition.distanceTo(onUpPosition) === 0) {
const intersects = getIntersects(onUpPosition, objects);
if (intersects.length > 0) {
const object = intersects[0].object;
if (object.userData.object !== undefined) {
// helper
editor.select(object.userData.object);
} else {
editor.select(object);
}
} else {
editor.select(null);
}
render();
}
}
function onMouseUp(event) {
const array = getMousePosition(canvas, event.clientX, event.clientY);
onUpPosition.fromArray(array);
handleClick();
document.removeEventListener("mouseup", onMouseUp, false);
}
function onMouseDown(event) {
event.preventDefault();
const array = getMousePosition(canvas, event.clientX, event.clientY);
onDownPosition.fromArray(array);
document.addEventListener("mouseup", onMouseUp, false);
}
function onTouchEnd(event) {
const touch = event.changedTouches[0];
const array = getMousePosition(canvas, touch.clientX, touch.clientY);
onUpPosition.fromArray(array);
handleClick();
document.removeEventListener("touchend", onTouchEnd, false);
}
function onTouchStart(event) {
const touch = event.changedTouches[0];
const array = getMousePosition(canvas, touch.clientX, touch.clientY);
onDownPosition.fromArray(array);
document.addEventListener("touchend", onTouchEnd, false);
}
function onDoubleClick(event) {
const array = getMousePosition(canvas, event.clientX, event.clientY);
onDoubleClickPosition.fromArray(array);
const intersects = getIntersects(onDoubleClickPosition, objects);
if (intersects.length > 0) {
const intersect = intersects[0];
signals.objectFocused.dispatch(intersect.object);
}
}
canvas.addEventListener("mousedown", onMouseDown, false);
canvas.addEventListener("touchstart", onTouchStart, false);
canvas.addEventListener("dblclick", onDoubleClick, false);
// controls need to be added *after* main logic,
// otherwise controls.enabled doesn't work.
const controls = new THREE.EditorControls(camera, canvas);
controls.addEventListener("change", function() {
transformControls.update();
signals.cameraChanged.dispatch(camera);
});
transformControls.addEventListener("mouseDown", function() {
const object = transformControls.object;
objectPositionOnDown = object.position.clone();
objectRotationOnDown = object.rotation.clone();
objectScaleOnDown = object.scale.clone();
controls.enabled = false;
});
transformControls.addEventListener("mouseUp", function() {
const object = transformControls.object;
if (object !== undefined) {
switch (transformControls.getMode()) {
case "translate":
if (!objectPositionOnDown.equals(object.position)) {
editor.execute(new SetPositionCommand(object, object.position, objectPositionOnDown));
}
break;
case "rotate":
if (!objectRotationOnDown.equals(object.rotation)) {
editor.execute(new SetRotationCommand(object, object.rotation, objectRotationOnDown));
}
break;
case "scale":
if (!objectScaleOnDown.equals(object.scale)) {
editor.execute(new SetScaleCommand(object, object.scale, objectScaleOnDown));
}
break;
}
}
controls.enabled = true;
});
// signals
signals.editorCleared.add(function() {
controls.center.set(0, 0, 0);
render();
});
signals.transformModeChanged.add(function(mode) {
transformControls.setMode(mode);
});
signals.snapChanged.add(function(dist) {
transformControls.setTranslationSnap(dist);
});
signals.spaceChanged.add(function(space) {
transformControls.setSpace(space);
});
signals.rendererChanged.add(function(newRenderer) {
renderer = newRenderer;
renderer.autoClear = false;
renderer.autoUpdateScene = false;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(canvas.offsetWidth, canvas.offsetHeight);
render();
});
signals.sceneGraphChanged.add(function() {
render();
});
signals.cameraChanged.add(function() {
render();
});
signals.objectSelected.add(function(object) {
selectionBox.visible = false;
transformControls.detach();
if (object !== null && object !== scene && object !== camera) {
box.setFromObject(object);
if (box.isEmpty() === false) {
selectionBox.setFromObject(object);
selectionBox.visible = true;
}
transformControls.attach(object);
}
render();
});
signals.objectFocused.add(function(object) {
controls.focus(object);
});
signals.geometryChanged.add(function(object) {
if (object !== undefined) {
selectionBox.setFromObject(object);
}
render();
});
signals.objectAdded.add(function(object) {
object.traverse(function(child) {
objects.push(child);
});
});
signals.objectChanged.add(function(object) {
if (editor.selected === object) {
selectionBox.setFromObject(object);
transformControls.update();
}
if (object instanceof THREE.PerspectiveCamera) {
object.updateProjectionMatrix();
}
if (editor.helpers[object.id] !== undefined) {
editor.helpers[object.id].update();
}
render();
});
signals.objectRemoved.add(function(object) {
object.traverse(function(child) {
objects.splice(objects.indexOf(child), 1);
});
});
signals.helperAdded.add(function(object) {
objects.push(object.getObjectByName("picker"));
});
signals.helperRemoved.add(function(object) {
objects.splice(objects.indexOf(object.getObjectByName("picker")), 1);
});
signals.materialChanged.add(function() {
render();
});
// fog
signals.sceneBackgroundChanged.add(function(backgroundColor) {
scene.background.setHex(backgroundColor);
render();
});
let currentFogType = null;
signals.sceneFogChanged.add(function(fogType, fogColor, fogNear, fogFar, fogDensity) {
if (currentFogType !== fogType) {
switch (fogType) {
case "None":
scene.fog = null;
break;
case "Fog":
scene.fog = new THREE.Fog();
break;
case "FogExp2":
scene.fog = new THREE.FogExp2();
break;
}
currentFogType = fogType;
}
if (scene.fog instanceof THREE.Fog) {
scene.fog.color.setHex(fogColor);
scene.fog.near = fogNear;
scene.fog.far = fogFar;
} else if (scene.fog instanceof THREE.FogExp2) {
scene.fog.color.setHex(fogColor);
scene.fog.density = fogDensity;
}
render();
});
//
signals.windowResize.add(function() {
// TODO: Move this out?
editor.DEFAULT_CAMERA.aspect = canvas.offsetWidth / canvas.offsetHeight;
editor.DEFAULT_CAMERA.updateProjectionMatrix();
camera.aspect = canvas.offsetWidth / canvas.offsetHeight;
camera.updateProjectionMatrix();
renderer.setSize(canvas.offsetWidth, canvas.offsetHeight);
render();
});
signals.showGridChanged.add(function(showGrid) {
grid.visible = showGrid;
render();
});
}
}

Просмотреть файл

@ -0,0 +1,52 @@
import THREE from "../../vendor/three";
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @constructor
*/
export default class AddObjectCommand extends Command {
constructor(object) {
super();
this.type = "AddObjectCommand";
this.object = object;
if (object !== undefined) {
this.name = "Add Object: " + object.name;
}
}
execute() {
this.editor.addObject(this.object);
this.editor.select(this.object);
}
undo() {
this.editor.removeObject(this.object);
this.editor.deselect();
}
toJSON() {
const output = super.toJSON();
output.object = this.object.toJSON();
return output;
}
fromJSON(json) {
super.fromJSON(json);
this.object = this.editor.objectByUuid(json.object.object.uuid);
if (this.object === undefined) {
const loader = new THREE.ObjectLoader();
this.object = loader.parse(json.object);
}
}
}

Просмотреть файл

@ -0,0 +1,89 @@
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param newParent THREE.Object3D
* @param newBefore THREE.Object3D
* @constructor
*/
const MoveObjectCommand = function(object, newParent, newBefore) {
Command.call(this);
this.type = "MoveObjectCommand";
this.name = "Move Object";
this.object = object;
this.oldParent = object !== undefined ? object.parent : undefined;
this.oldIndex = this.oldParent !== undefined ? this.oldParent.children.indexOf(this.object) : undefined;
this.newParent = newParent;
if (newBefore !== undefined) {
this.newIndex = newParent !== undefined ? newParent.children.indexOf(newBefore) : undefined;
} else {
this.newIndex = newParent !== undefined ? newParent.children.length : undefined;
}
if (this.oldParent === this.newParent && this.newIndex > this.oldIndex) {
this.newIndex--;
}
this.newBefore = newBefore;
};
MoveObjectCommand.prototype = {
execute: function() {
this.oldParent.remove(this.object);
const children = this.newParent.children;
children.splice(this.newIndex, 0, this.object);
this.object.parent = this.newParent;
this.editor.signals.sceneGraphChanged.dispatch();
},
undo: function() {
this.newParent.remove(this.object);
const children = this.oldParent.children;
children.splice(this.oldIndex, 0, this.object);
this.object.parent = this.oldParent;
this.editor.signals.sceneGraphChanged.dispatch();
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.newParentUuid = this.newParent.uuid;
output.oldParentUuid = this.oldParent.uuid;
output.newIndex = this.newIndex;
output.oldIndex = this.oldIndex;
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.object = this.editor.objectByUuid(json.objectUuid);
this.oldParent = this.editor.objectByUuid(json.oldParentUuid);
if (this.oldParent === undefined) {
this.oldParent = this.editor.scene;
}
this.newParent = this.editor.objectByUuid(json.newParentUuid);
if (this.newParent === undefined) {
this.newParent = this.editor.scene;
}
this.newIndex = json.newIndex;
this.oldIndex = json.oldIndex;
}
};
export default MoveObjectCommand;

Просмотреть файл

@ -0,0 +1,69 @@
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param cmdArray array containing command objects
* @constructor
*/
const MultiCmdsCommand = function(cmdArray) {
Command.call(this);
this.type = "MultiCmdsCommand";
this.name = "Multiple Changes";
this.cmdArray = cmdArray !== undefined ? cmdArray : [];
};
MultiCmdsCommand.prototype = {
execute: function() {
this.editor.signals.sceneGraphChanged.active = false;
for (let i = 0; i < this.cmdArray.length; i++) {
this.cmdArray[i].execute();
}
this.editor.signals.sceneGraphChanged.active = true;
this.editor.signals.sceneGraphChanged.dispatch();
},
undo: function() {
this.editor.signals.sceneGraphChanged.active = false;
for (let i = this.cmdArray.length - 1; i >= 0; i--) {
this.cmdArray[i].undo();
}
this.editor.signals.sceneGraphChanged.active = true;
this.editor.signals.sceneGraphChanged.dispatch();
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
const cmds = [];
for (let i = 0; i < this.cmdArray.length; i++) {
cmds.push(this.cmdArray[i].toJSON());
}
output.cmds = cmds;
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
const cmds = json.cmds;
for (let i = 0; i < cmds.length; i++) {
const cmd = new window[cmds[i].type](); // creates a new object of type "json.type"
cmd.fromJSON(cmds[i]);
this.cmdArray.push(cmd);
}
}
};
export default MultiCmdsCommand;

Просмотреть файл

@ -0,0 +1,85 @@
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @constructor
*/
const RemoveObjectCommand = function(object) {
Command.call(this);
this.type = "RemoveObjectCommand";
this.name = "Remove Object";
this.object = object;
this.parent = object !== undefined ? object.parent : undefined;
if (this.parent !== undefined) {
this.index = this.parent.children.indexOf(this.object);
}
};
RemoveObjectCommand.prototype = {
execute: function() {
const scope = this.editor;
this.object.traverse(function(child) {
scope.removeHelper(child);
});
this.parent.remove(this.object);
this.editor.select(this.parent);
this.editor.signals.objectRemoved.dispatch(this.object);
this.editor.signals.sceneGraphChanged.dispatch();
},
undo: function() {
const scope = this.editor;
this.object.traverse(function(child) {
if (child.geometry !== undefined) scope.addGeometry(child.geometry);
if (child.material !== undefined) scope.addMaterial(child.material);
scope.addHelper(child);
});
this.parent.children.splice(this.index, 0, this.object);
this.object.parent = this.parent;
this.editor.select(this.object);
this.editor.signals.objectAdded.dispatch(this.object);
this.editor.signals.sceneGraphChanged.dispatch();
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.object = this.object.toJSON();
output.index = this.index;
output.parentUuid = this.parent.uuid;
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.parent = this.editor.objectByUuid(json.parentUuid);
if (this.parent === undefined) {
this.parent = this.editor.scene;
}
this.index = json.index;
this.object = this.editor.objectByUuid(json.object.object.uuid);
if (this.object === undefined) {
const loader = new THREE.ObjectLoader();
this.object = loader.parse(json.object);
}
}
};
export default RemoveObjectCommand;

Просмотреть файл

@ -0,0 +1,67 @@
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param script javascript object
* @constructor
*/
const RemoveScriptCommand = function(object, script) {
Command.call(this);
this.type = "RemoveScriptCommand";
this.name = "Remove Script";
this.object = object;
this.script = script;
if (this.object && this.script) {
this.index = this.editor.scripts[this.object.uuid].indexOf(this.script);
}
};
RemoveScriptCommand.prototype = {
execute: function() {
if (this.editor.scripts[this.object.uuid] === undefined) return;
if (this.index !== -1) {
this.editor.scripts[this.object.uuid].splice(this.index, 1);
}
this.editor.signals.scriptRemoved.dispatch(this.script);
},
undo: function() {
if (this.editor.scripts[this.object.uuid] === undefined) {
this.editor.scripts[this.object.uuid] = [];
}
this.editor.scripts[this.object.uuid].splice(this.index, 0, this.script);
this.editor.signals.scriptAdded.dispatch(this.script);
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.script = this.script;
output.index = this.index;
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.script = json.script;
this.index = json.index;
this.object = this.editor.objectByUuid(json.objectUuid);
}
};
export default RemoveScriptCommand;

Просмотреть файл

@ -0,0 +1,64 @@
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param attributeName string
* @param newValue integer representing a hex color value
* @constructor
*/
const SetColorCommand = function(object, attributeName, newValue) {
Command.call(this);
this.type = "SetColorCommand";
this.name = "Set " + attributeName;
this.updatable = true;
this.object = object;
this.attributeName = attributeName;
this.oldValue = object !== undefined ? this.object[this.attributeName].getHex() : undefined;
this.newValue = newValue;
};
SetColorCommand.prototype = {
execute: function() {
this.object[this.attributeName].setHex(this.newValue);
this.editor.signals.objectChanged.dispatch(this.object);
},
undo: function() {
this.object[this.attributeName].setHex(this.oldValue);
this.editor.signals.objectChanged.dispatch(this.object);
},
update: function(cmd) {
this.newValue = cmd.newValue;
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.attributeName = this.attributeName;
output.oldValue = this.oldValue;
output.newValue = this.newValue;
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.object = this.editor.objectByUuid(json.objectUuid);
this.attributeName = json.attributeName;
this.oldValue = json.oldValue;
this.newValue = json.newValue;
}
};
export default SetColorCommand;

Просмотреть файл

@ -0,0 +1,75 @@
import THREE from "../../vendor/three";
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param newGeometry THREE.Geometry
* @constructor
*/
const SetGeometryCommand = function(object, newGeometry) {
Command.call(this);
this.type = "SetGeometryCommand";
this.name = "Set Geometry";
this.updatable = true;
this.object = object;
this.oldGeometry = object !== undefined ? object.geometry : undefined;
this.newGeometry = newGeometry;
};
SetGeometryCommand.prototype = {
execute: function() {
this.object.geometry.dispose();
this.object.geometry = this.newGeometry;
this.object.geometry.computeBoundingSphere();
this.editor.signals.geometryChanged.dispatch(this.object);
this.editor.signals.sceneGraphChanged.dispatch();
},
undo: function() {
this.object.geometry.dispose();
this.object.geometry = this.oldGeometry;
this.object.geometry.computeBoundingSphere();
this.editor.signals.geometryChanged.dispatch(this.object);
this.editor.signals.sceneGraphChanged.dispatch();
},
update: function(cmd) {
this.newGeometry = cmd.newGeometry;
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.oldGeometry = this.object.geometry.toJSON();
output.newGeometry = this.newGeometry.toJSON();
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.object = this.editor.objectByUuid(json.objectUuid);
function parseGeometry(data) {
const loader = new THREE.ObjectLoader();
return loader.parseGeometries([data])[data.uuid];
}
this.oldGeometry = parseGeometry(json.oldGeometry);
this.newGeometry = parseGeometry(json.newGeometry);
}
};
export default SetGeometryCommand;

Просмотреть файл

@ -0,0 +1,63 @@
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param attributeName string
* @param newValue number, string, boolean or object
* @constructor
*/
const SetGeometryValueCommand = function(object, attributeName, newValue) {
Command.call(this);
this.type = "SetGeometryValueCommand";
this.name = "Set Geometry." + attributeName;
this.object = object;
this.attributeName = attributeName;
this.oldValue = object !== undefined ? object.geometry[attributeName] : undefined;
this.newValue = newValue;
};
SetGeometryValueCommand.prototype = {
execute: function() {
this.object.geometry[this.attributeName] = this.newValue;
this.editor.signals.objectChanged.dispatch(this.object);
this.editor.signals.geometryChanged.dispatch();
this.editor.signals.sceneGraphChanged.dispatch();
},
undo: function() {
this.object.geometry[this.attributeName] = this.oldValue;
this.editor.signals.objectChanged.dispatch(this.object);
this.editor.signals.geometryChanged.dispatch();
this.editor.signals.sceneGraphChanged.dispatch();
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.attributeName = this.attributeName;
output.oldValue = this.oldValue;
output.newValue = this.newValue;
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.object = this.editor.objectByUuid(json.objectUuid);
this.attributeName = json.attributeName;
this.oldValue = json.oldValue;
this.newValue = json.newValue;
}
};
export default SetGeometryValueCommand;

Просмотреть файл

@ -0,0 +1,69 @@
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param attributeName string
* @param newValue integer representing a hex color value
* @constructor
*/
const SetMaterialColorCommand = function(object, attributeName, newValue, materialSlot) {
Command.call(this);
this.type = "SetMaterialColorCommand";
this.name = "Set Material." + attributeName;
this.updatable = true;
this.object = object;
this.material = this.editor.getObjectMaterial(object, materialSlot);
this.oldValue = this.material !== undefined ? this.material[attributeName].getHex() : undefined;
this.newValue = newValue;
this.attributeName = attributeName;
};
SetMaterialColorCommand.prototype = {
execute: function() {
this.material[this.attributeName].setHex(this.newValue);
this.editor.signals.materialChanged.dispatch(this.material);
},
undo: function() {
this.material[this.attributeName].setHex(this.oldValue);
this.editor.signals.materialChanged.dispatch(this.material);
},
update: function(cmd) {
this.newValue = cmd.newValue;
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.attributeName = this.attributeName;
output.oldValue = this.oldValue;
output.newValue = this.newValue;
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.object = this.editor.objectByUuid(json.objectUuid);
this.attributeName = json.attributeName;
this.oldValue = json.oldValue;
this.newValue = json.newValue;
}
};
export default SetMaterialColorCommand;

Просмотреть файл

@ -0,0 +1,66 @@
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param newMaterial THREE.Material
* @constructor
*/
const SetMaterialCommand = function(object, newMaterial, materialSlot) {
Command.call(this);
this.type = "SetMaterialCommand";
this.name = "New Material";
this.object = object;
this.materialSlot = materialSlot;
this.oldMaterial = this.editor.getObjectMaterial(object, materialSlot);
this.newMaterial = newMaterial;
};
SetMaterialCommand.prototype = {
execute: function() {
this.editor.setObjectMaterial(this.object, this.materialSlot, this.newMaterial);
this.editor.signals.materialChanged.dispatch(this.newMaterial);
},
undo: function() {
this.editor.setObjectMaterial(this.object, this.materialSlot, this.oldMaterial);
this.editor.signals.materialChanged.dispatch(this.oldMaterial);
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.oldMaterial = this.oldMaterial.toJSON();
output.newMaterial = this.newMaterial.toJSON();
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.object = this.editor.objectByUuid(json.objectUuid);
function parseMaterial(json) {
const loader = new THREE.ObjectLoader();
const images = loader.parseImages(json.images);
const textures = loader.parseTextures(json.textures, images);
const materials = loader.parseMaterials([json], textures);
return materials[json.uuid];
}
this.oldMaterial = parseMaterial(json.oldMaterial);
this.newMaterial = parseMaterial(json.newMaterial);
}
};
export default SetMaterialCommand;

Просмотреть файл

@ -0,0 +1,114 @@
import THREE from "../../vendor/three";
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param mapName string
* @param newMap THREE.Texture
* @constructor
*/
const SetMaterialMapCommand = function(object, mapName, newMap, materialSlot) {
Command.call(this);
this.type = "SetMaterialMapCommand";
this.name = "Set Material." + mapName;
this.object = object;
this.material = this.editor.getObjectMaterial(object, materialSlot);
this.oldMap = object !== undefined ? this.material[mapName] : undefined;
this.newMap = newMap;
this.mapName = mapName;
};
SetMaterialMapCommand.prototype = {
execute: function() {
this.material[this.mapName] = this.newMap;
this.material.needsUpdate = true;
this.editor.signals.materialChanged.dispatch(this.material);
},
undo: function() {
this.material[this.mapName] = this.oldMap;
this.material.needsUpdate = true;
this.editor.signals.materialChanged.dispatch(this.material);
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
// Note: The function 'extractFromCache' is copied from Object3D.toJSON()
// extract data from the cache hash
// remove metadata on each item
// and return as array
function extractFromCache(cache) {
const values = [];
for (const key in cache) {
const data = cache[key];
delete data.metadata;
values.push(data);
}
return values;
}
// serializes a map (THREE.Texture)
function serializeMap(map) {
if (map === null || map === undefined) return null;
const meta = {
geometries: {},
materials: {},
textures: {},
images: {}
};
const json = map.toJSON(meta);
const images = extractFromCache(meta.images);
if (images.length > 0) json.images = images;
json.sourceFile = map.sourceFile;
return json;
}
output.objectUuid = this.object.uuid;
output.mapName = this.mapName;
output.newMap = serializeMap(this.newMap);
output.oldMap = serializeMap(this.oldMap);
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
function parseTexture(json) {
let map = null;
if (json !== null) {
const loader = new THREE.ObjectLoader();
const images = loader.parseImages(json.images);
const textures = loader.parseTextures([json], images);
map = textures[json.uuid];
map.sourceFile = json.sourceFile;
}
return map;
}
this.object = this.editor.objectByUuid(json.objectUuid);
this.mapName = json.mapName;
this.oldMap = parseTexture(json.oldMap);
this.newMap = parseTexture(json.newMap);
}
};
export default SetMaterialMapCommand;

Просмотреть файл

@ -0,0 +1,73 @@
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param attributeName string
* @param newValue number, string, boolean or object
* @constructor
*/
const SetMaterialValueCommand = function(object, attributeName, newValue, materialSlot) {
Command.call(this);
this.type = "SetMaterialValueCommand";
this.name = "Set Material." + attributeName;
this.updatable = true;
this.object = object;
this.material = this.editor.getObjectMaterial(object, materialSlot);
this.oldValue = this.material !== undefined ? this.material[attributeName] : undefined;
this.newValue = newValue;
this.attributeName = attributeName;
};
SetMaterialValueCommand.prototype = {
execute: function() {
this.material[this.attributeName] = this.newValue;
this.material.needsUpdate = true;
this.editor.signals.objectChanged.dispatch(this.object);
this.editor.signals.materialChanged.dispatch(this.material);
},
undo: function() {
this.material[this.attributeName] = this.oldValue;
this.material.needsUpdate = true;
this.editor.signals.objectChanged.dispatch(this.object);
this.editor.signals.materialChanged.dispatch(this.material);
},
update: function(cmd) {
this.newValue = cmd.newValue;
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.attributeName = this.attributeName;
output.oldValue = this.oldValue;
output.newValue = this.newValue;
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.attributeName = json.attributeName;
this.oldValue = json.oldValue;
this.newValue = json.newValue;
this.object = this.editor.objectByUuid(json.objectUuid);
}
};
export default SetMaterialValueCommand;

Просмотреть файл

@ -0,0 +1,71 @@
import THREE from "../../vendor/three";
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param newPosition THREE.Vector3
* @param optionalOldPosition THREE.Vector3
* @constructor
*/
const SetPositionCommand = function(object, newPosition, optionalOldPosition) {
Command.call(this);
this.type = "SetPositionCommand";
this.name = "Set Position";
this.updatable = true;
this.object = object;
if (object !== undefined && newPosition !== undefined) {
this.oldPosition = object.position.clone();
this.newPosition = newPosition.clone();
}
if (optionalOldPosition !== undefined) {
this.oldPosition = optionalOldPosition.clone();
}
};
SetPositionCommand.prototype = {
execute: function() {
this.object.position.copy(this.newPosition);
this.object.updateMatrixWorld(true);
this.editor.signals.objectChanged.dispatch(this.object);
},
undo: function() {
this.object.position.copy(this.oldPosition);
this.object.updateMatrixWorld(true);
this.editor.signals.objectChanged.dispatch(this.object);
},
update: function(command) {
this.newPosition.copy(command.newPosition);
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.oldPosition = this.oldPosition.toArray();
output.newPosition = this.newPosition.toArray();
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.object = this.editor.objectByUuid(json.objectUuid);
this.oldPosition = new THREE.Vector3().fromArray(json.oldPosition);
this.newPosition = new THREE.Vector3().fromArray(json.newPosition);
}
};
export default SetPositionCommand;

Просмотреть файл

@ -0,0 +1,71 @@
import THREE from "../../vendor/three";
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param newRotation THREE.Euler
* @param optionalOldRotation THREE.Euler
* @constructor
*/
const SetRotationCommand = function(object, newRotation, optionalOldRotation) {
Command.call(this);
this.type = "SetRotationCommand";
this.name = "Set Rotation";
this.updatable = true;
this.object = object;
if (object !== undefined && newRotation !== undefined) {
this.oldRotation = object.rotation.clone();
this.newRotation = newRotation.clone();
}
if (optionalOldRotation !== undefined) {
this.oldRotation = optionalOldRotation.clone();
}
};
SetRotationCommand.prototype = {
execute: function() {
this.object.rotation.copy(this.newRotation);
this.object.updateMatrixWorld(true);
this.editor.signals.objectChanged.dispatch(this.object);
},
undo: function() {
this.object.rotation.copy(this.oldRotation);
this.object.updateMatrixWorld(true);
this.editor.signals.objectChanged.dispatch(this.object);
},
update: function(command) {
this.newRotation.copy(command.newRotation);
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.oldRotation = this.oldRotation.toArray();
output.newRotation = this.newRotation.toArray();
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.object = this.editor.objectByUuid(json.objectUuid);
this.oldRotation = new THREE.Euler().fromArray(json.oldRotation);
this.newRotation = new THREE.Euler().fromArray(json.newRotation);
}
};
export default SetRotationCommand;

Просмотреть файл

@ -0,0 +1,71 @@
import THREE from "../../vendor/three";
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param newScale THREE.Vector3
* @param optionalOldScale THREE.Vector3
* @constructor
*/
const SetScaleCommand = function(object, newScale, optionalOldScale) {
Command.call(this);
this.type = "SetScaleCommand";
this.name = "Set Scale";
this.updatable = true;
this.object = object;
if (object !== undefined && newScale !== undefined) {
this.oldScale = object.scale.clone();
this.newScale = newScale.clone();
}
if (optionalOldScale !== undefined) {
this.oldScale = optionalOldScale.clone();
}
};
SetScaleCommand.prototype = {
execute: function() {
this.object.scale.copy(this.newScale);
this.object.updateMatrixWorld(true);
this.editor.signals.objectChanged.dispatch(this.object);
},
undo: function() {
this.object.scale.copy(this.oldScale);
this.object.updateMatrixWorld(true);
this.editor.signals.objectChanged.dispatch(this.object);
},
update: function(command) {
this.newScale.copy(command.newScale);
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.oldScale = this.oldScale.toArray();
output.newScale = this.newScale.toArray();
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.object = this.editor.objectByUuid(json.objectUuid);
this.oldScale = new THREE.Vector3().fromArray(json.oldScale);
this.newScale = new THREE.Vector3().fromArray(json.newScale);
}
};
export default SetScaleCommand;

Просмотреть файл

@ -0,0 +1,83 @@
import SetUuidCommand from "./SetUuidCommand";
import SetValueCommand from "./SetValueCommand";
import AddObjectCommand from "./AddObjectCommand";
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param scene containing children to import
* @constructor
*/
const SetSceneCommand = function(scene) {
Command.call(this);
this.type = "SetSceneCommand";
this.name = "Set Scene";
this.cmdArray = [];
if (scene !== undefined) {
this.cmdArray.push(new SetUuidCommand(this.editor.scene, scene.uuid));
this.cmdArray.push(new SetValueCommand(this.editor.scene, "name", scene.name));
this.cmdArray.push(new SetValueCommand(this.editor.scene, "userData", JSON.parse(JSON.stringify(scene.userData))));
while (scene.children.length > 0) {
const child = scene.children.pop();
this.cmdArray.push(new AddObjectCommand(child));
}
}
};
SetSceneCommand.prototype = {
execute: function() {
this.editor.signals.sceneGraphChanged.active = false;
for (let i = 0; i < this.cmdArray.length; i++) {
this.cmdArray[i].execute();
}
this.editor.signals.sceneGraphChanged.active = true;
this.editor.signals.sceneGraphChanged.dispatch();
},
undo: function() {
this.editor.signals.sceneGraphChanged.active = false;
for (let i = this.cmdArray.length - 1; i >= 0; i--) {
this.cmdArray[i].undo();
}
this.editor.signals.sceneGraphChanged.active = true;
this.editor.signals.sceneGraphChanged.dispatch();
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
const cmds = [];
for (let i = 0; i < this.cmdArray.length; i++) {
cmds.push(this.cmdArray[i].toJSON());
}
output.cmds = cmds;
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
const cmds = json.cmds;
for (let i = 0; i < cmds.length; i++) {
const cmd = new window[cmds[i].type](); // creates a new object of type "json.type"
cmd.fromJSON(cmds[i]);
this.cmdArray.push(cmd);
}
}
};
export default SetSceneCommand;

Просмотреть файл

@ -0,0 +1,61 @@
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param newUuid string
* @constructor
*/
const SetUuidCommand = function(object, newUuid) {
Command.call(this);
this.type = "SetUuidCommand";
this.name = "Update UUID";
this.object = object;
this.oldUuid = object !== undefined ? object.uuid : undefined;
this.newUuid = newUuid;
};
SetUuidCommand.prototype = {
execute: function() {
this.object.uuid = this.newUuid;
this.editor.signals.objectChanged.dispatch(this.object);
this.editor.signals.sceneGraphChanged.dispatch();
},
undo: function() {
this.object.uuid = this.oldUuid;
this.editor.signals.objectChanged.dispatch(this.object);
this.editor.signals.sceneGraphChanged.dispatch();
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.oldUuid = this.oldUuid;
output.newUuid = this.newUuid;
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.oldUuid = json.oldUuid;
this.newUuid = json.newUuid;
this.object = this.editor.objectByUuid(json.oldUuid);
if (this.object === undefined) {
this.object = this.editor.objectByUuid(json.newUuid);
}
}
};
export default SetUuidCommand;

Просмотреть файл

@ -0,0 +1,66 @@
import Command from "../Command";
/**
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
/**
* @param object THREE.Object3D
* @param attributeName string
* @param newValue number, string, boolean or object
* @constructor
*/
const SetValueCommand = function(object, attributeName, newValue) {
Command.call(this);
this.type = "SetValueCommand";
this.name = "Set " + attributeName;
this.updatable = true;
this.object = object;
this.attributeName = attributeName;
this.oldValue = object !== undefined ? object[attributeName] : undefined;
this.newValue = newValue;
};
SetValueCommand.prototype = {
execute: function() {
this.object[this.attributeName] = this.newValue;
this.editor.signals.objectChanged.dispatch(this.object);
// this.editor.signals.sceneGraphChanged.dispatch();
},
undo: function() {
this.object[this.attributeName] = this.oldValue;
this.editor.signals.objectChanged.dispatch(this.object);
// this.editor.signals.sceneGraphChanged.dispatch();
},
update: function(cmd) {
this.newValue = cmd.newValue;
},
toJSON: function() {
const output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.attributeName = this.attributeName;
output.oldValue = this.oldValue;
output.newValue = this.newValue;
return output;
},
fromJSON: function(json) {
Command.prototype.fromJSON.call(this, json);
this.attributeName = json.attributeName;
this.oldValue = json.oldValue;
this.newValue = json.newValue;
this.object = this.editor.objectByUuid(json.objectUuid);
}
};
export default SetValueCommand;

66
src/renderer/vendor/react-ui-tree/index.scss поставляемый Normal file
Просмотреть файл

@ -0,0 +1,66 @@
@import "../../theme";
:global {
.f-no-select {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.m-tree {
position: relative;
overflow: hidden;
width: 100%;
padding-right: 3px;
}
.m-draggable {
position: absolute;
opacity: 0.8;
}
.m-node {
display: inline-block;
width: 100%;
&.placeholder > * {
visibility: hidden;
}
&.placeholder {
border: 1px dashed $blue;
}
.inner {
position: relative;
cursor: pointer;
padding-left: 10px;
}
.node {
display: inline-block;
width: 100%;
padding: 4px 5px;
&.is-active {
background-color: $selected;
}
}
.collapse {
position: absolute;
left: 0;
cursor: pointer;
}
.caret-right:before {
content: "\25B8";
}
.caret-down:before {
content: "\25BE";
}
}
}

2846
src/renderer/vendor/three/GLTFLoader.js поставляемый

Разница между файлами не показана из-за своего большого размера Загрузить разницу

6
src/renderer/vendor/three/index.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,6 @@
window.THREE = require("three");
require("three/examples/js/controls/EditorControls");
require("three/examples/js/controls/TransformControls");
require("three/examples/js/loaders/GLTFLoader");
export default THREE;

Просмотреть файл

@ -6725,6 +6725,10 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
signals@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/signals/-/signals-1.0.0.tgz#65f0c1599352b35372ecaae5a250e6107376ed69"
single-line-log@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364"