ShadowEditor核心类库。

This commit is contained in:
liteng 2018-11-16 20:38:26 +08:00
parent a63c43992c
commit 67a953e41d
341 changed files with 30039 additions and 0 deletions

1
.gitignore vendored
View File

@ -111,3 +111,4 @@ typings/
/ShadowEditor.ScriptEditor/obj
/ShadowEditor.ScriptEditor/ShadowEditor.ScriptEditor.csproj.user
/ShadowEditor.ScriptEditor/dist
/ShadowEditor.Core/dist

View File

@ -0,0 +1,7 @@
# ShadowEditor.Core
包管理器,提供包的管理和动态加载功能,避免开始加载资源过多,导致载入缓慢。
## 依赖项
[ShadowEditor.PackageManager](../ShadowEditor.PackageManager/): ShadowEditor包管理器。

View File

@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>Shadow Editor</title>
<link rel="shortcut icon" href="favicon.ico">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<!-- codemirror -->
<link rel="stylesheet" href="assets/css/codemirror.css">
<link rel="stylesheet" href="assets/css/theme/monokai.css">
<link rel="stylesheet" href="assets/css/addon/dialog.css">
<link rel="stylesheet" href="assets/css/addon/show-hint.css">
<link rel="stylesheet" href="assets/css/addon/tern.css">
<!-- iconfont -->
<link href="assets/css/icon/iconfont.css" rel="stylesheet" />
<!-- Shadow Editor -->
<link href="assets/css/main.css" rel="stylesheet" />
<link id="theme" href="assets/css/light.css" rel="stylesheet" />
</head>
<body>
<div id="container" tabindex="10"></div>
<!-- three.js -->
<script src="assets/js/three.js"></script>
<script src="assets/js/stats.min.js"></script>
<script src="assets/js/three.bas.js"></script>
<script src="assets/js/SPE.js"></script>
<script src="assets/js/mmdparser.min.js"></script>
<script src="assets/js/ammo.js"></script>
<script src="assets/js/VolumetricFire.js"></script>
<script src="assets/js/utils/BufferGeometryUtils.js"></script>
<script src="assets/js/shaders/NormalMapShader.js"></script>
<script src="assets/js/ShaderTerrain.js"></script>
<!-- controls -->
<script src="assets/js/controls/EditorControls.js"></script>
<script src="assets/js/controls/TransformControls.js"></script>
<!-- third-party -->
<script src="assets/js/jszip.min.js"></script>
<script src="assets/js/lzma.js"></script>
<script src="assets/js/ctm.js"></script>
<script src="assets/js/inflate.min.js"></script>
<script src="assets/js/gl-matrix.js"></script>
<script src="assets/js/pako.js"></script>
<script src="assets/js/SimplexNoise.js"></script>
<script src="assets/js/ImprovedNoise.js"></script>
<script src="assets/js/GPUComputationRenderer.js"></script>
<script src="assets/js/codemirror.js"></script>
<script src="assets/js/mode/javascript.js"></script>
<script src="assets/js/mode/glsl.js"></script>
<script src="assets/js/esprima.js"></script>
<script src="assets/js/jsonlint.js"></script>
<script src="assets/js/glslprep.min.js"></script>
<script src="assets/js/addon/dialog.js"></script>
<script src="assets/js/addon/show-hint.js"></script>
<script src="assets/js/addon/tern.js"></script>
<script src="assets/js/acorn/acorn.js"></script>
<script src="assets/js/acorn/acorn_loose.js"></script>
<script src="assets/js/acorn/walk.js"></script>
<script src="assets/js/ternjs/polyfill.js"></script>
<script src="assets/js/ternjs/signal.js"></script>
<script src="assets/js/ternjs/tern.js"></script>
<script src="assets/js/ternjs/def.js"></script>
<script src="assets/js/ternjs/comment.js"></script>
<script src="assets/js/ternjs/infer.js"></script>
<script src="assets/js/ternjs/doc_comment.js"></script>
<script src="assets/js/tern-threejs/threejs.js"></script>
<!-- geometries -->
<script src="assets/js/geometries/TeapotBufferGeometry.js"></script>
<!-- loaders -->
<script src="assets/js/loaders/AMFLoader.js"></script>
<script src="assets/js/loaders/AWDLoader.js"></script>
<script src="assets/js/loaders/BabylonLoader.js"></script>
<script src="assets/js/loaders/BinaryLoader.js"></script>
<script src="assets/js/loaders/ColladaLoader2.js"></script>
<script src="assets/js/loaders/FBXLoader.js"></script>
<script src="assets/js/loaders/GLTFLoader.js"></script>
<script src="assets/js/loaders/KMZLoader.js"></script>
<script src="assets/js/loaders/MD2Loader.js"></script>
<script src="assets/js/loaders/OBJLoader.js"></script>
<script src="assets/js/loaders/PLYLoader.js"></script>
<script src="assets/js/loaders/STLLoader.js"></script>
<script src="assets/js/loaders/TGALoader.js"></script>
<script src="assets/js/loaders/VTKLoader.js"></script>
<script src="assets/js/loaders/ctm/CTMLoader.js"></script>
<script src="assets/js/loaders/MMDLoader.js"></script>
<!-- exporters -->
<script src="assets/js/exporters/GLTFExporter.js"></script>
<script src="assets/js/exporters/OBJExporter.js"></script>
<script src="assets/js/exporters/PLYExporter.js"></script>
<script src="assets/js/exporters/STLBinaryExporter.js"></script>
<script src="assets/js/exporters/STLExporter.js"></script>
<!-- objects -->
<script src="assets/js/objects/Sky.js"></script>
<script src="assets/js/objects/Reflector.js"></script>
<script src="assets/js/objects/Lensflare.js"></script>
<!-- animation -->
<script src="assets/js/animation/CCDIKSolver.js"></script>
<script src="assets/js/animation/MMDPhysics.js"></script>
<!-- Shadow Editor -->
<script src="dist/ShadowEditor.js"></script>
<script>
var container = null;
var app = null;
var start = function () {
container = document.getElementById('container');
app = new Shadow.Application(container, {
server: location.origin
});
app.start();
};
window.onload = start;
</script>
</body>
</html>

View File

@ -0,0 +1,42 @@
import resolve from 'rollup-plugin-node-resolve';
function glsl() {
return {
transform(code, id) {
if (/\.glsl$/.test(id) === false) return;
var transformedCode = 'export default ' + JSON.stringify(
code
.replace(/[ \t]*\/\/.*\n/g, '') // remove //
.replace(/[ \t]*\/\*[\s\S]*?\*\//g, '') // remove /* */
.replace(/\n{2,}/g, '\n') // # \n+ to \n
) + ';';
return {
code: transformedCode,
map: {
mappings: ''
}
};
}
};
}
export default {
input: 'ShadowEditor.Core/src/index.js',
output: {
indent: '\t',
format: 'umd',
name: 'Shadow',
file: 'ShadowEditor.Core/dist/ShadowEditor.Core.js'
},
treeshake: true,
external: [],
plugins: [
glsl(),
resolve({
customResolveOptions: {
moduleDirectory: 'node_modules'
}
})
]
};

View File

@ -0,0 +1,151 @@
import Options from './Options';
import { UI } from './third_party';
import EventDispatcher from './event/EventDispatcher';
import Menubar from './editor/menubar/Menubar';
import Toolbar from './editor/Toolbar';
import Viewport from './editor/Viewport';
import Sidebar from './editor/sidebar/Sidebar';
import Sidebar2 from './editor/sidebar2/Sidebar';
import BottomPanel from './editor/bottom/BottomPanel';
import StatusBar from './editor/StatusBar';
import ScriptEditor from './editor/script/ScriptEditor';
import Player from './player/Player';
import Editor from './editor/Editor';
import Physics from './editor/Physics';
import API from './api/API';
/**
* 应用程序
* @author mrdoob / http://mrdoob.com/
* @author tengge / https://github.com/tengge1
*/
function Application(container, options) {
// 容器
this.container = container;
this.width = this.container.clientWidth;
this.height = this.container.clientHeight;
// 配置
this.options = new Options(options);
// 事件
this.event = new EventDispatcher(this);
this.call = this.event.call.bind(this.event);
this.on = this.event.on.bind(this.event);
var params = { app: this };
// 用户界面
this.ui = UI;
this.menubar = new Menubar(params); // 菜单栏
this.toolbar = new Toolbar(params); // 工具栏
this.viewport = new Viewport(params); // 场景编辑区
this.sidebar = new Sidebar(params); // 侧边栏
this.sidebar2 = new Sidebar2(params); // 侧边栏2
this.bottomPanel = new BottomPanel(params); // 底部面板
this.statusBar = new StatusBar(params); // 状态栏
this.script = new ScriptEditor(params); // 脚本编辑器
this.player = new Player(params); // 播放器面板
UI.create({
xtype: 'container',
parent: this.container,
children: [
new Menubar(params), {
xtype: 'div',
style: {
width: '100%',
flex: 1,
display: 'flex',
flexDirection: 'row',
},
children: [
this.toolbar, {
xtype: 'div',
style: {
position: 'relative',
flex: 1,
display: 'flex',
flexDirection: 'column'
},
children: [{
xtype: 'div',
style: {
position: 'relative',
flex: 1
},
children: [
this.viewport,
this.script,
this.player
]
},
this.bottomPanel,
this.statusBar
]
},
this.sidebar2,
this.sidebar
]
}
]
}).render();
// 核心
this.editor = new Editor(this); // 编辑器
this.physics = new Physics(params);
// Html5 Worker
// var script = document.querySelector('script[src$="ShadowEditor.js" i]').src; // http://localhost:2000/dist/ShadowEditor.js
// this.worker = new Worker(script);
}
// ------------------------- 程序控制 -------------------------------
Application.prototype.start = function () {
// 启动事件 - 事件要在ui创建完成后启动
this.event.start();
this.call('appStart', this);
this.call('appStarted', this);
// 启动物体引擎
this.physics.start();
this.call('resize', this);
this.log('程序启动成功。');
};
Application.prototype.stop = function () {
this.call('appStop', this);
this.call('appStoped', this);
this.log('程序已经停止');
this.event.stop();
};
// ----------------------- 记录日志 --------------------------------
Application.prototype.log = function (content) { // 普通日志
this.call('log', this, content);
};
Application.prototype.warn = function (content) { // 警告日志
this.call('log', this, content, 'warn');
};
Application.prototype.error = function (content) { // 错误日志
this.call('log', this, content, 'error');
};
// API
Object.assign(Application.prototype, API.prototype);
export default Application;

View File

@ -0,0 +1,26 @@
/**
* 配置选项
* @author tengge / https://github.com/tengge1
* @param {*} options 配置选项
*/
function Options(options) {
options = options || {};
// 服务器配置
this.server = options.server === undefined ? location.origin : options.server; // 服务端地址
// 外观配置
this.theme = options.theme || 'assets/css/light.css'; // 皮肤
// 帮助器配置
this.showGrid = true; // 是否显示网格
this.showCameraHelper = true; // 是否显示相机帮助器
this.showPointLightHelper = false; // 是否显示点光源帮助器
this.showDirectionalLightHelper = true; // 是否显示平行光帮助器
this.showSpotLightHelper = true; // 是否显示聚光灯帮助器
this.showHemisphereLightHelper = true; // 是否显示半球光帮助器
this.showRectAreaLightHelper = true; // 是否显示矩形光帮助器
this.showSkeletonHelper = false; // 是否显示骨骼帮助器
}
export default Options;

View File

@ -0,0 +1,48 @@
import AnimationType from './AnimationType';
var ID = -1;
/**
* 动画数据
* @author tengge / https://github.com/tengge1
* @param {*} options 选项
*/
function Animation(options) {
options = options || {};
// 基本信息
this.id = options.id || null; // MongoDB _id字段
this.uuid = options.uuid || THREE.Math.generateUUID(); // uuid
this.name = options.name || `动画${ID--}`; // 动画名称
this.target = options.target || null; // 动画对象uuid
this.type = options.type || AnimationType.Tween; // 动画类型
this.beginTime = options.beginTime || 0; // 开始时间(秒)
this.endTime = options.endTime || 10; // 结束时间(秒)
// 补间动画
this.beginStatus = options.beginStatus || 'Current'; // 开始状态Current、Custom
this.beginPositionX = options.beginPositionX || 0;
this.beginPositionY = options.beginPositionY || 0;
this.beginPositionZ = options.beginPositionZ || 0;
this.beginRotationX = options.beginRotationX || 0;
this.beginRotationY = options.beginRotationY || 0;
this.beginRotationZ = options.beginRotationZ || 0;
this.beginScaleLock = options.beginScaleLock === undefined ? true : options.beginScaleLock;
this.beginScaleX = options.beginScaleX || 1.0;
this.beginScaleY = options.beginScaleY || 1.0;
this.beginScaleZ = options.beginScaleZ || 1.0;
this.ease = options.ease || 'linear'; // linear, quadIn, quadOut, quadInOut, cubicIn, cubicOut, cubicInOut, quartIn, quartOut, quartInOut, quintIn, quintOut, quintInOut, sineIn, sineOut, sineInOut, backIn, backOut, backInOut, circIn, circOut, circInOut, bounceIn, bounceOut, bounceInOut, elasticIn, elasticOut, elasticInOut
this.endStatus = options.endStatus || 'Current';
this.endPositionX = options.endPositionX || 0;
this.endPositionY = options.endPositionY || 0;
this.endPositionZ = options.endPositionZ || 0;
this.endRotationX = options.endRotationX || 0;
this.endRotationY = options.endRotationY || 0;
this.endRotationZ = options.endRotationZ || 0;
this.endScaleLock = options.endScaleLock === undefined ? true : options.endScaleLock;
this.endScaleX = options.endScaleX || 1.0;
this.endScaleY = options.endScaleY || 1.0;
this.endScaleZ = options.endScaleZ || 1.0;
}
export default Animation;

View File

@ -0,0 +1,57 @@
import Animation from './Animation';
var ID = 1;
/**
* 动画组
* @param {*} options 选项
*/
function AnimationGroup(options) {
options = options || {};
this.id = options.id || null; // MongoDB _id字段
this.uuid = options.uuid || THREE.Math.generateUUID(); // uuid
this.name = options.name || `组-${ID++}`; // 组名
this.type = 'AnimationGroup'; // 组类型
this.index = options.index || ID; // 组序号
this.animations = options.animations || []; // 组动画
}
/**
* 添加
* @param {*} animation
*/
AnimationGroup.prototype.add = function (animation) {
this.insert(animation, this.animations.length - 1);
};
/**
* 插入
* @param {*} animation
* @param {*} index
*/
AnimationGroup.prototype.insert = function (animation, index = 0) {
if (!(animation instanceof Animation)) {
console.warn(`AnimationGroup: animation不是Animation的实例。`);
return;
}
this.animations.splice(index, 0, animation);
};
/**
* 移除
* @param {*} animation
*/
AnimationGroup.prototype.remove = function (animation) {
var index = this.animations.indexOf(animation);
this.removeAt(index);
};
/**
* 移除
* @param {*} index
*/
AnimationGroup.prototype.removeAt = function (index) {
this.animations.splice(index, 1);
};
export default AnimationGroup;

View File

@ -0,0 +1,98 @@
import AnimationGroup from './AnimationGroup';
/**
* 动画管理器
* @author tengge / https://github.com/tengge1
* @param {*} app 应用程序
*/
function AnimationManager(app) {
this.app = app;
this.animations = [];
};
/**
* 清空所有
*/
AnimationManager.prototype.clear = function () {
this.animations = [];
for (var i = 0; i < 3; i++) {
var group = new AnimationGroup({
name: `${i + 1}`,
index: i
});
this.animations.push(group);
}
};
/**
* 添加动画组
* @param {*} group 动画组
*/
AnimationManager.prototype.add = function (group) {
this.insert(group, this.animations.length);
};
/**
* 插入动画组
* @param {*} group 动画组
* @param {*} index 索引
*/
AnimationManager.prototype.insert = function (group, index = 0) {
if (!(group instanceof AnimationGroup)) {
console.warn(`AnimationManager: group不是AnimationGroup的实例。`);
return;
}
this.animations.splice(index, 0, group);
};
/**
* 删除组
* @param {*} group 动画组
*/
AnimationManager.prototype.remove = function (group) {
var index = this.animations.indexOf(group);
if (index > -1) {
this.animations.splice(index, 1);
}
};
/**
* 根据uuid删除组
* @param {*} uuid
*/
AnimationManager.prototype.removeByUUID = function (uuid) {
var index = this.animations.findIndex(n => n.uuid === uuid);
if (index > -1) {
this.animations.splice(index, 1);
}
};
/**
* 获取动画
*/
AnimationManager.prototype.getAnimationGroups = function () {
return this.animations;
};
/**
* 设置动画
* @param {*} animations
*/
AnimationManager.prototype.setAnimationGroups = function (animations) {
this.animations = animations;
};
/**
* 根据uuid获取动画
* @param {*} uuid
*/
AnimationManager.prototype.getAnimationByUUID = function (uuid) {
var group = this.animations.filter(n => n.animations.findIndex(m => m.uuid === uuid) > -1)[0];
if (group === undefined) {
return null;
}
return group.animations.filter(n => n.uuid === uuid)[0];
};
export default AnimationManager;

View File

@ -0,0 +1,22 @@
/**
* 动画类型
* @author tengge / https://github.com/tengge1
*/
var AnimationType = {
// 补间动画
Tween: 'Tween',
// 骨骼动画
Skeletal: 'Skeletal',
// 音频播放
Audio: 'Audio',
// 滤镜动画
Filter: 'Filter',
// 粒子动画
Particle: 'Particle'
};
export default AnimationType;

View File

@ -0,0 +1,456 @@
/*
* Ease
* Visit http://createjs.com/ for documentation, updates and examples.
*
* Copyright (c) 2010 gskinner.com, inc.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* @module TweenJS
* @author tweenjs / https://www.createjs.com/tweenjs
* @link https://github.com/CreateJS/TweenJS/blob/master/src/tweenjs/Ease.js
*/
/**
* The Ease class provides a collection of easing functions for use with TweenJS. It does not use the standard 4 param
* easing signature. Instead it uses a single param which indicates the current linear ratio (0 to 1) of the tween.
*
* Most methods on Ease can be passed directly as easing functions:
*
* createjs.Tween.get(target).to({x:100}, 500, createjs.Ease.linear);
*
* However, methods beginning with "get" will return an easing function based on parameter values:
*
* createjs.Tween.get(target).to({y:200}, 500, createjs.Ease.getPowIn(2.2));
*
* Please see the <a href="http://www.createjs.com/Demos/TweenJS/Tween_SparkTable">spark table demo</a> for an
* overview of the different ease types on <a href="http://tweenjs.com">TweenJS.com</a>.
*
* <em>Equations derived from work by Robert Penner.</em>
* @class Ease
* @static
**/
function Ease() {
throw "Ease cannot be instantiated.";
}
// static methods and properties
/**
* @method linear
* @param {Number} t
* @static
* @return {Number}
**/
Ease.linear = function (t) { return t; };
/**
* Identical to linear.
* @method none
* @param {Number} t
* @static
* @return {Number}
**/
Ease.none = Ease.linear;
/**
* Mimics the simple -100 to 100 easing in Adobe Flash/Animate.
* @method get
* @param {Number} amount A value from -1 (ease in) to 1 (ease out) indicating the strength and direction of the ease.
* @static
* @return {Function}
**/
Ease.get = function (amount) {
if (amount < -1) { amount = -1; }
else if (amount > 1) { amount = 1; }
return function (t) {
if (amount == 0) { return t; }
if (amount < 0) { return t * (t * -amount + 1 + amount); }
return t * ((2 - t) * amount + (1 - amount));
};
};
/**
* Configurable exponential ease.
* @method getPowIn
* @param {Number} pow The exponent to use (ex. 3 would return a cubic ease).
* @static
* @return {Function}
**/
Ease.getPowIn = function (pow) {
return function (t) {
return Math.pow(t, pow);
};
};
/**
* Configurable exponential ease.
* @method getPowOut
* @param {Number} pow The exponent to use (ex. 3 would return a cubic ease).
* @static
* @return {Function}
**/
Ease.getPowOut = function (pow) {
return function (t) {
return 1 - Math.pow(1 - t, pow);
};
};
/**
* Configurable exponential ease.
* @method getPowInOut
* @param {Number} pow The exponent to use (ex. 3 would return a cubic ease).
* @static
* @return {Function}
**/
Ease.getPowInOut = function (pow) {
return function (t) {
if ((t *= 2) < 1) return 0.5 * Math.pow(t, pow);
return 1 - 0.5 * Math.abs(Math.pow(2 - t, pow));
};
};
/**
* @method quadIn
* @param {Number} t
* @static
* @return {Number}
**/
Ease.quadIn = Ease.getPowIn(2);
/**
* @method quadOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.quadOut = Ease.getPowOut(2);
/**
* @method quadInOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.quadInOut = Ease.getPowInOut(2);
/**
* @method cubicIn
* @param {Number} t
* @static
* @return {Number}
**/
Ease.cubicIn = Ease.getPowIn(3);
/**
* @method cubicOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.cubicOut = Ease.getPowOut(3);
/**
* @method cubicInOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.cubicInOut = Ease.getPowInOut(3);
/**
* @method quartIn
* @param {Number} t
* @static
* @return {Number}
**/
Ease.quartIn = Ease.getPowIn(4);
/**
* @method quartOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.quartOut = Ease.getPowOut(4);
/**
* @method quartInOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.quartInOut = Ease.getPowInOut(4);
/**
* @method quintIn
* @param {Number} t
* @static
* @return {Number}
**/
Ease.quintIn = Ease.getPowIn(5);
/**
* @method quintOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.quintOut = Ease.getPowOut(5);
/**
* @method quintInOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.quintInOut = Ease.getPowInOut(5);
/**
* @method sineIn
* @param {Number} t
* @static
* @return {Number}
**/
Ease.sineIn = function (t) {
return 1 - Math.cos(t * Math.PI / 2);
};
/**
* @method sineOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.sineOut = function (t) {
return Math.sin(t * Math.PI / 2);
};
/**
* @method sineInOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.sineInOut = function (t) {
return -0.5 * (Math.cos(Math.PI * t) - 1);
};
/**
* Configurable "back in" ease.
* @method getBackIn
* @param {Number} amount The strength of the ease.
* @static
* @return {Function}
**/
Ease.getBackIn = function (amount) {
return function (t) {
return t * t * ((amount + 1) * t - amount);
};
};
/**
* @method backIn
* @param {Number} t
* @static
* @return {Number}
**/
Ease.backIn = Ease.getBackIn(1.7);
/**
* Configurable "back out" ease.
* @method getBackOut
* @param {Number} amount The strength of the ease.
* @static
* @return {Function}
**/
Ease.getBackOut = function (amount) {
return function (t) {
return (--t * t * ((amount + 1) * t + amount) + 1);
};
};
/**
* @method backOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.backOut = Ease.getBackOut(1.7);
/**
* Configurable "back in out" ease.
* @method getBackInOut
* @param {Number} amount The strength of the ease.
* @static
* @return {Function}
**/
Ease.getBackInOut = function (amount) {
amount *= 1.525;
return function (t) {
if ((t *= 2) < 1) return 0.5 * (t * t * ((amount + 1) * t - amount));
return 0.5 * ((t -= 2) * t * ((amount + 1) * t + amount) + 2);
};
};
/**
* @method backInOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.backInOut = Ease.getBackInOut(1.7);
/**
* @method circIn
* @param {Number} t
* @static
* @return {Number}
**/
Ease.circIn = function (t) {
return -(Math.sqrt(1 - t * t) - 1);
};
/**
* @method circOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.circOut = function (t) {
return Math.sqrt(1 - (--t) * t);
};
/**
* @method circInOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.circInOut = function (t) {
if ((t *= 2) < 1) return -0.5 * (Math.sqrt(1 - t * t) - 1);
return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
};
/**
* @method bounceIn
* @param {Number} t
* @static
* @return {Number}
**/
Ease.bounceIn = function (t) {
return 1 - Ease.bounceOut(1 - t);
};
/**
* @method bounceOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.bounceOut = function (t) {
if (t < 1 / 2.75) {
return (7.5625 * t * t);
} else if (t < 2 / 2.75) {
return (7.5625 * (t -= 1.5 / 2.75) * t + 0.75);
} else if (t < 2.5 / 2.75) {
return (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375);
} else {
return (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375);
}
};
/**
* @method bounceInOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.bounceInOut = function (t) {
if (t < 0.5) return Ease.bounceIn(t * 2) * .5;
return Ease.bounceOut(t * 2 - 1) * 0.5 + 0.5;
};
/**
* Configurable elastic ease.
* @method getElasticIn
* @param {Number} amplitude
* @param {Number} period
* @static
* @return {Function}
**/
Ease.getElasticIn = function (amplitude, period) {
var pi2 = Math.PI * 2;
return function (t) {
if (t == 0 || t == 1) return t;
var s = period / pi2 * Math.asin(1 / amplitude);
return -(amplitude * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * pi2 / period));
};
};
/**
* @method elasticIn
* @param {Number} t
* @static
* @return {Number}
**/
Ease.elasticIn = Ease.getElasticIn(1, 0.3);
/**
* Configurable elastic ease.
* @method getElasticOut
* @param {Number} amplitude
* @param {Number} period
* @static
* @return {Function}
**/
Ease.getElasticOut = function (amplitude, period) {
var pi2 = Math.PI * 2;
return function (t) {
if (t == 0 || t == 1) return t;
var s = period / pi2 * Math.asin(1 / amplitude);
return (amplitude * Math.pow(2, -10 * t) * Math.sin((t - s) * pi2 / period) + 1);
};
};
/**
* @method elasticOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.elasticOut = Ease.getElasticOut(1, 0.3);
/**
* Configurable elastic ease.
* @method getElasticInOut
* @param {Number} amplitude
* @param {Number} period
* @static
* @return {Function}
**/
Ease.getElasticInOut = function (amplitude, period) {
var pi2 = Math.PI * 2;
return function (t) {
var s = period / pi2 * Math.asin(1 / amplitude);
if ((t *= 2) < 1) return -0.5 * (amplitude * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * pi2 / period));
return amplitude * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * pi2 / period) * 0.5 + 1;
};
};
/**
* @method elasticInOut
* @param {Number} t
* @static
* @return {Number}
**/
Ease.elasticInOut = Ease.getElasticInOut(1, 0.3 * 1.5);
export default Ease;

View File

@ -0,0 +1,12 @@
import UIAPI from './UIAPI';
/**
* 编辑器API
*/
function API() {
}
Object.assign(API.prototype, UIAPI.prototype);
export default API;

View File

@ -0,0 +1,12 @@
/**
* 用户界面API
*/
function UIAPI() {
}
UIAPI.prototype.addMenu = function () {
alert('添加菜单');
};
export default UIAPI;

View File

@ -0,0 +1,56 @@
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
*/
function AddObjectCommand(object) {
Command.call(this);
this.type = 'AddObjectCommand';
this.object = object;
if (object !== undefined) {
this.name = '添加物体:' + object.name;
}
};
AddObjectCommand.prototype = Object.create(Command.prototype);
Object.assign(AddObjectCommand.prototype, {
constructor: AddObjectCommand,
execute: function () {
this.editor.addObject(this.object);
this.editor.select(this.object);
},
undo: function () {
this.editor.removeObject(this.object);
this.editor.deselect();
},
toJSON: function () {
var output = Command.prototype.toJSON.call(this);
output.object = this.object.toJSON();
return output;
},
fromJSON: function (json) {
Command.prototype.fromJSON.call(this, json);
this.object = this.editor.objectByUuid(json.object.object.uuid);
if (this.object === undefined) {
var loader = new THREE.ObjectLoader();
this.object = loader.parse(json.object);
}
}
});
export default AddObjectCommand;

View File

@ -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 script javascript object
* @constructor
*/
var AddScriptCommand = function (object, script) {
Command.call(this);
this.type = 'AddScriptCommand';
this.name = '添加脚本';
this.object = object;
this.script = script;
};
AddScriptCommand.prototype = Object.create(Command.prototype);
Object.assign(AddScriptCommand.prototype, {
constructor: AddScriptCommand,
execute: function () {
if (this.editor.scripts[this.object.uuid] === undefined) {
this.editor.scripts[this.object.uuid] = [];
}
this.editor.scripts[this.object.uuid].push(this.script);
this.editor.app.call('scriptAdded', this, this.script);
},
undo: function () {
if (this.editor.scripts[this.object.uuid] === undefined) return;
var index = this.editor.scripts[this.object.uuid].indexOf(this.script);
if (index !== - 1) {
this.editor.scripts[this.object.uuid].splice(index, 1);
}
this.editor.app.call('scriptRemoved', this, this.script);
},
toJSON: function () {
var output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.script = this.script;
return output;
},
fromJSON: function (json) {
Command.prototype.fromJSON.call(this, json);
this.script = json.script;
this.object = this.editor.objectByUuid(json.objectUuid);
}
});
export default AddScriptCommand;

View File

@ -0,0 +1,36 @@
/**
* 命令基类
* @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 editorRef pointer to main editor object used to initialize each command object with a reference to the editor
* @constructor
*/
function Command(editorRef) {
this.id = -1;
this.inMemory = false;
this.updatable = false;
this.type = '';
this.name = '';
if (editorRef !== undefined) {
Command.editor = editorRef;
}
this.editor = Command.editor;
};
Command.prototype.toJSON = function () {
var output = {};
output.type = this.type;
output.id = this.id;
output.name = this.name;
return output;
};
Command.prototype.fromJSON = function (json) {
this.inMemory = true;
this.type = json.type;
this.id = json.id;
this.name = json.name;
};
export default Command;

View File

@ -0,0 +1,94 @@
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
*/
function MoveObjectCommand(object, newParent, newBefore) {
Command.call(this);
this.type = 'MoveObjectCommand';
this.name = '移动物体';
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 = Object.create(Command.prototype);
Object.assign(MoveObjectCommand.prototype, {
constructor: MoveObjectCommand,
execute: function () {
this.oldParent.remove(this.object);
var children = this.newParent.children;
children.splice(this.newIndex, 0, this.object);
this.object.parent = this.newParent;
this.editor.app.call('sceneGraphChanged', this);
},
undo: function () {
this.newParent.remove(this.object);
var children = this.oldParent.children;
children.splice(this.oldIndex, 0, this.object);
this.object.parent = this.oldParent;
this.editor.app.call('sceneGraphChanged', this);
},
toJSON: function () {
var 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;

View File

@ -0,0 +1,62 @@
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
*/
function MultiCmdsCommand(cmdArray) {
Command.call(this);
this.type = 'MultiCmdsCommand';
this.name = '多种改变';
this.cmdArray = (cmdArray !== undefined) ? cmdArray : [];
};
MultiCmdsCommand.prototype = Object.create(Command.prototype);
Object.assign(MultiCmdsCommand.prototype, {
constructor: MultiCmdsCommand,
execute: function () {
for (var i = 0; i < this.cmdArray.length; i++) {
this.cmdArray[i].execute();
}
this.editor.app.call('sceneGraphChanged', this);
},
undo: function () {
for (var i = this.cmdArray.length - 1; i >= 0; i--) {
this.cmdArray[i].undo();
}
this.editor.app.call('sceneGraphChanged', this);
},
toJSON: function () {
var output = Command.prototype.toJSON.call(this);
var cmds = [];
for (var 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);
var cmds = json.cmds;
for (var i = 0; i < cmds.length; i++) {
var 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;

View File

@ -0,0 +1,87 @@
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
*/
function RemoveObjectCommand(object) {
Command.call(this);
this.type = 'RemoveObjectCommand';
this.name = '移除物体';
this.object = object;
this.parent = (object !== undefined) ? object.parent : undefined;
if (this.parent !== undefined) {
this.index = this.parent.children.indexOf(this.object);
}
};
RemoveObjectCommand.prototype = Object.create(Command.prototype);
Object.assign(RemoveObjectCommand.prototype, {
constructor: RemoveObjectCommand,
execute: function () {
var scope = this.editor;
this.object.traverse(function (child) {
scope.removeHelper(child);
});
this.parent.remove(this.object);
this.editor.select(this.parent);
this.editor.app.call('objectRemoved', this, this.object);
this.editor.app.call('sceneGraphChanged', this);
},
undo: function () {
var scope = this.editor;
this.object.traverse(function (child) {
scope.addHelper(child);
});
this.parent.children.splice(this.index, 0, this.object);
this.object.parent = this.parent;
this.editor.select(this.object);
this.editor.app.call('objectAdded', this, this.object);
this.editor.app.call('sceneGraphChanged', this);
},
toJSON: function () {
var 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) {
var loader = new THREE.ObjectLoader();
this.object = loader.parse(json.object);
}
}
});
export default RemoveObjectCommand;

View File

@ -0,0 +1,68 @@
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
*/
function RemoveScriptCommand(object, script) {
Command.call(this);
this.type = 'RemoveScriptCommand';
this.name = '移除脚本';
this.object = object;
this.script = script;
if (this.object && this.script) {
this.index = this.editor.scripts[this.object.uuid].indexOf(this.script);
}
};
RemoveScriptCommand.prototype = Object.create(Command.prototype);
Object.assign(RemoveScriptCommand.prototype, {
constructor: RemoveScriptCommand,
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.app.call('scriptRemoved', this, 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.app.call('scriptAdded', this, this.script);
},
toJSON: function () {
var 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;

View File

@ -0,0 +1,65 @@
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
*/
function SetColorCommand(object, attributeName, newValue) {
Command.call(this);
this.type = 'SetColorCommand';
this.name = '设置 ' + 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 = Object.create(Command.prototype);
Object.assign(SetColorCommand.prototype, {
constructor: SetColorCommand,
execute: function () {
this.object[this.attributeName].setHex(this.newValue);
this.editor.app.call('objectChanged', this, this.object);
},
undo: function () {
this.object[this.attributeName].setHex(this.oldValue);
this.editor.app.call('objectChanged', this, this.object);
},
update: function (cmd) {
this.newValue = cmd.newValue;
},
toJSON: function () {
var 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;

View File

@ -0,0 +1,75 @@
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
*/
function SetGeometryCommand(object, newGeometry) {
Command.call(this);
this.type = 'SetGeometryCommand';
this.name = '设置几何体';
this.updatable = true;
this.object = object;
this.oldGeometry = (object !== undefined) ? object.geometry : undefined;
this.newGeometry = newGeometry;
};
SetGeometryCommand.prototype = Object.create(Command.prototype);
Object.assign(SetGeometryCommand.prototype, {
constructor: SetGeometryCommand,
execute: function () {
this.object.geometry.dispose();
this.object.geometry = this.newGeometry;
this.object.geometry.computeBoundingSphere();
this.editor.app.call('geometryChanged', this, this.object);
this.editor.app.call('sceneGraphChanged', this);
},
undo: function () {
this.object.geometry.dispose();
this.object.geometry = this.oldGeometry;
this.object.geometry.computeBoundingSphere();
this.editor.app.call('geometryChanged', this, this.object);
this.editor.app.call('sceneGraphChanged', this);
},
update: function (cmd) {
this.newGeometry = cmd.newGeometry;
},
toJSON: function () {
var 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);
this.oldGeometry = parseGeometry(json.oldGeometry);
this.newGeometry = parseGeometry(json.newGeometry);
function parseGeometry(data) {
var loader = new THREE.ObjectLoader();
return loader.parseGeometries([data])[data.uuid];
}
}
});
export default SetGeometryCommand;

View File

@ -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 number, string, boolean or object
* @constructor
*/
function SetGeometryValueCommand(object, attributeName, newValue) {
Command.call(this);
this.type = 'SetGeometryValueCommand';
this.name = '设置几何体.' + attributeName;
this.object = object;
this.attributeName = attributeName;
this.oldValue = (object !== undefined) ? object.geometry[attributeName] : undefined;
this.newValue = newValue;
};
SetGeometryValueCommand.prototype = Object.create(Command.prototype);
Object.assign(SetGeometryValueCommand.prototype, {
constructor: SetGeometryValueCommand,
execute: function () {
this.object.geometry[this.attributeName] = this.newValue;
this.editor.app.call('objectChanged', this, this.object);
this.editor.app.call('geometryChanged', this);
this.editor.app.call('sceneGraphChanged', this);
},
undo: function () {
this.object.geometry[this.attributeName] = this.oldValue;
this.editor.app.call('objectChanged', this, this.object);
this.editor.app.call('geometryChanged', this);
this.editor.app.call('sceneGraphChanged', this);
},
toJSON: function () {
var 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;

View File

@ -0,0 +1,65 @@
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
*/
function SetMaterialColorCommand(object, attributeName, newValue) {
Command.call(this);
this.type = 'SetMaterialColorCommand';
this.name = '设置材质.' + attributeName;
this.updatable = true;
this.object = object;
this.attributeName = attributeName;
this.oldValue = (object !== undefined) ? this.object.material[this.attributeName].getHex() : undefined;
this.newValue = newValue;
};
SetMaterialColorCommand.prototype = Object.create(Command.prototype);
Object.assign(SetMaterialColorCommand.prototype, {
constructor: SetMaterialColorCommand,
execute: function () {
this.object.material[this.attributeName].setHex(this.newValue);
this.editor.app.call('materialChanged', this, this.object.material);
},
undo: function () {
this.object.material[this.attributeName].setHex(this.oldValue);
this.editor.app.call('materialChanged', this, this.object.material);
},
update: function (cmd) {
this.newValue = cmd.newValue;
},
toJSON: function () {
var 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;

View File

@ -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 newMaterial THREE.Material
* @constructor
*/
function SetMaterialCommand(object, newMaterial) {
Command.call(this);
this.type = 'SetMaterialCommand';
this.name = '新材质';
this.object = object;
this.oldMaterial = (object !== undefined) ? object.material : undefined;
this.newMaterial = newMaterial;
};
SetMaterialCommand.prototype = Object.create(Command.prototype);
Object.assign(SetMaterialCommand.prototype, {
constructor: SetMaterialCommand,
execute: function () {
this.object.material = this.newMaterial;
this.editor.app.call('materialChanged', this, this.newMaterial);
},
undo: function () {
this.object.material = this.oldMaterial;
this.editor.app.call('materialChanged', this, this.oldMaterial);
},
toJSON: function () {
var 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);
this.oldMaterial = parseMaterial(json.oldMaterial);
this.newMaterial = parseMaterial(json.newMaterial);
function parseMaterial(json) {
var loader = new THREE.ObjectLoader();
var images = loader.parseImages(json.images);
var textures = loader.parseTextures(json.textures, images);
var materials = loader.parseMaterials([json], textures);
return materials[json.uuid];
}
}
});
export default SetMaterialCommand;

View File

@ -0,0 +1,113 @@
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
*/
function SetMaterialMapCommand(object, mapName, newMap) {
Command.call(this);
this.type = 'SetMaterialMapCommand';
this.name = '设置材质.' + mapName;
this.object = object;
this.mapName = mapName;
this.oldMap = (object !== undefined) ? object.material[mapName] : undefined;
this.newMap = newMap;
};
SetMaterialMapCommand.prototype = Object.create(Command.prototype);
Object.assign(SetMaterialMapCommand.prototype, {
constructor: SetMaterialMapCommand,
execute: function () {
this.object.material[this.mapName] = this.newMap;
this.object.material.needsUpdate = true;
this.editor.app.call('materialChanged', this, this.object.material);
},
undo: function () {
this.object.material[this.mapName] = this.oldMap;
this.object.material.needsUpdate = true;
this.editor.app.call('materialChanged', this, this.object.material);
},
toJSON: function () {
var output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.mapName = this.mapName;
output.newMap = serializeMap(this.newMap);
output.oldMap = serializeMap(this.oldMap);
return output;
// serializes a map (THREE.Texture)
function serializeMap(map) {
if (map === null || map === undefined) return null;
var meta = {
geometries: {},
materials: {},
textures: {},
images: {}
};
var json = map.toJSON(meta);
var images = extractFromCache(meta.images);
if (images.length > 0) json.images = images;
json.sourceFile = map.sourceFile;
return json;
}
// 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) {
var values = [];
for (var key in cache) {
var data = cache[key];
delete data.metadata;
values.push(data);
}
return values;
}
},
fromJSON: function (json) {
Command.prototype.fromJSON.call(this, json);
this.object = this.editor.objectByUuid(json.objectUuid);
this.mapName = json.mapName;
this.oldMap = parseTexture(json.oldMap);
this.newMap = parseTexture(json.newMap);
function parseTexture(json) {
var map = null;
if (json !== null) {
var loader = new THREE.ObjectLoader();
var images = loader.parseImages(json.images);
var textures = loader.parseTextures([json], images);
map = textures[json.uuid];
map.sourceFile = json.sourceFile;
}
return map;
}
}
});
export default SetMaterialMapCommand;

View File

@ -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 number, string, boolean or object
* @constructor
*/
function SetMaterialValueCommand(object, attributeName, newValue) {
Command.call(this);
this.type = 'SetMaterialValueCommand';
this.name = '设置材质.' + attributeName;
this.updatable = true;
this.object = object;
this.oldValue = (object !== undefined) ? object.material[attributeName] : undefined;
this.newValue = newValue;
this.attributeName = attributeName;
};
SetMaterialValueCommand.prototype = Object.create(Command.prototype);
Object.assign(SetMaterialValueCommand.prototype, {
constructor: SetMaterialValueCommand,
execute: function () {
this.object.material[this.attributeName] = this.newValue;
this.object.material.needsUpdate = true;
this.editor.app.call('objectChanged', this, this.object);
this.editor.app.call('materialChanged', this, this.object.material);
},
undo: function () {
this.object.material[this.attributeName] = this.oldValue;
this.object.material.needsUpdate = true;
this.editor.app.call('objectChanged', this, this.object);
this.editor.app.call('materialChanged', this, this.object.material);
},
update: function (cmd) {
this.newValue = cmd.newValue;
},
toJSON: function () {
var 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;

View File

@ -0,0 +1,71 @@
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
*/
function SetPositionCommand(object, newPosition, optionalOldPosition) {
Command.call(this);
this.type = 'SetPositionCommand';
this.name = '设置位置';
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 = Object.create(Command.prototype);
Object.assign(SetPositionCommand.prototype, {
constructor: SetPositionCommand,
execute: function () {
this.object.position.copy(this.newPosition);
this.object.updateMatrixWorld(true);
this.editor.app.call('objectChanged', this, this.object);
},
undo: function () {
this.object.position.copy(this.oldPosition);
this.object.updateMatrixWorld(true);
this.editor.app.call('objectChanged', this, this.object);
},
update: function (command) {
this.newPosition.copy(command.newPosition);
},
toJSON: function () {
var 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;

View File

@ -0,0 +1,71 @@
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
*/
function SetRotationCommand(object, newRotation, optionalOldRotation) {
Command.call(this);
this.type = 'SetRotationCommand';
this.name = '设置旋转';
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 = Object.create(Command.prototype);
Object.assign(SetRotationCommand.prototype, {
constructor: SetRotationCommand,
execute: function () {
this.object.rotation.copy(this.newRotation);
this.object.updateMatrixWorld(true);
this.editor.app.call('objectChanged', this, this.object);
},
undo: function () {
this.object.rotation.copy(this.oldRotation);
this.object.updateMatrixWorld(true);
this.editor.app.call('objectChanged', this, this.object);
},
update: function (command) {
this.newRotation.copy(command.newRotation);
},
toJSON: function () {
var 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;

View File

@ -0,0 +1,71 @@
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
*/
function SetScaleCommand(object, newScale, optionalOldScale) {
Command.call(this);
this.type = 'SetScaleCommand';
this.name = '设置缩放';
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 = Object.create(Command.prototype);
Object.assign(SetScaleCommand.prototype, {
constructor: SetScaleCommand,
execute: function () {
this.object.scale.copy(this.newScale);
this.object.updateMatrixWorld(true);
this.editor.app.call('objectChanged', this, this.object);
},
undo: function () {
this.object.scale.copy(this.oldScale);
this.object.updateMatrixWorld(true);
this.editor.app.call('objectChanged', this, this.object);
},
update: function (command) {
this.newScale.copy(command.newScale);
},
toJSON: function () {
var 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;

View File

@ -0,0 +1,78 @@
import Command from './Command';
import SetUuidCommand from './SetUuidCommand';
import SetValueCommand from './SetValueCommand';
import AddObjectCommand from './AddObjectCommand';
/**
* 设置场景命令
* @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
*/
function SetSceneCommand(scene) {
Command.call(this);
this.type = 'SetSceneCommand';
this.name = '设置场景';
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) {
var child = scene.children.pop();
this.cmdArray.push(new AddObjectCommand(child));
}
}
};
SetSceneCommand.prototype = Object.create(Command.prototype);
Object.assign(SetSceneCommand.prototype, {
constructor: SetSceneCommand,
execute: function () {
for (var i = 0; i < this.cmdArray.length; i++) {
this.cmdArray[i].execute();
}
this.editor.app.call('sceneGraphChanged', this);
},
undo: function () {
for (var i = this.cmdArray.length - 1; i >= 0; i--) {
this.cmdArray[i].undo();
}
this.editor.app.call('sceneGraphChanged', this);
},
toJSON: function () {
var output = Command.prototype.toJSON.call(this);
var cmds = [];
for (var 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);
var cmds = json.cmds;
for (var i = 0; i < cmds.length; i++) {
var 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;

View File

@ -0,0 +1,84 @@
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
* @param attributeName string
* @param newValue string, object
* @param cursorPosition javascript object with format {line: 2, ch: 3}
* @param scrollInfo javascript object with values {left, top, width, height, clientWidth, clientHeight}
* @constructor
*/
function SetScriptValueCommand(object, script, attributeName, newValue, cursorPosition, scrollInfo) {
Command.call(this);
this.type = 'SetScriptValueCommand';
this.name = '设置脚本.' + attributeName;
this.updatable = true;
this.object = object;
this.script = script;
this.attributeName = attributeName;
this.oldValue = (script !== undefined) ? script[this.attributeName] : undefined;
this.newValue = newValue;
this.cursorPosition = cursorPosition;
this.scrollInfo = scrollInfo;
};
SetScriptValueCommand.prototype = Object.create(Command.prototype);
Object.assign(SetScriptValueCommand.prototype, {
constructor: SetScriptValueCommand,
execute: function () {
this.script[this.attributeName] = this.newValue;
this.editor.app.call('scriptChanged', this);
this.editor.app.call('refreshScriptEditor', this, this.object, this.script, this.cursorPosition, this.scrollInfo);
},
undo: function () {
this.script[this.attributeName] = this.oldValue;
this.editor.app.call('scriptChanged', this);
this.editor.app.call('refreshScriptEditor', this, this.object, this.script, this.cursorPosition, this.scrollInfo);
},
update: function (cmd) {
this.cursorPosition = cmd.cursorPosition;
this.scrollInfo = cmd.scrollInfo;
this.newValue = cmd.newValue;
},
toJSON: function () {
var output = Command.prototype.toJSON.call(this);
output.objectUuid = this.object.uuid;
output.index = this.editor.scripts[this.object.uuid].indexOf(this.script);
output.attributeName = this.attributeName;
output.oldValue = this.oldValue;
output.newValue = this.newValue;
output.cursorPosition = this.cursorPosition;
output.scrollInfo = this.scrollInfo;
return output;
},
fromJSON: function (json) {
Command.prototype.fromJSON.call(this, json);
this.oldValue = json.oldValue;
this.newValue = json.newValue;
this.attributeName = json.attributeName;
this.object = this.editor.objectByUuid(json.objectUuid);
this.script = this.editor.scripts[json.objectUuid][json.index];
this.cursorPosition = json.cursorPosition;
this.scrollInfo = json.scrollInfo;
}
});
export default SetScriptValueCommand;

View File

@ -0,0 +1,62 @@
import Command from './Command';
/**
* 设置uuid命令
* @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
*/
function SetUuidCommand(object, newUuid) {
Command.call(this);
this.type = 'SetUuidCommand';
this.name = '更新UUID';
this.object = object;
this.oldUuid = (object !== undefined) ? object.uuid : undefined;
this.newUuid = newUuid;
};
SetUuidCommand.prototype = Object.create(Command.prototype);
Object.assign(SetUuidCommand.prototype, {
constructor: SetUuidCommand,
execute: function () {
this.object.uuid = this.newUuid;
this.editor.app.call('objectChanged', this, this.object);
this.editor.app.call('sceneGraphChanged', this);
},
undo: function () {
this.object.uuid = this.oldUuid;
this.editor.app.call('objectChanged', this, this.object);
this.editor.app.call('sceneGraphChanged', this);
},
toJSON: function () {
var 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;

View File

@ -0,0 +1,65 @@
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
*/
function SetValueCommand(object, attributeName, newValue) {
Command.call(this);
this.type = 'SetValueCommand';
this.name = '设置' + attributeName;
this.updatable = true;
this.object = object;
this.attributeName = attributeName;
this.oldValue = (object !== undefined) ? object[attributeName] : undefined;
this.newValue = newValue;
};
SetValueCommand.prototype = Object.create(Command.prototype);
Object.assign(SetValueCommand.prototype, {
constructor: SetValueCommand,
execute: function () {
this.object[this.attributeName] = this.newValue;
this.editor.app.call('objectChanged', this, this.object);
},
undo: function () {
this.object[this.attributeName] = this.oldValue;
this.editor.app.call('objectChanged', this, this.object);
},
update: function (cmd) {
this.newValue = cmd.newValue;
},
toJSON: function () {
var 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;

View File

@ -0,0 +1,20 @@
import { UI } from '../third_party';
/**
* 所有组件基类
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function BaseComponent(options) {
UI.Control.call(this, options);
this.app = options.app
}
BaseComponent.prototype = Object.create(UI.Control.prototype);
BaseComponent.prototype.constructor = BaseComponent;
BaseComponent.prototype.render = function () {
};
export default BaseComponent;

View File

@ -0,0 +1,126 @@
import BaseComponent from './BaseComponent';
import SetValueCommand from '../command/SetValueCommand';
import RemoveObjectCommand from '../command/RemoveObjectCommand';
import AddObjectCommand from '../command/AddObjectCommand';
/**
* 基本信息组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function BasicComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
BasicComponent.prototype = Object.create(BaseComponent.prototype);
BasicComponent.prototype.constructor = BasicComponent;
BasicComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'basicPanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
borderTop: 0,
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
color: '#555',
fontWeight: 'bold'
},
text: '基本信息'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '名称'
}, {
xtype: 'input',
id: 'name',
scope: this.id,
style: {
width: '100px',
fontSize: '12px'
},
onChange: this.onChangeName.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '类型'
}, {
xtype: 'text',
id: 'type',
scope: this.id
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '可见性'
}, {
xtype: 'checkbox',
id: 'visible',
scope: this.id,
onChange: this.onChangeVisible.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
BasicComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
BasicComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
BasicComponent.prototype.updateUI = function () {
var container = UI.get('basicPanel', this.id);
var editor = this.app.editor;
if (editor.selected) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var name = UI.get('name', this.id);
var type = UI.get('type', this.id);
var visible = UI.get('visible', this.id);
name.setValue(this.selected.name);
type.setValue(this.selected.constructor.name);
visible.setValue(this.selected.visible);
};
BasicComponent.prototype.onChangeName = function () {
var name = UI.get('name', this.id);
var editor = this.app.editor;
editor.execute(new SetValueCommand(this.selected, 'name', name.getValue()));
};
BasicComponent.prototype.onChangeVisible = function () {
this.selected.visible = UI.get('visible', this.id).getValue();
};
export default BasicComponent;

View File

@ -0,0 +1,124 @@
import BaseComponent from './BaseComponent';
import SetValueCommand from '../command/SetValueCommand';
/**
* 相机组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function CameraComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
CameraComponent.prototype = Object.create(BaseComponent.prototype);
CameraComponent.prototype.constructor = CameraComponent;
CameraComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'cameraPanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
color: '#555',
fontWeight: 'bold'
},
text: '相机组件'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '视场'
}, {
xtype: 'number',
id: 'objectFov',
scope: this.id,
onChange: this.onSetObjectFov.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '近点'
}, {
xtype: 'number',
id: 'objectNear',
scope: this.id,
onChange: this.onSetObjectNear.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '远点'
}, {
xtype: 'number',
id: 'objectFar',
scope: this.id,
onChange: this.onSetObjectFar.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
CameraComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
CameraComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
CameraComponent.prototype.updateUI = function () {
var container = UI.get('cameraPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.PerspectiveCamera) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var objectFov = UI.get('objectFov', this.id);
var objectNear = UI.get('objectNear', this.id);
var objectFar = UI.get('objectFar', this.id);
objectFov.setValue(this.selected.fov);
objectNear.setValue(this.selected.near);
objectFar.setValue(this.selected.far);
};
CameraComponent.prototype.onSetObjectFov = function () {
var fov = UI.get('objectFov', this.id).getValue();
this.app.editor.execute(new SetValueCommand(this.selected, 'fov', fov));
};
CameraComponent.prototype.onSetObjectNear = function () {
var near = UI.get('objectNear', this.id).getValue();
this.app.editor.execute(new SetValueCommand(this.selected, 'near', near));
};
CameraComponent.prototype.onSetObjectFar = function () {
var far = UI.get('objectFar', this.id).getValue();
this.app.editor.execute(new SetValueCommand(this.selected, 'far', far));
};
export default CameraComponent;

View File

@ -0,0 +1,222 @@
import BaseComponent from './BaseComponent';
import SetValueCommand from '../command/SetValueCommand';
/**
* 火焰组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function FireComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
this.isPlaying = false;
}
FireComponent.prototype = Object.create(BaseComponent.prototype);
FireComponent.prototype.constructor = FireComponent;
FireComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'firePanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'label',
style: {
width: '100%',
color: '#555',
fontWeight: 'bold'
},
text: '火焰组件'
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '宽度'
}, {
xtype: 'int',
id: 'width',
scope: this.id,
range: [0, Infinity],
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '高度'
}, {
xtype: 'int',
id: 'height',
scope: this.id,
range: [0, Infinity],
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '深度'
}, {
xtype: 'int',
id: 'depth',
scope: this.id,
range: [0, Infinity],
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '切片厚度'
}, {
xtype: 'number',
id: 'sliceSpacing',
scope: this.id,
range: [0, Infinity],
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label'
}, {
xtype: 'button',
id: 'btnPreview',
scope: this.id,
text: '预览',
onClick: this.onPreview.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
FireComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
FireComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
FireComponent.prototype.updateUI = function () {
var container = UI.get('firePanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected.userData.type === 'Fire') {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var width = UI.get('width', this.id);
var height = UI.get('height', this.id);
var depth = UI.get('depth', this.id);
var sliceSpacing = UI.get('sliceSpacing', this.id);
var btnPreview = UI.get('btnPreview', this.id);
var fire = this.selected.userData.fire;
width.setValue(fire.mesh.userData.width);
height.setValue(fire.mesh.userData.height);
depth.setValue(fire.mesh.userData.depth);
sliceSpacing.setValue(fire.mesh.userData.sliceSpacing);
if (this.isPlaying) {
btnPreview.setText('取消');
} else {
btnPreview.setText('预览');
}
};
FireComponent.prototype.onChange = function () {
var width = UI.get('width', this.id);
var height = UI.get('height', this.id);
var depth = UI.get('depth', this.id);
var sliceSpacing = UI.get('sliceSpacing', this.id);
VolumetricFire.texturePath = 'assets/textures/VolumetricFire/';
var editor = this.app.editor;
var fire = new VolumetricFire(
width.getValue(),
height.getValue(),
depth.getValue(),
sliceSpacing.getValue(),
editor.camera
);
fire.mesh.name = this.selected.name;
fire.mesh.position.copy(this.selected.position);
fire.mesh.rotation.copy(this.selected.rotation);
fire.mesh.scale.copy(this.selected.scale);
fire.mesh.userData.type = 'Fire';
fire.mesh.userData.fire = fire;
fire.mesh.userData.width = width.getValue();
fire.mesh.userData.height = height.getValue();
fire.mesh.userData.depth = depth.getValue();
fire.mesh.userData.sliceSpacing = sliceSpacing.getValue();
var index = editor.scene.children.indexOf(this.selected);
if (index > -1) {
editor.scene.children[index] = fire.mesh;
fire.mesh.parent = this.selected.parent;
this.selected.parent = null;
this.app.call(`objectRemoved`, this, this.selected);
this.app.call(`objectAdded`, this, fire.mesh);
editor.select(fire.mesh);
this.app.call('sceneGraphChanged', this.id);
fire.update(0);
}
};
FireComponent.prototype.onPreview = function () {
if (this.isPlaying) {
this.stopPreview();
} else {
this.startPreview();
}
};
FireComponent.prototype.startPreview = function () {
var btnPreview = UI.get('btnPreview', this.id);
this.isPlaying = true;
btnPreview.setText('取消');
this.app.on(`animate.${this.id}`, this.onAnimate.bind(this));
};
FireComponent.prototype.stopPreview = function () {
var btnPreview = UI.get('btnPreview', this.id);
this.isPlaying = false;
btnPreview.setText('预览');
this.app.on(`animate.${this.id}`, null);
};
FireComponent.prototype.onAnimate = function (clock, deltaTime) {
var elapsed = clock.getElapsedTime();
var fire = this.selected.userData.fire;
fire.update(elapsed);
};
export default FireComponent;

View File

@ -0,0 +1,99 @@
import BaseComponent from './BaseComponent';
import PlaneGeometryComponent from './geometry/PlaneGeometryComponent';
import BoxGeometryComponent from './geometry/BoxGeometryComponent';
import CircleGeometryComponent from './geometry/CircleGeometryComponent';
import CylinderGeometryComponent from './geometry/CylinderGeometryComponent';
import SphereGeometryComponent from './geometry/SphereGeometryComponent';
import IcosahedronGeometryComponent from './geometry/IcosahedronGeometryComponent';
import TorusGeometryComponent from './geometry/TorusGeometryComponent';
import TorusKnotGeometryComponent from './geometry/TorusKnotGeometryComponent';
import LatheGeometryComponent from './geometry/LatheGeometryComponent';
import TeapotGeometryComponent from './geometry/TeapotGeometryComponent';
/**
* 几何体组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function GeometryComponent(options) {
BaseComponent.call(this, options);
}
GeometryComponent.prototype = Object.create(BaseComponent.prototype);
GeometryComponent.prototype.constructor = GeometryComponent;
GeometryComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'geometryPanel',
scope: this.id,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
color: '#555',
fontWeight: 'bold'
},
text: '几何组件'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '类型'
}, {
xtype: 'text',
id: 'name',
scope: this.id,
text: ''
}]
},
new PlaneGeometryComponent({ app: this.app }),
new BoxGeometryComponent({ app: this.app }),
new CircleGeometryComponent({ app: this.app }),
new CylinderGeometryComponent({ app: this.app }),
new SphereGeometryComponent({ app: this.app }),
new IcosahedronGeometryComponent({ app: this.app }),
new TorusGeometryComponent({ app: this.app }),
new TorusKnotGeometryComponent({ app: this.app }),
new LatheGeometryComponent({ app: this.app }),
new TeapotGeometryComponent({ app: this.app })
]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
};
GeometryComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
GeometryComponent.prototype.updateUI = function () {
var container = UI.get('geometryPanel', this.id);
var editor = this.app.editor;
var name = UI.get('name', this.id);
if (editor.selected && editor.selected instanceof THREE.Mesh) {
container.dom.style.display = '';
if (editor.selected.geometry instanceof THREE.TeapotBufferGeometry) {
name.setValue('TeapotBufferGeometry');
} else {
name.setValue(editor.selected.geometry.constructor.name);
}
} else {
container.dom.style.display = 'none';
name.setValue('');
}
};
export default GeometryComponent;

View File

@ -0,0 +1,158 @@
import BaseComponent from './BaseComponent';
import SetValueCommand from '../command/SetValueCommand';
import RemoveObjectCommand from '../command/RemoveObjectCommand';
import AddObjectCommand from '../command/AddObjectCommand';
/**
* LMesh组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function LMeshComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
this.isPlaying = false;
}
LMeshComponent.prototype = Object.create(BaseComponent.prototype);
LMeshComponent.prototype.constructor = LMeshComponent;
LMeshComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'lmeshPanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
width: '100%',
color: '#555',
fontWeight: 'bold'
},
text: 'LMesh组件'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '动画'
}, {
xtype: 'select',
id: 'anims',
scope: this.id,
onChange: this.onSelectAnim.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label'
}, {
xtype: 'button',
id: 'btnPreview',
scope: this.id,
text: '预览',
onClick: this.onPreview.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
LMeshComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
LMeshComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
LMeshComponent.prototype.updateUI = function () {
var container = UI.get('lmeshPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected.userData.type === 'lol') {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var anims = UI.get('anims', this.id);
var btnPreview = UI.get('btnPreview', this.id);
var model = this.selected.userData.model;
var animNames = model.getAnimations();
var options = {
};
animNames.forEach(n => {
options[n] = n;
});
anims.setOptions(options);
anims.setValue(animNames[0]);
if (this.isPlaying) {
btnPreview.setText('取消');
} else {
btnPreview.setText('预览');
}
};
LMeshComponent.prototype.onSelectAnim = function () {
var anims = UI.get('anims', this.id);
var model = this.selected.userData.model;
model.setAnimation(anims.getValue());
};
LMeshComponent.prototype.onPreview = function () {
if (this.isPlaying) {
this.stopPreview();
} else {
this.startPreview();
}
};
LMeshComponent.prototype.startPreview = function () {
var btnPreview = UI.get('btnPreview', this.id);
this.isPlaying = true;
btnPreview.setText('取消');
this.onSelectAnim();
this.app.on(`animate.${this.id}`, this.onAnimate.bind(this));
};
LMeshComponent.prototype.stopPreview = function () {
var btnPreview = UI.get('btnPreview', this.id);
this.isPlaying = false;
btnPreview.setText('预览');
this.app.on(`animate.${this.id}`, null);
};
LMeshComponent.prototype.onAnimate = function (clock, deltaTime) {
var model = this.selected.userData.model;
model.update(clock.getElapsedTime() * 1000);
};
export default LMeshComponent;

View File

@ -0,0 +1,342 @@
import BaseComponent from './BaseComponent';
import SetValueCommand from '../command/SetValueCommand';
/**
* 光源组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function LightComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
LightComponent.prototype = Object.create(BaseComponent.prototype);
LightComponent.prototype.constructor = LightComponent;
LightComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'lightPanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
color: '#555',
fontWeight: 'bold'
},
text: '光源组件'
}]
}, {
xtype: 'row',
id: 'objectColorRow',
scope: this.id,
children: [{
xtype: 'label',
text: '颜色'
}, {
xtype: 'color',
id: 'objectColor',
scope: this.id,
onChange: this.onChangeColor.bind(this)
}]
}, {
xtype: 'row',
id: 'objectIntensityRow',
scope: this.id,
children: [{
xtype: 'label',
text: '强度'
}, {
xtype: 'number',
id: 'objectIntensity',
scope: this.id,
range: [0, Infinity],
onChange: this.onChangeIntensity.bind(this)
}]
}, {
xtype: 'row',
id: 'objectDistanceRow',
scope: this.id,
children: [{
xtype: 'label',
text: '距离'
}, {
xtype: 'number',
id: 'objectDistance',
scope: this.id,
range: [0, Infinity],
onChange: this.onChangeDistance.bind(this)
}]
}, {
xtype: 'row',
id: 'objectAngleRow',
scope: this.id,
children: [{
xtype: 'label',
text: '角度'
}, {
xtype: 'number',
id: 'objectAngle',
scope: this.id,
precision: 3,
range: [0, Math.PI / 2],
onChange: this.onChangeAngle.bind(this)
}]
}, {
xtype: 'row',
id: 'objectPenumbraRow',
scope: this.id,
children: [{
xtype: 'label',
text: '半阴影'
}, {
xtype: 'number',
id: 'objectPenumbra',
scope: this.id,
range: [0, 1],
onChange: this.onChangePenumbra.bind(this)
}]
}, {
xtype: 'row',
id: 'objectDecayRow',
scope: this.id,
children: [{
xtype: 'label',
text: '衰减'
}, {
xtype: 'number',
id: 'objectDecay',
scope: this.id,
range: [0, Infinity],
onChange: this.onChangeDecay.bind(this)
}]
}, {
xtype: 'row',
id: 'objectSkyColorRow',
scope: this.id,
children: [{
xtype: 'label',
text: '天空颜色'
}, {
xtype: 'color',
id: 'objectSkyColor',
scope: this.id,
onChange: this.onChangeSkyColor.bind(this)
}]
}, {
xtype: 'row',
id: 'objectGroundColorRow',
scope: this.id,
children: [{
xtype: 'label',
text: '地面颜色'
}, {
xtype: 'color',
id: 'objectGroundColor',
scope: this.id,
onChange: this.onChangeGroundColor.bind(this)
}]
}, {
xtype: 'row',
id: 'objectWidthRow',
scope: this.id,
children: [{
xtype: 'label',
text: '宽度'
}, {
xtype: 'number',
id: 'objectWidth',
scope: this.id,
range: [0, Infinity],
onChange: this.onChangeWidth.bind(this)
}]
}, {
xtype: 'row',
id: 'objectHeightRow',
scope: this.id,
children: [{
xtype: 'label',
text: '高度'
}, {
xtype: 'number',
id: 'objectHeight',
scope: this.id,
range: [0, Infinity],
onChange: this.onChangeHeight.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
LightComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
LightComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
LightComponent.prototype.updateUI = function () {
var container = UI.get('lightPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Light) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var objectColorRow = UI.get('objectColorRow', this.id);
var objectIntensityRow = UI.get('objectIntensityRow', this.id);
var objectDistanceRow = UI.get('objectDistanceRow', this.id);
var objectAngleRow = UI.get('objectAngleRow', this.id);
var objectPenumbraRow = UI.get('objectPenumbraRow', this.id);
var objectDecayRow = UI.get('objectDecayRow', this.id);
var objectSkyColorRow = UI.get('objectSkyColorRow', this.id);
var objectGroundColorRow = UI.get('objectGroundColorRow', this.id);
var objectWidthRow = UI.get('objectWidthRow', this.id);
var objectHeightRow = UI.get('objectHeightRow', this.id);
var objectColor = UI.get('objectColor', this.id);
var objectIntensity = UI.get('objectIntensity', this.id);
var objectDistance = UI.get('objectDistance', this.id);
var objectAngle = UI.get('objectAngle', this.id);
var objectPenumbra = UI.get('objectPenumbra', this.id);
var objectDecay = UI.get('objectDecay', this.id);
var objectSkyColor = UI.get('objectSkyColor', this.id);
var objectGroundColor = UI.get('objectGroundColor', this.id);
var objectWidth = UI.get('objectWidth', this.id);
var objectHeight = UI.get('objectHeight', this.id);
if (this.selected instanceof THREE.HemisphereLight) {
objectColorRow.dom.style.display = 'none';
} else {
objectColorRow.dom.style.display = '';
objectColor.setValue(`#${this.selected.color.getHexString()}`);
}
objectIntensityRow.dom.style.display = '';
objectIntensity.setValue(this.selected.intensity);
if (this.selected instanceof THREE.PointLight || this.selected instanceof THREE.SpotLight) {
objectDistanceRow.dom.style.display = '';
objectDecayRow.dom.style.display = '';
objectDistance.setValue(this.selected.distance);
objectDecay.setValue(this.selected.decay);
} else {
objectDistanceRow.dom.style.display = 'none';
objectDecayRow.dom.style.display = 'none';
}
if (this.selected instanceof THREE.SpotLight) {
objectAngleRow.dom.style.display = '';
objectPenumbraRow.dom.style.display = '';
objectAngle.setValue(this.selected.angle);
objectPenumbra.setValue(this.selected.penumbra);
} else {
objectAngleRow.dom.style.display = 'none';
objectPenumbraRow.dom.style.display = 'none';
}
if (this.selected instanceof THREE.HemisphereLight) {
objectSkyColorRow.dom.style.display = '';
objectGroundColorRow.dom.style.display = '';
objectSkyColor.setValue(`#${this.selected.color.getHexString()}`);
objectGroundColor.setValue(`#${this.selected.groundColor.getHexString()}`);
} else {
objectSkyColorRow.dom.style.display = 'none';
objectGroundColorRow.dom.style.display = 'none';
}
if (this.selected instanceof THREE.RectAreaLight) {
objectWidthRow.dom.style.display = '';
objectHeightRow.dom.style.display = '';
objectWidth.setValue(this.selected.width);
objectHeight.setValue(this.selected.height);
} else {
objectWidthRow.dom.style.display = 'none';
objectHeightRow.dom.style.display = 'none';
}
};
LightComponent.prototype.onChangeColor = function () {
var objectColor = UI.get('objectColor', this.id);
this.selected.color = new THREE.Color(objectColor.getHexValue());
var helper = this.selected.children.filter(n => n.userData.type === 'helper')[0];
if (helper) {
helper.material.color = this.selected.color;
}
};
LightComponent.prototype.onChangeIntensity = function () {
var objectIntensity = UI.get('objectIntensity', this.id);
this.selected.intensity = objectIntensity.getValue();
};
LightComponent.prototype.onChangeDistance = function () {
var objectDistance = UI.get('objectDistance', this.id);
this.selected.distance = objectDistance.getValue();
};
LightComponent.prototype.onChangeAngle = function () {
var objectAngle = UI.get('objectAngle', this.id);
this.selected.angle = objectAngle.getValue();
};
LightComponent.prototype.onChangePenumbra = function () {
var objectPenumbra = UI.get('objectPenumbra', this.id);
this.selected.penumbra = objectPenumbra.getValue();
};
LightComponent.prototype.onChangeDecay = function () {
var objectDecay = UI.get('objectDecay', this.id);
this.selected.decay = objectDecay.getValue();
};
LightComponent.prototype.onChangeSkyColor = function () {
var objectSkyColor = UI.get('objectSkyColor', this.id);
this.selected.color = new THREE.Color(objectSkyColor.getHexValue());
var sky = this.selected.children.filter(n => n.userData.type === 'sky')[0];
if (sky) {
sky.material.uniforms.topColor.value = this.selected.color;
}
};
LightComponent.prototype.onChangeGroundColor = function () {
var objectGroundColor = UI.get('objectGroundColor', this.id);
this.selected.groundColor = new THREE.Color(objectGroundColor.getHexValue());
var sky = this.selected.children.filter(n => n.userData.type === 'sky')[0];
if (sky) {
sky.material.uniforms.bottomColor.value = this.selected.groundColor;
}
};
LightComponent.prototype.onChangeWidth = function () {
var objectWidth = UI.get('objectWidth', this.id);
this.selected.width = objectWidth.getValue();
};
LightComponent.prototype.onChangeHeight = function () {
var objectHeight = UI.get('objectHeight', this.id);
this.selected.height = objectHeight.getValue();
};
export default LightComponent;

View File

@ -0,0 +1,114 @@
import BaseComponent from './BaseComponent';
import SetValueCommand from '../command/SetValueCommand';
import RemoveObjectCommand from '../command/RemoveObjectCommand';
import AddObjectCommand from '../command/AddObjectCommand';
import MMDWindow from '../editor/window/MMDWindow';
/**
* MMD模型组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function MMDComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
MMDComponent.prototype = Object.create(BaseComponent.prototype);
MMDComponent.prototype.constructor = MMDComponent;
MMDComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'mmdPanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
width: '100%',
color: '#555',
fontWeight: 'bold'
},
text: 'MMD模型'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '模型动画'
}, {
xtype: 'input',
id: 'animation',
scope: this.id,
disabled: true,
style: {
width: '80px',
fontSize: '12px'
}
}, {
xtype: 'button',
text: '选择',
onClick: this.selectAnimation.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
MMDComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
MMDComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
MMDComponent.prototype.updateUI = function () {
var container = UI.get('mmdPanel', this.id);
var editor = this.app.editor;
if (editor.selected && (editor.selected.userData.Type === 'pmd' || editor.selected.userData.Type === 'pmx')) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var animation = UI.get('animation', this.id);
if (this.selected.userData.Animation) {
animation.setValue(this.selected.userData.Animation.Name);
}
};
MMDComponent.prototype.selectAnimation = function () {
if (this.mmdWindow === undefined) {
this.mmdWindow = new MMDWindow({
app: this.app,
onSelect: this.onSelectAnimation.bind(this)
});
this.mmdWindow.render();
}
this.mmdWindow.show();
};
MMDComponent.prototype.onSelectAnimation = function (data) {
this.selected.userData.Animation = {};
Object.assign(this.selected.userData.Animation, data);
this.updateUI();
};
export default MMDComponent;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,583 @@
import BaseComponent from './BaseComponent';
import SetValueCommand from '../command/SetValueCommand';
/**
* 粒子发射器组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function ParticleEmitterComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
this.isPlaying = false;
}
ParticleEmitterComponent.prototype = Object.create(BaseComponent.prototype);
ParticleEmitterComponent.prototype.constructor = ParticleEmitterComponent;
ParticleEmitterComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'particleEmitterPanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'label',
style: {
width: '100%',
color: '#555',
fontWeight: 'bold'
},
text: '粒子发射器'
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '位置'
}, {
xtype: 'number',
id: 'positionX',
scope: this.id,
onChange: this.onChangePosition.bind(this)
}, {
xtype: 'number',
id: 'positionY',
scope: this.id,
onChange: this.onChangePosition.bind(this)
}, {
xtype: 'number',
id: 'positionZ',
scope: this.id,
onChange: this.onChangePosition.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '位置发散'
}, {
xtype: 'number',
id: 'positionSpreadX',
scope: this.id,
onChange: this.onChangePosition.bind(this)
}, {
xtype: 'number',
id: 'positionSpreadY',
scope: this.id,
onChange: this.onChangePosition.bind(this)
}, {
xtype: 'number',
id: 'positionSpreadZ',
scope: this.id,
onChange: this.onChangePosition.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '速度'
}, {
xtype: 'number',
id: 'velocityX',
scope: this.id,
onChange: this.onChangeVelocity.bind(this)
}, {
xtype: 'number',
id: 'velocityY',
scope: this.id,
onChange: this.onChangeVelocity.bind(this)
}, {
xtype: 'number',
id: 'velocityZ',
scope: this.id,
onChange: this.onChangeVelocity.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '速度发散'
}, {
xtype: 'number',
id: 'velocitySpreadX',
scope: this.id,
onChange: this.onChangeVelocity.bind(this)
}, {
xtype: 'number',
id: 'velocitySpreadY',
scope: this.id,
onChange: this.onChangeVelocity.bind(this)
}, {
xtype: 'number',
id: 'velocitySpreadZ',
scope: this.id,
onChange: this.onChangeVelocity.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '加速度'
}, {
xtype: 'number',
id: 'accelerationX',
scope: this.id,
onChange: this.onChangeAcceleration.bind(this)
}, {
xtype: 'number',
id: 'accelerationY',
scope: this.id,
onChange: this.onChangeAcceleration.bind(this)
}, {
xtype: 'number',
id: 'accelerationZ',
scope: this.id,
onChange: this.onChangeAcceleration.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '加速度发散'
}, {
xtype: 'number',
id: 'accelerationSpreadX',
scope: this.id,
onChange: this.onChangeAcceleration.bind(this)
}, {
xtype: 'number',
id: 'accelerationSpreadY',
scope: this.id,
onChange: this.onChangeAcceleration.bind(this)
}, {
xtype: 'number',
id: 'accelerationSpreadZ',
scope: this.id,
onChange: this.onChangeAcceleration.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '颜色1'
}, {
xtype: 'color',
id: 'color1',
scope: this.id,
onChange: this.onChangeColor.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '颜色2'
}, {
xtype: 'color',
id: 'color2',
scope: this.id,
onChange: this.onChangeColor.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '颜色3'
}, {
xtype: 'color',
id: 'color3',
scope: this.id,
onChange: this.onChangeColor.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '颜色4'
}, {
xtype: 'color',
id: 'color4',
scope: this.id,
onChange: this.onChangeColor.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '尺寸'
}, {
xtype: 'number',
id: 'size',
scope: this.id,
onChange: this.onChangeSize.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '尺寸发散'
}, {
xtype: 'number',
id: 'sizeSpread',
scope: this.id,
onChange: this.onChangeSize.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '纹理'
}, {
xtype: 'texture',
id: 'texture',
scope: this.id,
onChange: this.onChangeTexture.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '粒子数量'
}, {
xtype: 'int',
range: [1, Infinity],
id: 'particleCount',
scope: this.id,
onChange: this.onChangeParticleCount.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '持续时长'
}, {
xtype: 'number',
id: 'maxAge',
scope: this.id,
onChange: this.onChangeMaxAge.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '持续时长发散'
}, {
xtype: 'number',
id: 'maxAgeSpread',
scope: this.id,
onChange: this.onChangeMaxAgeSpread.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label'
}, {
xtype: 'button',
id: 'btnPreview',
scope: this.id,
text: '预览',
onClick: this.onPreview.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
ParticleEmitterComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
ParticleEmitterComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
ParticleEmitterComponent.prototype.updateUI = function () {
var container = UI.get('particleEmitterPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected.userData.type === 'ParticleEmitter') {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var positionX = UI.get('positionX', this.id);
var positionY = UI.get('positionY', this.id);
var positionZ = UI.get('positionZ', this.id);
var positionSpreadX = UI.get('positionSpreadX', this.id);
var positionSpreadY = UI.get('positionSpreadY', this.id);
var positionSpreadZ = UI.get('positionSpreadZ', this.id);
var velocityX = UI.get('velocityX', this.id);
var velocityY = UI.get('velocityY', this.id);
var velocityZ = UI.get('velocityZ', this.id);
var velocitySpreadX = UI.get('velocitySpreadX', this.id);
var velocitySpreadY = UI.get('velocitySpreadY', this.id);
var velocitySpreadZ = UI.get('velocitySpreadZ', this.id);
var accelerationX = UI.get('accelerationX', this.id);
var accelerationY = UI.get('accelerationY', this.id);
var accelerationZ = UI.get('accelerationZ', this.id);
var accelerationSpreadX = UI.get('accelerationSpreadX', this.id);
var accelerationSpreadY = UI.get('accelerationSpreadY', this.id);
var accelerationSpreadZ = UI.get('accelerationSpreadZ', this.id);
var color1 = UI.get('color1', this.id);
var color2 = UI.get('color2', this.id);
var color3 = UI.get('color3', this.id);
var color4 = UI.get('color4', this.id);
var size = UI.get('size', this.id);
var sizeSpread = UI.get('sizeSpread', this.id);
var texture = UI.get('texture', this.id);
var particleCount = UI.get('particleCount', this.id);
var maxAge = UI.get('maxAge', this.id);
var maxAgeSpread = UI.get('maxAgeSpread', this.id);
var btnPreview = UI.get('btnPreview', this.id);
var group = this.selected.userData.group;
var emitter = group.emitters[0];
positionX.setValue(emitter.position.value.x);
positionY.setValue(emitter.position.value.y);
positionZ.setValue(emitter.position.value.z);
positionSpreadX.setValue(emitter.position.spread.x);
positionSpreadY.setValue(emitter.position.spread.y);
positionSpreadZ.setValue(emitter.position.spread.z);
velocityX.setValue(emitter.velocity.value.x);
velocityY.setValue(emitter.velocity.value.y);
velocityZ.setValue(emitter.velocity.value.z);
velocitySpreadX.setValue(emitter.velocity.spread.x);
velocitySpreadY.setValue(emitter.velocity.spread.y);
velocitySpreadZ.setValue(emitter.velocity.spread.z);
accelerationX.setValue(emitter.acceleration.value.x);
accelerationY.setValue(emitter.acceleration.value.y);
accelerationZ.setValue(emitter.acceleration.value.z);
accelerationSpreadX.setValue(emitter.acceleration.spread.x);
accelerationSpreadY.setValue(emitter.acceleration.spread.y);
accelerationSpreadZ.setValue(emitter.acceleration.spread.z);
color1.setValue(`#${emitter.color.value[0].getHexString()}`);
color2.setValue(`#${emitter.color.value[1].getHexString()}`);
color3.setValue(`#${emitter.color.value[2].getHexString()}`);
color4.setValue(`#${emitter.color.value[3].getHexString()}`);
size.setValue(emitter.size.value[0]);
sizeSpread.setValue(emitter.size.spread[0]);
texture.setValue(group.texture);
particleCount.setValue(emitter.particleCount);
maxAge.setValue(emitter.maxAge.value);
maxAgeSpread.setValue(emitter.maxAge.spread);
if (this.isPlaying) {
btnPreview.setText('取消');
} else {
btnPreview.setText('预览');
}
};
ParticleEmitterComponent.prototype.onChangePosition = function () {
var positionX = UI.get('positionX', this.id);
var positionY = UI.get('positionY', this.id);
var positionZ = UI.get('positionZ', this.id);
var positionSpreadX = UI.get('positionSpreadX', this.id);
var positionSpreadY = UI.get('positionSpreadY', this.id);
var positionSpreadZ = UI.get('positionSpreadZ', this.id);
var group = this.selected.userData.group;
var emitter = group.emitters[0];
emitter.position.value.x = positionX.getValue();
emitter.position.value.y = positionY.getValue();
emitter.position.value.z = positionZ.getValue();
emitter.position.spread.x = positionSpreadX.getValue();
emitter.position.spread.y = positionSpreadY.getValue();
emitter.position.spread.z = positionSpreadZ.getValue();
emitter.updateFlags.position = true;
};
ParticleEmitterComponent.prototype.onChangeVelocity = function () {
var velocityX = UI.get('velocityX', this.id);
var velocityY = UI.get('velocityY', this.id);
var velocityZ = UI.get('velocityZ', this.id);
var velocitySpreadX = UI.get('velocitySpreadX', this.id);
var velocitySpreadY = UI.get('velocitySpreadY', this.id);
var velocitySpreadZ = UI.get('velocitySpreadZ', this.id);
var group = this.selected.userData.group;
var emitter = group.emitters[0];
emitter.velocity.value.x = velocityX.getValue();
emitter.velocity.value.y = velocityY.getValue();
emitter.velocity.value.z = velocityZ.getValue();
emitter.velocity.spread.x = velocitySpreadX.getValue();
emitter.velocity.spread.y = velocitySpreadY.getValue();
emitter.velocity.spread.z = velocitySpreadZ.getValue();
emitter.updateFlags.velocity = true;
};
ParticleEmitterComponent.prototype.onChangeAcceleration = function () {
var accelerationX = UI.get('accelerationX', this.id);
var accelerationY = UI.get('accelerationY', this.id);
var accelerationZ = UI.get('accelerationZ', this.id);
var accelerationSpreadX = UI.get('accelerationSpreadX', this.id);
var accelerationSpreadY = UI.get('accelerationSpreadY', this.id);
var accelerationSpreadZ = UI.get('accelerationSpreadZ', this.id);
var group = this.selected.userData.group;
var emitter = group.emitters[0];
emitter.acceleration.value.x = accelerationX.getValue();
emitter.acceleration.value.y = accelerationY.getValue();
emitter.acceleration.value.z = accelerationZ.getValue();
emitter.acceleration.spread.x = accelerationSpreadX.getValue();
emitter.acceleration.spread.y = accelerationSpreadY.getValue();
emitter.acceleration.spread.z = accelerationSpreadZ.getValue();
emitter.updateFlags.acceleration = true;
};
ParticleEmitterComponent.prototype.onChangeColor = function () {
var color1 = UI.get('color1', this.id);
var color2 = UI.get('color2', this.id);
var color3 = UI.get('color3', this.id);
var color4 = UI.get('color4', this.id);
var group = this.selected.userData.group;
var emitter = group.emitters[0];
emitter.color.value[0] = new THREE.Color(color1.getHexValue());
emitter.color.value[1] = new THREE.Color(color2.getHexValue());
emitter.color.value[2] = new THREE.Color(color3.getHexValue());
emitter.color.value[3] = new THREE.Color(color4.getHexValue());
emitter.updateFlags.color = true;
};
ParticleEmitterComponent.prototype.onChangeSize = function () {
var size = UI.get('size', this.id);
var sizeSpread = UI.get('sizeSpread', this.id);
var group = this.selected.userData.group;
var emitter = group.emitters[0];
for (var i = 0; i < emitter.size.value.length; i++) {
emitter.size.value[i] = size.getValue();
emitter.size.spread[i] = sizeSpread.getValue();
}
emitter.updateFlags.size = true;
};
ParticleEmitterComponent.prototype.onChangeTexture = function () {
var texture = UI.get('texture', this.id);
var group = this.selected.userData.group;
var emitter = group.emitters[0];
texture = texture.getValue();
texture.needsUpdate = true;
group.texture = texture;
group.material.uniforms.texture.value = texture;
};
ParticleEmitterComponent.prototype.onChangeParticleCount = function () {
var particleCount = UI.get('particleCount', this.id);
var group = this.selected.userData.group;
var emitter = group.emitters[0];
emitter.particleCount = particleCount.getValue();
emitter.updateFlags.params = true;
};
ParticleEmitterComponent.prototype.onChangeMaxAge = function () {
var maxAge = UI.get('maxAge', this.id);
var group = this.selected.userData.group;
var emitter = group.emitters[0];
emitter.maxAge.value = maxAge.getValue();
emitter.updateFlags.params = true;
};
ParticleEmitterComponent.prototype.onChangeMaxAgeSpread = function () {
var maxAgeSpread = UI.get('maxAgeSpread', this.id);
var group = this.selected.userData.group;
var emitter = group.emitters[0];
emitter.maxAge.spread = maxAgeSpread.getValue();
emitter.updateFlags.params = true;
};
ParticleEmitterComponent.prototype.onPreview = function () {
if (this.isPlaying) {
this.stopPreview();
} else {
this.startPreview();
}
};
ParticleEmitterComponent.prototype.startPreview = function () {
var btnPreview = UI.get('btnPreview', this.id);
this.isPlaying = true;
btnPreview.setText('取消');
this.app.on(`animate.${this.id}`, this.onAnimate.bind(this));
};
ParticleEmitterComponent.prototype.stopPreview = function () {
var btnPreview = UI.get('btnPreview', this.id);
this.isPlaying = false;
btnPreview.setText('预览');
var group = this.selected.userData.group;
var emitter = this.selected.userData.emitter;
group.removeEmitter(emitter);
group.addEmitter(emitter);
group.tick(0);
this.app.on(`animate.${this.id}`, null);
};
ParticleEmitterComponent.prototype.onAnimate = function (clock, deltaTime) {
var group = this.selected.userData.group;
group.tick(deltaTime);
};
export default ParticleEmitterComponent;

View File

@ -0,0 +1,258 @@
import BaseComponent from './BaseComponent';
import SetValueCommand from '../command/SetValueCommand';
import RemoveObjectCommand from '../command/RemoveObjectCommand';
import AddObjectCommand from '../command/AddObjectCommand';
/**
* 反光组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function ReflectorComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
ReflectorComponent.prototype = Object.create(BaseComponent.prototype);
ReflectorComponent.prototype.constructor = ReflectorComponent;
ReflectorComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'reflectorPanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
color: '#555',
fontWeight: 'bold'
},
text: '反光组件'
}]
}, {
xtype: 'row',
scope: this.id,
children: [{
xtype: 'label',
text: '反光'
}, {
xtype: 'checkbox',
id: 'reflect',
scope: this.id,
onChange: this.onChangeReflect.bind(this)
}]
}, {
xtype: 'row',
id: 'colorRow',
scope: this.id,
children: [{
xtype: 'label',
text: '颜色'
}, {
xtype: 'color',
id: 'color',
scope: this.id,
value: 0xffffff,
onChange: this.onChangeReflect.bind(this)
}]
}, {
xtype: 'row',
id: 'sizeRow',
scope: this.id,
children: [{
xtype: 'label',
text: '贴图尺寸'
}, {
xtype: 'select',
id: 'size',
scope: this.id,
options: {
512: '512*512',
1024: '1024*1024',
2048: '2048*2048'
},
value: '1024',
onChange: this.onChangeReflect.bind(this)
}]
}, {
xtype: 'row',
id: 'clipBiasRow',
scope: this.id,
children: [{
xtype: 'label',
text: '裁剪偏移'
}, {
xtype: 'number',
id: 'clipBias',
scope: this.id,
value: 0,
onChange: this.onChangeReflect.bind(this)
}]
}, {
xtype: 'row',
id: 'recursionRow',
scope: this.id,
children: [{
xtype: 'label',
text: '递归'
}, {
xtype: 'checkbox',
id: 'recursion',
scope: this.id,
value: false,
onChange: this.onChangeReflect.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
ReflectorComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
ReflectorComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
ReflectorComponent.prototype.updateUI = function () {
var container = UI.get('reflectorPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Mesh) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var colorRow = UI.get('colorRow', this.id);
var sizeRow = UI.get('sizeRow', this.id);
var clipBiasRow = UI.get('clipBiasRow', this.id);
var recursionRow = UI.get('recursionRow', this.id);
var reflect = UI.get('reflect', this.id);
var color = UI.get('color', this.id);
var size = UI.get('size', this.id);
var clipBias = UI.get('clipBias', this.id);
var recursion = UI.get('recursion', this.id);
reflect.setValue(this.selected instanceof THREE.Reflector);
if (this.selected instanceof THREE.Reflector) {
colorRow.dom.style.display = '';
sizeRow.dom.style.display = '';
clipBiasRow.dom.style.display = '';
recursionRow.dom.style.display = '';
color.setHexValue(this.selected.userData.color);
size.setValue(this.selected.userData.size);
clipBias.setValue(this.selected.userData.clipBias);
recursion.setValue(this.selected.userData.recursion);
} else {
colorRow.dom.style.display = 'none';
sizeRow.dom.style.display = 'none';
clipBiasRow.dom.style.display = 'none';
recursionRow.dom.style.display = 'none';
}
};
ReflectorComponent.prototype.onChangeReflect = function () {
var reflect = UI.get('reflect', this.id);
var color = UI.get('color', this.id);
var size = UI.get('size', this.id);
var clipBias = UI.get('clipBias', this.id);
var recursion = UI.get('recursion', this.id);
var editor = this.app.editor;
if (reflect.getValue()) {
color = color.getHexValue();
if (!(this.selected instanceof THREE.Reflector) && !Array.isArray(this.selected.material) && this.selected.material.color) {
color = this.selected.material.color.getHex();
}
var reflector = new THREE.Reflector(this.selected.geometry, {
color: color,
textureWidth: parseInt(size.getValue()),
textureHeight: parseInt(size.getValue()),
clipBias: clipBias.getValue(),
recursion: recursion.getValue() ? 1 : 0
});
reflector.name = this.selected.name;
reflector.position.copy(this.selected.position);
reflector.rotation.copy(this.selected.rotation);
reflector.scale.copy(this.selected.scale);
reflector.castShadow = this.selected.castShadow;
reflector.receiveShadow = this.selected.receiveShadow;
if (this.selected instanceof THREE.Reflector) {
Object.assign(reflector.userData, this.selected.userData);
} else {
Object.assign(reflector.userData, this.selected.userData, {
mesh: this.selected
});
}
reflector.userData.color = color;
reflector.userData.size = size.getValue();
reflector.userData.clipBias = clipBias.getValue();
reflector.userData.recursion = recursion.getValue();
var index = editor.scene.children.indexOf(this.selected);
if (index > -1) {
editor.scene.children[index] = reflector;
reflector.parent = this.selected.parent;
this.selected.parent = null;
this.app.call(`objectRemoved`, this, this.selected);
this.app.call(`objectAdded`, this, reflector);
editor.select(reflector);
this.app.call('sceneGraphChanged', this.id);
}
} else {
if (this.selected instanceof THREE.Reflector) {
var mesh = this.selected.userData.mesh;
this.selected.userData.mesh = null;
mesh.name = this.selected.name;
mesh.position.copy(this.selected.position);
mesh.rotation.copy(this.selected.rotation);
mesh.scale.copy(this.selected.scale);
mesh.castShadow = this.selected.castShadow;
mesh.receiveShadow = this.selected.receiveShadow;
if (!Array.isArray(mesh.material) && mesh.material.color) {
mesh.material.color = new THREE.Color(color.getHexValue());
}
Object.assign(mesh.userData, this.selected.userData);
var index = editor.scene.children.indexOf(this.selected);
if (index > -1) {
editor.scene.children[index] = mesh;
mesh.parent = this.selected.parent;
this.selected.parent = null;
this.app.call(`objectRemoved`, this, this.selected);
this.app.call(`objectAdded`, this, mesh);
editor.select(mesh);
this.app.call('sceneGraphChanged', this.id);
}
}
}
};
export default ReflectorComponent;

View File

@ -0,0 +1,613 @@
import BaseComponent from './BaseComponent';
import Converter from '../utils/Converter';
import Ajax from '../utils/Ajax';
import TextureWindow from '../editor/window/TextureWindow';
/**
* 场景组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function SceneComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
SceneComponent.prototype = Object.create(BaseComponent.prototype);
SceneComponent.prototype.constructor = SceneComponent;
SceneComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'scenePanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
color: '#555',
fontWeight: 'bold'
},
text: '场景组件'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '背景'
}, {
xtype: 'select',
id: 'backgroundType',
scope: this.id,
options: {
'Color': '纯色',
'Image': '背景图片',
'SkyBox': '立体贴图'
},
onChange: this.onChangeBackgroundType.bind(this)
}]
}, {
xtype: 'row',
id: 'backgroundColorRow',
scope: this.id,
children: [{
xtype: 'label',
text: '背景颜色'
}, {
xtype: 'color',
id: 'backgroundColor',
scope: this.id,
onChange: this.update.bind(this)
}]
}, {
xtype: 'row',
id: 'backgroundImageRow',
scope: this.id,
children: [{
xtype: 'label',
text: '背景图片'
}, {
xtype: 'texture',
id: 'backgroundImage',
scope: this.id,
onChange: this.update.bind(this)
}]
}, {
xtype: 'row',
id: 'backgroundPosXRow',
scope: this.id,
children: [{
xtype: 'label',
text: 'x轴正向'
}, {
xtype: 'texture',
id: 'backgroundPosX',
scope: this.id,
onChange: this.update.bind(this)
}]
}, {
xtype: 'row',
id: 'backgroundNegXRow',
scope: this.id,
children: [{
xtype: 'label',
text: 'x轴负向'
}, {
xtype: 'texture',
id: 'backgroundNegX',
scope: this.id,
onChange: this.update.bind(this)
}]
}, {
xtype: 'row',
id: 'backgroundPosYRow',
scope: this.id,
children: [{
xtype: 'label',
text: 'y轴正向'
}, {
xtype: 'texture',
id: 'backgroundPosY',
scope: this.id,
onChange: this.update.bind(this)
}]
}, {
xtype: 'row',
id: 'backgroundNegYRow',
scope: this.id,
children: [{
xtype: 'label',
text: 'y轴负向'
}, {
xtype: 'texture',
id: 'backgroundNegY',
scope: this.id,
onChange: this.update.bind(this)
}]
}, {
xtype: 'row',
id: 'backgroundPosZRow',
scope: this.id,
children: [{
xtype: 'label',
text: 'z轴正向'
}, {
xtype: 'texture',
id: 'backgroundPosZ',
scope: this.id,
onChange: this.update.bind(this)
}]
}, {
xtype: 'row',
id: 'backgroundNegZRow',
scope: this.id,
children: [{
xtype: 'label',
text: 'z轴负向'
}, {
xtype: 'texture',
id: 'backgroundNegZ',
scope: this.id,
onChange: this.update.bind(this)
}]
}, {
xtype: 'row',
id: 'cubeTextureCommandRow',
scope: this.id,
style: {
display: 'none'
},
children: [{
xtype: 'button',
text: '获取',
onClick: this.onLoadCubeTexture.bind(this)
}, {
xtype: 'button',
text: '上传',
style: {
marginLeft: '8px'
},
onClick: this.onSaveCubeTexture.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '雾'
}, {
xtype: 'select',
id: 'fogType',
scope: this.id,
options: {
'None': '无',
'Fog': '线性',
'FogExp2': '指数型'
},
onChange: this.onChangeFogType.bind(this)
}]
}, {
xtype: 'row',
id: 'fogColorRow',
scope: this.id,
children: [{
xtype: 'label',
text: '雾颜色'
}, {
xtype: 'color',
id: 'fogColor',
scope: this.id,
onChange: this.update.bind(this)
}]
}, {
xtype: 'row',
id: 'fogNearRow',
scope: this.id,
children: [{
xtype: 'label',
text: '雾近点'
}, {
xtype: 'number',
id: 'fogNear',
scope: this.id,
range: [0, Infinity],
onChange: this.update.bind(this)
}]
}, {
xtype: 'row',
id: 'fogFarRow',
scope: this.id,
children: [{
xtype: 'label',
text: '雾远点'
}, {
xtype: 'number',
id: 'fogFar',
scope: this.id,
range: [0, Infinity],
onChange: this.update.bind(this)
}]
}, {
xtype: 'row',
id: 'fogDensityRow',
scope: this.id,
children: [{
xtype: 'label',
text: '雾浓度'
}, {
xtype: 'number',
id: 'fogDensity',
scope: this.id,
range: [0, 0.1],
precision: 3,
onChange: this.update.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
SceneComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
SceneComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
SceneComponent.prototype.updateUI = function () {
var container = UI.get('scenePanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Scene) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var scene = this.selected;
// 背景
var backgroundColorRow = UI.get('backgroundColorRow', this.id);
var backgroundImageRow = UI.get('backgroundImageRow', this.id);
var backgroundPosXRow = UI.get('backgroundPosXRow', this.id);
var backgroundNegXRow = UI.get('backgroundNegXRow', this.id);
var backgroundPosYRow = UI.get('backgroundPosYRow', this.id);
var backgroundNegYRow = UI.get('backgroundNegYRow', this.id);
var backgroundPosZRow = UI.get('backgroundPosZRow', this.id);
var backgroundNegZRow = UI.get('backgroundNegZRow', this.id);
var backgroundType = UI.get('backgroundType', this.id);
var backgroundColor = UI.get('backgroundColor', this.id);
var backgroundImage = UI.get('backgroundImage', this.id);
var backgroundPosX = UI.get('backgroundPosX', this.id);
var backgroundNegX = UI.get('backgroundNegX', this.id);
var backgroundPosY = UI.get('backgroundPosY', this.id);
var backgroundNegY = UI.get('backgroundNegY', this.id);
var backgroundPosZ = UI.get('backgroundPosZ', this.id);
var backgroundNegZ = UI.get('backgroundNegZ', this.id);
backgroundType.setValue(`${scene.background instanceof THREE.CubeTexture ? 'SkyBox' : (scene.background instanceof THREE.Texture ? 'Image' : 'Color')}`);
backgroundColorRow.dom.style.display = scene.background instanceof THREE.Color ? '' : 'none';
backgroundColor.setValue(`#${scene.background instanceof THREE.Color ? scene.background.getHexString() : 'aaaaaa'}`);
backgroundImageRow.dom.style.display = (scene.background instanceof THREE.Texture && !(scene.background instanceof THREE.CubeTexture)) ? '' : 'none';
backgroundImage.setValue((scene.background instanceof THREE.Texture && !(scene.background instanceof THREE.CubeTexture)) ? scene.background : null);
backgroundPosXRow.dom.style.display = scene.background instanceof THREE.CubeTexture ? '' : 'none';
backgroundNegXRow.dom.style.display = scene.background instanceof THREE.CubeTexture ? '' : 'none';
backgroundPosYRow.dom.style.display = scene.background instanceof THREE.CubeTexture ? '' : 'none';
backgroundNegYRow.dom.style.display = scene.background instanceof THREE.CubeTexture ? '' : 'none';
backgroundPosZRow.dom.style.display = scene.background instanceof THREE.CubeTexture ? '' : 'none';
backgroundNegZRow.dom.style.display = scene.background instanceof THREE.CubeTexture ? '' : 'none';
backgroundPosX.setValue(scene.background instanceof THREE.CubeTexture ? new THREE.Texture(scene.background.image[0]) : null);
backgroundNegX.setValue(scene.background instanceof THREE.CubeTexture ? new THREE.Texture(scene.background.image[1]) : null);
backgroundPosY.setValue(scene.background instanceof THREE.CubeTexture ? new THREE.Texture(scene.background.image[2]) : null);
backgroundNegY.setValue(scene.background instanceof THREE.CubeTexture ? new THREE.Texture(scene.background.image[3]) : null);
backgroundPosZ.setValue(scene.background instanceof THREE.CubeTexture ? new THREE.Texture(scene.background.image[4]) : null);
backgroundNegZ.setValue(scene.background instanceof THREE.CubeTexture ? new THREE.Texture(scene.background.image[5]) : null);
// 雾效
var fogColorRow = UI.get('fogColorRow', this.id);
var fogNearRow = UI.get('fogNearRow', this.id);
var fogFarRow = UI.get('fogFarRow', this.id);
var fogDensityRow = UI.get('fogDensityRow', this.id);
var fogType = UI.get('fogType', this.id);
var fogColor = UI.get('fogColor', this.id);
var fogNear = UI.get('fogNear', this.id);
var fogFar = UI.get('fogFar', this.id);
var fogDensity = UI.get('fogDensity', this.id);
fogType.setValue(scene.fog == null ? 'None' : ((scene.fog instanceof THREE.FogExp2) ? 'FogExp2' : 'Fog'));
fogColorRow.dom.style.display = scene.fog == null ? 'none' : '';
fogColor.setValue(`#${scene.fog == null ? 'aaaaaa' : scene.fog.color.getHexString()}`);
fogNearRow.dom.style.display = (scene.fog && scene.fog instanceof THREE.Fog) ? '' : 'none';
fogNear.setValue((scene.fog && scene.fog instanceof THREE.Fog) ? scene.fog.near : 0.1);
fogFarRow.dom.style.display = (scene.fog && scene.fog instanceof THREE.Fog) ? '' : 'none';
fogFar.setValue((scene.fog && scene.fog instanceof THREE.Fog) ? scene.fog.far : 50);
fogDensityRow.dom.style.display = (scene.fog && scene.fog instanceof THREE.FogExp2) ? '' : 'none';
fogDensity.setValue((scene.fog && scene.fog instanceof THREE.FogExp2) ? fog.density : 0.05);
};
SceneComponent.prototype.onChangeBackgroundType = function () { // 切换背景类型
var backgroundType = UI.get('backgroundType', this.id);
var backgroundColorRow = UI.get('backgroundColorRow', this.id);
var backgroundImageRow = UI.get('backgroundImageRow', this.id);
var backgroundPosXRow = UI.get('backgroundPosXRow', this.id);
var backgroundNegXRow = UI.get('backgroundNegXRow', this.id);
var backgroundPosYRow = UI.get('backgroundPosYRow', this.id);
var backgroundNegYRow = UI.get('backgroundNegYRow', this.id);
var backgroundPosZRow = UI.get('backgroundPosZRow', this.id);
var backgroundNegZRow = UI.get('backgroundNegZRow', this.id);
var cubeTextureCommandRow = UI.get('cubeTextureCommandRow', this.id);
switch (backgroundType.getValue()) {
case 'Color':
backgroundColorRow.dom.style.display = '';
backgroundImageRow.dom.style.display = 'none';
backgroundPosXRow.dom.style.display = 'none';
backgroundNegXRow.dom.style.display = 'none';
backgroundPosYRow.dom.style.display = 'none';
backgroundNegYRow.dom.style.display = 'none';
backgroundPosZRow.dom.style.display = 'none';
backgroundNegZRow.dom.style.display = 'none';
cubeTextureCommandRow.dom.style.display = 'none';
break;
case 'Image':
backgroundColorRow.dom.style.display = 'none';
backgroundImageRow.dom.style.display = '';
backgroundPosXRow.dom.style.display = 'none';
backgroundNegXRow.dom.style.display = 'none';
backgroundPosYRow.dom.style.display = 'none';
backgroundNegYRow.dom.style.display = 'none';
backgroundPosZRow.dom.style.display = 'none';
backgroundNegZRow.dom.style.display = 'none';
cubeTextureCommandRow.dom.style.display = 'none';
break;
case 'SkyBox':
backgroundColorRow.dom.style.display = 'none';
backgroundImageRow.dom.style.display = 'none';
backgroundPosXRow.dom.style.display = '';
backgroundNegXRow.dom.style.display = '';
backgroundPosYRow.dom.style.display = '';
backgroundNegYRow.dom.style.display = '';
backgroundPosZRow.dom.style.display = '';
backgroundNegZRow.dom.style.display = '';
cubeTextureCommandRow.dom.style.display = '';
break;
}
this.update();
};
SceneComponent.prototype.onLoadCubeTexture = function () { // 加载立体贴图
if (this.textureWindow === undefined) {
this.textureWindow = new TextureWindow({
app: this.app,
onSelect: this.onSelectCubeTexture.bind(this)
});
this.textureWindow.render();
}
this.textureWindow.show();
};
SceneComponent.prototype.onSelectCubeTexture = function (model) {
if (model.Type !== 'cube') {
UI.msg('只允许选择立体贴图!');
return;
}
var urls = model.Url.split(';');
var loader = new THREE.TextureLoader();
var promises = urls.map(url => {
return new Promise(resolve => {
loader.load(`${this.app.options.server}${url}`, texture => {
resolve(texture);
}, undefined, error => {
console.error(error);
UI.msg('立体贴图获取失败!');
});
});
});
Promise.all(promises).then(textures => {
UI.get('backgroundPosX', this.id).setValue(textures[0]);
UI.get('backgroundNegX', this.id).setValue(textures[1]);
UI.get('backgroundPosY', this.id).setValue(textures[2]);
UI.get('backgroundNegY', this.id).setValue(textures[3]);
UI.get('backgroundPosZ', this.id).setValue(textures[4]);
UI.get('backgroundNegZ', this.id).setValue(textures[5]);
this.textureWindow.hide();
this.update();
});
};
SceneComponent.prototype.onSaveCubeTexture = function () { // 保存立体贴图
var texturePosX = UI.get('backgroundPosX', this.id).getValue();
var textureNegX = UI.get('backgroundNegX', this.id).getValue();
var texturePosY = UI.get('backgroundPosY', this.id).getValue();
var textureNegY = UI.get('backgroundNegY', this.id).getValue();
var texturePosZ = UI.get('backgroundPosZ', this.id).getValue();
var textureNegZ = UI.get('backgroundNegZ', this.id).getValue();
if (!texturePosX || !textureNegX || !texturePosY || !textureNegY || !texturePosZ || !textureNegZ) {
UI.msg(`请上传所有立体贴图后再点击保存!`);
return;
}
var posXSrc = texturePosX.image.src;
var negXSrc = textureNegX.image.src;
var posYSrc = texturePosY.image.src;
var negYSrc = textureNegY.image.src;
var posZSrc = texturePosZ.image.src;
var negZSrc = textureNegZ.image.src;
if (posXSrc.startsWith('http') || negXSrc.startsWith('http') || posYSrc.startsWith('http') || negYSrc.startsWith('http') || posZSrc.startsWith('http') || negZSrc.startsWith('http')) {
UI.msg(`立体贴图已经存在于服务端,无需重复上传。`);
return;
}
// TODO: 下面代码转换出的DataURL太大不行
// 如果src是服务端地址则需要转成DataURL
// if (posXSrc.startsWith('http')) {
// posXSrc = Converter.canvasToDataURL(Converter.imageToCanvas(texturePosX.image));
// }
// if (negXSrc.startsWith('http')) {
// negXSrc = Converter.canvasToDataURL(Converter.imageToCanvas(textureNegX.image));
// }
// if (posYSrc.startsWith('http')) {
// posYSrc = Converter.canvasToDataURL(Converter.imageToCanvas(texturePosY.image));
// }
// if (negYSrc.startsWith('http')) {
// negYSrc = Converter.canvasToDataURL(Converter.imageToCanvas(textureNegY.image));
// }
// if (posZSrc.startsWith('http')) {
// posZSrc = Converter.canvasToDataURL(Converter.imageToCanvas(texturePosZ.image));
// }
// if (negZSrc.startsWith('http')) {
// negZSrc = Converter.canvasToDataURL(Converter.imageToCanvas(textureNegZ.image));
// }
var promises = [
Converter.dataURLtoFile(posXSrc, 'posX'),
Converter.dataURLtoFile(negXSrc, 'negX'),
Converter.dataURLtoFile(posYSrc, 'posY'),
Converter.dataURLtoFile(negYSrc, 'negY'),
Converter.dataURLtoFile(posZSrc, 'posZ'),
Converter.dataURLtoFile(negZSrc, 'negZ'),
];
Promise.all(promises).then(files => {
Ajax.post(`${this.app.options.server}/api/Texture/Add`, {
posX: files[0],
negX: files[1],
posY: files[2],
negY: files[3],
posZ: files[4],
negZ: files[5],
}, result => {
var obj = JSON.parse(result);
UI.msg(obj.Msg);
});
});
};
SceneComponent.prototype.onChangeFogType = function () { // 切换雾类型
var fogType = UI.get('fogType', this.id);
var fogColorRow = UI.get('fogColorRow', this.id);
var fogNearRow = UI.get('fogNearRow', this.id);
var fogFarRow = UI.get('fogFarRow', this.id);
var fogDensityRow = UI.get('fogDensityRow', this.id);
switch (fogType.getValue()) {
case 'None':
fogColorRow.dom.style.display = 'none';
fogNearRow.dom.style.display = 'none';
fogFarRow.dom.style.display = 'none';
fogDensityRow.dom.style.display = 'none';
break;
case 'Fog':
fogColorRow.dom.style.display = '';
fogNearRow.dom.style.display = '';
fogFarRow.dom.style.display = '';
fogDensityRow.dom.style.display = 'none';
break;
case 'FogExp2':
fogColorRow.dom.style.display = '';
fogNearRow.dom.style.display = 'none';
fogFarRow.dom.style.display = 'none';
fogDensityRow.dom.style.display = '';
break;
}
this.update();
};
SceneComponent.prototype.update = function () {
var scene = this.selected;
// 背景
var backgroundType = UI.get('backgroundType', this.id).getValue();
var backgroundColor = UI.get('backgroundColor', this.id).getHexValue();
var backgroundImage = UI.get('backgroundImage', this.id).getValue();
var backgroundPosX = UI.get('backgroundPosX', this.id).getValue();
var backgroundNegX = UI.get('backgroundNegX', this.id).getValue();
var backgroundPosY = UI.get('backgroundPosY', this.id).getValue();
var backgroundNegY = UI.get('backgroundNegY', this.id).getValue();
var backgroundPosZ = UI.get('backgroundPosZ', this.id).getValue();
var backgroundNegZ = UI.get('backgroundNegZ', this.id).getValue();
switch (backgroundType) {
case 'Color':
scene.background = new THREE.Color(backgroundColor);
break;
case 'Image':
if (backgroundImage) {
scene.background = backgroundImage;
}
break;
case 'SkyBox':
if (backgroundPosX && backgroundNegX && backgroundPosY && backgroundNegY && backgroundPosZ && backgroundNegZ) {
scene.background = new THREE.CubeTexture([
backgroundPosX.image,
backgroundNegX.image,
backgroundPosY.image,
backgroundNegY.image,
backgroundPosZ.image,
backgroundNegZ.image
]);
scene.background.needsUpdate = true;
}
break;
}
// 雾
var fogType = UI.get('fogType', this.id).getValue();
var fogColor = UI.get('fogColor', this.id).getHexValue();
var fogNear = UI.get('fogNear', this.id).getValue();
var fogFar = UI.get('fogFar', this.id).getValue();
var fogDensity = UI.get('fogDensity', this.id).getValue();
switch (fogType) {
case 'None':
scene.fog = null;
break;
case 'Fog':
scene.fog = new THREE.Fog(fogColor, fogNear, fogFar);
break;
case 'FogExp2':
scene.fog = new THREE.FogExp2(fogColor, fogDensity);
break;
}
};
export default SceneComponent;

View File

@ -0,0 +1,363 @@
import BaseComponent from './BaseComponent';
import SetValueCommand from '../command/SetValueCommand';
/**
* 阴影组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function ShadowComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
ShadowComponent.prototype = Object.create(BaseComponent.prototype);
ShadowComponent.prototype.constructor = ShadowComponent;
ShadowComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'shadowPanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
color: '#555',
fontWeight: 'bold'
},
text: '阴影组件'
}]
}, {
xtype: 'row',
id: 'objectShadowRow',
scope: this.id,
children: [{
xtype: 'label',
text: '阴影'
}, {
xtype: 'boolean',
id: 'objectCastShadow',
scope: this.id,
value: false,
text: '产生',
onChange: this.onChangeCastShadow.bind(this)
}, {
xtype: 'boolean',
id: 'objectReceiveShadow',
scope: this.id,
value: false,
text: '接收',
onChange: this.onChangeReceiveShadow.bind(this)
}]
}, {
xtype: 'row',
id: 'objectShadowRadiusRow',
scope: this.id,
children: [{
xtype: 'label',
text: '半径'
}, {
xtype: 'number',
id: 'objectShadowRadius',
scope: this.id,
value: 1,
onChange: this.onChangeShadowRadius.bind(this)
}]
}, {
xtype: 'row',
id: 'objectMapSizeRow',
scope: this.id,
children: [{
xtype: 'label',
text: '贴图尺寸'
}, {
xtype: 'select',
id: 'objectMapSize',
scope: this.id,
options: {
512: '512*512',
1024: '1024*1024',
2048: '2048*2048'
},
value: 512,
onChange: this.onChangeMapSize.bind(this)
}]
}, {
xtype: 'row',
id: 'objectBiasRow',
scope: this.id,
children: [{
xtype: 'label',
text: '偏差'
}, {
xtype: 'number',
id: 'objectBias',
scope: this.id,
value: 0,
range: [0, 1],
onChange: this.onChangeBias.bind(this)
}]
}, {
xtype: 'row',
id: 'objectCameraLeftRow',
scope: this.id,
children: [{
xtype: 'label',
text: '相机左'
}, {
xtype: 'number',
id: 'objectCameraLeft',
scope: this.id,
value: -5,
onChange: this.onChangeCameraLeft.bind(this)
}]
}, {
xtype: 'row',
id: 'objectCameraRightRow',
scope: this.id,
children: [{
xtype: 'label',
text: '相机右'
}, {
xtype: 'number',
id: 'objectCameraRight',
scope: this.id,
value: 5,
onChange: this.onChangeCameraRight.bind(this)
}]
}, {
xtype: 'row',
id: 'objectCameraTopRow',
scope: this.id,
children: [{
xtype: 'label',
text: '相机上'
}, {
xtype: 'number',
id: 'objectCameraTop',
scope: this.id,
value: 5,
onChange: this.onChangeCameraTop.bind(this)
}]
}, {
xtype: 'row',
id: 'objectCameraBottomRow',
scope: this.id,
children: [{
xtype: 'label',
text: '相机下'
}, {
xtype: 'number',
id: 'objectCameraBottom',
scope: this.id,
value: -5,
onChange: this.onChangeCameraBottom.bind(this)
}]
}, {
xtype: 'row',
id: 'objectCameraNearRow',
scope: this.id,
children: [{
xtype: 'label',
text: '相机近'
}, {
xtype: 'number',
id: 'objectCameraNear',
scope: this.id,
value: 0.5,
range: [0, Infinity],
onChange: this.onChangeCameraNear.bind(this)
}]
}, {
xtype: 'row',
id: 'objectCameraFarRow',
scope: this.id,
children: [{
xtype: 'label',
text: '相机远'
}, {
xtype: 'number',
id: 'objectCameraFar',
scope: this.id,
value: 0.5,
range: [0, Infinity],
onChange: this.onChangeCameraFar.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
ShadowComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
ShadowComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
ShadowComponent.prototype.updateUI = function () {
var container = UI.get('shadowPanel', this.id);
var editor = this.app.editor;
if (editor.selected && (editor.selected instanceof THREE.Mesh || editor.selected instanceof THREE.DirectionalLight || editor.selected instanceof THREE.PointLight || editor.selected instanceof THREE.SpotLight)) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var objectShadowRadiusRow = UI.get('objectShadowRadiusRow', this.id);
var objectMapSizeRow = UI.get('objectMapSizeRow', this.id);
var objectBiasRow = UI.get('objectBiasRow', this.id);
var objectCameraLeftRow = UI.get('objectCameraLeftRow', this.id);
var objectCameraRightRow = UI.get('objectCameraRightRow', this.id);
var objectCameraTopRow = UI.get('objectCameraTopRow', this.id);
var objectCameraBottomRow = UI.get('objectCameraBottomRow', this.id);
var objectCameraNearRow = UI.get('objectCameraNearRow', this.id);
var objectCameraFarRow = UI.get('objectCameraFarRow', this.id);
var objectCastShadow = UI.get('objectCastShadow', this.id);
var objectReceiveShadow = UI.get('objectReceiveShadow', this.id);
var objectShadowRadius = UI.get('objectShadowRadius', this.id);
var objectMapSize = UI.get('objectMapSize', this.id);
var objectBias = UI.get('objectBias', this.id);
var objectCameraLeft = UI.get('objectCameraLeft', this.id);
var objectCameraRight = UI.get('objectCameraRight', this.id);
var objectCameraTop = UI.get('objectCameraTop', this.id);
var objectCameraBottom = UI.get('objectCameraBottom', this.id);
var objectCameraNear = UI.get('objectCameraNear', this.id);
var objectCameraFar = UI.get('objectCameraFar', this.id);
objectCastShadow.setValue(this.selected.castShadow);
if (this.selected instanceof THREE.Light) {
objectReceiveShadow.dom.style.display = 'none';
objectShadowRadiusRow.dom.style.display = '';
objectMapSizeRow.dom.style.display = '';
objectBiasRow.dom.style.display = '';
objectCameraLeftRow.dom.style.display = '';
objectCameraRightRow.dom.style.display = '';
objectCameraTopRow.dom.style.display = '';
objectCameraBottomRow.dom.style.display = '';
objectCameraNearRow.dom.style.display = '';
objectCameraFarRow.dom.style.display = '';
objectShadowRadius.setValue(this.selected.shadow.radius);
var mapSize = this.selected.shadow.mapSize;
objectMapSize.setValue(mapSize.x);
objectBias.setValue(this.selected.shadow.bias);
objectCameraLeft.setValue(this.selected.shadow.camera.left);
objectCameraRight.setValue(this.selected.shadow.camera.right);
objectCameraTop.setValue(this.selected.shadow.camera.top);
objectCameraBottom.setValue(this.selected.shadow.camera.bottom);
objectCameraNear.setValue(this.selected.shadow.camera.near);
objectCameraFar.setValue(this.selected.shadow.camera.far);
} else {
objectReceiveShadow.dom.style.display = '';
objectShadowRadiusRow.dom.style.display = 'none';
objectMapSizeRow.dom.style.display = 'none';
objectBiasRow.dom.style.display = 'none';
objectCameraLeftRow.dom.style.display = 'none';
objectCameraRightRow.dom.style.display = 'none';
objectCameraTopRow.dom.style.display = 'none';
objectCameraBottomRow.dom.style.display = 'none';
objectCameraNearRow.dom.style.display = 'none';
objectCameraFarRow.dom.style.display = 'none';
objectReceiveShadow.setValue(this.selected.receiveShadow);
}
};
ShadowComponent.prototype.onChangeCastShadow = function () {
var objectCastShadow = UI.get('objectCastShadow', this.id);
this.selected.castShadow = objectCastShadow.getValue();
if (this.selected instanceof THREE.Mesh) {
this.updateMaterial(this.selected.material);
}
};
ShadowComponent.prototype.onChangeReceiveShadow = function () {
var objectReceiveShadow = UI.get('objectReceiveShadow', this.id);
this.selected.receiveShadow = objectReceiveShadow.getValue();
if (this.selected instanceof THREE.Mesh) {
this.updateMaterial(this.selected.material);
}
};
ShadowComponent.prototype.onChangeShadowRadius = function () {
var objectShadowRadius = UI.get('objectShadowRadius', this.id);
this.selected.shadow.radius = objectShadowRadius.getValue();
};
ShadowComponent.prototype.updateMaterial = function (material) {
if (Array.isArray(material)) {
material.forEach(n => {
n.needsUpdate = true;
});
} else {
material.needsUpdate = true;
}
};
ShadowComponent.prototype.onChangeMapSize = function () {
var objectMapSize = UI.get('objectMapSize', this.id);
var mapSize = objectMapSize.getValue();
this.selected.shadow.mapSize.x = this.selected.shadow.mapSize.y = parseInt(mapSize);
};
ShadowComponent.prototype.onChangeBias = function () {
var objectBias = UI.get('objectBias', this.id);
this.selected.shadow.bias = objectBias.getValue();
};
ShadowComponent.prototype.onChangeCameraLeft = function () {
var objectCameraLeft = UI.get('objectCameraLeft', this.id);
this.selected.shadow.camera.left = objectCameraLeft.getValue();
this.selected.shadow.camera.updateProjectionMatrix();
};
ShadowComponent.prototype.onChangeCameraRight = function () {
var objectCameraRight = UI.get('objectCameraRight', this.id);
this.selected.shadow.camera.right = objectCameraRight.getValue();
this.selected.shadow.camera.updateProjectionMatrix();
};
ShadowComponent.prototype.onChangeCameraTop = function () {
var objectCameraTop = UI.get('objectCameraTop', this.id);
this.selected.shadow.camera.top = objectCameraTop.getValue();
this.selected.shadow.camera.updateProjectionMatrix();
};
ShadowComponent.prototype.onChangeCameraBottom = function () {
var objectCameraBottom = UI.get('objectCameraBottom', this.id);
this.selected.shadow.camera.bottom = objectCameraBottom.getValue();
this.selected.shadow.camera.updateProjectionMatrix();
};
ShadowComponent.prototype.onChangeCameraNear = function () {
var objectCameraNear = UI.get('objectCameraNear', this.id);
this.selected.shadow.camera.near = objectCameraNear.getValue();
this.selected.shadow.camera.updateProjectionMatrix();
};
ShadowComponent.prototype.onChangeCameraFar = function () {
var objectCameraFar = UI.get('objectCameraFar', this.id);
this.selected.shadow.camera.far = objectCameraFar.getValue();
this.selected.shadow.camera.updateProjectionMatrix();
};
export default ShadowComponent;

View File

@ -0,0 +1,118 @@
import BaseComponent from './BaseComponent';
import SetValueCommand from '../command/SetValueCommand';
/**
* 烟组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function SmokeComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
this.isPlaying = false;
}
SmokeComponent.prototype = Object.create(BaseComponent.prototype);
SmokeComponent.prototype.constructor = SmokeComponent;
SmokeComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'smokePanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'label',
style: {
width: '100%',
color: '#555',
fontWeight: 'bold'
},
text: '烟组件'
}, {
xtype: 'row',
children: [{
xtype: 'label'
}, {
xtype: 'button',
id: 'btnPreview',
scope: this.id,
text: '预览',
onClick: this.onPreview.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
SmokeComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
SmokeComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
SmokeComponent.prototype.updateUI = function () {
var container = UI.get('smokePanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected.userData.type === 'Smoke') {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var btnPreview = UI.get('btnPreview', this.id);
if (this.isPlaying) {
btnPreview.setText('取消');
} else {
btnPreview.setText('预览');
}
};
SmokeComponent.prototype.onPreview = function () {
if (this.isPlaying) {
this.stopPreview();
} else {
this.startPreview();
}
};
SmokeComponent.prototype.startPreview = function () {
var btnPreview = UI.get('btnPreview', this.id);
this.isPlaying = true;
btnPreview.setText('取消');
this.app.on(`animate.${this.id}`, this.onAnimate.bind(this));
};
SmokeComponent.prototype.stopPreview = function () {
var btnPreview = UI.get('btnPreview', this.id);
this.isPlaying = false;
btnPreview.setText('预览');
this.app.on(`animate.${this.id}`, null);
};
SmokeComponent.prototype.onAnimate = function (clock, deltaTime) {
var elapsed = clock.getElapsedTime();
this.selected.update(elapsed);
};
export default SmokeComponent;

View File

@ -0,0 +1,238 @@
import BaseComponent from './BaseComponent';
import SetValueCommand from '../command/SetValueCommand';
import SetPositionCommand from '../command/SetPositionCommand';
import SetRotationCommand from '../command/SetRotationCommand';
import SetScaleCommand from '../command/SetScaleCommand';
/**
* 位移组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function TransformComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
TransformComponent.prototype = Object.create(BaseComponent.prototype);
TransformComponent.prototype.constructor = TransformComponent;
TransformComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'transformPanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
color: '#555',
fontWeight: 'bold'
},
text: '位移组件'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '平移'
}, {
xtype: 'number',
id: 'objectPositionX',
scope: this.id,
style: {
width: '40px'
},
onChange: this.onChangePosition.bind(this)
}, {
xtype: 'number',
id: 'objectPositionY',
scope: this.id,
style: {
width: '40px'
},
onChange: this.onChangePosition.bind(this)
}, {
xtype: 'number',
id: 'objectPositionZ',
scope: this.id,
style: {
width: '40px'
},
onChange: this.onChangePosition.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '旋转'
}, {
xtype: 'number',
id: 'objectRotationX',
scope: this.id,
step: 10,
unit: '°',
style: {
width: '40px'
},
onChange: this.onChangeRotation.bind(this)
}, {
xtype: 'number',
id: 'objectRotationY',
scope: this.id,
step: 10,
unit: '°',
style: {
width: '40px'
},
onChange: this.onChangeRotation.bind(this)
}, {
xtype: 'number',
id: 'objectRotationZ',
scope: this.id,
step: 10,
unit: '°',
style: {
width: '40px'
},
onChange: this.onChangeRotation.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '缩放'
}, {
xtype: 'checkbox',
id: 'objectScaleLock',
scope: this.id,
value: true,
style: {
position: 'absolute',
left: '50px'
}
}, {
xtype: 'number',
id: 'objectScaleX',
scope: this.id,
value: 1,
range: [0.01, Infinity],
style: {
width: '40px'
},
onChange: this.onChangeScale.bind(this)
}, {
xtype: 'number',
id: 'objectScaleY',
scope: this.id,
value: 1,
range: [0.01, Infinity],
style: {
width: '40px'
},
onChange: this.onChangeScale.bind(this)
}, {
xtype: 'number',
id: 'objectScaleZ',
scope: this.id,
value: 1,
range: [0.01, Infinity],
style: {
width: '40px'
},
onChange: this.onChangeScale.bind(this)
}]
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
TransformComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
TransformComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
TransformComponent.prototype.updateUI = function () {
var container = UI.get('transformPanel', this.id);
var editor = this.app.editor;
if (editor.selected) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var objectPositionX = UI.get('objectPositionX', this.id);
var objectPositionY = UI.get('objectPositionY', this.id);
var objectPositionZ = UI.get('objectPositionZ', this.id);
var objectRotationX = UI.get('objectRotationX', this.id);
var objectRotationY = UI.get('objectRotationY', this.id);
var objectRotationZ = UI.get('objectRotationZ', this.id);
var objectScaleX = UI.get('objectScaleX', this.id);
var objectScaleY = UI.get('objectScaleY', this.id);
var objectScaleZ = UI.get('objectScaleZ', this.id);
objectPositionX.setValue(this.selected.position.x);
objectPositionY.setValue(this.selected.position.y);
objectPositionZ.setValue(this.selected.position.z);
objectRotationX.setValue(this.selected.rotation.x * 180 / Math.PI);
objectRotationY.setValue(this.selected.rotation.y * 180 / Math.PI);
objectRotationZ.setValue(this.selected.rotation.z * 180 / Math.PI);
objectScaleX.setValue(this.selected.scale.x);
objectScaleY.setValue(this.selected.scale.y);
objectScaleZ.setValue(this.selected.scale.z);
};
TransformComponent.prototype.onChangePosition = function () {
var x = UI.get('objectPositionX', this.id).getValue();
var y = UI.get('objectPositionY', this.id).getValue();
var z = UI.get('objectPositionZ', this.id).getValue();
this.app.editor.execute(new SetPositionCommand(this.selected, new THREE.Vector3(x, y, z)));
};
TransformComponent.prototype.onChangeRotation = function () {
var x = UI.get('objectRotationX', this.id).getValue();
var y = UI.get('objectRotationY', this.id).getValue();
var z = UI.get('objectRotationZ', this.id).getValue();
this.app.editor.execute(new SetRotationCommand(this.selected, new THREE.Euler(x * Math.PI / 180, y * Math.PI / 180, z * Math.PI / 180)));
};
TransformComponent.prototype.onChangeScale = function (value) {
var x = UI.get('objectScaleX', this.id).getValue();
var y = UI.get('objectScaleY', this.id).getValue();
var z = UI.get('objectScaleZ', this.id).getValue();
var locked = UI.get('objectScaleLock', this.id).getValue();
if (locked) {
this.app.editor.execute(new SetScaleCommand(this.selected, new THREE.Vector3(value, value, value)));
} else {
this.app.editor.execute(new SetScaleCommand(this.selected, new THREE.Vector3(x, y, z)));
}
};
export default TransformComponent;

View File

@ -0,0 +1,194 @@
import BaseComponent from '../BaseComponent';
/**
* 动画基本信息组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function BasicAnimationComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
BasicAnimationComponent.prototype = Object.create(BaseComponent.prototype);
BasicAnimationComponent.prototype.constructor = BasicAnimationComponent;
BasicAnimationComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'basicAnimationPanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
borderTop: 0,
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
width: '100%',
color: '#555',
fontWeight: 'bold'
},
text: '基本信息'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '名称'
}, {
xtype: 'input',
id: 'name',
scope: this.id,
style: {
width: '120px'
},
onInput: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '目标'
}, {
xtype: 'input',
id: 'target',
scope: this.id,
disabled: true,
style: {
width: '80px',
marginRight: '8px'
}
}, {
xtype: 'button',
id: 'btnSetTarget',
scope: this.id,
text: '设置',
onClick: this.onSetTarget.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '类型'
}, {
xtype: 'select',
id: 'type',
scope: this.id,
options: {
Tween: '补间动画',
Skeletal: '骨骼动画',
Audio: '播放音乐',
Filter: '滤镜动画',
Particle: '粒子动画'
},
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '开始时间'
}, {
xtype: 'number',
id: 'beginTime',
scope: this.id,
range: [0, Infinity],
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '结束时间'
}, {
xtype: 'number',
id: 'endTime',
scope: this.id,
range: [0, Infinity],
onChange: this.onChange.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`animationSelected.${this.id}`, this.onAnimationSelected.bind(this));
this.app.on(`animationChanged.${this.id}`, this.onAnimationChanged.bind(this));
};
BasicAnimationComponent.prototype.onAnimationSelected = function (animation) {
this.updateUI(animation);
};
BasicAnimationComponent.prototype.onAnimationChanged = function (animation) {
this.updateUI(animation);
};
BasicAnimationComponent.prototype.updateUI = function (animation) {
var container = UI.get('basicAnimationPanel', this.id);
if (animation) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.animation = animation;
var name = UI.get('name', this.id);
var target = UI.get('target', this.id);
var type = UI.get('type', this.id);
var beginTime = UI.get('beginTime', this.id);
var endTime = UI.get('endTime', this.id);
name.setValue(this.animation.name);
if (this.animation.target === null) {
target.setValue('(无)');
} else {
var obj = this.app.editor.objectByUuid(this.animation.target);
if (obj === null) {
target.setValue('(无)');
console.warn(`BasicAnimationComponent: 动作物体${this.animation.target}在场景中不存在。`);
} else {
target.setValue(obj.name);
}
}
type.setValue(this.animation.type);
beginTime.setValue(this.animation.beginTime);
endTime.setValue(this.animation.endTime);
};
BasicAnimationComponent.prototype.onSetTarget = function () {
var selected = this.app.editor.selected;
if (selected == null) {
this.animation.target = null;
} else {
this.animation.target = selected.uuid;
}
this.app.call('animationChanged', this, this.animation);
};
BasicAnimationComponent.prototype.onChange = function () {
var name = UI.get('name', this.id);
var type = UI.get('type', this.id);
var beginTime = UI.get('beginTime', this.id);
var endTime = UI.get('endTime', this.id);
this.animation.name = name.getValue();
this.animation.type = type.getValue();
this.animation.beginTime = beginTime.getValue();
this.animation.endTime = endTime.getValue();
this.app.call('animationChanged', this, this.animation);
};
export default BasicAnimationComponent;

View File

@ -0,0 +1,556 @@
import BaseComponent from '../BaseComponent';
/**
* 补间动画组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function TweenAnimationComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
TweenAnimationComponent.prototype = Object.create(BaseComponent.prototype);
TweenAnimationComponent.prototype.constructor = TweenAnimationComponent;
TweenAnimationComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'tweenAnimationPanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
width: '100%',
color: '#555',
fontWeight: 'bold'
},
text: '补间动画'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '开始状态'
}, {
xtype: 'select',
id: 'beginStatus',
scope: this.id,
options: {
Current: '当前状态',
Custom: '自定义'
},
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
id: 'beginPositionRow',
scope: this.id,
style: {
display: 'none'
},
children: [{
xtype: 'label',
text: '平移'
}, {
xtype: 'number',
id: 'beginPositionX',
scope: this.id,
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'beginPositionY',
scope: this.id,
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'beginPositionZ',
scope: this.id,
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
id: 'beginRotationRow',
scope: this.id,
style: {
display: 'none'
},
children: [{
xtype: 'label',
text: '旋转'
}, {
xtype: 'number',
id: 'beginRotationX',
scope: this.id,
step: 10,
unit: '°',
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'beginRotationY',
scope: this.id,
step: 10,
unit: '°',
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'beginRotationZ',
scope: this.id,
step: 10,
unit: '°',
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
id: 'beginScaleRow',
scope: this.id,
style: {
display: 'none'
},
children: [{
xtype: 'label',
text: '缩放'
}, {
xtype: 'checkbox',
id: 'beginScaleLock',
scope: this.id,
value: true,
style: {
position: 'absolute',
left: '50px'
}
}, {
xtype: 'number',
id: 'beginScaleX',
scope: this.id,
value: 1,
range: [0.01, Infinity],
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'beginScaleY',
scope: this.id,
value: 1,
range: [0.01, Infinity],
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'beginScaleZ',
scope: this.id,
value: 1,
range: [0.01, Infinity],
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '插值函数'
}, {
xtype: 'select',
id: 'ease',
scope: this.id,
options: {
linear: 'Linear',
quadIn: 'Quad In',
quadOut: 'Quad Out',
quadInOut: 'Quad In Out',
cubicIn: 'Cubic In',
cubicOut: 'Cubic Out',
cubicInOut: 'Cubic InOut',
quartIn: 'Quart In',
quartOut: 'Quart Out',
quartInOut: 'Quart InOut',
quintIn: 'Quint In',
quintOut: 'Quint Out',
quintInOut: 'Quint In Out',
sineIn: 'Sine In',
sineOut: 'Sine Out',
sineInOut: 'Sine In Out',
backIn: 'Back In',
backOut: 'Back Out',
backInOut: 'Back In Out',
circIn: 'Circ In',
circOut: 'Circ Out',
circInOut: 'Circ In Out',
bounceIn: 'Bounce In',
bounceOut: 'Bounce Out',
bounceInOut: 'Bounce In Out',
elasticIn: 'Elastic In',
elasticOut: 'Elastic Out',
elasticInOut: 'Elastic In Out'
},
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '结束状态'
}, {
xtype: 'select',
id: 'endStatus',
scope: this.id,
options: {
Current: '当前状态',
Custom: '自定义'
},
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
id: 'endPositionRow',
scope: this.id,
style: {
display: 'none'
},
children: [{
xtype: 'label',
text: '平移'
}, {
xtype: 'number',
id: 'endPositionX',
scope: this.id,
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'endPositionY',
scope: this.id,
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'endPositionZ',
scope: this.id,
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
id: 'endRotationRow',
scope: this.id,
style: {
display: 'none'
},
children: [{
xtype: 'label',
text: '旋转'
}, {
xtype: 'number',
id: 'endRotationX',
scope: this.id,
step: 10,
unit: '°',
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'endRotationY',
scope: this.id,
step: 10,
unit: '°',
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'endRotationZ',
scope: this.id,
step: 10,
unit: '°',
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
id: 'endScaleRow',
scope: this.id,
style: {
display: 'none'
},
children: [{
xtype: 'label',
text: '缩放'
}, {
xtype: 'checkbox',
id: 'endScaleLock',
scope: this.id,
value: true,
style: {
position: 'absolute',
left: '50px'
}
}, {
xtype: 'number',
id: 'endScaleX',
scope: this.id,
value: 1,
range: [0.01, Infinity],
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'endScaleY',
scope: this.id,
value: 1,
range: [0.01, Infinity],
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'endScaleZ',
scope: this.id,
value: 1,
range: [0.01, Infinity],
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`animationSelected.${this.id}`, this.onAnimationSelected.bind(this));
this.app.on(`animationChanged.${this.id}`, this.onAnimationChanged.bind(this));
};
TweenAnimationComponent.prototype.onAnimationSelected = function (animation) {
this.updateUI(animation);
};
TweenAnimationComponent.prototype.onAnimationChanged = function (animation) {
this.updateUI(animation);
};
TweenAnimationComponent.prototype.updateUI = function (animation) {
var container = UI.get('tweenAnimationPanel', this.id);
if (animation && animation.type === 'Tween') {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.animation = animation;
var beginPositionRow = UI.get('beginPositionRow', this.id);
var beginRotationRow = UI.get('beginRotationRow', this.id);
var beginScaleRow = UI.get('beginScaleRow', this.id);
var endPositionRow = UI.get('endPositionRow', this.id);
var endRotationRow = UI.get('endRotationRow', this.id);
var endScaleRow = UI.get('endScaleRow', this.id);
var beginStatus = UI.get('beginStatus', this.id);
var beginPositionX = UI.get('beginPositionX', this.id);
var beginPositionY = UI.get('beginPositionY', this.id);
var beginPositionZ = UI.get('beginPositionZ', this.id);
var beginRotationX = UI.get('beginRotationX', this.id);
var beginRotationY = UI.get('beginRotationY', this.id);
var beginRotationZ = UI.get('beginRotationZ', this.id);
var beginScaleLock = UI.get('beginScaleLock', this.id);
var beginScaleX = UI.get('beginScaleX', this.id);
var beginScaleY = UI.get('beginScaleY', this.id);
var beginScaleZ = UI.get('beginScaleZ', this.id);
var ease = UI.get('ease', this.id);
var endStatus = UI.get('endStatus', this.id);
var endPositionX = UI.get('endPositionX', this.id);
var endPositionY = UI.get('endPositionY', this.id);
var endPositionZ = UI.get('endPositionZ', this.id);
var endRotationX = UI.get('endRotationX', this.id);
var endRotationY = UI.get('endRotationY', this.id);
var endRotationZ = UI.get('endRotationZ', this.id);
var endScaleLock = UI.get('endScaleLock', this.id);
var endScaleX = UI.get('endScaleX', this.id);
var endScaleY = UI.get('endScaleY', this.id);
var endScaleZ = UI.get('endScaleZ', this.id);
switch (this.animation.beginStatus) {
case 'Current':
beginPositionRow.dom.style.display = 'none';
beginRotationRow.dom.style.display = 'none';
beginScaleRow.dom.style.display = 'none';
break;
case 'Custom':
beginPositionRow.dom.style.display = '';
beginRotationRow.dom.style.display = '';
beginScaleRow.dom.style.display = '';
break;
}
switch (this.animation.endStatus) {
case 'Current':
endPositionRow.dom.style.display = 'none';
endRotationRow.dom.style.display = 'none';
endScaleRow.dom.style.display = 'none';
break;
case 'Custom':
endPositionRow.dom.style.display = '';
endRotationRow.dom.style.display = '';
endScaleRow.dom.style.display = '';
break;
}
beginStatus.setValue(this.animation.beginStatus);
beginPositionX.setValue(this.animation.beginPositionX);
beginPositionY.setValue(this.animation.beginPositionY);
beginPositionZ.setValue(this.animation.beginPositionZ);
beginRotationX.setValue(this.animation.beginRotationX * 180 / Math.PI);
beginRotationY.setValue(this.animation.beginRotationY * 180 / Math.PI);
beginRotationZ.setValue(this.animation.beginRotationZ * 180 / Math.PI);
beginScaleLock.setValue(this.animation.beginScaleLock);
beginScaleX.setValue(this.animation.beginScaleX);
beginScaleY.setValue(this.animation.beginScaleY);
beginScaleZ.setValue(this.animation.beginScaleZ);
ease.setValue(this.animation.ease);
endStatus.setValue(this.animation.endStatus);
endPositionX.setValue(this.animation.endPositionX);
endPositionY.setValue(this.animation.endPositionY);
endPositionZ.setValue(this.animation.endPositionZ);
endRotationX.setValue(this.animation.endRotationX * 180 / Math.PI);
endRotationY.setValue(this.animation.endRotationY * 180 / Math.PI);
endRotationZ.setValue(this.animation.endRotationZ * 180 / Math.PI);
endScaleLock.setValue(this.animation.endScaleLock);
endScaleX.setValue(this.animation.endScaleX);
endScaleY.setValue(this.animation.endScaleY);
endScaleZ.setValue(this.animation.endScaleZ);
};
TweenAnimationComponent.prototype.onChange = function () {
var beginPositionRow = UI.get('beginPositionRow', this.id);
var beginRotationRow = UI.get('beginRotationRow', this.id);
var beginScaleRow = UI.get('beginScaleRow', this.id);
var endPositionRow = UI.get('endPositionRow', this.id);
var endRotationRow = UI.get('endRotationRow', this.id);
var endScaleRow = UI.get('endScaleRow', this.id);
var beginStatus = UI.get('beginStatus', this.id);
var beginPositionX = UI.get('beginPositionX', this.id);
var beginPositionY = UI.get('beginPositionY', this.id);
var beginPositionZ = UI.get('beginPositionZ', this.id);
var beginRotationX = UI.get('beginRotationX', this.id);
var beginRotationY = UI.get('beginRotationY', this.id);
var beginRotationZ = UI.get('beginRotationZ', this.id);
var beginScaleLock = UI.get('beginScaleLock', this.id);
var beginScaleX = UI.get('beginScaleX', this.id);
var beginScaleY = UI.get('beginScaleY', this.id);
var beginScaleZ = UI.get('beginScaleZ', this.id);
var ease = UI.get('ease', this.id);
var endStatus = UI.get('endStatus', this.id);
var endPositionX = UI.get('endPositionX', this.id);
var endPositionY = UI.get('endPositionY', this.id);
var endPositionZ = UI.get('endPositionZ', this.id);
var endRotationX = UI.get('endRotationX', this.id);
var endRotationY = UI.get('endRotationY', this.id);
var endRotationZ = UI.get('endRotationZ', this.id);
var endScaleLock = UI.get('endScaleLock', this.id);
var endScaleX = UI.get('endScaleX', this.id);
var endScaleY = UI.get('endScaleY', this.id);
var endScaleZ = UI.get('endScaleZ', this.id);
switch (beginStatus.getValue()) {
case 'Current':
beginPositionRow.dom.style.display = 'none';
beginRotationRow.dom.style.display = 'none';
beginScaleRow.dom.style.display = 'none';
break;
case 'Custom':
beginPositionRow.dom.style.display = '';
beginRotationRow.dom.style.display = '';
beginScaleRow.dom.style.display = '';
break;
}
switch (endStatus.getValue()) {
case 'Current':
endPositionRow.dom.style.display = 'none';
endRotationRow.dom.style.display = 'none';
endScaleRow.dom.style.display = 'none';
break;
case 'Custom':
endPositionRow.dom.style.display = '';
endRotationRow.dom.style.display = '';
endScaleRow.dom.style.display = '';
break;
}
this.animation.beginStatus = beginStatus.getValue();
this.animation.beginPositionX = beginPositionX.getValue();
this.animation.beginPositionY = beginPositionY.getValue();
this.animation.beginPositionZ = beginPositionZ.getValue();
this.animation.beginRotationX = beginRotationX.getValue() * Math.PI / 180;
this.animation.beginRotationY = beginRotationY.getValue() * Math.PI / 180;
this.animation.beginRotationZ = beginRotationZ.getValue() * Math.PI / 180;
this.animation.beginScaleLock = beginScaleLock.getValue();
this.animation.beginScaleX = beginScaleX.getValue();
this.animation.beginScaleY = beginScaleY.getValue();
this.animation.beginScaleZ = beginScaleZ.getValue();
this.animation.ease = ease.getValue();
this.animation.endStatus = endStatus.getValue();
this.animation.endPositionX = endPositionX.getValue();
this.animation.endPositionY = endPositionY.getValue();
this.animation.endPositionZ = endPositionZ.getValue();
this.animation.endRotationX = endRotationX.getValue() * Math.PI / 180;
this.animation.endRotationY = endRotationY.getValue() * Math.PI / 180;
this.animation.endRotationZ = endRotationZ.getValue() * Math.PI / 180;
this.animation.endScaleLock = endScaleLock.getValue();
this.animation.endScaleX = endScaleX.getValue();
this.animation.endScaleY = endScaleY.getValue();
this.animation.endScaleZ = endScaleZ.getValue();
this.app.call('animationChanged', this, this.animation);
};
export default TweenAnimationComponent;

View File

@ -0,0 +1,92 @@
import BaseComponent from '../BaseComponent';
import SetGeometryCommand from '../../command/SetGeometryCommand';
/**
* 音频监听器组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function AudioListenerComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
AudioListenerComponent.prototype = Object.create(BaseComponent.prototype);
AudioListenerComponent.prototype.constructor = AudioListenerComponent;
AudioListenerComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'audioListenerPanel',
scope: this.id,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
width: '100%',
color: '#555',
fontWeight: 'bold'
},
text: '音频监听器'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '主音量'
}, {
xtype: 'number',
id: 'masterVolume',
scope: this.id,
range: [0, 1],
value: 1,
onChange: this.onChangeMasterVolume.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
AudioListenerComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
AudioListenerComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
AudioListenerComponent.prototype.updateUI = function () {
var container = UI.get('audioListenerPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.PerspectiveCamera && editor.selected.children.indexOf(editor.audioListener) > -1) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.audioListener;
var masterVolume = UI.get('masterVolume', this.id);
masterVolume.setValue(this.selected.getMasterVolume());
};
AudioListenerComponent.prototype.onChangeMasterVolume = function () {
var masterVolume = UI.get('masterVolume', this.id);
this.selected.setMasterVolume(masterVolume.getValue());
};
export default AudioListenerComponent;

View File

@ -0,0 +1,216 @@
import BaseComponent from '../BaseComponent';
import SetGeometryCommand from '../../command/SetGeometryCommand';
import AudioWindow from '../../editor/window/AudioWindow';
/**
* 背景音乐组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function BackgroundMusicComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
BackgroundMusicComponent.prototype = Object.create(BaseComponent.prototype);
BackgroundMusicComponent.prototype.constructor = BackgroundMusicComponent;
BackgroundMusicComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'backgroundMusicPanel',
scope: this.id,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
color: '#555',
fontWeight: 'bold'
},
text: '背景音乐'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '音频'
}, {
xtype: 'text',
id: 'name',
scope: this.id,
text: '(无)',
style: {
width: '80px',
border: '1px solid #ddd',
marginRight: '8px'
}
}, {
xtype: 'button',
text: '选择',
onClick: this.onSelect.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '自动播放'
}, {
xtype: 'checkbox',
id: 'autoplay',
scope: this.id,
value: false,
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '循环播放'
}, {
xtype: 'checkbox',
id: 'loop',
scope: this.id,
value: true,
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '音量'
}, {
xtype: 'number',
id: 'volumn',
scope: this.id,
range: [0, 1],
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label'
}, {
xtype: 'button',
id: 'btnPlay',
scope: this.id,
text: '播放',
style: {
display: 'none'
},
onClick: this.onPlay.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
BackgroundMusicComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
BackgroundMusicComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
BackgroundMusicComponent.prototype.updateUI = function () {
var container = UI.get('backgroundMusicPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Audio) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var name = UI.get('name', this.id);
var autoplay = UI.get('autoplay', this.id);
var loop = UI.get('loop', this.id);
var volumn = UI.get('volumn', this.id);
var btnPlay = UI.get('btnPlay', this.id);
name.setValue(this.selected.userData.Name);
autoplay.setValue(this.selected.userData.autoplay);
loop.setValue(this.selected.getLoop());
volumn.setValue(this.selected.getVolume());
if (this.selected.buffer) {
btnPlay.dom.style.display = '';
if (this.selected.isPlaying) {
btnPlay.setText('停止');
} else {
btnPlay.setText('播放');
}
} else {
btnPlay.dom.style.display = 'none';
}
};
BackgroundMusicComponent.prototype.onSelect = function () {
if (this.window === undefined) {
this.window = new AudioWindow({
app: this.app,
onSelect: this.onChange.bind(this)
});
this.window.render();
}
this.window.show();
};
BackgroundMusicComponent.prototype.onChange = function (obj) {
var name = UI.get('name', this.id);
var autoplay = UI.get('autoplay', this.id);
var loop = UI.get('loop', this.id);
var volumn = UI.get('volumn', this.id);
var btnPlay = UI.get('btnPlay', this.id);
if (obj) { // 仅选择窗口会传递obj参数
Object.assign(this.selected.userData, obj);
var loader = new THREE.AudioLoader();
loader.load(obj.Url, buffer => {
this.selected.setBuffer(buffer);
btnPlay.dom.style.display = '';
});
}
this.selected.userData.autoplay = autoplay.getValue(); // 这里不能给this.selected赋值否则音频会自动播放
this.selected.setLoop(loop.getValue());
this.selected.setVolume(volumn.getValue());
this.updateUI();
if (this.window) {
this.window.hide();
}
};
BackgroundMusicComponent.prototype.onPlay = function () {
var btnPlay = UI.get('btnPlay', this.id);
if (this.selected.buffer) {
btnPlay.dom.style.display = '';
if (this.selected.isPlaying) {
this.selected.stop();
btnPlay.setText('播放');
} else {
this.selected.play();
btnPlay.setText('停止');
}
} else {
btnPlay.dom.style.display = 'none';
}
};
export default BackgroundMusicComponent;

View File

@ -0,0 +1,169 @@
import BaseComponent from '../BaseComponent';
import SetGeometryCommand from '../../command/SetGeometryCommand';
/**
* 正方体组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function BoxGeometryComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
BoxGeometryComponent.prototype = Object.create(BaseComponent.prototype);
BoxGeometryComponent.prototype.constructor = BoxGeometryComponent;
BoxGeometryComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'geometryPanel',
scope: this.id,
style: {
borderTop: 0,
marginTop: '8px',
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'row',
children: [{
xtype: 'label',
text: '宽度'
}, {
xtype: 'number',
id: 'width',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '高度'
}, {
xtype: 'number',
id: 'height',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '深度'
}, {
xtype: 'number',
id: 'depth',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '宽度分段'
}, {
xtype: 'int',
id: 'widthSegments',
scope: this.id,
value: 1,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '高度分段'
}, {
xtype: 'int',
id: 'heightSegments',
scope: this.id,
value: 1,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '深度分段'
}, {
xtype: 'int',
id: 'depthSegments',
scope: this.id,
value: 1,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
BoxGeometryComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
BoxGeometryComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
BoxGeometryComponent.prototype.updateUI = function () {
var container = UI.get('geometryPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.BoxBufferGeometry) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var width = UI.get('width', this.id);
var height = UI.get('height', this.id);
var depth = UI.get('depth', this.id);
var widthSegments = UI.get('widthSegments', this.id);
var heightSegments = UI.get('heightSegments', this.id);
var depthSegments = UI.get('depthSegments', this.id);
width.setValue(this.selected.geometry.parameters.width);
height.setValue(this.selected.geometry.parameters.height);
depth.setValue(this.selected.geometry.parameters.depth);
widthSegments.setValue(this.selected.geometry.parameters.widthSegments);
heightSegments.setValue(this.selected.geometry.parameters.heightSegments);
depthSegments.setValue(this.selected.geometry.parameters.depthSegments);
};
BoxGeometryComponent.prototype.onChangeGeometry = function () {
var width = UI.get('width', this.id);
var height = UI.get('height', this.id);
var depth = UI.get('depth', this.id);
var widthSegments = UI.get('widthSegments', this.id);
var heightSegments = UI.get('heightSegments', this.id);
var depthSegments = UI.get('depthSegments', this.id);
this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.BoxBufferGeometry(
width.getValue(),
height.getValue(),
depth.getValue(),
widthSegments.getValue(),
heightSegments.getValue(),
depthSegments.getValue()
)));
};
export default BoxGeometryComponent;

View File

@ -0,0 +1,132 @@
import BaseComponent from '../BaseComponent';
import SetGeometryCommand from '../../command/SetGeometryCommand';
/**
* 圆形组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function CircleGeometryComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
CircleGeometryComponent.prototype = Object.create(BaseComponent.prototype);
CircleGeometryComponent.prototype.constructor = CircleGeometryComponent;
CircleGeometryComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'geometryPanel',
scope: this.id,
style: {
borderTop: 0,
marginTop: '8px',
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
text: '半径'
}, {
xtype: 'number',
id: 'radius',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '分段'
}, {
xtype: 'int',
id: 'segments',
scope: this.id,
value: 16,
range: [3, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '开始弧度'
}, {
xtype: 'number',
id: 'thetaStart',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '转过弧度'
}, {
xtype: 'number',
id: 'thetaLength',
scope: this.id,
value: Math.PI * 2,
onChange: this.onChangeGeometry.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
CircleGeometryComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
CircleGeometryComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
CircleGeometryComponent.prototype.updateUI = function () {
var container = UI.get('geometryPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.CircleBufferGeometry) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var radius = UI.get('radius', this.id);
var segments = UI.get('segments', this.id);
var thetaStart = UI.get('thetaStart', this.id);
var thetaLength = UI.get('thetaLength', this.id);
radius.setValue(this.selected.geometry.parameters.radius);
segments.setValue(this.selected.geometry.parameters.segments);
thetaStart.setValue(this.selected.geometry.parameters.thetaStart === undefined ? 0 : this.selected.geometry.parameters.thetaStart);
thetaLength.setValue(this.selected.geometry.parameters.thetaLength === undefined ? Math.PI * 2 : this.selected.geometry.parameters.thetaLength);
};
CircleGeometryComponent.prototype.onChangeGeometry = function () {
var radius = UI.get('radius', this.id);
var segments = UI.get('segments', this.id);
var thetaStart = UI.get('thetaStart', this.id);
var thetaLength = UI.get('thetaLength', this.id);
this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.CircleBufferGeometry(
radius.getValue(),
segments.getValue(),
thetaStart.getValue(),
thetaLength.getValue()
)));
};
export default CircleGeometryComponent;

View File

@ -0,0 +1,165 @@
import BaseComponent from '../BaseComponent';
import SetGeometryCommand from '../../command/SetGeometryCommand';
/**
* 圆柱组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function CylinderGeometryComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
CylinderGeometryComponent.prototype = Object.create(BaseComponent.prototype);
CylinderGeometryComponent.prototype.constructor = CylinderGeometryComponent;
CylinderGeometryComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'geometryPanel',
scope: this.id,
style: {
borderTop: 0,
marginTop: '8px',
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
text: '顶部半径'
}, {
xtype: 'number',
id: 'radiusTop',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '底部半径'
}, {
xtype: 'number',
id: 'radiusBottom',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '高度'
}, {
xtype: 'number',
id: 'height',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '圆形分段'
}, {
xtype: 'int',
id: 'radialSegments',
scope: this.id,
value: 16,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '高度分段'
}, {
xtype: 'int',
id: 'heightSegments',
scope: this.id,
value: 1,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '两端开口'
}, {
xtype: 'checkbox',
id: 'openEnded',
scope: this.id,
value: false,
onChange: this.onChangeGeometry.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
CylinderGeometryComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
CylinderGeometryComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
CylinderGeometryComponent.prototype.updateUI = function () {
var container = UI.get('geometryPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.CylinderBufferGeometry) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var radiusTop = UI.get('radiusTop', this.id);
var radiusBottom = UI.get('radiusBottom', this.id);
var height = UI.get('height', this.id);
var radialSegments = UI.get('radialSegments', this.id);
var heightSegments = UI.get('heightSegments', this.id);
var openEnded = UI.get('openEnded', this.id);
radiusTop.setValue(this.selected.geometry.parameters.radiusTop);
radiusBottom.setValue(this.selected.geometry.parameters.radiusBottom);
height.setValue(this.selected.geometry.parameters.height);
radialSegments.setValue(this.selected.geometry.parameters.radialSegments);
heightSegments.setValue(this.selected.geometry.parameters.heightSegments);
openEnded.setValue(this.selected.geometry.parameters.openEnded);
};
CylinderGeometryComponent.prototype.onChangeGeometry = function () {
var radiusTop = UI.get('radiusTop', this.id);
var radiusBottom = UI.get('radiusBottom', this.id);
var height = UI.get('height', this.id);
var radialSegments = UI.get('radialSegments', this.id);
var heightSegments = UI.get('heightSegments', this.id);
var openEnded = UI.get('openEnded', this.id);
this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.CylinderBufferGeometry(
radiusTop.getValue(),
radiusBottom.getValue(),
height.getValue(),
radialSegments.getValue(),
heightSegments.getValue(),
openEnded.getValue()
)));
};
export default CylinderGeometryComponent;

View File

@ -0,0 +1,100 @@
import BaseComponent from '../BaseComponent';
import SetGeometryCommand from '../../command/SetGeometryCommand';
/**
* 二十面体组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function IcosahedronGeometryComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
IcosahedronGeometryComponent.prototype = Object.create(BaseComponent.prototype);
IcosahedronGeometryComponent.prototype.constructor = IcosahedronGeometryComponent;
IcosahedronGeometryComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'geometryPanel',
scope: this.id,
style: {
borderTop: 0,
marginTop: '8px',
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
text: '半径'
}, {
xtype: 'number',
id: 'radius',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '面片分段'
}, {
xtype: 'int',
id: 'detail',
scope: this.id,
value: 1,
range: [0, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
IcosahedronGeometryComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
IcosahedronGeometryComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
IcosahedronGeometryComponent.prototype.updateUI = function () {
var container = UI.get('geometryPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.IcosahedronBufferGeometry) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var radius = UI.get('radius', this.id);
var detail = UI.get('detail', this.id);
radius.setValue(this.selected.geometry.parameters.radius);
detail.setValue(this.selected.geometry.parameters.detail);
};
IcosahedronGeometryComponent.prototype.onChangeGeometry = function () {
var radius = UI.get('radius', this.id);
var detail = UI.get('detail', this.id);
this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.IcosahedronBufferGeometry(
radius.getValue(),
detail.getValue()
)));
};
export default IcosahedronGeometryComponent;

View File

@ -0,0 +1,118 @@
import BaseComponent from '../BaseComponent';
import SetGeometryCommand from '../../command/SetGeometryCommand';
/**
* 车床组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function LatheGeometryComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
LatheGeometryComponent.prototype = Object.create(BaseComponent.prototype);
LatheGeometryComponent.prototype.constructor = LatheGeometryComponent;
LatheGeometryComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'geometryPanel',
scope: this.id,
style: {
borderTop: 0,
marginTop: '8px',
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
text: '径向分段'
}, {
xtype: 'int',
id: 'segments',
scope: this.id,
value: 16,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '开始弧度'
}, {
xtype: 'number',
id: 'phiStart',
scope: this.id,
value: 0,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '转过弧度'
}, {
xtype: 'number',
id: 'phiLength',
scope: this.id,
value: Math.PI * 2,
onChange: this.onChangeGeometry.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
LatheGeometryComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
LatheGeometryComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
LatheGeometryComponent.prototype.updateUI = function () {
var container = UI.get('geometryPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.LatheBufferGeometry) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var segments = UI.get('segments', this.id);
var phiStart = UI.get('phiStart', this.id);
var phiLength = UI.get('phiLength', this.id);
segments.setValue(this.selected.geometry.parameters.segments);
phiStart.setValue(this.selected.geometry.parameters.phiStart);
phiLength.setValue(this.selected.geometry.parameters.phiLength);
};
LatheGeometryComponent.prototype.onChangeGeometry = function () {
var segments = UI.get('segments', this.id);
var phiStart = UI.get('phiStart', this.id);
var phiLength = UI.get('phiLength', this.id);
var points = this.selected.geometry.parameters.points;
this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.LatheBufferGeometry(
points,
segments.getValue(),
phiStart.getValue(),
phiLength.getValue()
)));
};
export default LatheGeometryComponent;

View File

@ -0,0 +1,136 @@
import BaseComponent from '../BaseComponent';
import SetGeometryCommand from '../../command/SetGeometryCommand';
/**
* 平板组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function PlaneGeometryComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
PlaneGeometryComponent.prototype = Object.create(BaseComponent.prototype);
PlaneGeometryComponent.prototype.constructor = PlaneGeometryComponent;
PlaneGeometryComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'geometryPanel',
scope: this.id,
style: {
borderTop: 0,
marginTop: '8px',
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'row',
children: [{
xtype: 'label',
text: '宽度'
}, {
xtype: 'number',
id: 'width',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '高度'
}, {
xtype: 'number',
id: 'height',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '宽度分段'
}, {
xtype: 'int',
id: 'widthSegments',
scope: this.id,
value: 1,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '高度分段'
}, {
xtype: 'int',
id: 'heightSegments',
scope: this.id,
value: 1,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
PlaneGeometryComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
PlaneGeometryComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
PlaneGeometryComponent.prototype.updateUI = function () {
var container = UI.get('geometryPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.PlaneBufferGeometry) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var width = UI.get('width', this.id);
var height = UI.get('height', this.id);
var widthSegments = UI.get('widthSegments', this.id);
var heightSegments = UI.get('heightSegments', this.id);
width.setValue(this.selected.geometry.parameters.width);
height.setValue(this.selected.geometry.parameters.height);
widthSegments.setValue(this.selected.geometry.parameters.widthSegments);
heightSegments.setValue(this.selected.geometry.parameters.heightSegments);
};
PlaneGeometryComponent.prototype.onChangeGeometry = function () {
var width = UI.get('width', this.id);
var height = UI.get('height', this.id);
var widthSegments = UI.get('widthSegments', this.id);
var heightSegments = UI.get('heightSegments', this.id);
this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.PlaneBufferGeometry(
width.getValue(),
height.getValue(),
widthSegments.getValue(),
heightSegments.getValue()
)));
};
export default PlaneGeometryComponent;

View File

@ -0,0 +1,181 @@
import BaseComponent from '../BaseComponent';
import SetGeometryCommand from '../../command/SetGeometryCommand';
/**
* 球体组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function SphereGeometryComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
SphereGeometryComponent.prototype = Object.create(BaseComponent.prototype);
SphereGeometryComponent.prototype.constructor = SphereGeometryComponent;
SphereGeometryComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'geometryPanel',
scope: this.id,
style: {
borderTop: 0,
marginTop: '8px',
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
text: '半径'
}, {
xtype: 'number',
id: 'radius',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '宽度分段'
}, {
xtype: 'int',
id: 'widthSegments',
scope: this.id,
value: 1,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '高度分段'
}, {
xtype: 'int',
id: 'heightSegments',
scope: this.id,
value: 1,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '开始经度'
}, {
xtype: 'number',
id: 'phiStart',
scope: this.id,
value: 0,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '转过经度'
}, {
xtype: 'number',
id: 'phiLength',
scope: this.id,
value: Math.PI * 2,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '开始纬度'
}, {
xtype: 'number',
id: 'thetaStart',
scope: this.id,
value: 0,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '转过纬度'
}, {
xtype: 'number',
id: 'thetaLength',
scope: this.id,
value: Math.PI / 2,
onChange: this.onChangeGeometry.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
SphereGeometryComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
SphereGeometryComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
SphereGeometryComponent.prototype.updateUI = function () {
var container = UI.get('geometryPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.SphereBufferGeometry) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var radius = UI.get('radius', this.id);
var widthSegments = UI.get('widthSegments', this.id);
var heightSegments = UI.get('heightSegments', this.id);
var phiStart = UI.get('phiStart', this.id);
var phiLength = UI.get('phiLength', this.id);
var thetaStart = UI.get('thetaStart', this.id);
var thetaLength = UI.get('thetaLength', this.id);
radius.setValue(this.selected.geometry.parameters.radius);
widthSegments.setValue(this.selected.geometry.parameters.widthSegments);
heightSegments.setValue(this.selected.geometry.parameters.heightSegments);
phiStart.setValue(this.selected.geometry.parameters.phiStart);
phiLength.setValue(this.selected.geometry.parameters.phiLength);
thetaStart.setValue(this.selected.geometry.parameters.thetaStart);
thetaLength.setValue(this.selected.geometry.parameters.thetaLength);
};
SphereGeometryComponent.prototype.onChangeGeometry = function () {
var radius = UI.get('radius', this.id);
var widthSegments = UI.get('widthSegments', this.id);
var heightSegments = UI.get('heightSegments', this.id);
var phiStart = UI.get('phiStart', this.id);
var phiLength = UI.get('phiLength', this.id);
var thetaStart = UI.get('thetaStart', this.id);
var thetaLength = UI.get('thetaLength', this.id);
this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.SphereBufferGeometry(
radius.getValue(),
widthSegments.getValue(),
heightSegments.getValue(),
phiStart.getValue(),
phiLength.getValue(),
thetaStart.getValue(),
thetaLength.getValue()
)));
};
export default SphereGeometryComponent;

View File

@ -0,0 +1,197 @@
import BaseComponent from '../BaseComponent';
import SetGeometryCommand from '../../command/SetGeometryCommand';
/**
* 茶壶组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function TeapotGeometryComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
TeapotGeometryComponent.prototype = Object.create(BaseComponent.prototype);
TeapotGeometryComponent.prototype.constructor = TeapotGeometryComponent;
TeapotGeometryComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'geometryPanel',
scope: this.id,
style: {
borderTop: 0,
marginTop: '8px',
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'row',
children: [{
xtype: 'label',
text: '尺寸'
}, {
xtype: 'number',
id: 'size',
scope: this.id,
value: 3,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '分段'
}, {
xtype: 'int',
id: 'segments',
scope: this.id,
value: 10,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '壶底'
}, {
xtype: 'checkbox',
id: 'bottom',
scope: this.id,
value: true,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '壶盖'
}, {
xtype: 'checkbox',
id: 'lid',
scope: this.id,
value: true,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '壶身'
}, {
xtype: 'checkbox',
id: 'body',
scope: this.id,
value: true,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '壶盖填满'
}, {
xtype: 'checkbox',
id: 'fitLid',
scope: this.id,
value: true,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '布林'
}, {
xtype: 'checkbox',
id: 'blinn',
scope: this.id,
value: true,
onChange: this.onChangeGeometry.bind(this)
}]
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
TeapotGeometryComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
TeapotGeometryComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
TeapotGeometryComponent.prototype.updateUI = function () {
var container = UI.get('geometryPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.TeapotBufferGeometry) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var size = UI.get('size', this.id);
var segments = UI.get('segments', this.id);
var bottom = UI.get('bottom', this.id);
var lid = UI.get('lid', this.id);
var body = UI.get('body', this.id);
var fitLid = UI.get('fitLid', this.id);
var blinn = UI.get('blinn', this.id);
size.setValue(this.selected.geometry.parameters.size);
segments.setValue(this.selected.geometry.parameters.segments);
bottom.setValue(this.selected.geometry.parameters.bottom);
lid.setValue(this.selected.geometry.parameters.lid);
body.setValue(this.selected.geometry.parameters.body);
fitLid.setValue(this.selected.geometry.parameters.fitLid);
blinn.setValue(this.selected.geometry.parameters.blinn);
};
TeapotGeometryComponent.prototype.onChangeGeometry = function () {
var size = UI.get('size', this.id);
var segments = UI.get('segments', this.id);
var bottom = UI.get('bottom', this.id);
var lid = UI.get('lid', this.id);
var body = UI.get('body', this.id);
var fitLid = UI.get('fitLid', this.id);
var blinn = UI.get('blinn', this.id);
var geometry = new THREE.TeapotBufferGeometry(
size.getValue(),
segments.getValue(),
bottom.getValue(),
lid.getValue(),
body.getValue(),
fitLid.getValue(),
blinn.getValue()
);
geometry.type = 'TeapotBufferGeometry';
geometry.parameters = {
size: size.getValue(),
segments: segments.getValue(),
bottom: bottom.getValue(),
lid: lid.getValue(),
body: body.getValue(),
fitLid: fitLid.getValue(),
blinn: blinn.getValue()
};
this.app.editor.execute(new SetGeometryCommand(this.selected, geometry));
};
export default TeapotGeometryComponent;

View File

@ -0,0 +1,149 @@
import BaseComponent from '../BaseComponent';
import SetGeometryCommand from '../../command/SetGeometryCommand';
/**
* 花托组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function TorusGeometryComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
TorusGeometryComponent.prototype = Object.create(BaseComponent.prototype);
TorusGeometryComponent.prototype.constructor = TorusGeometryComponent;
TorusGeometryComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'geometryPanel',
scope: this.id,
style: {
borderTop: 0,
marginTop: '8px',
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
text: '半径'
}, {
xtype: 'number',
id: 'radius',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '管粗'
}, {
xtype: 'number',
id: 'tube',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '管粗分段'
}, {
xtype: 'int',
id: 'radialSegments',
scope: this.id,
value: 16,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '半径分段'
}, {
xtype: 'int',
id: 'tubularSegments',
scope: this.id,
value: 16,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '旋转弧度'
}, {
xtype: 'number',
id: 'arc',
scope: this.id,
value: Math.PI * 2,
onChange: this.onChangeGeometry.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
TorusGeometryComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
TorusGeometryComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
TorusGeometryComponent.prototype.updateUI = function () {
var container = UI.get('geometryPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.TorusBufferGeometry) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var radius = UI.get('radius', this.id);
var tube = UI.get('tube', this.id);
var radialSegments = UI.get('radialSegments', this.id);
var tubularSegments = UI.get('tubularSegments', this.id);
var arc = UI.get('arc', this.id);
radius.setValue(this.selected.geometry.parameters.radius);
tube.setValue(this.selected.geometry.parameters.tube);
radialSegments.setValue(this.selected.geometry.parameters.radialSegments);
tubularSegments.setValue(this.selected.geometry.parameters.tubularSegments);
arc.setValue(this.selected.geometry.parameters.arc);
};
TorusGeometryComponent.prototype.onChangeGeometry = function () {
var radius = UI.get('radius', this.id);
var tube = UI.get('tube', this.id);
var radialSegments = UI.get('radialSegments', this.id);
var tubularSegments = UI.get('tubularSegments', this.id);
var arc = UI.get('arc', this.id);
this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.TorusBufferGeometry(
radius.getValue(),
tube.getValue(),
radialSegments.getValue(),
tubularSegments.getValue(),
arc.getValue()
)));
};
export default TorusGeometryComponent;

View File

@ -0,0 +1,165 @@
import BaseComponent from '../BaseComponent';
import SetGeometryCommand from '../../command/SetGeometryCommand';
/**
* 环面纽结组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function TorusKnotGeometryComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
TorusKnotGeometryComponent.prototype = Object.create(BaseComponent.prototype);
TorusKnotGeometryComponent.prototype.constructor = TorusKnotGeometryComponent;
TorusKnotGeometryComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'geometryPanel',
scope: this.id,
style: {
borderTop: 0,
marginTop: '8px',
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
text: '半径'
}, {
xtype: 'number',
id: 'radius',
scope: this.id,
value: 16,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '管粗'
}, {
xtype: 'number',
id: 'tube',
scope: this.id,
value: 1,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '管长分段'
}, {
xtype: 'int',
id: 'tubularSegments',
scope: this.id,
value: 16,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '管粗分段'
}, {
xtype: 'int',
id: 'radialSegments',
scope: this.id,
value: 16,
range: [1, Infinity],
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '管长弧度'
}, {
xtype: 'number',
id: 'p',
scope: this.id,
value: 20,
onChange: this.onChangeGeometry.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '扭曲弧度'
}, {
xtype: 'number',
id: 'q',
scope: this.id,
value: 20,
onChange: this.onChangeGeometry.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
TorusKnotGeometryComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
TorusKnotGeometryComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
TorusKnotGeometryComponent.prototype.updateUI = function () {
var container = UI.get('geometryPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.geometry instanceof THREE.TorusKnotBufferGeometry) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var radius = UI.get('radius', this.id);
var tube = UI.get('tube', this.id);
var tubularSegments = UI.get('tubularSegments', this.id);
var radialSegments = UI.get('radialSegments', this.id);
var p = UI.get('p', this.id);
var q = UI.get('q', this.id);
radius.setValue(this.selected.geometry.parameters.radius);
tube.setValue(this.selected.geometry.parameters.tube);
tubularSegments.setValue(this.selected.geometry.parameters.tubularSegments);
radialSegments.setValue(this.selected.geometry.parameters.radialSegments);
p.setValue(this.selected.geometry.parameters.p);
q.setValue(this.selected.geometry.parameters.q);
};
TorusKnotGeometryComponent.prototype.onChangeGeometry = function () {
var radius = UI.get('radius', this.id);
var tube = UI.get('tube', this.id);
var tubularSegments = UI.get('tubularSegments', this.id);
var radialSegments = UI.get('radialSegments', this.id);
var p = UI.get('p', this.id);
var q = UI.get('q', this.id);
this.app.editor.execute(new SetGeometryCommand(this.selected, new THREE.TorusKnotBufferGeometry(
radius.getValue(),
tube.getValue(),
tubularSegments.getValue(),
radialSegments.getValue(),
p.getValue(),
q.getValue()
)));
};
export default TorusKnotGeometryComponent;

View File

@ -0,0 +1,175 @@
import BaseComponent from '../BaseComponent';
import PerlinTerrain from '../../object/terrain/PerlinTerrain';
/**
* 柏林地形组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function PerlinTerrainComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
PerlinTerrainComponent.prototype = Object.create(BaseComponent.prototype);
PerlinTerrainComponent.prototype.constructor = PerlinTerrainComponent;
PerlinTerrainComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'perlinPanel',
scope: this.id,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
width: '100%',
color: '#555',
fontWeight: 'bold'
},
text: '柏林地形'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '宽度'
}, {
xtype: 'int',
id: 'width',
scope: this.id,
range: [0, Infinity],
value: 1000,
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '深度'
}, {
xtype: 'int',
id: 'depth',
scope: this.id,
range: [0, Infinity],
value: 1000,
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '宽度分段'
}, {
xtype: 'int',
id: 'widthSegments',
scope: this.id,
range: [0, Infinity],
value: 256,
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '深度分段'
}, {
xtype: 'int',
id: 'depthSegments',
scope: this.id,
range: [0, Infinity],
value: 256,
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '质量'
}, {
xtype: 'int',
id: 'quality',
scope: this.id,
range: [0, Infinity],
value: 80,
onChange: this.onChange.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
PerlinTerrainComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
PerlinTerrainComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
PerlinTerrainComponent.prototype.updateUI = function () {
var container = UI.get('perlinPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof PerlinTerrain) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var width = UI.get('width', this.id);
var depth = UI.get('depth', this.id);
var widthSegments = UI.get('widthSegments', this.id);
var depthSegments = UI.get('depthSegments', this.id);
var quality = UI.get('quality', this.id);
width.setValue(this.selected.userData.width);
depth.setValue(this.selected.userData.depth);
widthSegments.setValue(this.selected.userData.widthSegments);
depthSegments.setValue(this.selected.userData.depthSegments);
quality.setValue(this.selected.userData.quality);
};
PerlinTerrainComponent.prototype.onChange = function () {
var width = UI.get('width', this.id);
var depth = UI.get('depth', this.id);
var widthSegments = UI.get('widthSegments', this.id);
var depthSegments = UI.get('depthSegments', this.id);
var quality = UI.get('quality', this.id);
var terrain = new PerlinTerrain(
width.getValue(),
depth.getValue(),
widthSegments.getValue(),
depthSegments.getValue(),
quality.getValue()
);
var editor = this.app.editor;
var index = editor.scene.children.indexOf(this.selected);
if (index > -1) {
editor.scene.children[index] = terrain;
terrain.parent = this.selected.parent;
this.selected.parent = null;
this.app.call(`objectRemoved`, this, this.selected);
this.app.call(`objectAdded`, this, terrain);
editor.select(terrain);
this.app.call('sceneGraphChanged', this.id);
}
};
export default PerlinTerrainComponent;

View File

@ -0,0 +1,174 @@
import BaseComponent from '../BaseComponent';
import Sky from '../../object/component/Sky';
/**
* 天空组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function SkyComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
SkyComponent.prototype = Object.create(BaseComponent.prototype);
SkyComponent.prototype.constructor = SkyComponent;
SkyComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'skyPanel',
scope: this.id,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
width: '100%',
color: '#555',
fontWeight: 'bold'
},
text: '天空'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '浑浊度'
}, {
xtype: 'number',
id: 'turbidity',
scope: this.id,
range: [0, Infinity],
value: 10,
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '瑞利'
}, {
xtype: 'number',
id: 'rayleigh',
scope: this.id,
range: [0, Infinity],
value: 2,
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '亮度'
}, {
xtype: 'number',
id: 'luminance',
scope: this.id,
range: [0, Infinity],
value: 1,
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: 'Mie系数'
}, {
xtype: 'number',
id: 'mieCoefficient',
scope: this.id,
range: [0, Infinity],
value: 0.005,
unit: '%',
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: 'Mie方向'
}, {
xtype: 'number',
id: 'mieDirectionalG',
scope: this.id,
range: [0, Infinity],
value: 0.005,
onChange: this.onChange.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
SkyComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
SkyComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
SkyComponent.prototype.updateUI = function () {
var container = UI.get('skyPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof Sky) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var turbidity = UI.get('turbidity', this.id);
var rayleigh = UI.get('rayleigh', this.id);
var luminance = UI.get('luminance', this.id);
var mieCoefficient = UI.get('mieCoefficient', this.id);
var mieDirectionalG = UI.get('mieDirectionalG', this.id);
turbidity.setValue(this.selected.userData.turbidity);
rayleigh.setValue(this.selected.userData.rayleigh);
luminance.setValue(this.selected.userData.luminance);
mieCoefficient.setValue(this.selected.userData.mieCoefficient * 100);
mieDirectionalG.setValue(this.selected.userData.mieDirectionalG);
};
SkyComponent.prototype.onChange = function () {
var turbidity = UI.get('turbidity', this.id);
var rayleigh = UI.get('rayleigh', this.id);
var luminance = UI.get('luminance', this.id);
var mieCoefficient = UI.get('mieCoefficient', this.id);
var mieDirectionalG = UI.get('mieDirectionalG', this.id);
this.selected.userData.turbidity = turbidity.getValue();
this.selected.userData.rayleigh = rayleigh.getValue();
this.selected.userData.luminance = luminance.getValue();
this.selected.userData.mieCoefficient = mieCoefficient.getValue() / 100;
this.selected.userData.mieDirectionalG = mieDirectionalG.getValue();
var sky = this.selected.children.filter(n => n instanceof THREE.Sky)[0];
if (sky) {
var uniforms = sky.material.uniforms;
uniforms.turbidity.value = turbidity.getValue();
uniforms.rayleigh.value = rayleigh.getValue();
uniforms.luminance.value = luminance.getValue();
uniforms.mieCoefficient.value = mieCoefficient.getValue() / 100;
uniforms.mieDirectionalG.value = mieDirectionalG.getValue();
sky.material.needsUpdate = true;
}
this.app.call(`objectSelected`, this, this.selected);
};
export default SkyComponent;

View File

@ -0,0 +1,140 @@
import BaseComponent from '../BaseComponent';
/**
* 基本信息组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function PhysicsWorldComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
PhysicsWorldComponent.prototype = Object.create(BaseComponent.prototype);
PhysicsWorldComponent.prototype.constructor = PhysicsWorldComponent;
PhysicsWorldComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'objectPanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
color: '#555',
fontWeight: 'bold'
},
text: '物理环境'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '碰撞配置'
}, {
xtype: 'select',
id: 'configType',
scope: this.id,
options: {
'btDefaultCollisionConfiguration': '默认碰撞配置', // 无法使用布料
'btSoftBodyRigidBodyCollisionConfiguration': '软体刚体碰撞配置'
},
onChange: this.onChangeConfigType.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '重力常数'
}, {
xtype: 'number',
id: 'gravityConstantX',
scope: this.id,
onChange: this.onChangeGravityConstant.bind(this)
}, {
xtype: 'number',
id: 'gravityConstantY',
scope: this.id,
onChange: this.onChangeGravityConstant.bind(this)
}, {
xtype: 'number',
id: 'gravityConstantZ',
scope: this.id,
onChange: this.onChangeGravityConstant.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
PhysicsWorldComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
PhysicsWorldComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
PhysicsWorldComponent.prototype.updateUI = function () {
var container = UI.get('objectPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Scene) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = app.physics;
var configType = UI.get('configType', this.id);
var gravityConstantX = UI.get('gravityConstantX', this.id);
var gravityConstantY = UI.get('gravityConstantY', this.id);
var gravityConstantZ = UI.get('gravityConstantZ', this.id);
if (this.selected.collisionConfiguration instanceof Ammo.btSoftBodyRigidBodyCollisionConfiguration) {
configType.setValue('btSoftBodyRigidBodyCollisionConfiguration');
} else {
configType.setValue('btDefaultCollisionConfiguration');
}
var gravity = this.selected.world.getGravity();
gravityConstantX.setValue(gravity.x());
gravityConstantY.setValue(gravity.y());
gravityConstantZ.setValue(gravity.z());
};
PhysicsWorldComponent.prototype.onChangeConfigType = function () {
var configType = UI.get('configType', this.id);
if (configType === 'btSoftBodyRigidBodyCollisionConfiguration') {
this.selected.collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
} else {
this.selected.collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
}
this.selected.dispatcher = new Ammo.btCollisionDispatcher(this.selected.collisionConfiguration);
this.selected.world.dispatcher = this.selected.dispatcher;
};
PhysicsWorldComponent.prototype.onChangeGravityConstant = function () {
var gravityConstantX = UI.get('gravityConstantX', this.id);
var gravityConstantY = UI.get('gravityConstantY', this.id);
var gravityConstantZ = UI.get('gravityConstantZ', this.id);
var gravity = new Ammo.btVector3(gravityConstantX.getValue(), gravityConstantY.getValue(), gravityConstantZ.getValue());
this.selected.world.setGravity(gravity);
this.selected.world.getWorldInfo().set_m_gravity(gravity);
};
export default PhysicsWorldComponent;

View File

@ -0,0 +1,159 @@
import BaseComponent from '../BaseComponent';
/**
* 刚体组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function RigidBodyComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
RigidBodyComponent.prototype = Object.create(BaseComponent.prototype);
RigidBodyComponent.prototype.constructor = RigidBodyComponent;
RigidBodyComponent.prototype.render = function () {
var data = {
xtype: 'div',
id: 'rigidBodyPanel',
scope: this.id,
parent: this.parent,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
width: '100%',
color: '#555',
fontWeight: 'bold'
},
text: '刚体'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '形状'
}, {
xtype: 'input',
id: 'shape',
scope: this.id,
disabled: true,
style: {
width: '100px',
fontSize: '12px'
},
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '质量'
}, {
xtype: 'number',
id: 'mass',
scope: this.id,
style: {
width: '100px',
fontSize: '12px'
},
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '惯性'
}, {
xtype: 'number',
id: 'inertiaX',
scope: this.id,
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'inertiaY',
scope: this.id,
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}, {
xtype: 'number',
id: 'inertiaZ',
scope: this.id,
style: {
width: '40px'
},
onChange: this.onChange.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
RigidBodyComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
RigidBodyComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
RigidBodyComponent.prototype.updateUI = function () {
var container = UI.get('rigidBodyPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof THREE.Mesh && editor.selected.userData.physics) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var shape = UI.get('shape', this.id);
var mass = UI.get('mass', this.id);
var inertiaX = UI.get('inertiaX', this.id);
var inertiaY = UI.get('inertiaY', this.id);
var inertiaZ = UI.get('inertiaZ', this.id);
var physics = this.selected.userData.physics;
shape.setValue(physics.shape);
mass.setValue(physics.mass);
inertiaX.setValue(physics.inertia.x);
inertiaY.setValue(physics.inertia.y);
inertiaZ.setValue(physics.inertia.z);
};
RigidBodyComponent.prototype.onChange = function () {
var shape = UI.get('shape', this.id);
var mass = UI.get('mass', this.id);
var inertiaX = UI.get('inertiaX', this.id);
var inertiaY = UI.get('inertiaY', this.id);
var inertiaZ = UI.get('inertiaZ', this.id);
var physics = this.selected.userData.physics;
physics.shape = shape.getValue();
physics.mass = mass.getValue();
physics.inertia.x = inertiaX.getValue();
physics.inertia.y = inertiaY.getValue();
physics.inertia.z = inertiaZ.getValue();
};
export default RigidBodyComponent;

View File

@ -0,0 +1,5 @@
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

View File

@ -0,0 +1,10 @@
precision mediump float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

View File

@ -0,0 +1,3 @@
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

View File

@ -0,0 +1,3 @@
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

View File

@ -0,0 +1,175 @@
import BaseComponent from '../BaseComponent';
import PerlinTerrain from '../../object/terrain/PerlinTerrain';
/**
* 水组件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function WaterComponent(options) {
BaseComponent.call(this, options);
this.selected = null;
}
WaterComponent.prototype = Object.create(BaseComponent.prototype);
WaterComponent.prototype.constructor = WaterComponent;
WaterComponent.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'perlinPanel',
scope: this.id,
cls: 'Panel',
style: {
display: 'none'
},
children: [{
xtype: 'row',
children: [{
xtype: 'label',
style: {
width: '100%',
color: '#555',
fontWeight: 'bold'
},
text: '柏林地形'
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '宽度'
}, {
xtype: 'int',
id: 'width',
scope: this.id,
range: [0, Infinity],
value: 1000,
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '深度'
}, {
xtype: 'int',
id: 'depth',
scope: this.id,
range: [0, Infinity],
value: 1000,
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '宽度分段'
}, {
xtype: 'int',
id: 'widthSegments',
scope: this.id,
range: [0, Infinity],
value: 256,
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '深度分段'
}, {
xtype: 'int',
id: 'depthSegments',
scope: this.id,
range: [0, Infinity],
value: 256,
onChange: this.onChange.bind(this)
}]
}, {
xtype: 'row',
children: [{
xtype: 'label',
text: '质量'
}, {
xtype: 'int',
id: 'quality',
scope: this.id,
range: [0, Infinity],
value: 80,
onChange: this.onChange.bind(this)
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this));
};
WaterComponent.prototype.onObjectSelected = function () {
this.updateUI();
};
WaterComponent.prototype.onObjectChanged = function () {
this.updateUI();
};
WaterComponent.prototype.updateUI = function () {
var container = UI.get('perlinPanel', this.id);
var editor = this.app.editor;
if (editor.selected && editor.selected instanceof PerlinTerrain) {
container.dom.style.display = '';
} else {
container.dom.style.display = 'none';
return;
}
this.selected = editor.selected;
var width = UI.get('width', this.id);
var depth = UI.get('depth', this.id);
var widthSegments = UI.get('widthSegments', this.id);
var depthSegments = UI.get('depthSegments', this.id);
var quality = UI.get('quality', this.id);
width.setValue(this.selected.userData.width);
depth.setValue(this.selected.userData.depth);
widthSegments.setValue(this.selected.userData.widthSegments);
depthSegments.setValue(this.selected.userData.depthSegments);
quality.setValue(this.selected.userData.quality);
};
WaterComponent.prototype.onChange = function () {
var width = UI.get('width', this.id);
var depth = UI.get('depth', this.id);
var widthSegments = UI.get('widthSegments', this.id);
var depthSegments = UI.get('depthSegments', this.id);
var quality = UI.get('quality', this.id);
var terrain = new PerlinTerrain(
width.getValue(),
depth.getValue(),
widthSegments.getValue(),
depthSegments.getValue(),
quality.getValue()
);
var editor = this.app.editor;
var index = editor.scene.children.indexOf(this.selected);
if (index > -1) {
editor.scene.children[index] = terrain;
terrain.parent = this.selected.parent;
this.selected.parent = null;
this.app.call(`objectRemoved`, this, this.selected);
this.app.call(`objectAdded`, this, terrain);
editor.select(terrain);
this.app.call('sceneGraphChanged', this.id);
}
};
export default WaterComponent;

View File

@ -0,0 +1,213 @@
import Command from '../command/Command';
import { UI } from '../third_party';
/**
* 历史记录
* @author dforrer / https://github.com/dforrer
* Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
*/
function History(editor) {
this.app = editor.app;
this.editor = editor;
this.undos = [];
this.redos = [];
this.lastCmdTime = new Date();
this.idCounter = 0;
Command.call(this, editor);
};
History.prototype = Object.create(Command.prototype);
Object.assign(History.prototype, {
constructor: History,
execute: function (cmd, optionalName) {
var lastCmd = this.undos[this.undos.length - 1];
var timeDifference = new Date().getTime() - this.lastCmdTime.getTime();
var 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;
this.lastCmdTime = new Date();
// clearing all the redo-commands
this.redos = [];
this.app.call('historyChanged', this, cmd);
},
undo: function () {
var 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.app.call('historyChanged', this, cmd);
}
return cmd;
},
redo: function () {
var 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.app.call('historyChanged', this, cmd);
}
return cmd;
},
toJSON: function () {
var history = {};
history.undos = [];
history.redos = [];
// Append Undos to History
for (var 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 (var i = 0; i < this.redos.length; i++) {
if (this.redos[i].hasOwnProperty("json")) {
history.redos.push(this.redos[i].json);
}
}
return history;
},
fromJSON: function (json) {
if (json === undefined) return;
for (var i = 0; i < json.undos.length; i++) {
var cmdJSON = json.undos[i];
var 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 (var i = 0; i < json.redos.length; i++) {
var cmdJSON = json.redos[i];
var 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.app.call('historyChanged', this, this.undos[this.undos.length - 1]);
},
clear: function () {
this.undos = [];
this.redos = [];
this.idCounter = 0;
this.app.call('historyChanged', this);
},
goToState: function (id) {
var 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 {
while (true) {
cmd = this.undos[this.undos.length - 1]; // next cmd to pop
if (cmd === undefined || id === cmd.id) break;
cmd = this.undo();
}
}
this.editor.app.call('sceneGraphChanged', this);
this.editor.app.call('historyChanged', this, cmd);
},
enableSerialization: function (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);
var cmd = this.redo();
while (cmd !== undefined) {
if (!cmd.hasOwnProperty("json")) {
cmd.json = cmd.toJSON();
}
cmd = this.redo();
}
this.goToState(id);
}
});
export default History;

View File

@ -0,0 +1,21 @@
/**
* 模型对象userData字段保存数据模板
* @author tengge / https://github.com/tengge1
*/
function UserData() {
this.ID = ''; // 模型ID例如5b7e38653fb63b0b50d332eb
this.Name = ''; // 模型名称
this.TotalPinYin = ''; // 模型名称全拼
this.FirstPinYin = ''; // 模型名称拼音首字母
this.Type = ''; // 模型类型
this.Url = ''; // 模型文件url
this.Thumbnail = ''; // 模型缩略图
this.Server = true; // 是否从服务端加载
this.CustomData = {}; // 自定义数据
}
export default UserData;

View File

@ -0,0 +1,75 @@
/**
* 本地存储
* @author mrdoob / http://mrdoob.com/
*/
function Storage() {
var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
if (indexedDB === undefined) {
console.warn('Storage: IndexedDB不可用。');
return { init: function () { }, get: function () { }, set: function () { }, clear: function () { } };
}
var name = 'threejs-editor';
var version = 1;
var database;
return {
init: function (callback) {
var request = indexedDB.open(name, version);
request.onupgradeneeded = function (event) {
var 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) {
var transaction = database.transaction(['states'], 'readwrite');
var objectStore = transaction.objectStore('states');
var request = objectStore.get(0);
request.onsuccess = function (event) {
callback(event.target.result);
};
},
set: function (data, callback) {
var start = performance.now();
var transaction = database.transaction(['states'], 'readwrite');
var objectStore = transaction.objectStore('states');
var request = objectStore.put(data, 0);
request.onsuccess = function (event) {
console.log('[' + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + ']', '保存到IndexedDB中。 ' + (performance.now() - start).toFixed(2) + 'ms');
};
},
clear: function () {
if (database === undefined) return;
var transaction = database.transaction(['states'], 'readwrite');
var objectStore = transaction.objectStore('states');
var request = objectStore.clear();
request.onsuccess = function (event) {
console.log('[' + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + ']', '清空IndexedDB。');
};
}
};
};
export default Storage;

View File

@ -0,0 +1,9 @@
/**
* 场景对象自定义数据类型
* @author tengge / https://github.com/tengge1
*/
function UserData() {
type: 'obj'
}
export default UserData;

View File

@ -0,0 +1,14 @@
import UserDataItemBase from './UserDataItemBase';
function AudioUserDataItem(options) {
UserDataItemBase.call(this, options);
this.type = 'audio';
this.name = options.name || '(未知)';
this.url = options.url || '';
};
AudioUserDataItem.prototype = Object.create(UserDataItemBase.prototype);
AudioUserDataItem.prototype.constructor = AudioUserDataItem;
export default AudioUserDataItem;

View File

@ -0,0 +1,8 @@
/**
* 用户自定义数据部件基类
*/
function UserDataItemBase(options) {
this.type = options.type || 'unknown';
}
export default UserDataItemBase;

View File

@ -0,0 +1,396 @@
import History from '../core/History';
import Storage from '../core/Storage';
import AnimationManager from '../animation/AnimationManager';
/**
* 编辑器
* @author mrdoob / http://mrdoob.com/
* @author tengge / https://github.com/tengge1
*/
function Editor(app) {
this.app = app;
this.app.editor = this;
// 基础
this.history = new History(this);
this.storage = new Storage();
// 场景
this.scene = new THREE.Scene();
this.scene.name = '场景';
this.scene.background = new THREE.Color(0xaaaaaa);
this.sceneHelpers = new THREE.Scene();
this.sceneID = null; // 当前场景ID
this.sceneName = null; // 当前场景名称
// 相机
this.DEFAULT_CAMERA = new THREE.PerspectiveCamera(50, 1, 0.1, 10000);
this.DEFAULT_CAMERA.name = '默认相机';
this.DEFAULT_CAMERA.userData.isDefault = true;
this.DEFAULT_CAMERA.position.set(20, 10, 20);
this.DEFAULT_CAMERA.lookAt(new THREE.Vector3());
this.camera = this.DEFAULT_CAMERA.clone();
// 渲染器
this.renderer = new THREE.WebGLRenderer({
antialias: true
});
this.renderer.gammaInput = false;
this.renderer.gammaOutput = false;
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.renderer.autoClear = false;
this.renderer.autoUpdateScene = false;
this.renderer.setPixelRatio(window.devicePixelRatio);
this.app.viewport.container.dom.appendChild(this.renderer.domElement);
this.renderer.setSize(this.app.viewport.container.dom.offsetWidth, this.app.viewport.container.dom.offsetHeight);
// 音频监听器
this.audioListener = new THREE.AudioListener();
this.audioListener.name = '音频监听器';
// 物体
this.object = {};
// 物体
this.objects = [];
// 脚本 格式:{ uuid: { id: 'mongoDB id', name: 'Script Name', type: 'Script Type', source: 'Source Code', uuid: 'uuid' }}
// 其中uuid是创建脚本时自动生成不可改变关联时使用id是mongo数据库ID字段name随便填写typejavascriptvertexShader, fragmentShader, jsonsource源码。
this.scripts = {};
this.animation = new AnimationManager(this.app);
// 帮助器
this.helpers = {};
// 当前选中物体
this.selected = null;
// 网格
this.grid = new THREE.GridHelper(30, 30, 0x444444, 0x888888);
this.grid.visible = this.app.options.showGrid;
this.sceneHelpers.add(this.grid);
// 平移旋转缩放控件
this.transformControls = new THREE.TransformControls(this.camera, this.app.viewport.container.dom);
this.sceneHelpers.add(this.transformControls);
// 编辑器控件
this.controls = new THREE.EditorControls(this.camera, this.app.viewport.container.dom);
// 性能控件
this.stats = new Stats();
this.stats.dom.style.position = 'absolute';
this.stats.dom.style.left = '8px';
this.stats.dom.style.top = '8px';
this.stats.dom.style.zIndex = 'initial';
this.app.viewport.container.dom.appendChild(this.stats.dom);
// 事件
this.app.on(`appStarted.${this.id}`, this.onAppStarted.bind(this));
this.app.on(`optionsChanged.${this.id}`, this.onOptionsChanged.bind(this));
};
Editor.prototype.onAppStarted = function () {
this.clear();
};
// -------------------- 场景 --------------------------
Editor.prototype.setScene = function (scene) { // 设置场景
// 移除原有物体
var objects = this.scene.children;
while (objects.length > 0) {
this.removeObject(objects[0]);
}
// 添加新物体
var children = scene.children.slice();
scene.children.length = 0;
this.scene = scene;
children.forEach(n => {
this.addObject(n);
});
this.app.call('sceneGraphChanged', this);
};
Editor.prototype.clear = function (addObject = true) { // 清空场景
this.history.clear();
this.storage.clear();
this.camera.copy(this.DEFAULT_CAMERA);
if (this.camera.children.findIndex(o => o instanceof THREE.AudioListener) === -1) {
this.camera.add(this.audioListener);
}
if (this.scene.background instanceof THREE.Texture) {
this.scene.background = new THREE.Color(0xaaaaaa);
} else if (this.scene.background instanceof THREE.Color) {
this.scene.background.setHex(0xaaaaaa);
}
this.scene.fog = null;
var objects = this.scene.children;
while (objects.length > 0) {
this.removeObject(objects[0]);
}
this.textures = {};
this.scripts = {};
this.animation.clear();
this.deselect();
// 添加默认元素
if (addObject) {
var light1 = new THREE.AmbientLight(0xffffff, 0.24);
light1.name = '环境光';
this.addObject(light1);
var light2 = new THREE.DirectionalLight(0xffffff, 0.56);
light2.name = '平行光';
light2.castShadow = true;
light2.position.set(5, 10, 7.5);
light2.shadow.mapSize.x = 2048;
light2.shadow.mapSize.y = 2048;
light2.shadow.camera.left = -100;
light2.shadow.camera.right = 100;
light2.shadow.camera.top = 100;
light2.shadow.camera.bottom = -100;
this.addObject(light2);
}
this.app.call('editorCleared', this);
this.app.call('scriptChanged', this);
this.app.call('animationChanged', this);
};
// ---------------------- 物体 ---------------------------
Editor.prototype.objectByUuid = function (uuid) { // 根据uuid获取物体
return this.scene.getObjectByProperty('uuid', uuid, true);
};
Editor.prototype.addObject = function (object) { // 添加物体
this.scene.add(object);
object.traverse(child => {
this.addHelper(child);
});
this.app.call('objectAdded', this, object);
this.app.call('sceneGraphChanged', this);
};
Editor.prototype.moveObject = function (object, parent, before) { // 移动物体
if (parent === undefined) {
parent = this.scene;
}
parent.add(object);
// sort children array
if (before !== undefined) {
var index = parent.children.indexOf(before);
parent.children.splice(index, 0, object);
parent.children.pop();
}
this.app.call('sceneGraphChanged', this);
};
Editor.prototype.removeObject = function (object) { // 移除物体
if (object.parent === null) { // 避免删除相机或场景
return;
}
object.traverse(child => {
this.removeHelper(child);
});
object.parent.remove(object);
this.app.call('objectRemoved', this, object);
this.app.call('sceneGraphChanged', this);
};
// ------------------------- 帮助 ------------------------------
Editor.prototype.addHelper = function (object) { // 添加物体帮助器
var options = this.app.options;
var helper = null;
if (object instanceof THREE.Camera) { // 相机
helper = new THREE.CameraHelper(object, 1);
helper.visible = options.showCameraHelper;
} else if (object instanceof THREE.PointLight) { // 点光源
helper = new THREE.PointLightHelper(object, 1);
helper.visible = options.showPointLightHelper;
} else if (object instanceof THREE.DirectionalLight) { // 平行光
helper = new THREE.DirectionalLightHelper(object, 1);
helper.visible = options.showDirectionalLightHelper;
} else if (object instanceof THREE.SpotLight) { // 聚光灯
helper = new THREE.SpotLightHelper(object, 1);
helper.visible = options.showSpotLightHelper;
} else if (object instanceof THREE.HemisphereLight) { // 半球光
helper = new THREE.HemisphereLightHelper(object, 1);
helper.visible = options.showHemisphereLightHelper;
} else if (object instanceof THREE.RectAreaLight) { // 矩形光
helper = new THREE.RectAreaLightHelper(object, 0xffffff);
helper.visible = options.showRectAreaLightHelper;
} else if (object instanceof THREE.SkinnedMesh) { // 骨骼
helper = new THREE.SkeletonHelper(object);
helper.visible = options.showSkeletonHelper;
} else {
// 该类型物体没有帮助器
return;
}
var geometry = new THREE.SphereBufferGeometry(2, 4, 2);
var material = new THREE.MeshBasicMaterial({
color: 0xff0000,
visible: false
});
var 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.objects.push(picker);
};
Editor.prototype.removeHelper = function (object) { // 移除物体帮助
if (this.helpers[object.id] !== undefined) {
var helper = this.helpers[object.id];
helper.parent.remove(helper);
delete this.helpers[object.id];
var objects = this.objects;
objects.splice(objects.indexOf(helper.getObjectByName('picker')), 1);
}
};
Editor.prototype.onOptionsChanged = function (options) { // 帮助器改变事件
this.grid.visible = options.showGrid === undefined ? true : options.showGrid;
Object.keys(this.helpers).forEach(n => {
var helper = this.helpers[n];
if (helper instanceof THREE.CameraHelper) {
helper.visible = options.showCameraHelper;
} else if (helper instanceof THREE.PointLightHelper) {
helper.visible = options.showPointLightHelper;
} else if (helper instanceof THREE.DirectionalLightHelper) {
helper.visible = options.showDirectionalLightHelper;
} else if (helper instanceof THREE.SpotLightHelper) {
helper.visible = options.showSpotLightHelper;
} else if (helper instanceof THREE.HemisphereLightHelper) {
helper.visible = options.showHemisphereLightHelper;
} else if (helper instanceof THREE.RectAreaLightHelper) {
helper.visible = options.showRectAreaLightHelper;
} else if (helper instanceof THREE.SkeletonHelper) {
helper.visible = options.showSkeletonHelper;
}
});
};
// ------------------------ 脚本 ----------------------------
Editor.prototype.addScript = function (object, script) { // 添加脚本
if (this.scripts[object.uuid] === undefined) {
this.scripts[object.uuid] = [];
}
this.scripts[object.uuid].push(script);
this.app.call('scriptAdded', this, script);
};
Editor.prototype.removeScript = function (object, script) { // 移除脚本
if (this.scripts[object.uuid] === undefined) {
return;
}
var index = this.scripts[object.uuid].indexOf(script);
if (index !== -1) {
this.scripts[object.uuid].splice(index, 1);
}
this.app.call('scriptRemoved', this);
};
// ------------------------ 选中事件 --------------------------------
Editor.prototype.select = function (object) { // 选中物体
if (this.selected === object) {
return;
}
this.selected = object;
this.app.call('objectSelected', this, object);
};
Editor.prototype.selectById = function (id) { // 根据id选中物体
if (id === this.camera.id) {
this.select(this.camera);
return;
}
this.select(this.scene.getObjectById(id, true));
};
Editor.prototype.selectByUuid = function (uuid) { // 根据uuid选中物体
var _this = this;
this.scene.traverse(function (child) {
if (child.uuid === uuid) {
_this.select(child);
}
});
};
Editor.prototype.deselect = function () { // 取消选中物体
this.select(null);
};
// ---------------------- 焦点事件 --------------------------
Editor.prototype.focus = function (object) { // 设置焦点
this.app.call('objectFocused', this, object);
};
Editor.prototype.focusById = function (id) { // 根据id设置交点
var obj = this.scene.getObjectById(id, true);
if (obj) {
this.focus(obj);
}
};
// ----------------------- 命令事件 --------------------------
Editor.prototype.execute = function (cmd, optionalName) { // 执行事件
this.history.execute(cmd, optionalName);
};
Editor.prototype.undo = function () { // 撤销事件
this.history.undo();
};
Editor.prototype.redo = function () { // 重做事件
this.history.redo();
};
export default Editor;

View File

@ -0,0 +1,171 @@
/**
* 物理
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function Physics(options) {
this.app = options.app;
this.app.physics = this;
// 各种参数
var gravityConstant = -9.8;
this.margin = 0.05;
this.transformAux1 = new Ammo.btTransform();
this.time = 0;
this.armMovement = 0;
// 物理环境配置
this.collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration(); // 软体刚体碰撞配置
this.dispatcher = new Ammo.btCollisionDispatcher(this.collisionConfiguration); // 碰撞调度器
this.broadphase = new Ammo.btDbvtBroadphase(); // dbvt粗测
this.solver = new Ammo.btSequentialImpulseConstraintSolver(); // 顺序脉冲约束求解器
this.softBodySolver = new Ammo.btDefaultSoftBodySolver(); // 默认软体求解器
this.world = new Ammo.btSoftRigidDynamicsWorld(this.dispatcher, this.broadphase, this.solver, this.collisionConfiguration, this.softBodySolver);
var gravity = new Ammo.btVector3(0, gravityConstant, 0);
this.world.setGravity(gravity);
this.world.getWorldInfo().set_m_gravity(gravity);
this.rigidBodies = [];
// 扔球
this.enableThrowBall = false;
}
Physics.prototype.start = function () {
this.app.on(`animate.Physics`, this.update.bind(this));
this.app.on(`mThrowBall.Physics`, this.onEnableThrowBall.bind(this));
};
/**
* 创建刚体
* @param {*} threeObject three.js中Object3D对象
* @param {*} physicsShape 物理形状
* @param {*} mass 重力
* @param {*} pos 位置
* @param {*} quat 旋转
*/
Physics.prototype.createRigidBody = function (threeObject, physicsShape, mass, pos, quat) {
threeObject.position.copy(pos);
threeObject.quaternion.copy(quat);
var transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
transform.setRotation(new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w));
var motionState = new Ammo.btDefaultMotionState(transform);
// 惯性
var localInertia = new Ammo.btVector3(0, 0, 0);
physicsShape.calculateLocalInertia(mass, localInertia);
var rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, physicsShape, localInertia);
var body = new Ammo.btRigidBody(rbInfo);
threeObject.userData.physicsBody = body;
// 重力大于0才响应物理事件
if (mass > 0) {
this.rigidBodies.push(threeObject);
body.setActivationState(4);
}
this.world.addRigidBody(body);
return body;
};
/**
* 创建一个平板
* @param {*} sx 长度
* @param {*} sy 厚度
* @param {*} sz 宽度
* @param {*} mass 重力
* @param {*} pos 位置
* @param {*} quat 旋转
* @param {*} material 材质
*/
Physics.prototype.createParalellepiped = function (sx, sy, sz, mass, pos, quat, material) {
var threeObject = new THREE.Mesh(new THREE.BoxBufferGeometry(sx, sy, sz, 1, 1, 1), material);
var shape = new Ammo.btBoxShape(new Ammo.btVector3(sx * 0.5, sy * 0.5, sz * 0.5));
shape.setMargin(this.margin);
this.createRigidBody(threeObject, shape, mass, pos, quat);
return threeObject;
};
Physics.prototype.onThrowBall = function (event) {
var mouse = new THREE.Vector2();
var raycaster = new THREE.Raycaster();
var camera = this.app.editor.camera;
var width = UI.get('viewport').dom.clientWidth;
var height = UI.get('viewport').dom.clientHeight;
mouse.set((event.offsetX / width) * 2 - 1, -(event.offsetY / height) * 2 + 1);
raycaster.setFromCamera(mouse, camera);
// Creates a ball and throws it
var ballMass = 35;
var ballRadius = 0.4;
var ballMaterial = new THREE.MeshPhongMaterial({
color: 0x202020
});
var ball = new THREE.Mesh(new THREE.SphereBufferGeometry(ballRadius, 14, 10), ballMaterial);
ball.castShadow = true;
ball.receiveShadow = true;
this.app.editor.scene.add(ball);
var ballShape = new Ammo.btSphereShape(ballRadius);
ballShape.setMargin(this.margin);
var pos = new THREE.Vector3();
pos.copy(raycaster.ray.direction);
pos.add(raycaster.ray.origin);
var quat = new THREE.Quaternion();
quat.set(0, 0, 0, 1);
var ballBody = this.createRigidBody(ball, ballShape, ballMass, pos, quat);
pos.copy(raycaster.ray.direction);
pos.multiplyScalar(24);
ballBody.setLinearVelocity(new Ammo.btVector3(pos.x, pos.y, pos.z));
};
Physics.prototype.update = function (clock, deltaTime) {
this.world.stepSimulation(deltaTime, 10);
// Update rigid bodies
for (var i = 0, il = this.rigidBodies.length; i < il; i++) {
var objThree = this.rigidBodies[i];
var objPhys = objThree.userData.physicsBody;
var ms = objPhys.getMotionState();
if (ms) {
ms.getWorldTransform(this.transformAux1);
var p = this.transformAux1.getOrigin();
var q = this.transformAux1.getRotation();
objThree.position.set(p.x(), p.y(), p.z());
objThree.quaternion.set(q.x(), q.y(), q.z(), q.w());
}
}
};
Physics.prototype.onEnableThrowBall = function () {
if (this.enableThrowBall) {
this.enableThrowBall = false;
UI.get('mThrowBall').dom.innerHTML = '开启探测小球';
this.app.on(`click.Physics`, null);
} else {
this.enableThrowBall = true;
UI.get('mThrowBall').dom.innerHTML = '关闭探测小球';
this.app.on(`click.Physics`, this.onThrowBall.bind(this));
}
};
export default Physics;

View File

@ -0,0 +1,100 @@
import { UI } from '../third_party';
/**
* 状态栏
* @author mrdoob / http://mrdoob.com/
* @author tengge / https://github.com/tengge1
*/
function StatusBar(options) {
UI.Control.call(this, options);
this.app = options.app;
};
StatusBar.prototype = Object.create(UI.Control.prototype);
StatusBar.prototype.constructor = StatusBar;
StatusBar.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
cls: 'statusBar',
children: [{
xtype: 'row',
children: [{
xtype: 'label',
text: '物体'
}, {
xtype: 'text',
id: 'objectsText',
scope: this.id,
text: '0' // 物体数
}, {
xtype: 'label',
text: '顶点'
}, {
xtype: 'text',
id: 'verticesText',
scope: this.id,
text: '0' // 顶点数
}, {
xtype: 'label',
text: '三角形'
}, {
xtype: 'text',
id: 'trianglesText',
scope: this.id,
text: '0' // 三角形数
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on('objectAdded.' + this.id, this.onUpdateInfo.bind(this));
this.app.on('objectRemoved.' + this.id, this.onUpdateInfo.bind(this));
this.app.on('geometryChanged.' + this.id, this.onUpdateInfo.bind(this));
};
StatusBar.prototype.onUpdateInfo = function () {
var editor = this.app.editor;
var scene = editor.scene;
var objects = 0, vertices = 0, triangles = 0;
for (var i = 0, l = scene.children.length; i < l; i++) {
var object = scene.children[i];
object.traverseVisible(function (object) {
objects++;
if (object instanceof THREE.Mesh) {
var geometry = object.geometry;
if (geometry instanceof THREE.Geometry) {
vertices += geometry.vertices.length;
triangles += geometry.faces.length;
} else if (geometry instanceof THREE.BufferGeometry) {
if (geometry.index !== null) {
vertices += geometry.index.count * 3;
triangles += geometry.index.count;
} else {
vertices += geometry.attributes.position.count;
triangles += geometry.attributes.position.count / 3;
}
}
}
});
}
var objectsText = UI.get('objectsText', this.id);
var verticesText = UI.get('verticesText', this.id);
var trianglesText = UI.get('trianglesText', this.id);
objectsText.setValue(objects.format());
verticesText.setValue(vertices.format());
trianglesText.setValue(triangles.format());
};
export default StatusBar;

View File

@ -0,0 +1,134 @@
import { UI } from '../third_party';
import ModelWindow from './window/ModelWindow';
/**
* 工具栏
* @author tengge / https://github.com/tengge1
*/
function Toolbar(options) {
UI.Control.call(this, options);
this.app = options.app;
};
Toolbar.prototype = Object.create(UI.Control.prototype);
Toolbar.prototype.constructor = Toolbar;
Toolbar.prototype.render = function () {
var _this = this;
var data = {
xtype: 'div',
parent: this.parent,
cls: 'toolbar',
children: [{
xtype: 'iconbutton',
id: 'selectBtn',
scope: this.id,
icon: 'icon-select',
title: '选择',
onClick: this.enterSelectMode.bind(this)
}, {
xtype: 'iconbutton',
id: 'translateBtn',
scope: this.id,
icon: 'icon-translate',
cls: 'Button IconButton selected',
title: '平移(W)',
onClick: this.enterTranslateMode.bind(this)
}, {
xtype: 'iconbutton',
id: 'rotateBtn',
scope: this.id,
icon: 'icon-rotate',
title: '旋转(E)',
onClick: this.enterRotateMode.bind(this)
}, {
xtype: 'iconbutton',
id: 'scaleBtn',
scope: this.id,
icon: 'icon-scale',
title: '缩放(R)',
onClick: this.enterScaleMode.bind(this)
}, {
xtype: 'hr'
}, {
xtype: 'iconbutton',
icon: 'icon-model-view',
title: '模型',
onClick: this.showModelWindow.bind(this)
}]
};
var control = UI.create(data);
control.render();
this.app.on(`changeMode.${this.id}`, this.onChangeMode.bind(this));
};
// --------------------------------- 选择模式 -------------------------------------
Toolbar.prototype.enterSelectMode = function () {
this.app.call('changeMode', this, 'select');
};
// -------------------------------- 平移模式 --------------------------------------
Toolbar.prototype.enterTranslateMode = function () {
this.app.call('changeMode', this, 'translate');
};
// -------------------------------- 旋转模式 ---------------------------------------
Toolbar.prototype.enterRotateMode = function () {
this.app.call('changeMode', this, 'rotate');
};
// -------------------------------- 缩放模式 ---------------------------------------
Toolbar.prototype.enterScaleMode = function () {
this.app.call('changeMode', this, 'scale');
};
// ------------------------------ 模式改变事件 -------------------------------------
Toolbar.prototype.onChangeMode = function (mode) {
var selectBtn = UI.get('selectBtn', this.id);
var translateBtn = UI.get('translateBtn', this.id);
var rotateBtn = UI.get('rotateBtn', this.id);
var scaleBtn = UI.get('scaleBtn', this.id);
selectBtn.unselect();
translateBtn.unselect();
rotateBtn.unselect();
scaleBtn.unselect();
switch (mode) {
case 'select':
selectBtn.select();
break;
case 'translate':
translateBtn.select();
break;
case 'rotate':
rotateBtn.select();
break;
case 'scale':
scaleBtn.select();
break;
}
};
// -------------------------------- 模型窗口 ---------------------------------------
Toolbar.prototype.showModelWindow = function () {
if (this.modelWindow == null) {
this.modelWindow = new ModelWindow({
parent: this.app.container,
app: this.app
});
this.modelWindow.render();
}
this.modelWindow.show();
};
export default Toolbar;

View File

@ -0,0 +1,26 @@
import { UI } from '../third_party';
/**
* 场景编辑区
* @author mrdoob / http://mrdoob.com/
* @author tengge / https://github.com/tengge1
*/
function Viewport(options) {
UI.Control.call(this, options);
this.app = options.app;
};
Viewport.prototype = Object.create(UI.Control.prototype);
Viewport.prototype.constructor = Viewport;
Viewport.prototype.render = function () {
this.container = UI.create({
xtype: 'div',
id: 'viewport',
parent: this.parent,
cls: 'viewport'
});
this.container.render();
};
export default Viewport;

View File

@ -0,0 +1,432 @@
import { UI } from '../../third_party';
import AnimationGroup from '../../animation/AnimationGroup';
import Animation from '../../animation/Animation';
const STOP = 0;
const PLAY = 1;
const PAUSE = 2;
/**
* 动画面板
* @author tengge / https://github.com/tengge1
*/
function AnimationPanel(options) {
UI.Control.call(this, options);
this.app = options.app;
this.status = STOP;
this.sliderLeft = 0;
this.speed = 4;
this.isDragging = false;
};
AnimationPanel.prototype = Object.create(UI.Control.prototype);
AnimationPanel.prototype.constructor = AnimationPanel;
AnimationPanel.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
cls: 'animation-panel',
children: [{
xtype: 'div',
cls: 'controls',
children: [{
xtype: 'iconbutton',
icon: 'icon-add',
onClick: this.onAddGroup.bind(this)
}, {
xtype: 'iconbutton',
icon: 'icon-delete',
onClick: this.onRemoveGroup.bind(this)
}, {
xtype: 'div',
style: {
width: '2px',
height: '20px',
borderLeft: '1px solid #aaa',
borderRight: '1px solid #aaa',
boxSizing: 'border-box',
margin: '5px 8px'
}
}, {
xtype: 'iconbutton',
icon: 'icon-backward',
onClick: this.onBackward.bind(this)
}, {
xtype: 'iconbutton',
id: 'btnPlay',
scope: this.id,
icon: 'icon-play',
onClick: this.onPlay.bind(this)
}, {
xtype: 'iconbutton',
id: 'btnPause',
scope: this.id,
icon: 'icon-pause',
style: {
display: 'none'
},
onClick: this.onPause.bind(this)
}, {
xtype: 'iconbutton',
icon: 'icon-forward',
onClick: this.onForward.bind(this)
}, {
xtype: 'iconbutton',
icon: 'icon-stop',
onClick: this.onStop.bind(this)
}, {
xtype: 'text',
id: 'time',
scope: this.id,
style: {
marginLeft: '8px',
color: '#555',
fontSize: '12px'
},
text: '00:00'
}, {
xtype: 'text',
id: 'speed',
scope: this.id,
style: {
marginLeft: '8px',
color: '#aaa',
fontSize: '12px'
},
text: 'X 1'
}, {
xtype: 'toolbarfiller'
}, {
xtype: 'text',
scope: this.id,
style: {
marginLeft: '8px',
color: '#aaa',
fontSize: '12px'
},
text: '说明:双击时间轴下方添加动画。'
}]
}, {
xtype: 'div',
cls: 'box',
children: [{
xtype: 'div',
cls: 'left-area',
id: 'groupInfo',
scope: this.id
}, {
xtype: 'div',
cls: 'right-area',
children: [{
xtype: 'timeline',
id: 'timeline',
cls: 'timeline',
scope: this.id
}, {
xtype: 'div',
cls: 'groups',
id: 'groups',
scope: this.id,
children: []
}, {
xtype: 'div',
cls: 'slider',
id: 'slider',
scope: this.id
}]
}]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`appStarted.${this.id}`, this.onAppStarted.bind(this));
};
AnimationPanel.prototype.onAppStarted = function () {
var timeline = UI.get('timeline', this.id);
var groups = UI.get('groups', this.id);
timeline.updateUI();
groups.dom.style.width = timeline.dom.clientWidth + 'px';
groups.dom.addEventListener(`click`, this.onClick.bind(this));
groups.dom.addEventListener(`dblclick`, this.onDblClick.bind(this));
groups.dom.addEventListener(`mousedown`, this.onMouseDown.bind(this));
groups.dom.addEventListener(`mousemove`, this.onMouseMove.bind(this));
document.body.addEventListener(`mouseup`, this.onMouseUp.bind(this));
this.app.on(`animationChanged.${this.id}`, this.updateUI.bind(this));
this.app.on(`resetAnimation.${this.id}`, this.onResetAnimation.bind(this));
this.app.on(`startAnimation.${this.id}`, this.onPlay.bind(this));
};
AnimationPanel.prototype.updateUI = function () {
var animations = this.app.editor.animation.getAnimationGroups();
var groupInfo = UI.get('groupInfo', this.id);
var timeline = UI.get('timeline', this.id);
var groups = UI.get('groups', this.id);
while (groupInfo.dom.children.length) {
var child = groupInfo.dom.children[0];
groupInfo.dom.removeChild(child);
}
while (groups.dom.children.length) {
var child = groups.dom.children[0];
child.data = null;
groups.dom.removeChild(child);
}
animations.forEach(n => {
// 动画组信息区
var groupName = document.createElement('div');
groupName.className = 'group-info';
groupName.innerHTML = `<input type="checkbox" data-uuid="${n.uuid}" />${n.name}`;
groupInfo.dom.appendChild(groupName);
// 动画区
var group = document.createElement('div');
group.className = 'group';
group.setAttribute('droppable', true);
group.data = n;
group.addEventListener('dragenter', this.onDragEnterGroup.bind(this));
group.addEventListener('dragover', this.onDragOverGroup.bind(this));
group.addEventListener('dragleave', this.onDragLeaveGroup.bind(this));
group.addEventListener('drop', this.onDropGroup.bind(this));
groups.dom.appendChild(group);
n.animations.forEach(m => {
var item = document.createElement('div');
item.data = m;
item.className = 'item';
item.setAttribute('draggable', true);
item.setAttribute('droppable', false);
item.style.left = m.beginTime * timeline.scale + 'px';
item.style.width = (m.endTime - m.beginTime) * timeline.scale + 'px';
item.innerHTML = m.name;
item.addEventListener('dragstart', this.onDragStartAnimation.bind(this));
item.addEventListener('dragend', this.onDragEndAnimation.bind(this));
group.appendChild(item);
});
});
};
AnimationPanel.prototype.updateSlider = function () {
var timeline = UI.get('timeline', this.id);
var slider = UI.get('slider', this.id);
var time = UI.get('time', this.id);
var speed = UI.get('speed', this.id);
slider.dom.style.left = this.sliderLeft + 'px';
var animationTime = this.sliderLeft / timeline.scale;
var minute = ('0' + Math.floor(animationTime / 60)).slice(-2);
var second = ('0' + Math.floor(animationTime % 60)).slice(-2);
time.setValue(`${minute}:${second}`);
if (this.speed >= 4) {
speed.dom.innerHTML = `X ${this.speed / 4}`;
} else {
speed.dom.innerHTML = `X 1/${4 / this.speed}`;
}
this.app.call('animationTime', this, animationTime);
};
AnimationPanel.prototype.onAnimate = function () {
var timeline = UI.get('timeline', this.id);
this.sliderLeft += this.speed / 4;
if (this.sliderLeft >= timeline.dom.clientWidth) {
this.sliderLeft = 0;
}
this.updateSlider();
};
AnimationPanel.prototype.onAddGroup = function () {
var group = new AnimationGroup();
this.app.editor.animation.add(group);
this.updateUI();
};
AnimationPanel.prototype.onRemoveGroup = function () {
var inputs = document.querySelectorAll('.animation-panel .left-area input:checked');
var uuids = [];
inputs.forEach(n => {
uuids.push(n.getAttribute('data-uuid'));
});
if (uuids.length === 0) {
UI.msg('请勾选需要删除的组!');
return;
}
UI.confirm('询问', '删除组会删除组上的所有动画。是否删除?', (event, btn) => {
if (btn === 'ok') {
uuids.forEach(n => {
this.app.editor.animation.removeByUUID(n);
});
this.updateUI();
}
});
};
// ----------------------------------- 播放器事件 -------------------------------------------
AnimationPanel.prototype.onPlay = function () {
if (this.status === PLAY) {
return;
}
this.status = PLAY;
UI.get('btnPlay', this.id).dom.style.display = 'none';
UI.get('btnPause', this.id).dom.style.display = '';
this.app.on(`animate.${this.id}`, this.onAnimate.bind(this));
};
AnimationPanel.prototype.onPause = function () {
if (this.status === PAUSE) {
return;
}
this.status = PAUSE;
UI.get('btnPlay', this.id).dom.style.display = '';
UI.get('btnPause', this.id).dom.style.display = 'none';
this.app.on(`animate.${this.id}`, null);
this.updateSlider();
};
AnimationPanel.prototype.onForward = function () {
if (this.speed >= 16) {
return;
}
this.speed *= 2;
};
AnimationPanel.prototype.onBackward = function () {
if (this.speed <= 1) {
return;
}
this.speed /= 2;
};
AnimationPanel.prototype.onStop = function () {
if (this.status === STOP) {
return;
}
this.status = STOP;
UI.get('btnPlay', this.id).dom.style.display = '';
UI.get('btnPause', this.id).dom.style.display = 'none';
this.app.on(`animate.${this.id}`, null);
this.sliderLeft = 0;
this.updateSlider();
};
AnimationPanel.prototype.onResetAnimation = function () {
this.onStop();
this.speed = 4;
};
AnimationPanel.prototype.onClick = function (event) {
if (event.target.data.type === 'AnimationGroup') {
return;
}
this.app.call('tabSelected', this, 'animation');
this.app.call('animationSelected', this, event.target.data);
};
AnimationPanel.prototype.onDblClick = function (event) {
var timeline = UI.get('timeline', this.id);
if (event.target.data && event.target.data.type === 'AnimationGroup') {
event.stopPropagation();
var animation = new Animation({
beginTime: event.offsetX / timeline.scale,
endTime: (event.offsetX + 80) / timeline.scale
});
event.target.data.add(animation);
this.app.call('animationChanged', this);
}
};
AnimationPanel.prototype.onMouseDown = function () {
if (this.isDragging) {
return;
}
this.isDragging = true;
};
AnimationPanel.prototype.onMouseMove = function () {
};
AnimationPanel.prototype.onMouseUp = function () {
this.isDragging = false;
};
// ----------------------- 拖动动画事件 ---------------------------------------------
AnimationPanel.prototype.onDragStartAnimation = function (event) {
event.dataTransfer.setData('uuid', event.target.data.uuid);
event.dataTransfer.setData('offsetX', event.offsetX);
};
AnimationPanel.prototype.onDragEndAnimation = function (event) {
event.dataTransfer.clearData();
};
AnimationPanel.prototype.onDragEnterGroup = function (event) {
event.preventDefault();
};
AnimationPanel.prototype.onDragOverGroup = function (event) {
event.preventDefault();
};
AnimationPanel.prototype.onDragLeaveGroup = function (event) {
event.preventDefault();
};
AnimationPanel.prototype.onDropGroup = function (event) {
event.preventDefault();
var uuid = event.dataTransfer.getData('uuid');
var offsetX = event.dataTransfer.getData('offsetX');
var groups = this.app.editor.animation.getAnimationGroups();
var group = groups.filter(n => n.animations.findIndex(m => m.uuid === uuid) > -1)[0];
var animation = group.animations.filter(n => n.uuid === uuid)[0];
group.remove(animation);
var timeline = UI.get('timeline', this.id);
var length = animation.endTime - animation.beginTime;
animation.beginTime = (event.offsetX - offsetX) / timeline.scale;
animation.endTime = animation.beginTime + length;
if (event.target.data instanceof Animation) { // 拖动到其他动画上
event.target.parentElement.data.add(animation);
} else { // 拖动到动画组上
event.target.data.add(animation);
}
this.updateUI();
};
export default AnimationPanel;

View File

@ -0,0 +1,30 @@
import { UI } from '../../third_party';
/**
* 纹理面板
* @author tengge / https://github.com/tengge1
*/
function AudioPanel(options) {
UI.Control.call(this, options);
this.app = options.app;
};
AudioPanel.prototype = Object.create(UI.Control.prototype);
AudioPanel.prototype.constructor = AudioPanel;
AudioPanel.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
cls: 'Panel',
style: {
position: 'relative'
},
children: []
};
var control = UI.create(data);
control.render();
};
export default AudioPanel;

View File

@ -0,0 +1,197 @@
import { Control } from '../../third_party';
import AnimationPanel from './AnimationPanel';
import ModelPanel from './ModelPanel';
import TexturePanel from './TexturePanel';
import AudioPanel from './AudioPanel';
import ParticlePanel from './ParticlePanel';
import LogPanel from './LogPanel';
/**
* 底部面板
* @author mrdoob / http://mrdoob.com/
* @author tengge / https://github.com/tengge1
*/
function BottomPanel(options) {
Control.call(this, options);
this.app = options.app;
};
BottomPanel.prototype = Object.create(Control.prototype);
BottomPanel.prototype.constructor = BottomPanel;
BottomPanel.prototype.render = function () {
var data = {
xtype: 'div',
cls: 'sidebar bottomPanel',
parent: this.parent,
children: [{
xtype: 'div',
cls: 'tabs',
style: {
position: 'sticky',
top: 0,
zIndex: 20
},
children: [{
xtype: 'text',
id: 'animationTab',
text: '动画',
onClick: () => {
this.selectTab('animation');
}
}, {
xtype: 'text',
id: 'modelTab',
text: '模型',
onClick: () => {
this.selectTab('model');
}
}, {
xtype: 'text',
id: 'textureTab',
text: '纹理',
onClick: () => {
this.selectTab('texture');
}
}, {
xtype: 'text',
id: 'audioTab',
text: '音频',
onClick: () => {
this.selectTab('audio');
}
}, {
xtype: 'text',
id: 'particleTab',
text: '粒子',
onClick: () => {
this.selectTab('particle');
}
}, {
xtype: 'text',
id: 'logTab',
text: '日志',
onClick: () => {
this.selectTab('log');
}
}]
}, {
xtype: 'div',
id: 'animationPanel',
style: {
flex: 1
},
children: [
new AnimationPanel({ app: this.app })
]
}, {
xtype: 'div',
id: 'modelPanel',
style: {
flex: 1
},
children: [
new ModelPanel({ app: this.app })
]
}, {
xtype: 'div',
id: 'texturePanel',
style: {
flex: 1
},
children: [
new TexturePanel({ app: this.app })
]
}, {
xtype: 'div',
id: 'audioPanel',
style: {
flex: 1
},
children: [
new AudioPanel({ app: this.app })
]
}, {
xtype: 'div',
id: 'particlePanel',
style: {
flex: 1
},
children: [
new ParticlePanel({ app: this.app })
]
}, {
xtype: 'div',
id: 'logPanel',
children: [
new LogPanel({ app: this.app })
]
}]
};
var control = UI.create(data);
control.render();
this.app.on(`appStarted.${this.id}`, () => {
this.selectTab('animation');
});
};
BottomPanel.prototype.selectTab = function (tabName) {
var animationTab = UI.get('animationTab');
var modelTab = UI.get('modelTab');
var textureTab = UI.get('textureTab');
var audioTab = UI.get('audioTab');
var particleTab = UI.get('particleTab');
var logTab = UI.get('logTab');
var animationPanel = UI.get('animationPanel');
var modelPanel = UI.get('modelPanel');
var texturePanel = UI.get('texturePanel');
var audioPanel = UI.get('audioPanel');
var particlePanel = UI.get('particlePanel');
var logPanel = UI.get('logPanel');
animationTab.dom.className = '';
modelTab.dom.className = '';
textureTab.dom.className = '';
audioTab.dom.className = '';
particleTab.dom.className = '';
logTab.dom.className = '';
animationPanel.dom.style.display = 'none';
modelPanel.dom.style.display = 'none';
texturePanel.dom.style.display = 'none';
audioPanel.dom.style.display = 'none';
particlePanel.dom.style.display = 'none';
logPanel.dom.style.display = 'none';
switch (tabName) {
case 'animation':
animationTab.dom.className = 'selected';
animationPanel.dom.style.display = '';
break;
case 'model':
modelTab.dom.className = 'selected';
modelPanel.dom.style.display = '';
break;
case 'texture':
textureTab.dom.className = 'selected';
texturePanel.dom.style.display = '';
break;
case 'audio':
audioTab.dom.className = 'selected';
audioPanel.dom.style.display = '';
break;
case 'particle':
particleTab.dom.className = 'selected';
particlePanel.dom.style.display = '';
break;
case 'log':
logTab.dom.className = 'selected';
logPanel.dom.style.display = '';
break;
}
};
export default BottomPanel;

View File

@ -0,0 +1,85 @@
import { UI } from '../../third_party';
/**
* 日志面板
* @author tengge / https://github.com/tengge1
*/
function LogPanel(options) {
UI.Control.call(this, options);
this.app = options.app;
};
LogPanel.prototype = Object.create(UI.Control.prototype);
LogPanel.prototype.constructor = LogPanel;
LogPanel.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
cls: 'Panel',
style: {
position: 'relative'
},
children: [{
xtype: 'button',
text: '清空',
onClick: this.onClearLog.bind(this)
}, {
xtype: 'br'
}, {
xtype: 'div',
style: {
height: '140px',
marginTop: '8px',
backgroundColor: '#fff',
overflowY: 'auto'
},
id: 'logContent'
}]
};
var control = UI.create(data);
control.render();
this.app.on(`log.${this.id}`, this.onLog.bind(this));
};
LogPanel.prototype.onLog = function (content, type) {
var dom = UI.get('logContent').dom;
var date = new Date();
var hour = date.getHours();
var minute = date.getMinutes();
var second = date.getSeconds();
hour = hour < 10 ? '0' + hour : hour;
minute = minute < 10 ? '0' + minute : minute;
second = second < 10 ? '0' + second : second;
content = `<span style="font-weight: bold; margin-right: 8px">${hour}:${minute}:${second}</span>${content}`;
var box = document.createElement('div');
box.innerHTML = content;
if (dom.children.length === 0) {
dom.appendChild(box);
} else {
dom.insertBefore(box, dom.children[0]);
}
if (type === 'warn') {
box.style.backgroundColor = '#fffbe5';
box.style.color = '#5c3c00';
} else if (type === 'error') {
box.style.backgroundColor = '#fff0f0';
box.style.color = '#ff0000';
}
};
LogPanel.prototype.onClearLog = function () {
var dom = UI.get('logContent').dom;
dom.innerHTML = '';
this.onLog('清空日志');
};
export default LogPanel;

View File

@ -0,0 +1,30 @@
import { UI } from '../../third_party';
/**
* 模型面板
* @author tengge / https://github.com/tengge1
*/
function ModelPanel(options) {
UI.Control.call(this, options);
this.app = options.app;
};
ModelPanel.prototype = Object.create(UI.Control.prototype);
ModelPanel.prototype.constructor = ModelPanel;
ModelPanel.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
cls: 'Panel',
style: {
position: 'relative'
},
children: []
};
var control = UI.create(data);
control.render();
};
export default ModelPanel;

View File

@ -0,0 +1,30 @@
import { UI } from '../../third_party';
/**
* 粒子面板
* @author tengge / https://github.com/tengge1
*/
function ParticlePanel(options) {
UI.Control.call(this, options);
this.app = options.app;
};
ParticlePanel.prototype = Object.create(UI.Control.prototype);
ParticlePanel.prototype.constructor = ParticlePanel;
ParticlePanel.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
cls: 'Panel',
style: {
position: 'relative'
},
children: []
};
var control = UI.create(data);
control.render();
};
export default ParticlePanel;

View File

@ -0,0 +1,30 @@
import { UI } from '../../third_party';
/**
* 纹理面板
* @author tengge / https://github.com/tengge1
*/
function TexturePanel(options) {
UI.Control.call(this, options);
this.app = options.app;
};
TexturePanel.prototype = Object.create(UI.Control.prototype);
TexturePanel.prototype.constructor = TexturePanel;
TexturePanel.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
cls: 'Panel',
style: {
position: 'relative'
},
children: []
};
var control = UI.create(data);
control.render();
};
export default TexturePanel;

View File

@ -0,0 +1,119 @@
import { Control } from '../../third_party';
import TextureWindow from '../window/TextureWindow';
/**
* 纹理选择控件
* @param {*} options
*/
function TextureSelectControl(options) {
Control.call(this, options);
this.app = options.app;
this.texture = null;
this.onChange = options.onChange || null;
}
TextureSelectControl.prototype = Object.create(Control.prototype);
TextureSelectControl.prototype.constructor = TextureSelectControl;
TextureSelectControl.prototype.render = function () {
this.dom = document.createElement('div');
this.dom.className = 'Texture';
this.canvas = document.createElement('canvas');
this.canvas.width = 32;
this.canvas.height = 16;
this.dom.appendChild(this.canvas);
this.canvas.addEventListener('click', this.onClick.bind(this));
this.name = document.createElement('input');
this.name.disabled = true;
this.dom.appendChild(this.name);
this.parent.appendChild(this.dom);
};
TextureSelectControl.prototype.updateUI = function () {
var canvas = this.dom.children[0];
var name = this.dom.children[1];
var context = canvas.getContext('2d');
var texture = this.texture;
if (texture !== undefined && texture !== null) {
var image = texture.image;
if (image !== undefined && image.width > 0) {
name.value = texture.name;
var scale = canvas.width / image.width;
context.drawImage(image, 0, 0, image.width * scale, image.height * scale);
} else {
name.value = '无图片';
context.clearRect(0, 0, canvas.width, canvas.height);
}
} else {
name.value = '';
if (context !== null) {
context.clearRect(0, 0, canvas.width, canvas.height);
}
}
};
TextureSelectControl.prototype.getValue = function () {
return this.texture;
};
TextureSelectControl.prototype.setValue = function (texture) {
this.texture = texture;
this.updateUI();
};
TextureSelectControl.prototype.onClick = function () {
if (this.window === undefined) {
this.window = new TextureWindow({
app: this.app,
onSelect: this.onSelect.bind(this)
});
this.window.render();
}
this.window.show();
};
TextureSelectControl.prototype.onSelect = function (data) {
var urls = data.Url.split(';'); // 立体贴图data.Url多于一张只取第一个。
if (data.Type === 'video') { // 视频贴图
var video = document.createElement('video');
video.src = `${this.app.options.server}${urls[0]}`;
video.loop = 'loop';
video.autoplay = 'autoplay';
video.onplay = () => {
var texture = new THREE.VideoTexture(video);
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.format = THREE.RGBFormat;
this.texture = texture;
this.texture.name = data.Name;
this.window.hide();
this.updateUI();
this.onChange();
}
} else { // 其他
var loader = new THREE.TextureLoader();
loader.load(`${this.app.options.server}${urls[0]}`, texture => {
this.texture = texture;
this.texture.name = data.Name;
this.window.hide();
this.updateUI();
this.onChange();
});
}
};
export default TextureSelectControl;

View File

@ -0,0 +1,282 @@
import { UI } from '../../third_party';
import ModelWindow from '../window/ModelWindow';
import TextureWindow from '../window/TextureWindow';
import AudioWindow from '../window/AudioWindow';
import MMDWindow from '../window/MMDWindow';
import StringUtils from '../../utils/StringUtils';
/**
* 资源菜单
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function AssetMenu(options) {
UI.Control.call(this, options);
this.app = options.app;
}
AssetMenu.prototype = Object.create(UI.Control.prototype);
AssetMenu.prototype.constructor = AssetMenu;
AssetMenu.prototype.render = function () {
var container = UI.create({
xtype: 'div',
parent: this.parent,
cls: 'menu',
children: [{
xtype: 'div',
cls: 'title',
html: '资源'
}, {
xtype: 'div',
cls: 'options',
children: [{
xtype: 'div',
html: '模型管理',
cls: 'option',
onClick: this.onManageModel.bind(this)
}, {
xtype: 'div',
html: '纹理管理',
cls: 'option',
onClick: this.onManageTexture.bind(this)
}, {
xtype: 'div',
html: '音频管理',
cls: 'option',
onClick: this.onManageAudio.bind(this)
}, {
xtype: 'div',
html: 'MMD资源管理',
cls: 'option',
onClick: this.onManageMMD.bind(this)
}, {
xtype: 'hr'
}, {
xtype: 'div',
html: '导出几何体',
cls: 'option',
onClick: this.onExportGeometry.bind(this)
}, {
xtype: 'div',
html: '导出物体',
cls: 'option',
onClick: this.onExportObject.bind(this)
}, {
xtype: 'div',
html: '导出场景',
cls: 'option',
onClick: this.onExportScene.bind(this)
}, {
xtype: 'hr'
}, {
xtype: 'div',
html: '导出gltf文件',
cls: 'option',
onClick: this.onExportGLTF.bind(this)
}, {
xtype: 'div',
html: '导出obj文件',
cls: 'option',
onClick: this.onExportOBJ.bind(this)
}, {
xtype: 'div',
html: '导出ply文件',
cls: 'option',
onClick: this.onExportPLY.bind(this)
}, {
xtype: 'div',
id: 'mExportSTLB',
html: '导出stl二进制文件',
cls: 'option',
onClick: this.onExportSTLB.bind(this)
}, {
xtype: 'div',
id: 'mExportSTL',
html: '导出stl文件',
cls: 'option',
onClick: this.onExportSTL.bind(this)
}]
}]
});
container.render();
}
// --------------------------------- 模型管理 --------------------------------------
AssetMenu.prototype.onManageModel = function () {
if (this.modelWindow == null) {
this.modelWindow = new ModelWindow({ parent: this.app.container, app: this.app });
this.modelWindow.render();
}
this.modelWindow.show();
};
// --------------------------------- 纹理管理 --------------------------------------
AssetMenu.prototype.onManageTexture = function () {
if (this.textureWindow == null) {
this.textureWindow = new TextureWindow({ parent: this.app.container, app: this.app });
this.textureWindow.render();
}
this.textureWindow.show();
};
// --------------------------------- 音频管理 --------------------------------------
AssetMenu.prototype.onManageAudio = function () {
if (this.audioWindow == null) {
this.audioWindow = new AudioWindow({ parent: this.app.container, app: this.app });
this.audioWindow.render();
}
this.audioWindow.show();
};
// ------------------------------- MMD资源管理 --------------------------------------
AssetMenu.prototype.onManageMMD = function () {
if (this.mmdWindow == null) {
this.mmdWindow = new MMDWindow({ parent: this.app.container, app: this.app });
this.mmdWindow.render();
}
this.mmdWindow.show();
};
// ------------------------------- 导出几何体 ----------------------------------------
AssetMenu.prototype.onExportGeometry = function () {
var editor = this.app.editor;
var object = editor.selected;
if (object === null) {
UI.msg('请选择物体');
return;
}
var geometry = object.geometry;
if (geometry === undefined) {
UI.msg('选中的对象不具有Geometry属性。');
return;
}
var output = geometry.toJSON();
try {
output = JSON.stringify(output, parseNumber, '\t');
output = output.replace(/[\n\t]+([\d\.e\-\[\]]+)/g, '$1');
} catch (e) {
output = JSON.stringify(output);
}
StringUtils.saveString(output, 'geometry.json');
};
// ------------------------------- 导出物体 ------------------------------------------
AssetMenu.prototype.onExportObject = function () {
var editor = this.app.editor;
var object = editor.selected;
if (object === null) {
UI.msg('请选择对象');
return;
}
var output = object.toJSON();
try {
output = JSON.stringify(output, parseNumber, '\t');
output = output.replace(/[\n\t]+([\d\.e\-\[\]]+)/g, '$1');
} catch (e) {
output = JSON.stringify(output);
}
StringUtils.saveString(output, 'model.json');
};
// ------------------------------- 导出场景 ------------------------------------------
AssetMenu.prototype.onExportScene = function () {
var editor = this.app.editor;
var output = editor.scene.toJSON();
try {
output = JSON.stringify(output, parseNumber, '\t');
output = output.replace(/[\n\t]+([\d\.e\-\[\]]+)/g, '$1');
} catch (e) {
output = JSON.stringify(output);
}
StringUtils.saveString(output, 'scene.json');
};
// ------------------------------ 导出gltf文件 ----------------------------------------
AssetMenu.prototype.onExportGLTF = function () {
var exporter = new THREE.GLTFExporter();
exporter.parse(app.editor.scene, function (result) {
StringUtils.saveString(JSON.stringify(result), 'model.gltf');
});
};
// ------------------------------ 导出obj文件 -----------------------------------------
AssetMenu.prototype.onExportOBJ = function () {
var editor = this.app.editor;
var object = editor.selected;
if (object === null) {
UI.msg('请选择对象');
return;
}
var exporter = new THREE.OBJExporter();
StringUtils.saveString(exporter.parse(object), 'model.obj');
};
// ------------------------------- 导出ply文件 ----------------------------------------
AssetMenu.prototype.onExportPLY = function () {
var editor = this.app.editor;
var object = editor.selected;
if (object === null) {
UI.msg('请选择对象');
return;
}
var exporter = new THREE.PLYExporter();
StringUtils.saveString(exporter.parse(object, {
excludeAttributes: ['normal', 'uv', 'color', 'index']
}), 'model.ply');
};
// ------------------------------- 导出stl二进制文件 -----------------------------------
AssetMenu.prototype.onExportSTLB = function () {
var editor = this.app.editor;
var exporter = new THREE.STLBinaryExporter();
StringUtils.saveString(exporter.parse(editor.scene), 'model.stl');
};
// ------------------------------- 导出stl文件 -----------------------------------------
AssetMenu.prototype.onExportSTL = function () {
var editor = this.app.editor;
var exporter = new THREE.STLExporter();
StringUtils.saveString(exporter.parse(editor.scene), 'model.stl');
};
export default AssetMenu;

View File

@ -0,0 +1,205 @@
import { UI } from '../../third_party';
import AddObjectCommand from '../../command/AddObjectCommand';
import Sky from '../../object/component/Sky';
import Fire from '../../object/component/Fire';
import Water from '../../object/water/Water';
import Smoke from '../../object/component/Smoke';
import ParticleEmitter from '../../object/component/ParticleEmitter';
import PlysicsUtils from '../../physics/PlysicsUtils';
/**
* 组件菜单
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function ComponentMenu(options) {
UI.Control.call(this, options);
this.app = options.app;
}
ComponentMenu.prototype = Object.create(UI.Control.prototype);
ComponentMenu.prototype.constructor = ComponentMenu;
ComponentMenu.prototype.render = function () {
var container = UI.create({
xtype: 'div',
parent: this.parent,
cls: 'menu',
children: [{
xtype: 'div',
cls: 'title',
html: '组件'
}, {
xtype: 'div',
cls: 'options',
children: [{
xtype: 'div',
html: '背景音乐',
cls: 'option',
onClick: this.onAddBackgroundMusic.bind(this)
}, {
xtype: 'div',
html: '粒子发射器',
cls: 'option',
onClick: this.ParticleEmitter.bind(this)
}, {
xtype: 'div',
html: '天空',
cls: 'option',
onClick: this.onAddSky.bind(this)
}, {
xtype: 'div',
html: '火焰',
cls: 'option',
onClick: this.onAddFire.bind(this)
}, {
xtype: 'div',
html: '液体',
cls: 'option',
onClick: this.onAddWater.bind(this)
}, {
xtype: 'div',
html: '烟',
cls: 'option',
onClick: this.onAddSmoke.bind(this)
}, {
xtype: 'hr'
}, {
xtype: 'div',
html: '刚体',
cls: 'option',
onClick: this.addRigidBody.bind(this)
}, {
xtype: 'hr'
}, {
xtype: 'div',
html: '曲线编辑器',
cls: 'option',
onClick: this.curveEditor.bind(this)
}, {
xtype: 'div',
html: '发型编辑器',
cls: 'option',
onClick: this.hairEditor.bind(this)
}, {
xtype: 'div',
html: '服装编辑器',
cls: 'option',
onClick: this.clothingEditor.bind(this)
}]
}]
});
container.render();
}
// ---------------------------- 添加背景音乐 ----------------------------------
ComponentMenu.prototype.onAddBackgroundMusic = function () {
var editor = this.app.editor;
var listener = editor.audioListener;
var audio = new THREE.Audio(listener);
audio.name = `背景音乐`;
audio.autoplay = false;
audio.setLoop(true);
audio.setVolume(1.0);
audio.userData.autoplay = true;
this.app.editor.execute(new AddObjectCommand(audio));
};
// ---------------------------- 添加粒子发射器 --------------------------------------------
ComponentMenu.prototype.ParticleEmitter = function () {
var emitter = new ParticleEmitter();
this.app.editor.execute(new AddObjectCommand(emitter));
emitter.userData.group.tick(0);
};
// ---------------------------- 天空 ----------------------------------------
ComponentMenu.prototype.onAddSky = function () {
var obj = new Sky();
obj.name = '天空';
obj.userData.type = 'Sky';
this.app.editor.execute(new AddObjectCommand(obj));
};
// ---------------------------- 添加火焰 -------------------------------------
ComponentMenu.prototype.onAddFire = function () {
var editor = this.app.editor;
var fire = new Fire(editor.camera);
editor.execute(new AddObjectCommand(fire));
fire.userData.fire.update(0);
};
// -------------------------- 添加水 ---------------------------------------
ComponentMenu.prototype.onAddWater = function () {
var editor = this.app.editor;
var water = new Water(editor.renderer);
editor.execute(new AddObjectCommand(water));
this.app.on(`animate.${this.id}`, () => {
water.update();
});
};
// ------------------------------ 添加烟 ------------------------------------
ComponentMenu.prototype.onAddSmoke = function () {
var editor = this.app.editor;
var camera = editor.camera;
var renderer = editor.renderer;
var smoke = new Smoke(camera, renderer);
smoke.position.y = 3;
editor.execute(new AddObjectCommand(smoke));
smoke.update(0);
};
// --------------------------- 添加刚体 ------------------------------------
ComponentMenu.prototype.addRigidBody = function () {
var selected = this.app.editor.selected;
if (!selected) {
UI.msg('请选择几何体!');
return;
}
if (PlysicsUtils.addRigidBodyData(selected) !== false) {
this.app.call('objectChanged', this, selected);
UI.msg('添加刚体组件成功!');
}
};
// --------------------------- 曲线编辑器 -------------------------------------
ComponentMenu.prototype.curveEditor = function () {
UI.msg('待开发');
};
// --------------------------- 发型编辑器 --------------------------------------
ComponentMenu.prototype.hairEditor = function () {
UI.msg('待开发');
};
// --------------------------- 服装编辑器 --------------------------------------
ComponentMenu.prototype.clothingEditor = function () {
UI.msg('待开发');
};
export default ComponentMenu;

View File

@ -0,0 +1,192 @@
import { UI } from '../../third_party';
import AddObjectCommand from '../../command/AddObjectCommand';
import RemoveObjectCommand from '../../command/RemoveObjectCommand';
/**
* 编辑菜单
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function EditMenu(options) {
UI.Control.call(this, options);
this.app = options.app;
}
EditMenu.prototype = Object.create(UI.Control.prototype);
EditMenu.prototype.constructor = EditMenu;
EditMenu.prototype.render = function () {
var _this = this;
var container = UI.create({
xtype: 'div',
parent: this.parent,
cls: 'menu',
children: [{
xtype: 'div',
cls: 'title',
html: '编辑'
}, {
xtype: 'div',
cls: 'options',
children: [{
xtype: 'div',
id: 'undo',
scope: this.id,
html: '撤销(Ctrl+Z)',
cls: 'option inactive',
onClick: this.undo.bind(this)
}, {
xtype: 'div',
id: 'redo',
scope: this.id,
html: '重做(Ctrl+Shift+Z)',
cls: 'option inactive',
onClick: this.redo.bind(this)
}, {
xtype: 'div',
id: 'clearHistory',
scope: this.id,
html: '清空历史记录',
cls: 'option inactive',
onClick: this.clearHistory.bind(this)
}, {
xtype: 'hr'
}, {
xtype: 'div',
id: 'clone',
scope: this.id,
html: '复制',
cls: 'option inactive',
onClick: this.clone.bind(this)
}, {
xtype: 'div',
id: 'delete',
scope: this.id,
html: '删除(Del)',
cls: 'option inactive',
onClick: this.delete.bind(this)
}]
}]
});
container.render();
this.app.on(`historyChanged.${this.id}`, this.onHistoryChanged.bind(this));
this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this));
}
// --------------------- 撤销 --------------------------
EditMenu.prototype.undo = function () {
var history = this.app.editor.history;
if (history.undos.length === 0) {
return;
}
this.app.editor.undo();
};
// --------------------- 重做 -----------------------------
EditMenu.prototype.redo = function () {
var history = this.app.editor.history;
if (history.redos.length === 0) {
return;
}
this.app.editor.redo();
};
// -------------------- 清空历史记录 --------------------------------
EditMenu.prototype.clearHistory = function () {
var editor = this.app.editor;
var history = editor.history;
if (history.undos.length === 0 && history.redos.length === 0) {
return;
}
UI.confirm('询问', '撤销/重做历史记录将被清空。确定吗?', function (event, btn) {
if (btn === 'ok') {
editor.history.clear();
}
});
};
// -------------------------- 复制 -----------------------------------
EditMenu.prototype.clone = function () {
var editor = this.app.editor;
var object = editor.selected;
if (object == null || object.parent == null) { // 避免复制场景或相机
return;
}
object = object.clone();
editor.execute(new AddObjectCommand(object));
};
// ----------------------- 删除 -----------------------------------
EditMenu.prototype.delete = function () {
var editor = this.app.editor;
var object = editor.selected;
if (object == null || object.parent == null) { // 避免删除场景或相机
return;
}
UI.confirm('询问', '删除 ' + object.name + '?', function (event, btn) {
if (btn === 'ok') {
editor.execute(new RemoveObjectCommand(object));
}
});
};
// ---------------------- 事件 -----------------------
EditMenu.prototype.onHistoryChanged = function () {
var history = this.app.editor.history;
var undo = UI.get('undo', this.id);
var redo = UI.get('redo', this.id);
var clearHistory = UI.get('clearHistory', this.id);
if (history.undos.length === 0) {
undo.dom.classList.add('inactive');
} else {
undo.dom.classList.remove('inactive');
}
if (history.redos.length === 0) {
redo.dom.classList.add('inactive');
} else {
redo.dom.classList.remove('inactive');
}
if (history.undos.length === 0 && history.redos.length === 0) {
clearHistory.dom.classList.add('inactive');
} else {
clearHistory.dom.classList.remove('inactive');
}
};
EditMenu.prototype.onObjectSelected = function () {
var editor = this.app.editor;
var clone = UI.get('clone', this.id);
var deleteBtn = UI.get('delete', this.id);
if (editor.selected && editor.selected.parent != null) {
clone.dom.classList.remove('inactive');
deleteBtn.dom.classList.remove('inactive');
} else {
clone.dom.classList.add('inactive');
deleteBtn.dom.classList.add('inactive');
}
};
export default EditMenu;

View File

@ -0,0 +1,198 @@
import { UI } from '../../third_party';
import AddObjectCommand from '../../command/AddObjectCommand';
import Group from '../../object/geometry/Group';
import Plane from '../../object/geometry/Plane';
import Box from '../../object/geometry/Box';
import Circle from '../../object/geometry/Circle';
import Cylinder from '../../object/geometry/Cylinder';
import Sphere from '../../object/geometry/Sphere';
import Icosahedron from '../../object/geometry/Icosahedron';
import Torus from '../../object/geometry/Torus';
import TorusKnot from '../../object/geometry/TorusKnot';
import Teapot from '../../object/geometry/Teapot';
import Lathe from '../../object/geometry/Lathe';
import Sprite from '../../object/geometry/Sprite';
import Text from '../../object/geometry/Text';
/**
* 几何体菜单
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function GeometryMenu(options) {
UI.Control.call(this, options);
this.app = options.app;
}
GeometryMenu.prototype = Object.create(UI.Control.prototype);
GeometryMenu.prototype.constructor = GeometryMenu;
GeometryMenu.prototype.render = function () {
var container = UI.create({
xtype: 'div',
parent: this.parent,
cls: 'menu',
children: [{
xtype: 'div',
cls: 'title',
html: '几何体'
}, {
xtype: 'div',
cls: 'options',
children: [{
xtype: 'div',
html: '组',
cls: 'option',
onClick: this.addGroup.bind(this)
}, {
xtype: 'hr'
}, {
xtype: 'div',
html: '平板',
cls: 'option',
onClick: this.addPlane.bind(this)
}, {
xtype: 'div',
html: '正方体',
cls: 'option',
onClick: this.addBox.bind(this)
}, {
xtype: 'div',
html: '圆',
cls: 'option',
onClick: this.addCircle.bind(this)
}, {
xtype: 'div',
html: '圆柱体',
cls: 'option',
onClick: this.addCylinder.bind(this)
}, {
xtype: 'div',
html: '球体',
cls: 'option',
onClick: this.addSphere.bind(this)
}, {
xtype: 'div',
html: '二十面体',
cls: 'option',
onClick: this.addIcosahedron.bind(this)
}, {
xtype: 'div',
html: '轮胎',
cls: 'option',
onClick: this.addTorus.bind(this)
}, {
xtype: 'div',
html: '扭结',
cls: 'option',
onClick: this.addTorusKnot.bind(this)
}, {
xtype: 'div',
html: '茶壶',
cls: 'option',
onClick: this.addTeaport.bind(this)
}, {
xtype: 'div',
html: '酒杯',
cls: 'option',
onClick: this.addLathe.bind(this)
}, {
xtype: 'div',
id: 'mAddSprite',
html: '精灵',
cls: 'option',
onClick: this.addSprite.bind(this)
}, {
xtype: 'div',
html: '文本',
cls: 'option',
onClick: this.addText.bind(this)
}]
}]
});
container.render();
}
// ------------------------- 组 ---------------------------------
GeometryMenu.prototype.addGroup = function () {
this.app.editor.execute(new AddObjectCommand(new Group()));
};
// ------------------------- 平板 -------------------------------
GeometryMenu.prototype.addPlane = function () {
this.app.editor.execute(new AddObjectCommand(new Plane()));
};
// ------------------------ 正方体 -----------------------------
GeometryMenu.prototype.addBox = function () {
this.app.editor.execute(new AddObjectCommand(new Box()));
};
// ------------------------ 圆 ----------------------------------
GeometryMenu.prototype.addCircle = function () {
this.app.editor.execute(new AddObjectCommand(new Circle()));
};
// ------------------------圆柱体 -------------------------------
GeometryMenu.prototype.addCylinder = function () {
this.app.editor.execute(new AddObjectCommand(new Cylinder()));
};
// ------------------------ 球体 -------------------------------
GeometryMenu.prototype.addSphere = function () {
this.app.editor.execute(new AddObjectCommand(new Sphere()));
};
// ----------------------- 二十面体 -----------------------------
GeometryMenu.prototype.addIcosahedron = function () {
this.app.editor.execute(new AddObjectCommand(new Icosahedron()));
};
// ----------------------- 轮胎 ---------------------------------
GeometryMenu.prototype.addTorus = function () {
this.app.editor.execute(new AddObjectCommand(new Torus()));
};
// ----------------------- 纽结 ---------------------------------
GeometryMenu.prototype.addTorusKnot = function () {
this.app.editor.execute(new AddObjectCommand(new TorusKnot()));
};
// ---------------------- 茶壶 ----------------------------------
GeometryMenu.prototype.addTeaport = function () {
this.app.editor.execute(new AddObjectCommand(new Teapot()));
};
// ---------------------- 酒杯 ----------------------------------
GeometryMenu.prototype.addLathe = function () {
this.app.editor.execute(new AddObjectCommand(new Lathe()));
};
// ---------------------- 精灵 -----------------------------------
GeometryMenu.prototype.addSprite = function () {
this.app.editor.execute(new AddObjectCommand(new Sprite()));
};
// ---------------------- 文本 ----------------------------------
GeometryMenu.prototype.addText = function () {
UI.prompt('请输入', null, '一些文字', (event, value) => {
this.app.editor.execute(new AddObjectCommand(new Text(value)));
});
};
export default GeometryMenu;

View File

@ -0,0 +1,71 @@
import { UI } from '../../third_party';
/**
* 帮助菜单
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function HelpMenu(options) {
UI.Control.call(this, options);
this.app = options.app;
}
HelpMenu.prototype = Object.create(UI.Control.prototype);
HelpMenu.prototype.constructor = HelpMenu;
HelpMenu.prototype.render = function () {
var _this = this;
var container = UI.create({
xtype: 'div',
parent: this.parent,
cls: 'menu',
children: [{
xtype: 'div',
cls: 'title',
html: '帮助'
}, {
xtype: 'div',
cls: 'options',
children: [{
xtype: 'div',
cls: 'option',
html: '源码',
onClick: () => {
window.open('https://github.com/tengge1/ShadowEditor', '_blank');
}
}, {
xtype: 'div',
cls: 'option',
html: '示例',
onClick: () => {
window.open('https://github.com/tengge1/ShadowEditor-examples', '_blank');
}
}, {
xtype: 'div',
cls: 'option',
html: '文档',
onClick: () => {
window.open('https://tengge1.github.io/ShadowEditor/', '_blank');
}
}, {
xtype: 'div',
cls: 'option',
html: '关于',
onClick: () => {
UI.alert(
`About`,
`Name: ShadowEditor<br />
Author: tengge<br />
License: MIT<br />
Thanks to three.js and everyone who helped us.`
);
}
}]
}]
});
container.render();
};
export default HelpMenu;

View File

@ -0,0 +1,177 @@
import { UI } from '../../third_party';
import AddObjectCommand from '../../command/AddObjectCommand';
import PointLight from '../../object/light/PointLight';
import HemisphereLight from '../../object/light/HemisphereLight';
import RectAreaLight from '../../object/light/RectAreaLight';
/**
* 光源菜单
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function LightMenu(options) {
UI.Control.call(this, options);
this.app = options.app;
}
LightMenu.prototype = Object.create(UI.Control.prototype);
LightMenu.prototype.constructor = LightMenu;
LightMenu.prototype.render = function () {
var container = UI.create({
xtype: 'div',
parent: this.parent,
cls: 'menu',
children: [{
xtype: 'div',
cls: 'title',
html: '光源'
}, {
xtype: 'div',
cls: 'options',
children: [{
xtype: 'div',
html: '环境光',
cls: 'option',
onClick: this.addAmbientLight.bind(this)
}, {
xtype: 'div',
html: '平行光',
cls: 'option',
onClick: this.addDirectionalLight.bind(this)
}, {
xtype: 'div',
html: '点光源',
cls: 'option',
onClick: this.addPointLight.bind(this)
}, {
xtype: 'div',
html: '聚光灯',
cls: 'option',
onClick: this.addSpotLight.bind(this)
}, {
xtype: 'div',
html: '半球光',
cls: 'option',
onClick: this.addHemisphereLight.bind(this)
}, {
xtype: 'div',
html: '矩形光',
cls: 'option',
onClick: this.addRectAreaLight.bind(this)
}]
}]
});
container.render();
}
// ------------------------- 环境光 ------------------------------
LightMenu.prototype.addAmbientLight = function () {
var editor = this.app.editor;
var color = 0xaaaaaa;
var light = new THREE.AmbientLight(color);
light.name = '环境光';
editor.execute(new AddObjectCommand(light));
};
// ------------------------- 平行光 ------------------------------
LightMenu.prototype.addDirectionalLight = function () {
var editor = this.app.editor;
var color = 0xffffff;
var intensity = 1;
var light = new THREE.DirectionalLight(color, intensity);
light.name = '平行光';
light.castShadow = true;
light.shadow.mapSize.x = 2048;
light.shadow.mapSize.y = 2048;
light.shadow.camera.left = -100;
light.shadow.camera.right = 100;
light.shadow.camera.top = 100;
light.shadow.camera.bottom = -100;
light.position.set(5, 10, 7.5);
editor.execute(new AddObjectCommand(light));
};
// ------------------------- 点光源 ------------------------------
LightMenu.prototype.addPointLight = function () {
var editor = this.app.editor;
var color = 0xffffff;
var intensity = 1;
var distance = 0;
var light = new PointLight(color, intensity, distance);
light.name = '点光源';
light.position.y = 5;
light.castShadow = true;
editor.execute(new AddObjectCommand(light));
};
// ------------------------- 聚光灯 ------------------------------
LightMenu.prototype.addSpotLight = function () {
var editor = this.app.editor;
var color = 0xffffff;
var intensity = 1;
var distance = 0;
var angle = Math.PI * 0.1;
var penumbra = 0;
var light = new THREE.SpotLight(color, intensity, distance, angle, penumbra);
light.name = '聚光灯';
light.castShadow = true;
light.position.set(5, 10, 7.5);
editor.execute(new AddObjectCommand(light));
};
// ------------------------- 半球光 ------------------------------
LightMenu.prototype.addHemisphereLight = function () {
var editor = this.app.editor;
var skyColor = 0x00aaff;
var groundColor = 0xffaa00;
var intensity = 1;
var light = new HemisphereLight(skyColor, groundColor, intensity);
light.name = '半球光';
light.position.set(0, 10, 0);
editor.execute(new AddObjectCommand(light));
};
// ------------------------- 矩形光 ------------------------------
LightMenu.prototype.addRectAreaLight = function () {
var editor = this.app.editor;
var color = 0xffffff;
var intensity = 1;
var width = 20;
var height = 10;
var light = new RectAreaLight(color, intensity, width, height);
light.name = '矩形光';
light.position.set(0, 6, 0);
editor.execute(new AddObjectCommand(light));
};
export default LightMenu;

View File

@ -0,0 +1,29 @@
import { UI } from '../../third_party';
/**
* Logo标志
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function Logo(options) {
UI.Control.call(this, options);
this.app = options.app;
}
Logo.prototype = Object.create(UI.Control.prototype);
Logo.prototype.constructor = Logo;
Logo.prototype.render = function () {
var _this = this;
var container = UI.create({
xtype: 'div',
parent: this.parent,
cls: 'logo',
html: '<i class="iconfont icon-shadow"></i>'
});
container.render();
}
export default Logo;

View File

@ -0,0 +1,62 @@
import { UI } from '../../third_party';
import Logo from './Logo';
import SceneMenu from './SceneMenu';
import EditMenu from './EditMenu';
import GeometryMenu from './GeometryMenu';
import LightMenu from './LightMenu';
import AssetMenu from './AssetMenu';
import TerrainMenu from './TerrainMenu';
import PhysicsMenu from './PhysicsMenu';
import ComponentMenu from './ComponentMenu';
import PlayMenu from './PlayMenu';
import OptionsMenu from './OptionsMenu';
import HelpMenu from './HelpMenu';
import StatusMenu from './StatusMenu';
/**
* 菜单栏
* @author mrdoob / http://mrdoob.com/
* @author tengge / https://github.com/tengge1
*/
function Menubar(options) {
UI.Control.call(this, options);
this.app = options.app;
};
Menubar.prototype = Object.create(UI.Control.prototype);
Menubar.prototype.constructor = Menubar;
Menubar.prototype.render = function () {
var params = { app: this.app };
var container = UI.create({
xtype: 'div',
id: 'menubar',
cls: 'menubar',
parent: this.parent,
children: [
// Logo
new Logo(params),
// 左侧
new SceneMenu(params),
new EditMenu(params),
new GeometryMenu(params),
new LightMenu(params),
new AssetMenu(params),
new TerrainMenu(params),
new PhysicsMenu(params),
new ComponentMenu(params),
new PlayMenu(params),
new OptionsMenu(params),
new HelpMenu(params),
// 右侧
new StatusMenu(params)
]
});
container.render();
};
export default Menubar;

Some files were not shown because too many files have changed in this diff Show More