Commit e4eb14b9 by 张恒

增加移动端、打印、数据可视化模块代码

parent 9fda1878

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

node_modules
.git
.vscode
.DS_Store
\ No newline at end of file
#开发环境配置
VITE_APP_ENV = 'development'
#接口地址
VITE_APP_API=
#调试参数
VITE_APP_DEBUG_KEY=avue-data
VITE_APP_DEBUG_SWITCH=false
#页面基础路径
VITE_APP_BASE=/
\ No newline at end of file
# 生产环境配置
VITE_APP_ENV = 'lib'
#接口地址
VITE_APP_API=
#调试参数
VITE_APP_DEBUG_KEY=avue-data
VITE_APP_DEBUG_SWITCH=false
#页面基础路径
VITE_APP_BASE=/
#生产环境配置
VITE_APP_ENV = 'production'
#接口地址
VITE_APP_API=
#调试参数
VITE_APP_DEBUG_KEY=avue-data
VITE_APP_DEBUG_SWITCH=false
#页面基础路径
VITE_APP_BASE=/
#是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip
\ No newline at end of file
.DS_Store
node_modules
/dist
/src/avue-draggable
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
\ No newline at end of file
# 忽略的文件和文件夹
node_modules/
dist/
lib/
dc/
.vscode/
docs/
public/
src/avue-draggable
src/test
# 只包含packages和public文件
!src/
!LICENSE
!README.md
# 排除根目录下的配置文件
/*
\ No newline at end of file
@avue:registry=https://git.avuejs.com/api/packages/avue/npm/
//git.avuejs.com/api/packages/avue/npm/:_authToken=feda95ee3bb5888a3fec1e6b11deb60d7faeedba
\ No newline at end of file
MIT License
Copyright (c) 2020 smallwei
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.
## 源码
### 后端
- [NODE 版本](https://git.avuejs.com/avue/avue-data-server)
- [JAVA Colud版本](https://git.avuejs.com/avue/Data-Server)
- [JAVA Boot版本](https://git.avuejs.com/avue/Data-Server-Boot)
### 教程
- [文档](https://www.yuque.com/smallwei/avue-data)
- [视频](https://data.avuejs.com/video.json)
## 调试
### 配置密钥
```
参考 [配置密钥](https://www.yuque.com/smallwei/avue-data/xg54owx6dd238lbd),配置密钥到.npmrc文件
```
### 依赖
```
pnpm install
```
### 运行
```
pnpm dev
```
### 打包
```
pnpm build
```
### 打包单独部署库(index.umd.js)
```
pnpm lib
```
pnpm build
del ./dist/img/**
del ./dist/3dEditer/**
scp -P 35738 -r ./dist/* ssh root@8.130.12.92:/back/avue/avue/avue-data
\ No newline at end of file
## 2025-08-05
### v3.3.0
- 新增响应式断点配置,支持多设备适配优化
- 新增主题切换动画效果,提升视觉体验
- 新增组件批量操作功能,支持多选删除、复制、移动
- 新增数据看板模板库,内置多种行业数据展示模板
- 新增国际化支持,支持中英文切换
- 新增组件性能监控面板,实时显示渲染性能指标
- 新增键盘快捷键支持,提升操作效率
- 新增组件版本管理,支持组件回滚和版本比较
- 优化拖拽交互体验,增加磁性吸附和智能对齐
- 优化数据绑定机制,支持复杂数据结构绑定
- 优化 WebSocket 连接池管理,提升大并发场景稳定性
- 优化图表渲染性能,大数据量场景下提升 50%渲染速度
- 重构组件通信机制,简化父子组件数据传递
- 修复暗黑模式下部分组件样式显示异常
- 修复大屏预览模式下缩放比例计算错误
- 修复数据源切换时组件状态丢失问题
- 修复表格组件在数据量较大时的滚动卡顿问题
## 2025-06-24
### v3.2.4
- 修复大屏导入问题
- 修复在构建页面中新增宽度配置项,修正间距属性名称为 gridGutter,提升组件配置灵活性
- 新增格栅布局配置,支持行数和间距设置,增强组件灵活性
- 更新 carousel 组件属性,支持动态配置以增强灵活性和可定制性
- 修复复选择元素时的消息内容,确保显示正确的 ID 值
- 添加表格行悬停效果,确保背景色透明以提升可读性
- 优化动画显示和隐藏逻辑,增强组件的可控性,修复相关显示问题
- 增强动画管理功能,添加移入和移出动画支持,优化动画选择界面以提升用户体验
- 移除无用的 keys 引用,简化 registerConfig 配置以提高代码可读性
- 增强 WebSocket 和 MQTT 连接管理,添加重连机制和错误处理以提高稳定性
- 更新 view.html 文件,添加暗黑模式支持和新脚本以增强功能
- 更新 mqtt 导入方式,改为直接从 mqtt 包中导入以提高兼容性
- 更新 monaco-editor 组件,禁用工具栏并调整高度以提升用户体验
- 修复 openCode 函数参数传递,添加 isObject 属性以支持对象类型处理
- 修复数据初始化问题,将 dataChart 从 undefined 更改为数组,并移除不必要的错误处理日志
## 2025-04-21
### v3.2.3
- 优化 iframe 工具 UI,增强按钮功能和样式
- 新增多大屏交互模块例子
- 优化 JSON 导入验证和错误处理机制
- 优化 contentmenu.vue 参数处理和格式化
- 优化环境文件中的基础路径配置
- 重构 view.html 中的脚本源
- 修复 imglist.vue 图片路径格式问题
- 修复内容菜单中未定义值的定位问题
- 修复 group 组件中 gptBox 未禁用导致的意外行为
## 2025-03-15
### v3.2.2
- 优化表格滚动速度,提高流畅度
- 新增 clappr 播放器 CDN 支持
- 优化 header 错误日志处理机制
- 修复树形菜单权限问题
- 修复图表显示异常问题
- 优化 vue 组件加载性能
## 2025-03-02
### v3.2.1
- 新增路径变量支持
- 新增全局大模型开关
- 优化大模型配置文件
- 优化组件绑定数据
- 修复组件销毁生命周期名字
- 修复图片组件绑定对象为空问题
- 修复导入保存大屏 id 错乱
- 修复树形菜单权限
## 2025-02-09
### v3.2.0
- 大模型功能全面升级,支持智能生成样式和组件代码,提升代码质量
- 优化菜单分类结构和组件展示,提升用户体验
- 重构 util 工具类,增加常用函数,提高代码复用性
- 新增表格排序、筛选、列宽调整等功能
- 修复批量导入组件异常问题
- 修复条件判断参数错误问题
- 修复并优化其他已知问题
## 2025-01-06
### v3.1.3
- 新增 ele 基础组件库
- 新增 graph 图形组件
- 优化 vue 组件
- 优化 progress 组件配置
- 优化邮件菜单交互
- 修复 ws 传递参数
- 修复 echart 变量问题
- 修复地图不显示
## 2024-12-19
### v3.1.2
- 新增外部链接库引入
- 新增 F12 禁止打开功能
- 新增生产环境禁止 debugger 功能
- 新增静态库 svg 功能
- 新增 iframe 交互
- 优化初始化配置类
- 修复分组复制重复
- 修复若干 bug
## 2024-12-06
### v3.1.1
- 新增顶部组件隐藏
- 新增地图穿透功能
- 新增生产环境禁止 debugger 功能
- 新增折叠面板组件
- 优化设计器交互 UI
- 修复字体无法加载
- 修复若干 bug
## 2024-11-27
### v3.1.0
- 新增顶部组件菜单管理
- 新增交互判断语法
- 新增 vue、echart、html 可以根据 id 绑定组件库
- 新增屏幕管理轮播功能
- 优化 vue、echart、html 远程组件加载方式
- 优化交互事件交互
- 修复 vue、echart、html 编辑器不显示值
- 修复若干 bug
- 删除组件事件选项,全部采用交互中得自定义
## 2024-11-16
### v3.0.5
- 新增组建旋转功能
- 新增独立打包地址 buildUrl
- 新增 login 登录和 fill 填报组件
- 新增图片批量上传
- 优化全局变量参数赋值
- 修复基于 mqtt 的屏幕控制器
- 修复函数为空导致页面报错
- 修复字典新增不更新
## 2024-11-07
### v3.0.4
- 修复数据绑定核心问题
- 优化 mqtt 配置选项
## 2024-11-04
### v3.0.3
- 优化 provide 绑定集合
- 修复数据绑定错误
- 修复若干 bug
- 修复 graph 组件 fillColor 属性
- 修复 record 数据集格式化方法
## 2024-10-31
### v3.0.2
- 新增独立部署
- 优化数据交互
- 重构打包方式
- 修复事件不回显问题
- 修复 sqlserver 数据库链接池错误
## 2024-10-25
### v3.0.1
- 新增 3D 模型编辑器
- 新增数据源支持多绑定
- 新增 vue 模块 css 的 scoped 模式
- 修复全局 css 样式绑定问题
- 优化数据填报
- 优化拖拽组件
- 优化数据绑定模块
- 优化若干 bug
- 优化若干样式问题
## 2024-10-20
### v3.0.0
- 3.0.0 正式版本发布
# 使用 Nginx 镜像作为最终镜像
FROM nginx:alpine
# 设置工作目录
WORKDIR /usr/share/nginx/html
# 复制文件
COPY ./avue-data/dist/ /usr/share/nginx/html/
# 暴露端口
EXPOSE 80
# 将 nginx 配置复制到容器中
COPY ./avue-data/docker/nginx.conf /etc/nginx/conf.d/default.conf
# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
server {
listen 80;
server_name localhost;
location / {
error_page 404 /index.html;
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://app:10002/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
\ No newline at end of file
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/css/loading.css">
<link rel="stylesheet" href="/cdn/iconfont/iconfont.css">
<link rel="stylesheet" href="/cdn/animate/3.5.1/animate.css">
<script src="/components.js"></script>
<script src="/components.json"></script>
<script src="/config.js"></script>
<script src="/cdn/clappr.min.js"></script>
<script src="/cdn/FileSaver.min.js"></script>
<script src="/cdn/xlsx.full.min.js"></script>
<script src="/cdn/jszip.min.js"></script>
<script src="/cdn/html2canvas/html2canvas.min.js"></script>
<script src="/cdn/qrious.min.js"></script>
<script src="/cdn/jquery.min.js"></script>
<script src="/cdn/echarts/5.4.0/echarts.min.js"></script>
<script src="/cdn/echarts-wordcloud.min.js"></script>
<script src="/cdn/echarts-gl.min.js"></script>
<style>
html,
body,
#app {
height: 100%;
margin: 0px;
padding: 0px;
}
#loader-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 99999;
background-color: #151a26;
}
#loader-wrapper .loader-box {
position: fixed;
left: calc(50% - 250px);
top: calc(50% - 100px);
margin: 0 auto;
width: 500px;
height: 200px;
text-align: center;
vertical-align: center;
font-weight: bold;
color: #87888E;
font-size: 35px;
}
#loader-wrapper .loader-box>span {
opacity: 0.4;
display: inline-block;
animation: bouncingLoader 1s infinite alternate;
}
#loader-wrapper .loader-box>span:nth-child(2) {
animation-delay: 0.1s;
}
#loader-wrapper .loader-box>span:nth-child(3) {
animation-delay: 0.2s;
}
#loader-wrapper .loader-box>span:nth-child(4) {
animation-delay: 0.3s;
}
#loader-wrapper .loader-box>span:nth-child(5) {
animation-delay: 0.4s;
}
#loader-wrapper .loader-box>span:nth-child(6) {
animation-delay: 0.5s;
}
#loader-wrapper .loader-box>span:nth-child(7) {
animation-delay: 0.6s;
}
@keyframes bouncingLoader {
0% {
transform: translateY(0);
}
50% {
transform: translateY(25px);
}
100% {
transform: translateY(0);
}
}
#loader-wrapper .load_title {
font-weight: bold;
z-index: 1002;
position: absolute;
top: 50%;
margin-top: 15px;
color: #87888E;
font-size: 18px;
width: 100%;
height: 30px;
text-align: center;
opacity: 0.4;
line-height: 30px;
}
.wwads-cn {
position: fixed !important;
right: 5px;
bottom: 40px;
z-index: 1024;
max-width: 180px;
}
</style>
</head>
<body>
<div data-sticky-width="100" class="wwads-cn wwads-vertical wwads-sticky" data-id="220"></div>
<div id="app">
<div id="loader-wrapper">
<div class="loader-box">
<span>LEARUN-DATA</span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<div class="load_title">加载中…</div>
</div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/css/loading.css">
<link rel="stylesheet" href="/cdn/iconfont/iconfont.css">
<link rel="stylesheet" href="/cdn/animate/3.5.1/animate.css">
<script src="/components.js"></script>
<script src="/components.json"></script>
<script src="/iot.js"></script>
<script src="/cdn/clappr.min.js"></script>
<script src="/cdn/jsmpeg.min.js"></script>
<script src="/cdn/FileSaver.min.js"></script>
<script src="/cdn/xlsx.full.min.js"></script>
<script src="/cdn/jszip.min.js"></script>
<script src="/cdn/html2canvas/html2canvas.min.js"></script>
<script src="/cdn/qrious.min.js"></script>
<script src="/cdn/jquery.min.js"></script>
<script src="/cdn/echarts/5.4.0/echarts.min.js"></script>
<script src="/cdn/echarts-wordcloud.min.js"></script>
<script src="/cdn/echarts-gl.min.js"></script>
<style>
html,
body,
#app {
height: 100%;
margin: 0px;
padding: 0px;
}
#loader-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 99999;
background-color: #151a26;
}
#loader-wrapper .loader-box {
position: fixed;
left: calc(50% - 250px);
top: calc(50% - 100px);
margin: 0 auto;
width: 500px;
height: 200px;
text-align: center;
vertical-align: center;
font-weight: bold;
color: #87888E;
font-size: 35px;
}
#loader-wrapper .loader-box>span {
opacity: 0.4;
display: inline-block;
animation: bouncingLoader 1s infinite alternate;
}
#loader-wrapper .loader-box>span:nth-child(2) {
animation-delay: 0.1s;
}
#loader-wrapper .loader-box>span:nth-child(3) {
animation-delay: 0.2s;
}
#loader-wrapper .loader-box>span:nth-child(4) {
animation-delay: 0.3s;
}
#loader-wrapper .loader-box>span:nth-child(5) {
animation-delay: 0.4s;
}
#loader-wrapper .loader-box>span:nth-child(6) {
animation-delay: 0.5s;
}
#loader-wrapper .loader-box>span:nth-child(7) {
animation-delay: 0.6s;
}
@keyframes bouncingLoader {
0% {
transform: translateY(0);
}
50% {
transform: translateY(25px);
}
100% {
transform: translateY(0);
}
}
#loader-wrapper .load_title {
font-weight: bold;
z-index: 1002;
position: absolute;
top: 50%;
margin-top: 15px;
color: #87888E;
font-size: 18px;
width: 100%;
height: 30px;
text-align: center;
opacity: 0.4;
line-height: 30px;
}
.wwads-cn {
position: fixed !important;
right: 5px;
bottom: 40px;
z-index: 1024;
max-width: 180px;
}
</style>
<script charset="UTF-8" src="https://cdn.wwads.cn/js/makemoney.js" async></script>
</head>
<body data-theme="light">
<div data-sticky-width="100" class="wwads-cn wwads-vertical wwads-sticky" data-id="220"></div>
<div id="app">
<div id="loader-wrapper">
<div class="loader-box">
<span>IOT-SCADA</span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<div class="load_title">加载中…</div>
</div>
</div>
<script type="module" src="/src/iot/main.js"></script>
</body>
</html>
\ No newline at end of file
import {
resolve
} from 'path'
export default {
//打包index.umd.js的配置
publicDir: 'access', // 指定public目录的位置
build: {
cssCodeSplit: true,
lib: {
entry: resolve(__dirname, 'src/index.js'),
name: 'AvueData',
fileName: 'index',
formats: ['umd']
},
outDir: resolve(__dirname, 'public/lib'),
assetsDir: "assets",
minify: 'esbuild',
esbuild: {
// 进一步定制压缩选项
drop: ['console', 'debugger'], // 删除 console 和 debugger 语句
minifyWhitespace: true, // 删除空白字符
minifySyntax: true, // 删除多余的语法
minifyIdentifiers: true, // 压缩变量名
},
rollupOptions: {
external: ['vue', 'axios', '@smallwei/avue','element-plus'],
output: {
compact: true,
globals: {
'element-plus':'ElementPlus',
vue: 'Vue',
'@smallwei/avue': 'AVUE',
axios: 'axios'
},
},
},
},
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "@avue/avue-data",
"version": "3.2.4",
"main": "src/index.js",
"files": [
"lib",
"README.md",
"LICENSE"
],
"scripts": {
"dev": "vite --host",
"build": "vite build",
"serve": "vite preview --host",
"lib": "vite build --mode lib"
},
"dependencies": {
"@avue/avue-draggable": "^0.2.10",
"@element-plus/icons-vue": "^2.3.1",
"@kjgl77/datav-vue3": "^1.5.0",
"@smallwei/avue": "^3.5.6",
"animate.css": "^4.1.1",
"axios": "0.19.0",
"crypto-js": "^4.1.1",
"dayjs": "^1.10.6",
"disable-devtool": "^0.3.8",
"element-plus": "^2.8.2",
"highlight.js": "^11.8.0",
"js-cookie": "^3.0.0",
"lodash": "^4.17.21",
"marked": "^15.0.6",
"mockjs": "^1.1.0",
"monaco-editor": "^0.34.1",
"mqtt": "^5.12.0",
"nprogress": "^0.2.0",
"sql-formatter": "^15.4.9",
"store2": "^2.14.2",
"three": "^0.169.0",
"three-gpu-pathtracer": "^0.0.23",
"vite-plugin-mock": "^2.9.4",
"vue": "^3.4.27",
"vue-3d-model": "v2.0.0-alpha.4",
"vue-i18n": "^9.1.9",
"vue-json-viewer": "3",
"vue-router": "^4.1.5",
"vue3-sketch-ruler": "1.3.7",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"sass": "^1.37.5",
"unplugin-auto-import": "^0.11.2",
"vite": "^4.5.14",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vue-setup-extend": "^0.4.0"
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
How to implement additional commands for undo/redo functionality?
===
### Basics ###
After evaluating different design patterns for undo/redo we decided to use the [command-pattern](http://en.wikipedia.org/wiki/Command_pattern) for implementing undo/redo functionality in the three.js-editor.
This means that every action is encapsulated in a command-object which contains all the relevant information to restore the previous state.
In our implementation we store the old and the new state separately (we don't store the complete state but rather the attribute and value which has changed).
It would also be possible to only store the difference between the old and the new state.
**Before implementing your own command you should look if you can't reuse one of the already existing ones.**
For numbers, strings or booleans the Set...ValueCommand-commands can be used.
Then there are separate commands for:
- setting a color property (THREE.Color)
- setting maps (THREE.Texture)
- setting geometries
- setting materials
- setting position, rotation and scale
### Template for new commands ###
Every command needs a constructor. In the constructor
```javascript
function DoSomethingCommand( editor ) {
Command.call( this, editor ); // Required: Call default constructor
this.type = 'DoSomethingCommand'; // Required: has to match the object-name!
this.name = 'Set/Do/Update Something'; // Required: description of the command, used in Sidebar.History
// TODO: store all the relevant information needed to
// restore the old and the new state
}
```
And as part of the prototype you need to implement four functions
- **execute:** which is also used for redo
- **undo:** which reverts the changes made by 'execute'
- **toJSON:** which serializes the command so that the undo/redo-history can be preserved across a browser refresh
- **fromJSON:** which deserializes the command
```javascript
DoSomethingCommand.prototype = {
execute: function () {
// TODO: apply changes to 'object' to reach the new state
},
undo: function () {
// TODO: restore 'object' to old state
},
toJSON: function () {
var output = Command.prototype.toJSON.call( this ); // Required: Call 'toJSON'-method of prototype 'Command'
// TODO: serialize all the necessary information as part of 'output' (JSON-format)
// so that it can be restored in 'fromJSON'
return output;
},
fromJSON: function ( json ) {
Command.prototype.fromJSON.call( this, json ); // Required: Call 'fromJSON'-method of prototype 'Command'
// TODO: restore command from json
}
};
```
### Executing a command ###
To execute a command we need an instance of the main editor-object. The editor-object functions as the only entry point through which all commands have to go to be added as part of the undo/redo-history.
On **editor** we then call **.execute(...)*** with the new command-object which in turn calls **history.execute(...)** and adds the command to the undo-stack.
```javascript
editor.execute( new DoSomethingCommand() );
```
### Updatable commands ###
Some commands are also **updatable**. By default a command is not updatable. Making a command updatable means that you
have to implement a fifth function 'update' as part of the prototype. In it only the 'new' state gets updated while the old one stays the same.
Here as an example is the update-function of **SetColorCommand**:
```javascript
update: function ( cmd ) {
this.newValue = cmd.newValue;
},
```
#### List of updatable commands
- SetColorCommand
- SetGeometryCommand
- SetMaterialColorCommand
- SetMaterialValueCommand
- SetPositionCommand
- SetRotationCommand
- SetScaleCommand
- SetValueCommand
- SetScriptValueCommand
The idea behind 'updatable commands' is that two commands of the same type which occur
within a short period of time should be merged into one.
**For example:** Dragging with your mouse over the x-position field in the sidebar
leads to hundreds of minor changes to the x-position.
The user expectation is not to undo every single change that happened while they dragged
the mouse cursor but rather to go back to the position before they started to drag their mouse.
When editing a script the changes are also merged into one undo-step.
Writing unit tests for undo-redo commands
===
### Overview ###
Writing unit tests for undo/redo commands is easy.
The main idea to simulate a scene, execute actions and perform undo and redo.
Following steps are required.
1. Create a new unit test file
2. Include the new command and the unit test file in the editor's test suite
3. Write the test
4. Execute the test
Each of the listed steps will now be described in detail.
### 1. Create a new unit test file ###
Create a new file in path `test/unit/editor/TestDoSomethingCommand.js`.
### 2. Include the new command in the editor test suite ###
Navigate to the editor test suite `test/unit/unittests_editor.html` and open it.
Within the file, go to the `<!-- command object classes -->` and include the new command:
```html
// <!-- command object classes -->
//...
<script src="../../editor/js/commands/AddScriptCommand.js"></script>
<script src="../../editor/js/commands/DoSomethingCommand.js"></script> // add this line
<script src="../../editor/js/commands/MoveObjectCommand.js"></script>
//...
```
It is recommended to keep the script inclusions in alphabetical order, if possible.
Next, in the same file, go to `<!-- Undo-Redo tests -->` and include the test file for the new command:
```html
// <!-- Undo-Redo tests -->
//...
<script src="editor/TestAddScriptCommand.js"></script>
<script src="editor/TestDoSomethingCommand.js"></script> // add this line
<script src="editor/TestMoveObjectCommand.js"></script>
//...
```
Again, keeping the alphabetical order is recommended.
### 3. Write the test ###
#### Template ####
Open the unit test file `test/unit/editor/TestDoSomethingCommand.js` and paste following code:
```javascript
module( "DoSomethingCommand" );
test("Test DoSomethingCommand (Undo and Redo)", function() {
var editor = new Editor();
var box = aBox( 'Name your box' );
// other available objects from "CommonUtilities.js"
// var sphere = aSphere( 'Name your sphere' );
// var pointLight = aPointLight( 'Name your pointLight' );
// var perspectiveCamera = aPerspectiveCamera( 'Name your perspectiveCamera' );
// in most cases you'll need to add the object to work with
editor.execute( new AddObjectCommand( editor, box ) );
// your test begins here...
} );
```
The predefined code is just meant to ease the development, you do not have to stick with it.
However, the test should cover at least one `editor.execute()`, one `editor.undo()` and one `editor.redo()` call.
Best practice is to call `editor.execute( new DoSomethingCommand( {custom parameters} ) )` **twice**. Since you'll have to do one undo (go one step back), it is recommended to have a custom state for comparison. Try to avoid assertions `ok()` against default values.
#### Assertions ####
After performing `editor.execute()` twice, you can do your first assertion to check whether the executes are done correctly.
Next, you perform `editor.undo()` and check if the last action was undone.
Finally, perform `editor.redo()` and verify if the values are as expected.
### 4. Execute the test ###
Open the editor's unit test suite `test/unit/unittests_editor.html` in your browser and check the results from the test framework.
{
"metadata": {
"type": "App"
},
"project": {
"shadows": true,
"vr": false
},
"camera": {
"metadata": {
"version": 4.5,
"type": "Object",
"generator": "Object3D.toJSON"
},
"object": {
"uuid": "0C0DD0AD-3A7F-4ECD-A9FE-CECD97D5CBD9",
"type": "PerspectiveCamera",
"name": "Camera",
"layers": 1,
"matrix": [0.939236,0,-0.343272,0,-0.147782,0.902586,-0.404351,0,0.309832,0.430511,0.847741,0,11.713146,19.228675,40.388679,1],
"fov": 50,
"zoom": 1,
"near": 0.1,
"far": 100000,
"focus": 10,
"aspect": 1.428977,
"filmGauge": 35,
"filmOffset": 0
}
},
"scene": {
"metadata": {
"version": 4.5,
"type": "Object",
"generator": "Object3D.toJSON"
},
"geometries": [
{
"uuid": "BBEE74D1-E43D-4C32-A9F3-4656E78C26F3",
"type": "PlaneGeometry",
"width": 30,
"height": 40,
"widthSegments": 1,
"heightSegments": 1
},
{
"uuid": "C1722F5F-89AD-45D8-B78C-D1D34AF2A012",
"type": "BoxGeometry",
"width": 2,
"height": 1,
"depth": 1,
"widthSegments": 1,
"heightSegments": 1,
"depthSegments": 1
},
{
"uuid": "327EFFCF-649C-4EF3-86D4-B422C5A86E89",
"type": "CylinderGeometry",
"radiusTop": 0.5,
"radiusBottom": 0.5,
"height": 2,
"radialSegments": 32,
"heightSegments": 1,
"openEnded": false
},
{
"uuid": "0791211B-BB02-4E57-82B5-64C05DE92B39",
"type": "SphereGeometry",
"radius": 0.5,
"widthSegments": 32,
"heightSegments": 16,
"phiStart": 0,
"phiLength": 6.28,
"thetaStart": 0,
"thetaLength": 3.14
},
{
"uuid": "73F12A47-9EA7-47FD-BCF3-89B8219B2626",
"type": "BoxGeometry",
"width": 2,
"height": 1,
"depth": 1,
"widthSegments": 1,
"heightSegments": 1,
"depthSegments": 1
},
{
"uuid": "3BDEB9FB-BDD4-44AD-8A47-008BED1C8982",
"type": "CylinderGeometry",
"radiusTop": 0.5,
"radiusBottom": 0.5,
"height": 2,
"radialSegments": 32,
"heightSegments": 1,
"openEnded": false
}],
"materials": [
{
"uuid": "2F69AF3A-DDF5-4BBA-87B5-80159F90DDBF",
"type": "MeshPhongMaterial",
"color": 86015,
"emissive": 0,
"specular": 1118481,
"shininess": 30,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true
},
{
"uuid": "D98FC4D1-169E-420A-92EA-20E55009A46D",
"type": "MeshBasicMaterial",
"color": 63744,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true,
"wireframe": true
},
{
"uuid": "3B9DE64D-E1C8-4C24-9F73-3A9E10E3E655",
"type": "MeshPhongMaterial",
"color": 16777215,
"emissive": 0,
"specular": 1118481,
"shininess": 30,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true
},
{
"uuid": "043B208C-1F83-42C6-802C-E0E35621C27C",
"type": "MeshPhongMaterial",
"color": 16777215,
"emissive": 0,
"specular": 1118481,
"shininess": 30,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true
},
{
"uuid": "40EC9BDA-91C0-4671-937A-2BCB6DA7EEBB",
"type": "MeshBasicMaterial",
"color": 63744,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true,
"wireframe": true
}],
"object": {
"uuid": "31517222-A9A7-4EAF-B5F6-60751C0BABA3",
"type": "Scene",
"name": "Scene",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
"children": [
{
"uuid": "EBBB1E63-6318-4752-AE2E-440A4E0B3EF3",
"type": "Mesh",
"name": "Ground",
"layers": 1,
"matrix": [1,0,0,0,0,0.000796,-1,0,0,1,0.000796,0,0,0,0,1],
"geometry": "BBEE74D1-E43D-4C32-A9F3-4656E78C26F3",
"material": "2F69AF3A-DDF5-4BBA-87B5-80159F90DDBF"
},
{
"uuid": "6EE2E764-43E0-48E0-85F2-E0C8823C20DC",
"type": "DirectionalLight",
"name": "DirectionalLight 1",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,10,20,15,1],
"color": 16777215,
"intensity": 1,
"shadow": {
"camera": {
"uuid": "3BC010F7-9766-4087-BA04-1D4FD7721ABA",
"type": "OrthographicCamera",
"layers": 1,
"zoom": 1,
"left": -5,
"right": 5,
"top": 5,
"bottom": -5,
"near": 0.5,
"far": 500
}
}
},
{
"uuid": "38219749-1E67-45F2-AB15-E64BA0940CAD",
"type": "Mesh",
"name": "Brick",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0.5,0,1],
"geometry": "C1722F5F-89AD-45D8-B78C-D1D34AF2A012",
"material": "D98FC4D1-169E-420A-92EA-20E55009A46D",
"children": [
{
"uuid": "711A5955-8F17-4A8B-991A-7604D27E6FA0",
"type": "Mesh",
"name": "Cylinder",
"layers": 1,
"matrix": [0.000795,0.000795,1,0,-1.000001,-0.000001,0.000795,0,0.000001,-1.000001,0.000795,0,0,0,0,1],
"geometry": "327EFFCF-649C-4EF3-86D4-B422C5A86E89",
"material": "3B9DE64D-E1C8-4C24-9F73-3A9E10E3E655"
}]
},
{
"uuid": "18FFA67C-F893-4E7A-8A76-8D996DEBE0C6",
"type": "Mesh",
"name": "Ball",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0.5,3.55,1],
"geometry": "0791211B-BB02-4E57-82B5-64C05DE92B39",
"material": "043B208C-1F83-42C6-802C-E0E35621C27C"
},
{
"uuid": "6D660D49-39B8-40C3-95F6-E4E007AA8D79",
"type": "Mesh",
"name": "Paddle",
"layers": 1,
"matrix": [2,0,0,0,0,1,0,0,0,0,1,0,0,0.5,15.95,1],
"geometry": "73F12A47-9EA7-47FD-BCF3-89B8219B2626",
"material": "40EC9BDA-91C0-4671-937A-2BCB6DA7EEBB",
"children": [
{
"uuid": "4F5F884C-9E1B-45E6-8F1E-4D538A46D8CB",
"type": "Mesh",
"name": "Cylinder",
"layers": 1,
"matrix": [0.000795,0.000795,1,0,-1.000001,-0.000001,0.000795,0,0.000001,-1.000001,0.000795,0,0,0,0,1],
"geometry": "3BDEB9FB-BDD4-44AD-8A47-008BED1C8982",
"material": "3B9DE64D-E1C8-4C24-9F73-3A9E10E3E655"
}]
},
{
"uuid": "B0BEAF69-8B5D-4D87-ADCA-FDE83A02762D",
"type": "PointLight",
"name": "PointLight 2",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,-11.65,6.949,-20.682,1],
"color": 16777215,
"intensity": 1,
"distance": 0,
"decay": 1,
"shadow": {
"camera": {
"uuid": "2F0DA21A-EFB8-4E9A-83C5-A601D6113780",
"type": "PerspectiveCamera",
"layers": 1,
"fov": 90,
"zoom": 1,
"near": 0.5,
"far": 500,
"focus": 10,
"aspect": 1,
"filmGauge": 35,
"filmOffset": 0
}
}
}],
"background": 11184810
}
},
"scripts": {
"6D660D49-39B8-40C3-95F6-E4E007AA8D79": [
{
"name": "User",
"source": "function pointermove( event ) {\n\n\tthis.position.x = ( event.clientX / player.width ) * 30 - 15;\n\n}\n\n// function update( event ) {}"
}],
"31517222-A9A7-4EAF-B5F6-60751C0BABA3": [
{
"name": "Game Logic",
"source": "var ball = this.getObjectByName( 'Ball' );\n\nvar direction = new THREE.Vector3();\ndirection.x = Math.random() - 0.5;\ndirection.z = - 0.5;\ndirection.normalize();\n\nvar speed = new THREE.Vector3();\n\n//\n\nvar group = new THREE.Group();\nthis.add( group );\n\nvar paddle = this.getObjectByName( 'Paddle' );\npaddle.material.visible = false;\ngroup.add( paddle );\n\nvar brick = this.getObjectByName( 'Brick' );\n\nfor ( var j = 0; j < 8; j ++ ) {\n\n\tvar material = new THREE.MeshPhongMaterial( { color: Math.random() * 0xffffff } );\n\n\tfor ( var i = 0; i < 12; i ++ ) {\n\t\t\n\t\tvar object = brick.clone();\n\t\tobject.position.x = i * 2.2 - 12;\n\t\tobject.position.z = j * 1.4 - 12;\n\t\tgroup.add( object );\n\n\t\tvar cylinder = object.getObjectByName( 'Cylinder' );\n\t\tcylinder.material = material;\n\n\t}\n\t\n}\n\nbrick.visible = false;\nbrick.material.visible = false;\n\n//\n\nvar raycaster = new THREE.Raycaster();\n\nfunction update( event ) {\n\t\n\tif ( ball.position.x < - 15 || ball.position.x > 15 ) direction.x = - direction.x;\n\tif ( ball.position.z < - 20 || ball.position.z > 20 ) direction.z = - direction.z;\n\n\tball.position.x = Math.max( - 15, Math.min( 15, ball.position.x ) );\n\tball.position.z = Math.max( - 20, Math.min( 20, ball.position.z ) );\n\t\t\n\traycaster.set( ball.position, direction );\n\t\n\tvar intersections = raycaster.intersectObjects( group.children );\n\t\n\tif ( intersections.length > 0 ) {\n\t\n\t\tvar intersection = intersections[ 0 ];\n\t\t\n\t\tif ( intersection.distance < 0.5 ) {\n\t\t\t\n\t\t\tif ( intersection.object !== paddle ) {\n\n\t\t\t\tgroup.remove( intersection.object );\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tdirection.reflect( intersection.face.normal );\n\t\t\t\n\t\t}\n\t\t\n\t}\n\n\tball.position.add( speed.copy( direction ).multiplyScalar( event.delta / 40 ) );\n\t\n}"
}]
}
}
{
"metadata": {
"type": "App"
},
"project": {
"shadows": true,
"vr": false
},
"camera": {
"metadata": {
"version": 4.5,
"type": "Object",
"generator": "Object3D.toJSON"
},
"object": {
"uuid": "60EBAF60-53DA-47B0-A028-8FC031B708F6",
"type": "PerspectiveCamera",
"name": "Camera",
"layers": 1,
"matrix": [0.970041,0,-0.242943,0,-0.048226,0.980099,-0.192562,0,0.238108,0.198509,0.950736,0,1.548,1.29,6.18,1],
"fov": 50,
"zoom": 1,
"near": 0.1,
"far": 100000,
"focus": 10,
"aspect": 1.428977,
"filmGauge": 35,
"filmOffset": 0
}
},
"scene": {
"metadata": {
"version": 4.5,
"type": "Object",
"generator": "Object3D.toJSON"
},
"geometries": [
{
"uuid": "6D90C4BE-EBA6-4E21-8F54-7CFDAA61F30B",
"type": "PlaneGeometry",
"width": 10,
"height": 10,
"widthSegments": 1,
"heightSegments": 1
},
{
"uuid": "D3008B2A-ACDD-43CC-87F7-4F942607D21A",
"type": "BoxGeometry",
"width": 1,
"height": 1,
"depth": 1,
"widthSegments": 1,
"heightSegments": 1,
"depthSegments": 1
},
{
"uuid": "F482ACD4-013A-49CF-AE0F-C9FF4ADAE409",
"type": "CylinderGeometry",
"radiusTop": 0,
"radiusBottom": 0.4,
"height": 0.75,
"radialSegments": 4,
"heightSegments": 1,
"openEnded": false
},
{
"uuid": "51CDDCED-BC71-4B1B-A485-725B6A48204B",
"type": "IcosahedronGeometry",
"radius": 0.4,
"detail": 2
}],
"materials": [
{
"uuid": "4AE8130E-B6A8-47BC-ACCF-060973C74044",
"type": "MeshPhongMaterial",
"color": 16777215,
"emissive": 0,
"specular": 1118481,
"shininess": 30,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true
},
{
"uuid": "B5943856-E404-45D9-A427-4774202C2CD0",
"type": "MeshPhongMaterial",
"color": 37119,
"emissive": 0,
"specular": 1118481,
"shininess": 30,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true
},
{
"uuid": "3F872310-2067-4BE4-9250-5B3F4E43797E",
"type": "MeshPhongMaterial",
"color": 15859456,
"emissive": 0,
"specular": 1118481,
"shininess": 30,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true
},
{
"uuid": "E1826901-7922-4584-A25D-6D487E2C9BBD",
"type": "MeshPhongMaterial",
"color": 16711680,
"emissive": 0,
"specular": 1118481,
"shininess": 30,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true
}],
"object": {
"uuid": "3741222A-BD8F-401C-A5D2-5A907E891896",
"type": "Scene",
"name": "Scene",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
"children": [
{
"uuid": "B7CBBC6F-EC26-49B5-8D0D-67D9C535924B",
"type": "Group",
"name": "Dummy",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,1,4,1],
"children": [
{
"uuid": "60B69C58-4201-43FD-815E-AD2EDFBBD0CE",
"type": "PerspectiveCamera",
"name": "PerspectiveCamera",
"layers": 1,
"matrix": [-1,0,0,0,0,1,0,0,0,0,-1,0,0,0,0,1],
"fov": 50,
"zoom": 1,
"near": 0.1,
"far": 100,
"focus": 10,
"aspect": 1,
"filmGauge": 35,
"filmOffset": 0
}]
},
{
"uuid": "A460C230-DC88-4A8F-A3FB-AA0FE735F3ED",
"type": "Mesh",
"name": "Plane",
"layers": 1,
"matrix": [1,0,0,0,0,0.040785,-0.999168,0,0,0.999168,0.040785,0,0,-0.5,0,1],
"geometry": "6D90C4BE-EBA6-4E21-8F54-7CFDAA61F30B",
"material": "4AE8130E-B6A8-47BC-ACCF-060973C74044"
},
{
"uuid": "26DAAD69-725D-43B7-AF9D-990A99DEF8C5",
"type": "Mesh",
"name": "Box",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
"geometry": "D3008B2A-ACDD-43CC-87F7-4F942607D21A",
"material": "B5943856-E404-45D9-A427-4774202C2CD0"
},
{
"uuid": "AAAFF2D6-4725-4AFC-A9FE-26419B11011F",
"type": "Mesh",
"name": "Cylinder",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,-1.3,-0.15,0,1],
"geometry": "F482ACD4-013A-49CF-AE0F-C9FF4ADAE409",
"material": "3F872310-2067-4BE4-9250-5B3F4E43797E"
},
{
"uuid": "B855E267-A266-4098-ACD6-6A1FDE7B88BA",
"type": "Mesh",
"name": "Icosahedron",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,1.3,-0.1,0,1],
"geometry": "51CDDCED-BC71-4B1B-A485-725B6A48204B",
"material": "E1826901-7922-4584-A25D-6D487E2C9BBD"
},
{
"uuid": "E2939A7B-5E40-438A-8C1B-32126FBC6892",
"type": "PointLight",
"name": "PointLight 1",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,-0.939,1.271,-1.143,1],
"color": 9474221,
"intensity": 0.75,
"distance": 0,
"decay": 1,
"shadow": {
"camera": {
"uuid": "EFF42F46-1E27-4B36-B9D9-CF7D879D258E",
"type": "PerspectiveCamera",
"layers": 1,
"fov": 90,
"zoom": 1,
"near": 0.5,
"far": 500,
"focus": 10,
"aspect": 1,
"filmGauge": 35,
"filmOffset": 0
}
}
},
{
"uuid": "3412781E-27CC-43C3-A5DB-54C0C8E42ED6",
"type": "PointLight",
"name": "PointLight 2",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0.881,0.083,1.254,1],
"color": 12773063,
"intensity": 1,
"distance": 0,
"decay": 1,
"shadow": {
"camera": {
"uuid": "81E800FE-E8A7-4A9E-AFAA-4F04FD56AFE4",
"type": "PerspectiveCamera",
"layers": 1,
"fov": 90,
"zoom": 1,
"near": 0.5,
"far": 500,
"focus": 10,
"aspect": 1,
"filmGauge": 35,
"filmOffset": 0
}
}
}],
"background": 11184810
}
},
"scripts": {
"60B69C58-4201-43FD-815E-AD2EDFBBD0CE": [
{
"name": "Player Camera",
"source": "player.setCamera( this );"
}],
"B7CBBC6F-EC26-49B5-8D0D-67D9C535924B": [
{
"name": "Orbit",
"source": "function update( event ) {\n\n\tvar time = event.time * 0.001;\n\n\tthis.position.x = Math.sin( time ) * 4;\n\tthis.position.z = Math.cos( time ) * 4;\n\tthis.lookAt( scene.position );\n\n}"
}]
}
}
{
"metadata": {
"type": "App"
},
"project": {
"shadows": true,
"vr": false
},
"camera": {
"metadata": {
"version": 4.5,
"type": "Object",
"generator": "Object3D.toJSON"
},
"object": {
"uuid": "056199EB-6985-481B-97CC-A57FB7C87809",
"type": "PerspectiveCamera",
"name": "Camera",
"layers": 1,
"matrix": [0.707107,0,-0.707107,0,-0.235702,0.942809,-0.235702,0,0.666667,0.333333,0.666667,0,4.182,2.091,4.182,1],
"fov": 50,
"zoom": 1,
"near": 0.1,
"far": 100000,
"focus": 10,
"aspect": 0.666193,
"filmGauge": 35,
"filmOffset": 0
}
},
"scene": {
"metadata": {
"version": 4.5,
"type": "Object",
"generator": "Object3D.toJSON"
},
"geometries": [
{
"uuid": "C3C0CE7D-10B8-43FC-8F74-011CC6E57800",
"type": "PlaneGeometry",
"width": 100,
"height": 100,
"widthSegments": 1,
"heightSegments": 1
}],
"materials": [
{
"uuid": "3A9449D2-62DB-4BB4-ABBD-6F3F9D46DE1A",
"type": "MeshStandardMaterial",
"color": 5465019,
"roughness": 1,
"metalness": 0,
"emissive": 0,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true
},
{
"uuid": "F5361474-F5F1-412F-8D99-3699B868092D",
"type": "SpriteMaterial",
"color": 16777215,
"transparent": true,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true
}],
"object": {
"uuid": "3741222A-BD8F-401C-A5D2-5A907E891896",
"type": "Scene",
"name": "Scene",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
"children": [
{
"uuid": "05B57416-1BE5-4A96-BB05-9D9CD112D52B",
"type": "Mesh",
"name": "Ground",
"layers": 1,
"matrix": [1,0,0,0,0,0.000796,-1,0,0,1,0.000796,0,0,-0.5,0,1],
"geometry": "C3C0CE7D-10B8-43FC-8F74-011CC6E57800",
"material": "3A9449D2-62DB-4BB4-ABBD-6F3F9D46DE1A"
},
{
"uuid": "0A3CB873-07E6-4EEB-830B-68192504111B",
"type": "Sprite",
"name": "Particle",
"layers": 1,
"matrix": [0.04,0,0,0,0,0.04,0,0,0,0,0.04,0,0,0,0,1],
"material": "F5361474-F5F1-412F-8D99-3699B868092D"
},
{
"uuid": "40E5CDA4-0E39-4265-9293-3E9EC3207F61",
"type": "PointLight",
"name": "PointLight",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,1.183,0,1],
"color": 16777215,
"intensity": 1,
"distance": 0,
"decay": 1,
"shadow": {
"camera": {
"uuid": "B6D3493E-E5C9-4D65-9E26-BB788D127BE1",
"type": "PerspectiveCamera",
"layers": 1,
"fov": 90,
"zoom": 1,
"near": 0.5,
"far": 500,
"focus": 10,
"aspect": 1,
"filmGauge": 35,
"filmOffset": 0
}
}
}],
"background": 2171689,
"fog": {
"type": "Fog",
"color": 2171688,
"near": 1,
"far": 50
}
}
},
"scripts": {
"3741222A-BD8F-401C-A5D2-5A907E891896": [
{
"name": "Fountain",
"source": "var original = this.getObjectByName( 'Particle' );\n\nvar particles = [];\n\nfor ( var i = 0; i < 100; i ++ ) {\n\n\tvar particle = original.clone();\n\tparticle.userData.velocity = new THREE.Vector3();\n\tthis.add( particle );\n\n\tparticles.push( particle );\n\n}\n\nfunction update( event ) {\n\t\n\tvar particle = particles.shift();\n\tparticles.push( particle );\n\t\t\n\tvar velocity = particle.userData.velocity;\n\tvelocity.x = Math.random() * 0.1 - 0.05;\n\tvelocity.y = Math.random() * 0.1 + 0.1;\n\tvelocity.z = Math.random() * 0.1 - 0.05;\n\n\tfor ( var i = 0; i < particles.length; i ++ ) {\n\n\t\tvar particle = particles[ i ];\n\n\t\tvar velocity = particle.userData.velocity;\n\n\t\tvelocity.y -= 0.0098;\n\n\t\tparticle.position.add( velocity );\n\n\t\tif ( particle.position.y < 0 ) {\n\n\t\t\tparticle.position.y = 0;\n\n\t\t\tvelocity.y = - velocity.y;\n\t\t\tvelocity.multiplyScalar( 0.6 );\n\n\t\t}\n\n\t}\n\n}"
}]
}
}
{
"metadata": {
"type": "App"
},
"project": {
"shadows": true,
"vr": false
},
"camera": {
"metadata": {
"version": 4.5,
"type": "Object",
"generator": "Object3D.toJSON"
},
"object": {
"uuid": "4AC7ADED-CC22-4B16-8218-2E0A0C38C8F8",
"type": "PerspectiveCamera",
"name": "Camera",
"layers": 1,
"matrix": [0.952212,0,-0.305438,0,-0.17743,0.813973,-0.553142,0,0.248618,0.580902,0.775075,0,1.865,4.357,5.813,1],
"fov": 50,
"zoom": 1,
"near": 0.1,
"far": 100000,
"focus": 10,
"aspect": 1.428977,
"filmGauge": 35,
"filmOffset": 0
}
},
"scene": {
"metadata": {
"version": 4.5,
"type": "Object",
"generator": "Object3D.toJSON"
},
"geometries": [
{
"uuid": "490CEBA3-6A25-4BE1-B517-C5FB11A5D18A",
"type": "PlaneGeometry",
"width": 6,
"height": 4,
"widthSegments": 1,
"heightSegments": 1
},
{
"uuid": "D9A92F2D-2F08-4851-99C7-12D8D1CA13C7",
"type": "BoxGeometry",
"width": 0.1,
"height": 0.1,
"depth": 0.1,
"widthSegments": 1,
"heightSegments": 1,
"depthSegments": 1
},
{
"uuid": "5E63B8CF-E225-4ABC-994A-4D06BD4E21EB",
"type": "BoxGeometry",
"width": 0.2,
"height": 0.2,
"depth": 1,
"widthSegments": 1,
"heightSegments": 1,
"depthSegments": 1
},
{
"uuid": "D61532B4-24C3-4BC4-B56B-7245E8163E09",
"type": "BoxGeometry",
"width": 0.2,
"height": 0.2,
"depth": 1,
"widthSegments": 1,
"heightSegments": 1,
"depthSegments": 1
}],
"materials": [
{
"uuid": "7EDF7C08-6325-418A-BBAB-89341C694730",
"type": "MeshPhongMaterial",
"color": 16777215,
"emissive": 0,
"specular": 16777215,
"shininess": 30,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true
},
{
"uuid": "B1CAF098-FE36-45E1-BEBE-8D6AC04821CC",
"type": "MeshPhongMaterial",
"color": 16711680,
"emissive": 0,
"specular": 1118481,
"shininess": 30,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true
},
{
"uuid": "FBDBE66D-B613-4741-802D-5AE1DE07DE46",
"type": "MeshPhongMaterial",
"color": 2752767,
"emissive": 0,
"specular": 1118481,
"shininess": 30,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true
}],
"object": {
"uuid": "31517222-A9A7-4EAF-B5F6-60751C0BABA3",
"type": "Scene",
"name": "Scene",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
"children": [
{
"uuid": "B47D0BFC-D63A-4CBB-985E-9C4DBDF086E4",
"type": "Mesh",
"name": "Ground",
"layers": 1,
"matrix": [1,0,0,0,0,0.000796,-1,0,0,1,0.000796,0,0,-0.1,0,1],
"geometry": "490CEBA3-6A25-4BE1-B517-C5FB11A5D18A",
"material": "7EDF7C08-6325-418A-BBAB-89341C694730"
},
{
"uuid": "CE13E58A-4E8B-4F72-9E2E-7DE57C58F989",
"type": "Mesh",
"name": "Ball",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
"geometry": "D9A92F2D-2F08-4851-99C7-12D8D1CA13C7",
"material": "B1CAF098-FE36-45E1-BEBE-8D6AC04821CC"
},
{
"uuid": "2AAEA3AA-EC45-492B-B450-10473D1EC6C5",
"type": "Mesh",
"name": "Pad 1",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,-2.4,0,0,1],
"geometry": "5E63B8CF-E225-4ABC-994A-4D06BD4E21EB",
"material": "FBDBE66D-B613-4741-802D-5AE1DE07DE46"
},
{
"uuid": "F1DD46A7-6584-4A37-BC76-852C3911077E",
"type": "Mesh",
"name": "Pad 2",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,2.4,0,0,1],
"geometry": "D61532B4-24C3-4BC4-B56B-7245E8163E09",
"material": "FBDBE66D-B613-4741-802D-5AE1DE07DE46"
},
{
"uuid": "C62AAE9F-9E51-46A5-BD2B-71BA804FC0B3",
"type": "DirectionalLight",
"name": "DirectionalLight",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,1,2,1.5,1],
"color": 16777215,
"intensity": 1,
"shadow": {
"camera": {
"uuid": "2CF1F42A-8992-4E8D-8D94-7CC20979344C",
"type": "OrthographicCamera",
"layers": 1,
"zoom": 1,
"left": -5,
"right": 5,
"top": 5,
"bottom": -5,
"near": 0.5,
"far": 500
}
}
}],
"background": 11184810
}
},
"scripts": {
"31517222-A9A7-4EAF-B5F6-60751C0BABA3": [
{
"name": "Game logic",
"source": "var ball = this.getObjectByName( 'Ball' );\n\nvar position = ball.position;\n\nvar velocity = new THREE.Vector3();\n\nvar direction = new THREE.Vector3();\ndirection.x = Math.random() - 0.5;\ndirection.z = Math.random() - 0.5;\ndirection.normalize().multiplyScalar( 0.1 );\n\nvar pad1 = this.getObjectByName( 'Pad 1' );\nvar pad2 = this.getObjectByName( 'Pad 2' );\n\nvar raycaster = new THREE.Raycaster();\nvar objects = [ pad1, pad2 ];\n\n//\n\nfunction pointermove( event ) {\n\n\tpad1.position.z = ( event.clientX / player.width ) * 3 - 1.5;\n\tpad2.position.z = - pad1.position.z;\n\n}\n\nfunction update( event ) {\n\t\n\tif ( position.x < -3 || position.x > 3 ) direction.x = - direction.x;\n\tif ( position.z < -2 || position.z > 2 ) direction.z = - direction.z;\n\t\n\tposition.x = Math.max( - 3, Math.min( 3, position.x ) );\n\tposition.z = Math.max( - 2, Math.min( 2, position.z ) );\n\t\n\traycaster.set( position, direction );\n\t\n\tvar intersections = raycaster.intersectObjects( objects );\n\t\n\tif ( intersections.length > 0 ) {\n\n\t\tvar intersection = intersections[ 0 ];\n\t\t\n\t\tif ( intersection.distance < 0.1 ) {\n\t\t\t\n\t\t\tdirection.reflect( intersection.face.normal );\n\t\t\t\n\t\t}\n\t\t\n\t}\n\n\tposition.add( velocity.copy( direction ).multiplyScalar( event.delta / 20 ) );\n\n}"
}]
}
}
{
"metadata": {
"type": "App"
},
"project": {
"shadows": true,
"vr": false
},
"camera": {
"metadata": {
"version": 4.5,
"type": "Object",
"generator": "Object3D.toJSON"
},
"object": {
"uuid": "4AC7ADED-CC22-4B16-8218-2E0A0C38C8F8",
"type": "PerspectiveCamera",
"name": "Camera",
"layers": 1,
"matrix": [0.605503,0,-0.795843,0,-0.261526,0.944464,-0.198978,0,0.751645,0.328615,0.571876,0,2.571484,1.124239,1.956469,1],
"fov": 50,
"zoom": 1,
"near": 0.1,
"far": 10000,
"focus": 10,
"aspect": 1.428977,
"filmGauge": 35,
"filmOffset": 0
}
},
"scene": {
"metadata": {
"version": 4.5,
"type": "Object",
"generator": "Object3D.toJSON"
},
"geometries": [
{
"uuid": "EA781333-F3AE-470D-9110-A9724FCB42AA",
"type": "IcosahedronGeometry",
"radius": 1,
"detail": 24
}],
"materials": [
{
"uuid": "50ED51F1-DEA4-4B61-8082-BF41609E8C27",
"type": "ShaderMaterial",
"depthFunc": 3,
"depthTest": true,
"depthWrite": true,
"wireframe": true,
"uniforms": {
"time": {
"value": 0
}
},
"vertexShader": "uniform float time;\nvarying vec3 vPosition;\nvoid main() {\n\tvPosition = position;\n\tvPosition.x += sin( time + vPosition.z * 4.0 ) / 4.0;\n\tvPosition.y += cos( time + vPosition.z * 4.0 ) / 4.0;\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( vPosition, 1.0 );\n}",
"fragmentShader": "varying vec3 vPosition;\nvoid main() {\n\tgl_FragColor = vec4( vPosition * 2.0, 1.0 );\n}"
}],
"object": {
"uuid": "5FC9ACA9-2A93-474D-AA32-FACC76551914",
"type": "Scene",
"name": "Scene",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
"children": [
{
"uuid": "FC7B6CF2-6386-4F47-9CE6-8ADB9FCA6E1F",
"type": "Mesh",
"name": "Icosahedron 1",
"layers": 1,
"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
"geometry": "EA781333-F3AE-470D-9110-A9724FCB42AA",
"material": "50ED51F1-DEA4-4B61-8082-BF41609E8C27"
}],
"background": 11184810
}
},
"scripts": {
"FC7B6CF2-6386-4F47-9CE6-8ADB9FCA6E1F": [
{
"name": "",
"source": "function update( event ) {\n\n\tthis.material.uniforms.time.value = event.time / 500.0;\n\n}"
}]
}
}
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<path d="m247.8 474.12c61.315 0 112.09-26.692 147.65-59.495l-1.6947 75.58c-0.17697 10.509 8.2063 19.178 18.721 19.349h0.32318c10.364 0 18.855-8.3198 19.033-18.721l2.5382-126.18c0.0827-5.1592-1.9294-10.135-5.5843-13.784-3.6492-3.6492-8.5987-5.6603-13.784-5.5718l-126.73 2.5383c-10.509 0.18371-18.885 8.8469-18.708 19.362 0.17794 10.401 8.6689 18.714 19.032 18.714h0.32991l83.195-1.7957c-29.35 27.815-72.656 51.93-124.33 51.93-105.91 0-177.05-91.548-177.05-177.05-0.31704-10.248-6.2951-19.251-18.532-19.479-12.237-0.22764-19.196 8.1831-19.543 19.486 0 103.89 86.446 215.13 215.13 215.13zm4.3538-435.25c-61.315 0-112.09 26.692-147.65 59.495l1.6947-75.579c0.17698-10.509-8.2063-19.178-18.721-19.349h-0.32317c-10.364 0-18.855 8.3198-19.033 18.721l-2.5383 126.18c-0.08272 5.1592 1.9294 10.135 5.5844 13.784 3.6492 3.6492 8.5987 5.6603 13.784 5.5719l126.73-2.5383c10.509-0.18371 18.885-8.8469 18.708-19.362-0.17794-10.401-8.6689-18.714-19.032-18.714h-0.32991l-83.195 1.7957c29.35-27.815 72.656-51.93 124.33-51.93 105.91 0 177.05 91.548 177.05 177.05 0.31703 10.248 6.2951 19.251 18.532 19.479 12.237 0.22763 19.196-8.1831 19.543-19.486 0-103.89-86.446-215.13-215.13-215.13z"/>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<path d="m450.25 44.182-133.13 1.1699c-9.691 0.076-17.482 7.9849-17.406 17.67 0.076 9.638 27.155 17.4 17.535 17.4h0.1289l90.35-0.84375-116.88 116.87c-6.85 6.844-6.85 17.949 0 24.793 3.425 3.425 7.9075 5.1387 12.396 5.1387 4.483 0 8.9734-1.7137 12.398-5.1387l116.87-116.88-0.84179 90.344c-0.07 9.685 7.7193 17.594 17.404 17.67h0.12891c9.62 0 17.459-7.7624 17.535-17.4l1.168-133.13c1.3e-4 -0.01637-2e-3 -0.03246-2e-3 -0.04883 3e-3 -0.51471-0.0239-1.0297-0.0664-1.543-4e-3 -0.05325-9.6e-4 -0.10695-6e-3 -0.16016-2.4e-4 -0.0027-2e-3 -0.0051-2e-3 -0.0078-0.26224-2.8345-1.2132-5.6145-2.8516-8.0742-0.31876-0.47858-0.66235-0.94631-1.0332-1.3984-0.36831-0.45029-0.76079-0.88383-1.1758-1.2988-0.41658-0.41735-0.85218-0.81156-1.3047-1.1816-0.24176-0.19804-0.49656-0.36957-0.7461-0.55273-0.21514-0.15762-0.42293-0.32538-0.64453-0.47266-0.35614-0.23723-0.72494-0.4459-1.0938-0.6543-0.12574-0.07096-0.24567-0.15292-0.37304-0.2207-0.42481-0.22631-0.86059-0.42442-1.2988-0.61328-0.0824-0.03554-0.16117-0.07901-0.24414-0.11328-0.42706-0.17629-0.86181-0.32571-1.2988-0.4668-0.10761-0.03483-0.21201-0.07858-0.32031-0.11133-0.37604-0.11341-0.75724-0.19952-1.1387-0.28711-0.18044-0.04161-0.35715-0.09494-0.53906-0.13086-0.30867-0.06068-0.62071-0.09471-0.93164-0.13867-0.25679-0.03651-0.51062-0.0862-0.76953-0.11133-0.56319-0.0544-1.128-0.08394-1.6934-0.08398zm-387.45 255.62c-9.62 0-17.459 7.7663-17.535 17.404l-1.1699 133.12c-1.22e-4 0.0164 2e-3 0.0325 2e-3 0.0488-0.0025 0.51395 0.02395 1.0285 0.06641 1.541 0.0046 0.0557 0.0026 0.11233 0.0078 0.16797 0.31299 3.3855 1.6059 6.695 3.8809 9.4727 0.3666 0.44761 0.75628 0.88287 1.1738 1.3008h2e-3c0.20731 0.20728 0.42966 0.38925 0.64453 0.58398 0.21795 0.19755 0.42764 0.40626 0.6543 0.5918 0.29789 0.24417 0.61028 0.46004 0.91992 0.68164 0.15919 0.11373 0.31004 0.23952 0.47266 0.34766 0.37454 0.24954 0.76185 0.46983 1.1504 0.6875 0.10897 0.061 0.21208 0.1328 0.32227 0.1914 0.38726 0.20616 0.78506 0.38353 1.1836 0.5586 0.12552 0.0552 0.24628 0.12161 0.37305 0.17382 0.36908 0.15187 0.74642 0.27673 1.123 0.40235 0.16645 0.0556 0.32796 0.12318 0.49609 0.17383 0.46956 0.14111 0.94417 0.25454 1.4219 0.35547 0.07285 0.0154 0.14372 0.0382 0.2168 0.0527 0.4682 0.0927 0.94102 0.15666 1.4141 0.21094 0.09821 0.0113 0.19446 0.0313 0.29297 0.041 0.57076 0.0558 1.6783-0.48578 1.7168 0.0859h0.13477l133.13-1.1699c9.685-0.07 17.476-7.9831 17.4-17.662-0.076-9.69-8.1259-17.387-17.67-17.404l-90.338 0.83996 116.87-116.87c6.844-6.844 6.85-17.943 0-24.793s-17.955-6.85-24.799 0l-116.87 116.86 0.84375-90.344c0.076-9.679-7.7154-17.586-17.4-17.662z"/>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<path d="m256 0c-1.2452 0-2.4586 0.12791-3.6387 0.35156-0.0659 0.012567-0.13349 0.018008-0.19922 0.03125-0.57894 0.11595-1.146 0.26357-1.7051 0.42969-0.047 0.01399-0.0957 0.022767-0.14258 0.037109-1.8151 0.55436-3.5159 1.3682-5.0625 2.3965-0.0312 0.020703-0.0646 0.037706-0.0957 0.058594-0.32576 0.21931-0.63201 0.46307-0.94336 0.70117-0.70605 0.53787-1.3906 1.1059-2.0234 1.7422l-75.18 75.828c-7.563 7.628-7.5048 19.937 0.11718 27.5 7.628 7.557 19.941 7.5048 27.498-0.11719l41.932-42.291v169.89h-169.88l42.295-41.926c7.622-7.563 7.6782-19.87 0.11523-27.498s-19.876-7.6802-27.498-0.11719l-75.828 75.18c-0.00297 3e-3 -0.00485 7e-3 -0.00781 0.01-2.2463 2.2302-3.9501 5.0041-4.8984 8.1094-0.004708 0.0154-0.007049 0.0315-0.011719 0.0469-0.17839 0.58935-0.3326 1.1892-0.45508 1.8008-0.013248 0.0657-0.018677 0.13332-0.03125 0.19922-0.10655 0.56223-0.19883 1.1288-0.25586 1.707-0.062671 0.63522-0.095706 1.2794-0.095706 1.9314s0.033035 1.2962 0.095703 1.9316c0.057025 0.57824 0.14931 1.1448 0.25586 1.707 0.012572 0.0659 0.018003 0.13349 0.03125 0.19922 0.11595 0.57894 0.26357 1.146 0.42969 1.7051 0.014761 0.0496 0.023909 0.10093 0.039062 0.15039 0.56503 1.8473 1.396 3.5783 2.4512 5.1465 0.35511 0.52775 0.73293 1.0381 1.1367 1.5274 0.00265 3e-3 0.00516 7e-3 0.00781 0.01 0.067158 0.0815 0.14629 0.15391 0.21484 0.23438 0.33891 0.39681 0.68535 0.78801 1.0547 1.1562 0.01376 0.0137 0.025257 0.0293 0.039063 0.043l75.826 75.18c3.791 3.753 8.7375 5.6328 13.689 5.6328 5.003 0 10.007-1.9189 13.805-5.7559 7.563-7.628 7.5048-19.935-0.11719-27.498l-42.289-41.926h169.89v169.89l-41.926-42.289c-7.557-7.635-19.87-7.667-27.498-0.12305-7.622 7.563-7.6802 19.872-0.11719 27.5l75.18 75.826c3e-3 3e-3 7e-3 5e-3 0.01 8e-3 1.3458 1.3556 2.8885 2.5158 4.584 3.4297 0.0637 0.0343 0.13128 0.0602 0.19532 0.0937 0.50006 0.26239 1.0086 0.50958 1.5332 0.72852 0.0287 0.012 0.0591 0.0194 0.0879 0.0312 0.55148 0.22706 1.1139 0.43292 1.6894 0.60937 0.13059 0.0401 0.26501 0.0662 0.39649 0.10352 0.48459 0.13741 0.97149 0.26916 1.4707 0.36914 0.0657 0.0132 0.13332 0.0187 0.19922 0.0312 1.1798 0.22361 2.3932 0.35152 3.6384 0.35152 1.8194 0 3.572-0.26909 5.2422-0.73633 0.15543-0.0435 0.31454-0.0738 0.46875-0.12109 1.1011-0.33792 2.1539-0.78025 3.1621-1.2988 0.11472-0.0588 0.23407-0.10685 0.34765-0.16797 0.37259-0.20111 0.72848-0.42812 1.0859-0.65234 0.1868-0.11676 0.38143-0.22052 0.56445-0.34375 0.10467-0.0707 0.19953-0.15388 0.30274-0.22657 0.93571-0.65673 1.8271-1.3857 2.6445-2.209l75.18-75.826c7.556-7.628 7.503-19.937-0.125-27.5-7.635-7.557-19.941-7.505-27.498 0.12305l-41.932 42.299v-169.9h169.89l-42.293 41.926c-7.628 7.563-7.68 19.87-0.12305 27.498 3.804 3.837 8.8076 5.7559 13.811 5.7559 4.945 0 9.8984-1.8808 13.689-5.6328l75.826-75.18c0.0141-0.014 0.0269-0.0289 0.041-0.043 0.2515-0.2508 0.48121-0.52292 0.71875-0.78711 0.18447-0.20534 0.38168-0.40105 0.55664-0.61328 0.404-0.48955 0.78339-0.9993 1.1387-1.5274 0.0249-0.0371 0.0437-0.0779 0.0684-0.11523 0.32713-0.4935 0.63802-0.99972 0.91992-1.5234 0.0324-0.0604 0.0561-0.12484 0.0879-0.18555 0.26306-0.50133 0.51109-1.0112 0.73047-1.5371 0.0125-0.0301 0.0208-0.0617 0.0332-0.0918 0.22497-0.54707 0.42834-1.105 0.60352-1.6758 0.0473-0.15419 0.0776-0.31334 0.12109-0.46875 0.13019-0.46537 0.25974-0.93139 0.35547-1.4102 0.0128-0.0637 0.0171-0.12948 0.0293-0.19336 0.22362-1.1801 0.35153-2.3934 0.35153-3.6386 0-1.3125-0.13302-2.5925-0.38086-3.832-4e-4 -2e-3 -2e-3 -4e-3 -2e-3 -6e-3 -0.0955-0.47674-0.22387-0.94085-0.35352-1.4043-0.0435-0.15541-0.0738-0.31456-0.12109-0.46875-0.18478-0.60207-0.39663-1.192-0.63672-1.7676-5.6e-4 -1e-3 -1e-3 -3e-3 -2e-3 -4e-3 -0.24542-0.58801-0.51657-1.1617-0.81641-1.7188-2e-3 -3e-3 -4e-3 -6e-3 -6e-3 -0.01-0.30165-0.55963-0.62921-1.1039-0.98242-1.6289-8e-4 -1e-3 -1e-3 -3e-3 -2e-3 -4e-3 -0.35459-0.52676-0.73364-1.035-1.1367-1.5234-0.17496-0.21223-0.37217-0.40794-0.55664-0.61328-0.23754-0.26419-0.46725-0.53631-0.71875-0.78711-0.0141-0.014-0.0269-0.029-0.041-0.043l-75.826-75.18c-7.635-7.563-19.943-7.5048-27.5 0.11718-7.557 7.628-7.505 19.935 0.12305 27.498l42.295 41.932h-169.89v-169.89l41.926 42.289c3.804 3.837 8.8076 5.7559 13.811 5.7559 4.945 0 9.8955-1.8738 13.688-5.6328 7.628-7.563 7.68-19.872 0.12304-27.5l-75.18-75.826c-0.0137-0.013805-0.0292-0.025303-0.043-0.039063-0.44175-0.44306-0.90413-0.86662-1.3867-1.2656-5e-3 -0.00382-9e-3 -0.0079-0.0137-0.011719-3.3608-2.7735-7.6688-4.4395-12.367-4.4395z"/>
</svg>
<!DOCTYPE html>
<html lang="en">
<head>
<title>3D视图编辑器</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link rel="apple-touch-icon" href="images/icon.png">
<link rel="shortcut icon" href="../files/favicon_white.ico" media="(prefers-color-scheme: dark)" />
<link rel="shortcut icon" href="../files/favicon.ico" media="(prefers-color-scheme: light)" />
<script src="../../cdn/axios/1.3.6/axios.min.js"></script>
</head>
<body>
<link rel="stylesheet" href="css/main.css">
<style>
#loader {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
color: white;
display: none;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loader-message {
font-size: 24px;
}
</style>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "../examples/jsm/",
"three/examples/": "../examples/",
"three-gpu-pathtracer": "../build/index.module.js",
"three-mesh-bvh": "../build/index1.module.js"
}
}
</script>
<script src="../examples/jsm/libs/draco/draco_encoder.js"></script>
<link rel="stylesheet" href="js/libs/codemirror/codemirror.css">
<link rel="stylesheet" href="js/libs/codemirror/theme/monokai.css">
<script src="js/libs/codemirror/codemirror.js"></script>
<script src="js/libs/codemirror/mode/javascript.js"></script>
<script src="js/libs/codemirror/mode/glsl.js"></script>
<script src="js/libs/esprima.js"></script>
<script src="js/libs/jsonlint.js"></script>
<script src="../build/ffmpeg.min.js"></script>
<link rel="stylesheet" href="js/libs/codemirror/addon/dialog.css">
<link rel="stylesheet" href="js/libs/codemirror/addon/show-hint.css">
<link rel="stylesheet" href="js/libs/codemirror/addon/tern.css">
<script src="js/libs/codemirror/addon/dialog.js"></script>
<script src="js/libs/codemirror/addon/show-hint.js"></script>
<script src="js/libs/codemirror/addon/tern.js"></script>
<script src="js/libs/acorn/acorn.js"></script>
<script src="js/libs/acorn/acorn_loose.js"></script>
<script src="js/libs/acorn/walk.js"></script>
<script src="js/libs/ternjs/polyfill.js"></script>
<script src="js/libs/ternjs/signal.js"></script>
<script src="js/libs/ternjs/tern.js"></script>
<script src="js/libs/ternjs/def.js"></script>
<script src="js/libs/ternjs/comment.js"></script>
<script src="js/libs/ternjs/infer.js"></script>
<script src="js/libs/ternjs/doc_comment.js"></script>
<script src="js/libs/tern-threejs/threejs.js"></script>
<script src="js/libs/signals.min.js"></script>
<script src="../../config.js"></script>
<script src="index.js"></script>
<script type="module">
import * as THREE from 'three';
import { Editor } from './js/Editor.js';
import { Viewport } from './js/Viewport.js';
import { Toolbar } from './js/Toolbar.js';
import { Script } from './js/Script.js';
import { Player } from './js/Player.js';
import { Sidebar } from './js/Sidebar.js';
import { Menubar } from './js/Menubar.js';
import { Resizer } from './js/Resizer.js';
window.URL = window.URL || window.webkitURL;
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
//
const editor = new Editor();
window.$AvueData = window.$AvueData(editor).load();
window.editor = editor; // Expose editor to Console
window.THREE = THREE; // Expose THREE to APP Scripts and Console
const viewport = new Viewport(editor);
document.body.appendChild(viewport.dom);
const toolbar = new Toolbar(editor);
document.body.appendChild(toolbar.dom);
const script = new Script(editor);
document.body.appendChild(script.dom);
const player = new Player(editor);
document.body.appendChild(player.dom);
const sidebar = new Sidebar(editor);
document.body.appendChild(sidebar.dom);
const menubar = new Menubar(editor);
document.body.appendChild(menubar.dom);
const resizer = new Resizer(editor);
document.body.appendChild(resizer.dom);
//
editor.storage.init(function () {
editor.storage.get(async function (state) {
if (isLoadingFromHash) return;
if (state !== undefined) {
await editor.fromJSON(state);
}
const selected = editor.config.getKey('selected');
if (selected !== undefined) {
editor.selectByUuid(selected);
}
});
//
let timeout;
function saveState () {
if (editor.config.getKey('autosave') === false) {
return;
}
clearTimeout(timeout);
timeout = setTimeout(function () {
editor.signals.savingStarted.dispatch();
timeout = setTimeout(function () {
editor.storage.set(editor.toJSON());
editor.signals.savingFinished.dispatch();
}, 100);
}, 1000);
}
const signals = editor.signals;
signals.geometryChanged.add(saveState);
signals.objectAdded.add(saveState);
signals.objectChanged.add(saveState);
signals.objectRemoved.add(saveState);
signals.materialChanged.add(saveState);
signals.sceneBackgroundChanged.add(saveState);
signals.sceneEnvironmentChanged.add(saveState);
signals.sceneFogChanged.add(saveState);
signals.sceneGraphChanged.add(saveState);
signals.scriptChanged.add(saveState);
signals.historyChanged.add(saveState);
});
//
document.addEventListener('dragover', function (event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
});
document.addEventListener('drop', function (event) {
event.preventDefault();
if (event.dataTransfer.types[0] === 'text/plain') return; // Outliner drop
if (event.dataTransfer.items) {
// DataTransferItemList supports folders
editor.loader.loadItemList(event.dataTransfer.items);
} else {
editor.loader.loadFiles(event.dataTransfer.files);
}
});
function onWindowResize () {
editor.signals.windowResize.dispatch();
}
window.addEventListener('resize', onWindowResize);
onWindowResize();
//
let isLoadingFromHash = false;
const hash = window.location.hash;
if (hash.slice(1, 6) === 'file=') {
const file = hash.slice(6);
if (confirm(editor.strings.getKey('prompt/file/open'))) {
const loader = new THREE.FileLoader();
loader.crossOrigin = '';
loader.load(file, function (text) {
editor.clear();
editor.fromJSON(JSON.parse(text));
});
isLoadingFromHash = true;
}
}
// ServiceWorker
if ('serviceWorker' in navigator) {
try {
navigator.serviceWorker.register('sw.js');
} catch (error) {
}
}
</script>
</body>
</html>
\ No newline at end of file
function getUrlParameter (name) {
const url = window.location.href;
const urlObj = new URL(url);
const params = new URLSearchParams(urlObj.search);
return params.get(name);
}
const url = window.$website.url + '/component'
function showLoader () {
const loader = document.createElement('div');
loader.id = 'loader';
const message = document.createElement('div');
message.className = 'loader-message';
message.innerText = '加载中...';
loader.appendChild(message);
document.body.appendChild(loader);
// 显示遮罩
loader.style.display = 'flex';
}
function closeLoader () {
const loader = document.getElementById('loader');
if (loader) {
loader.style.display = 'none';
document.body.removeChild(loader);
}
}
const getObj = (id) => axios({
url: url + '/detail',
method: 'get',
params: {
id
}
});
const updateObj = (data) => axios({
url: url + '/update',
method: 'post',
data: data
});
window.$AvueData = function (editor) {
const id = getUrlParameter('id');
editor.clear()
function AvueData (id, editor) {
this.editor = editor;
this.id = id;
this.save = (json) => {
showLoader()
updateObj({
id: this.id,
content: JSON.stringify(json)
}).then(res => {
closeLoader()
alert('保存成功')
})
}
this.load = () => {
showLoader()
getObj(this.id).then(res => {
closeLoader()
try {
const json = JSON.parse(res.data.data.content);
function onEditorCleared () {
this.editor.fromJSON(json);
this.editor.signals.editorCleared.remove(onEditorCleared);
}
this.editor.signals.editorCleared.add(onEditorCleared);
this.editor.clear();
} catch (e) {
this.editor.clear()
console.error(e);
}
})
return this
}
return this;
}
return new AvueData(id, editor)
}
\ No newline at end of file
/**
* @param editor pointer to main editor object used to initialize
* each command object with a reference to the editor
* @constructor
*/
class Command {
constructor( editor ) {
this.id = - 1;
this.inMemory = false;
this.updatable = false;
this.type = '';
this.name = '';
this.editor = editor;
}
toJSON() {
const output = {};
output.type = this.type;
output.id = this.id;
output.name = this.name;
return output;
}
fromJSON( json ) {
this.inMemory = true;
this.type = json.type;
this.id = json.id;
this.name = json.name;
}
}
export { Command };
function Config() {
const name = 'threejs-editor';
const userLanguage = navigator.language.split( '-' )[ 0 ];
const suggestedLanguage = [ 'fr', 'ja', 'zh', 'ko' ].includes( userLanguage ) ? userLanguage : 'en';
const storage = {
'language': suggestedLanguage,
'autosave': true,
'project/title': '',
'project/editable': false,
'project/vr': false,
'project/renderer/antialias': true,
'project/renderer/shadows': true,
'project/renderer/shadowType': 1, // PCF
'project/renderer/toneMapping': 0, // NoToneMapping
'project/renderer/toneMappingExposure': 1,
'settings/history': false,
'settings/shortcuts/translate': 'w',
'settings/shortcuts/rotate': 'e',
'settings/shortcuts/scale': 'r',
'settings/shortcuts/undo': 'z',
'settings/shortcuts/focus': 'f'
};
if ( window.localStorage[ name ] === undefined ) {
window.localStorage[ name ] = JSON.stringify( storage );
} else {
const data = JSON.parse( window.localStorage[ name ] );
for ( const key in data ) {
storage[ key ] = data[ key ];
}
}
return {
getKey: function ( key ) {
return storage[ key ];
},
setKey: function () { // key, value, key, value ...
for ( let i = 0, l = arguments.length; i < l; i += 2 ) {
storage[ arguments[ i ] ] = arguments[ i + 1 ];
}
window.localStorage[ name ] = JSON.stringify( storage );
console.log( '[' + /\d\d\:\d\d\:\d\d/.exec( new Date() )[ 0 ] + ']', 'Saved config to LocalStorage.' );
},
clear: function () {
delete window.localStorage[ name ];
}
};
}
export { Config };
import * as THREE from 'three';
class EditorControls extends THREE.EventDispatcher {
constructor( object, domElement ) {
super();
// API
this.enabled = true;
this.center = new THREE.Vector3();
this.panSpeed = 0.002;
this.zoomSpeed = 0.1;
this.rotationSpeed = 0.005;
// internals
var scope = this;
var vector = new THREE.Vector3();
var delta = new THREE.Vector3();
var box = new THREE.Box3();
var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2 };
var state = STATE.NONE;
var center = this.center;
var normalMatrix = new THREE.Matrix3();
var pointer = new THREE.Vector2();
var pointerOld = new THREE.Vector2();
var spherical = new THREE.Spherical();
var sphere = new THREE.Sphere();
var pointers = [];
var pointerPositions = {};
// events
var changeEvent = { type: 'change' };
this.focus = function ( target ) {
var distance;
box.setFromObject( target );
if ( box.isEmpty() === false ) {
box.getCenter( center );
distance = box.getBoundingSphere( sphere ).radius;
} else {
// Focusing on an Group, AmbientLight, etc
center.setFromMatrixPosition( target.matrixWorld );
distance = 0.1;
}
delta.set( 0, 0, 1 );
delta.applyQuaternion( object.quaternion );
delta.multiplyScalar( distance * 4 );
object.position.copy( center ).add( delta );
scope.dispatchEvent( changeEvent );
};
this.pan = function ( delta ) {
var distance = object.position.distanceTo( center );
delta.multiplyScalar( distance * scope.panSpeed );
delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) );
object.position.add( delta );
center.add( delta );
scope.dispatchEvent( changeEvent );
};
this.zoom = function ( delta ) {
var distance = object.position.distanceTo( center );
delta.multiplyScalar( distance * scope.zoomSpeed );
if ( delta.length() > distance ) return;
delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) );
object.position.add( delta );
scope.dispatchEvent( changeEvent );
};
this.rotate = function ( delta ) {
vector.copy( object.position ).sub( center );
spherical.setFromVector3( vector );
spherical.theta += delta.x * scope.rotationSpeed;
spherical.phi += delta.y * scope.rotationSpeed;
spherical.makeSafe();
vector.setFromSpherical( spherical );
object.position.copy( center ).add( vector );
object.lookAt( center );
scope.dispatchEvent( changeEvent );
};
//
function onPointerDown( event ) {
if ( scope.enabled === false ) return;
if ( pointers.length === 0 ) {
domElement.setPointerCapture( event.pointerId );
domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
}
//
if ( isTrackingPointer( event ) ) return;
//
addPointer( event );
if ( event.pointerType === 'touch' ) {
onTouchStart( event );
} else {
onMouseDown( event );
}
}
function onPointerMove( event ) {
if ( scope.enabled === false ) return;
if ( event.pointerType === 'touch' ) {
onTouchMove( event );
} else {
onMouseMove( event );
}
}
function onPointerUp( event ) {
removePointer( event );
switch ( pointers.length ) {
case 0:
domElement.releasePointerCapture( event.pointerId );
domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
break;
case 1:
var pointerId = pointers[ 0 ];
var position = pointerPositions[ pointerId ];
// minimal placeholder event - allows state correction on pointer-up
onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } );
break;
}
}
// mouse
function onMouseDown( event ) {
if ( event.button === 0 ) {
state = STATE.ROTATE;
} else if ( event.button === 1 ) {
state = STATE.ZOOM;
} else if ( event.button === 2 ) {
state = STATE.PAN;
}
pointerOld.set( event.clientX, event.clientY );
}
function onMouseMove( event ) {
pointer.set( event.clientX, event.clientY );
var movementX = pointer.x - pointerOld.x;
var movementY = pointer.y - pointerOld.y;
if ( state === STATE.ROTATE ) {
scope.rotate( delta.set( - movementX, - movementY, 0 ) );
} else if ( state === STATE.ZOOM ) {
scope.zoom( delta.set( 0, 0, movementY ) );
} else if ( state === STATE.PAN ) {
scope.pan( delta.set( - movementX, movementY, 0 ) );
}
pointerOld.set( event.clientX, event.clientY );
}
function onMouseUp() {
state = STATE.NONE;
}
function onMouseWheel( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
// Normalize deltaY due to https://bugzilla.mozilla.org/show_bug.cgi?id=1392460
scope.zoom( delta.set( 0, 0, event.deltaY > 0 ? 1 : - 1 ) );
}
function contextmenu( event ) {
event.preventDefault();
}
this.dispose = function () {
domElement.removeEventListener( 'contextmenu', contextmenu );
domElement.removeEventListener( 'dblclick', onMouseUp );
domElement.removeEventListener( 'wheel', onMouseWheel );
domElement.removeEventListener( 'pointerdown', onPointerDown );
};
domElement.addEventListener( 'contextmenu', contextmenu );
domElement.addEventListener( 'dblclick', onMouseUp );
domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
domElement.addEventListener( 'pointerdown', onPointerDown );
// touch
var touches = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
var prevTouches = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
var prevDistance = null;
function onTouchStart( event ) {
trackPointer( event );
switch ( pointers.length ) {
case 1:
touches[ 0 ].set( event.pageX, event.pageY, 0 ).divideScalar( window.devicePixelRatio );
touches[ 1 ].set( event.pageX, event.pageY, 0 ).divideScalar( window.devicePixelRatio );
break;
case 2:
var position = getSecondPointerPosition( event );
touches[ 0 ].set( event.pageX, event.pageY, 0 ).divideScalar( window.devicePixelRatio );
touches[ 1 ].set( position.x, position.y, 0 ).divideScalar( window.devicePixelRatio );
prevDistance = touches[ 0 ].distanceTo( touches[ 1 ] );
break;
}
prevTouches[ 0 ].copy( touches[ 0 ] );
prevTouches[ 1 ].copy( touches[ 1 ] );
}
function onTouchMove( event ) {
trackPointer( event );
function getClosest( touch, touches ) {
var closest = touches[ 0 ];
for ( var touch2 of touches ) {
if ( closest.distanceTo( touch ) > touch2.distanceTo( touch ) ) closest = touch2;
}
return closest;
}
switch ( pointers.length ) {
case 1:
touches[ 0 ].set( event.pageX, event.pageY, 0 ).divideScalar( window.devicePixelRatio );
touches[ 1 ].set( event.pageX, event.pageY, 0 ).divideScalar( window.devicePixelRatio );
scope.rotate( touches[ 0 ].sub( getClosest( touches[ 0 ], prevTouches ) ).multiplyScalar( - 1 ) );
break;
case 2:
var position = getSecondPointerPosition( event );
touches[ 0 ].set( event.pageX, event.pageY, 0 ).divideScalar( window.devicePixelRatio );
touches[ 1 ].set( position.x, position.y, 0 ).divideScalar( window.devicePixelRatio );
var distance = touches[ 0 ].distanceTo( touches[ 1 ] );
scope.zoom( delta.set( 0, 0, prevDistance - distance ) );
prevDistance = distance;
var offset0 = touches[ 0 ].clone().sub( getClosest( touches[ 0 ], prevTouches ) );
var offset1 = touches[ 1 ].clone().sub( getClosest( touches[ 1 ], prevTouches ) );
offset0.x = - offset0.x;
offset1.x = - offset1.x;
scope.pan( offset0.add( offset1 ) );
break;
}
prevTouches[ 0 ].copy( touches[ 0 ] );
prevTouches[ 1 ].copy( touches[ 1 ] );
}
function addPointer( event ) {
pointers.push( event.pointerId );
}
function removePointer( event ) {
delete pointerPositions[ event.pointerId ];
for ( var i = 0; i < pointers.length; i ++ ) {
if ( pointers[ i ] == event.pointerId ) {
pointers.splice( i, 1 );
return;
}
}
}
function isTrackingPointer( event ) {
for ( var i = 0; i < pointers.length; i ++ ) {
if ( pointers[ i ] == event.pointerId ) return true;
}
return false;
}
function trackPointer( event ) {
var position = pointerPositions[ event.pointerId ];
if ( position === undefined ) {
position = new THREE.Vector2();
pointerPositions[ event.pointerId ] = position;
}
position.set( event.pageX, event.pageY );
}
function getSecondPointerPosition( event ) {
var pointerId = ( event.pointerId === pointers[ 0 ] ) ? pointers[ 1 ] : pointers[ 0 ];
return pointerPositions[ pointerId ];
}
}
}
export { EditorControls };
import * as Commands from './commands/Commands.js';
class History {
constructor( editor ) {
this.editor = editor;
this.undos = [];
this.redos = [];
this.lastCmdTime = Date.now();
this.idCounter = 0;
this.historyDisabled = false;
this.config = editor.config;
// signals
const scope = this;
this.editor.signals.startPlayer.add( function () {
scope.historyDisabled = true;
} );
this.editor.signals.stopPlayer.add( function () {
scope.historyDisabled = false;
} );
}
execute( cmd, optionalName ) {
const lastCmd = this.undos[ this.undos.length - 1 ];
const timeDifference = Date.now() - this.lastCmdTime;
const isUpdatableCmd = lastCmd &&
lastCmd.updatable &&
cmd.updatable &&
lastCmd.object === cmd.object &&
lastCmd.type === cmd.type &&
lastCmd.script === cmd.script &&
lastCmd.attributeName === cmd.attributeName;
if ( isUpdatableCmd && cmd.type === 'SetScriptValueCommand' ) {
// When the cmd.type is "SetScriptValueCommand" the timeDifference is ignored
lastCmd.update( cmd );
cmd = lastCmd;
} else if ( isUpdatableCmd && timeDifference < 500 ) {
lastCmd.update( cmd );
cmd = lastCmd;
} else {
// the command is not updatable and is added as a new part of the history
this.undos.push( cmd );
cmd.id = ++ this.idCounter;
}
cmd.name = ( optionalName !== undefined ) ? optionalName : cmd.name;
cmd.execute();
cmd.inMemory = true;
if ( this.config.getKey( 'settings/history' ) ) {
cmd.json = cmd.toJSON(); // serialize the cmd immediately after execution and append the json to the cmd
}
this.lastCmdTime = Date.now();
// clearing all the redo-commands
this.redos = [];
this.editor.signals.historyChanged.dispatch( cmd );
}
undo() {
if ( this.historyDisabled ) {
alert( this.editor.strings.getKey( 'prompt/history/forbid' ) );
return;
}
let cmd = undefined;
if ( this.undos.length > 0 ) {
cmd = this.undos.pop();
if ( cmd.inMemory === false ) {
cmd.fromJSON( cmd.json );
}
}
if ( cmd !== undefined ) {
cmd.undo();
this.redos.push( cmd );
this.editor.signals.historyChanged.dispatch( cmd );
}
return cmd;
}
redo() {
if ( this.historyDisabled ) {
alert( this.editor.strings.getKey( 'prompt/history/forbid' ) );
return;
}
let cmd = undefined;
if ( this.redos.length > 0 ) {
cmd = this.redos.pop();
if ( cmd.inMemory === false ) {
cmd.fromJSON( cmd.json );
}
}
if ( cmd !== undefined ) {
cmd.execute();
this.undos.push( cmd );
this.editor.signals.historyChanged.dispatch( cmd );
}
return cmd;
}
toJSON() {
const history = {};
history.undos = [];
history.redos = [];
if ( ! this.config.getKey( 'settings/history' ) ) {
return history;
}
// Append Undos to History
for ( let i = 0; i < this.undos.length; i ++ ) {
if ( this.undos[ i ].hasOwnProperty( 'json' ) ) {
history.undos.push( this.undos[ i ].json );
}
}
// Append Redos to History
for ( let i = 0; i < this.redos.length; i ++ ) {
if ( this.redos[ i ].hasOwnProperty( 'json' ) ) {
history.redos.push( this.redos[ i ].json );
}
}
return history;
}
fromJSON( json ) {
if ( json === undefined ) return;
for ( let i = 0; i < json.undos.length; i ++ ) {
const cmdJSON = json.undos[ i ];
const cmd = new Commands[ cmdJSON.type ]( this.editor ); // creates a new object of type "json.type"
cmd.json = cmdJSON;
cmd.id = cmdJSON.id;
cmd.name = cmdJSON.name;
this.undos.push( cmd );
this.idCounter = ( cmdJSON.id > this.idCounter ) ? cmdJSON.id : this.idCounter; // set last used idCounter
}
for ( let i = 0; i < json.redos.length; i ++ ) {
const cmdJSON = json.redos[ i ];
const cmd = new Commands[ cmdJSON.type ]( this.editor ); // creates a new object of type "json.type"
cmd.json = cmdJSON;
cmd.id = cmdJSON.id;
cmd.name = cmdJSON.name;
this.redos.push( cmd );
this.idCounter = ( cmdJSON.id > this.idCounter ) ? cmdJSON.id : this.idCounter; // set last used idCounter
}
// Select the last executed undo-command
this.editor.signals.historyChanged.dispatch( this.undos[ this.undos.length - 1 ] );
}
clear() {
this.undos = [];
this.redos = [];
this.idCounter = 0;
this.editor.signals.historyChanged.dispatch();
}
goToState( id ) {
if ( this.historyDisabled ) {
alert( this.editor.strings.getKey( 'prompt/history/forbid' ) );
return;
}
this.editor.signals.sceneGraphChanged.active = false;
this.editor.signals.historyChanged.active = false;
let cmd = this.undos.length > 0 ? this.undos[ this.undos.length - 1 ] : undefined; // next cmd to pop
if ( cmd === undefined || id > cmd.id ) {
cmd = this.redo();
while ( cmd !== undefined && id > cmd.id ) {
cmd = this.redo();
}
} else {
while ( true ) {
cmd = this.undos[ this.undos.length - 1 ]; // next cmd to pop
if ( cmd === undefined || id === cmd.id ) break;
this.undo();
}
}
this.editor.signals.sceneGraphChanged.active = true;
this.editor.signals.historyChanged.active = true;
this.editor.signals.sceneGraphChanged.dispatch();
this.editor.signals.historyChanged.dispatch( cmd );
}
enableSerialization( id ) {
/**
* because there might be commands in this.undos and this.redos
* which have not been serialized with .toJSON() we go back
* to the oldest command and redo one command after the other
* while also calling .toJSON() on them.
*/
this.goToState( - 1 );
this.editor.signals.sceneGraphChanged.active = false;
this.editor.signals.historyChanged.active = false;
let cmd = this.redo();
while ( cmd !== undefined ) {
if ( ! cmd.hasOwnProperty( 'json' ) ) {
cmd.json = cmd.toJSON();
}
cmd = this.redo();
}
this.editor.signals.sceneGraphChanged.active = true;
this.editor.signals.historyChanged.active = true;
this.goToState( id );
}
}
export { History };
const LoaderUtils = {
createFilesMap: function ( files ) {
const map = {};
for ( let i = 0; i < files.length; i ++ ) {
const file = files[ i ];
map[ file.name ] = file;
}
return map;
},
getFilesFromItemList: function ( items, onDone ) {
// TOFIX: setURLModifier() breaks when the file being loaded is not in root
let itemsCount = 0;
let itemsTotal = 0;
const files = [];
const filesMap = {};
function onEntryHandled() {
itemsCount ++;
if ( itemsCount === itemsTotal ) {
onDone( files, filesMap );
}
}
function handleEntry( entry ) {
if ( entry.isDirectory ) {
const reader = entry.createReader();
reader.readEntries( function ( entries ) {
for ( let i = 0; i < entries.length; i ++ ) {
handleEntry( entries[ i ] );
}
onEntryHandled();
} );
} else if ( entry.isFile ) {
entry.file( function ( file ) {
files.push( file );
filesMap[ entry.fullPath.slice( 1 ) ] = file;
onEntryHandled();
} );
}
itemsTotal ++;
}
for ( let i = 0; i < items.length; i ++ ) {
const item = items[ i ];
if ( item.kind === 'file' ) {
handleEntry( item.webkitGetAsEntry() );
}
}
}
};
export { LoaderUtils };
import { Box3, Vector3 } from 'three';
import { UIPanel, UIRow, UIHorizontalRule, UIText } from './libs/ui.js';
import { AddObjectCommand } from './commands/AddObjectCommand.js';
import { RemoveObjectCommand } from './commands/RemoveObjectCommand.js';
import { SetPositionCommand } from './commands/SetPositionCommand.js';
import { clone } from '../../examples/jsm/utils/SkeletonUtils.js';
function MenubarEdit( editor ) {
const strings = editor.strings;
const container = new UIPanel();
container.setClass( 'menu' );
const title = new UIPanel();
title.setClass( 'title' );
title.setTextContent( strings.getKey( 'menubar/edit' ) );
container.add( title );
const options = new UIPanel();
options.setClass( 'options' );
container.add( options );
// Undo
const undo = new UIRow();
undo.setClass( 'option' );
undo.setTextContent( strings.getKey( 'menubar/edit/undo' ) );
undo.add( new UIText( 'CTRL+Z' ).setClass( 'key' ) );
undo.onClick( function () {
editor.undo();
} );
options.add( undo );
// Redo
const redo = new UIRow();
redo.setClass( 'option' );
redo.setTextContent( strings.getKey( 'menubar/edit/redo' ) );
redo.add( new UIText( 'CTRL+SHIFT+Z' ).setClass( 'key' ) );
redo.onClick( function () {
editor.redo();
} );
options.add( redo );
function onHistoryChanged() {
const history = editor.history;
undo.setClass( 'option' );
redo.setClass( 'option' );
if ( history.undos.length == 0 ) {
undo.setClass( 'inactive' );
}
if ( history.redos.length == 0 ) {
redo.setClass( 'inactive' );
}
}
editor.signals.historyChanged.add( onHistoryChanged );
onHistoryChanged();
// ---
options.add( new UIHorizontalRule() );
// Center
let option = new UIRow();
option.setClass( 'option' );
option.setTextContent( strings.getKey( 'menubar/edit/center' ) );
option.onClick( function () {
const object = editor.selected;
if ( object === null || object.parent === null ) return; // avoid centering the camera or scene
const aabb = new Box3().setFromObject( object );
const center = aabb.getCenter( new Vector3() );
const newPosition = new Vector3();
newPosition.x = object.position.x - center.x;
newPosition.y = object.position.y - center.y;
newPosition.z = object.position.z - center.z;
editor.execute( new SetPositionCommand( editor, object, newPosition ) );
} );
options.add( option );
// Clone
option = new UIRow();
option.setClass( 'option' );
option.setTextContent( strings.getKey( 'menubar/edit/clone' ) );
option.onClick( function () {
let object = editor.selected;
if ( object === null || object.parent === null ) return; // avoid cloning the camera or scene
object = clone( object );
editor.execute( new AddObjectCommand( editor, object ) );
} );
options.add( option );
// Delete
option = new UIRow();
option.setClass( 'option' );
option.setTextContent( strings.getKey( 'menubar/edit/delete' ) );
option.add( new UIText( 'DEL' ).setClass( 'key' ) );
option.onClick( function () {
const object = editor.selected;
if ( object !== null && object.parent !== null ) {
editor.execute( new RemoveObjectCommand( editor, object ) );
}
} );
options.add( option );
return container;
}
export { MenubarEdit };
import { UIPanel, UIRow } from './libs/ui.js';
function MenubarHelp( editor ) {
const strings = editor.strings;
const container = new UIPanel();
container.setClass( 'menu' );
const title = new UIPanel();
title.setClass( 'title' );
title.setTextContent( strings.getKey( 'menubar/help' ) );
container.add( title );
const options = new UIPanel();
options.setClass( 'options' );
container.add( options );
// Source code
let option = new UIRow();
option.setClass( 'option' );
option.setTextContent( strings.getKey( 'menubar/help/source_code' ) );
option.onClick( function () {
window.open( 'https://github.com/mrdoob/three.js/tree/master/editor', '_blank' );
} );
options.add( option );
/*
// Icon
let option = new UIRow();
option.setClass( 'option' );
option.setTextContent( strings.getKey( 'menubar/help/icons' ) );
option.onClick( function () {
window.open( 'https://www.flaticon.com/packs/interface-44', '_blank' );
} );
options.add( option );
*/
// About
option = new UIRow();
option.setClass( 'option' );
option.setTextContent( strings.getKey( 'menubar/help/about' ) );
option.onClick( function () {
window.open( 'https://threejs.org', '_blank' );
} );
options.add( option );
// Manual
option = new UIRow();
option.setClass( 'option' );
option.setTextContent( strings.getKey( 'menubar/help/manual' ) );
option.onClick( function () {
window.open( 'https://github.com/mrdoob/three.js/wiki/Editor-Manual', '_blank' );
} );
options.add( option );
return container;
}
export { MenubarHelp };
import * as THREE from 'three';
import { UIPanel, UIText } from './libs/ui.js';
import { UIBoolean } from './libs/ui.three.js';
function MenubarStatus( editor ) {
const strings = editor.strings;
const container = new UIPanel();
container.setClass( 'menu right' );
const autosave = new UIBoolean( editor.config.getKey( 'autosave' ), strings.getKey( 'menubar/status/autosave' ) );
autosave.text.setColor( '#888' );
autosave.onChange( function () {
const value = this.getValue();
editor.config.setKey( 'autosave', value );
if ( value === true ) {
editor.signals.sceneGraphChanged.dispatch();
}
} );
container.add( autosave );
editor.signals.savingStarted.add( function () {
autosave.text.setTextDecoration( 'underline' );
} );
editor.signals.savingFinished.add( function () {
autosave.text.setTextDecoration( 'none' );
} );
const version = new UIText( 'r' + THREE.REVISION );
version.setClass( 'title' );
version.setOpacity( 0.5 );
container.add( version );
return container;
}
export { MenubarStatus };
import { UIHorizontalRule, UIPanel, UIRow } from './libs/ui.js';
function MenubarView( editor ) {
const signals = editor.signals;
const strings = editor.strings;
const container = new UIPanel();
container.setClass( 'menu' );
const title = new UIPanel();
title.setClass( 'title' );
title.setTextContent( strings.getKey( 'menubar/view' ) );
container.add( title );
const options = new UIPanel();
options.setClass( 'options' );
container.add( options );
// Helpers
const states = {
gridHelper: true,
cameraHelpers: true,
lightHelpers: true,
skeletonHelpers: true
};
// Grid Helper
let option = new UIRow().addClass( 'option' ).addClass( 'toggle' ).setTextContent( strings.getKey( 'menubar/view/gridHelper' ) ).onClick( function () {
states.gridHelper = ! states.gridHelper;
this.toggleClass( 'toggle-on', states.gridHelper );
signals.showHelpersChanged.dispatch( states );
} ).toggleClass( 'toggle-on', states.gridHelper );
options.add( option );
// Camera Helpers
option = new UIRow().addClass( 'option' ).addClass( 'toggle' ).setTextContent( strings.getKey( 'menubar/view/cameraHelpers' ) ).onClick( function () {
states.cameraHelpers = ! states.cameraHelpers;
this.toggleClass( 'toggle-on', states.cameraHelpers );
signals.showHelpersChanged.dispatch( states );
} ).toggleClass( 'toggle-on', states.cameraHelpers );
options.add( option );
// Light Helpers
option = new UIRow().addClass( 'option' ).addClass( 'toggle' ).setTextContent( strings.getKey( 'menubar/view/lightHelpers' ) ).onClick( function () {
states.lightHelpers = ! states.lightHelpers;
this.toggleClass( 'toggle-on', states.lightHelpers );
signals.showHelpersChanged.dispatch( states );
} ).toggleClass( 'toggle-on', states.lightHelpers );
options.add( option );
// Skeleton Helpers
option = new UIRow().addClass( 'option' ).addClass( 'toggle' ).setTextContent( strings.getKey( 'menubar/view/skeletonHelpers' ) ).onClick( function () {
states.skeletonHelpers = ! states.skeletonHelpers;
this.toggleClass( 'toggle-on', states.skeletonHelpers );
signals.showHelpersChanged.dispatch( states );
} ).toggleClass( 'toggle-on', states.skeletonHelpers );
options.add( option );
//
options.add( new UIHorizontalRule() );
// Fullscreen
option = new UIRow();
option.setClass( 'option' );
option.setTextContent( strings.getKey( 'menubar/view/fullscreen' ) );
option.onClick( function () {
if ( document.fullscreenElement === null ) {
document.documentElement.requestFullscreen();
} else if ( document.exitFullscreen ) {
document.exitFullscreen();
}
// Safari
if ( document.webkitFullscreenElement === null ) {
document.documentElement.webkitRequestFullscreen();
} else if ( document.webkitExitFullscreen ) {
document.webkitExitFullscreen();
}
} );
options.add( option );
// XR (Work in progress)
if ( 'xr' in navigator ) {
if ( 'offerSession' in navigator.xr ) {
signals.offerXR.dispatch( 'immersive-ar' );
} else {
navigator.xr.isSessionSupported( 'immersive-ar' )
.then( function ( supported ) {
if ( supported ) {
const option = new UIRow();
option.setClass( 'option' );
option.setTextContent( 'AR' );
option.onClick( function () {
signals.enterXR.dispatch( 'immersive-ar' );
} );
options.add( option );
} else {
navigator.xr.isSessionSupported( 'immersive-vr' )
.then( function ( supported ) {
if ( supported ) {
const option = new UIRow();
option.setClass( 'option' );
option.setTextContent( 'VR' );
option.onClick( function () {
signals.enterXR.dispatch( 'immersive-vr' );
} );
options.add( option );
}
} );
}
} );
}
}
//
return container;
}
export { MenubarView };
import { UIPanel } from './libs/ui.js';
import { MenubarAdd } from './Menubar.Add.js';
import { MenubarEdit } from './Menubar.Edit.js';
import { MenubarFile } from './Menubar.File.js';
import { MenubarView } from './Menubar.View.js';
import { MenubarHelp } from './Menubar.Help.js';
import { MenubarStatus } from './Menubar.Status.js';
function Menubar( editor ) {
const container = new UIPanel();
container.setId( 'menubar' );
container.add( new MenubarFile( editor ) );
container.add( new MenubarEdit( editor ) );
container.add( new MenubarAdd( editor ) );
container.add( new MenubarView( editor ) );
container.add( new MenubarHelp( editor ) );
container.add( new MenubarStatus( editor ) );
return container;
}
export { Menubar };
import { UIPanel } from './libs/ui.js';
import { APP } from './libs/app.js';
function Player( editor ) {
const signals = editor.signals;
const container = new UIPanel();
container.setId( 'player' );
container.setPosition( 'absolute' );
container.setDisplay( 'none' );
//
const player = new APP.Player();
container.dom.appendChild( player.dom );
window.addEventListener( 'resize', function () {
player.setSize( container.dom.clientWidth, container.dom.clientHeight );
} );
signals.windowResize.add( function () {
player.setSize( container.dom.clientWidth, container.dom.clientHeight );
} );
signals.startPlayer.add( function () {
container.setDisplay( '' );
player.load( editor.toJSON() );
player.setSize( container.dom.clientWidth, container.dom.clientHeight );
player.play();
} );
signals.stopPlayer.add( function () {
container.setDisplay( 'none' );
player.stop();
player.dispose();
} );
return container;
}
export { Player };
import { UIElement } from './libs/ui.js';
function Resizer( editor ) {
const signals = editor.signals;
const dom = document.createElement( 'div' );
dom.id = 'resizer';
function onPointerDown( event ) {
if ( event.isPrimary === false ) return;
dom.ownerDocument.addEventListener( 'pointermove', onPointerMove );
dom.ownerDocument.addEventListener( 'pointerup', onPointerUp );
}
function onPointerUp( event ) {
if ( event.isPrimary === false ) return;
dom.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
dom.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
}
function onPointerMove( event ) {
// PointerEvent's movementX/movementY are 0 in WebKit
if ( event.isPrimary === false ) return;
const offsetWidth = document.body.offsetWidth;
const clientX = event.clientX;
const cX = clientX < 0 ? 0 : clientX > offsetWidth ? offsetWidth : clientX;
const x = Math.max( 335, offsetWidth - cX ); // .TabbedPanel min-width: 335px
dom.style.right = x + 'px';
document.getElementById( 'sidebar' ).style.width = x + 'px';
document.getElementById( 'player' ).style.right = x + 'px';
document.getElementById( 'script' ).style.right = x + 'px';
document.getElementById( 'viewport' ).style.right = x + 'px';
signals.windowResize.dispatch();
}
dom.addEventListener( 'pointerdown', onPointerDown );
return new UIElement( dom );
}
export { Resizer };
import * as THREE from 'three';
const mouse = new THREE.Vector2();
const raycaster = new THREE.Raycaster();
class Selector {
constructor( editor ) {
const signals = editor.signals;
this.editor = editor;
this.signals = signals;
// signals
signals.intersectionsDetected.add( ( intersects ) => {
if ( intersects.length > 0 ) {
const object = intersects[ 0 ].object;
if ( object.userData.object !== undefined ) {
// helper
this.select( object.userData.object );
} else {
this.select( object );
}
} else {
this.select( null );
}
} );
}
getIntersects( raycaster ) {
const objects = [];
this.editor.scene.traverseVisible( function ( child ) {
objects.push( child );
} );
this.editor.sceneHelpers.traverseVisible( function ( child ) {
if ( child.name === 'picker' ) objects.push( child );
} );
return raycaster.intersectObjects( objects, false );
}
getPointerIntersects( point, camera ) {
mouse.set( ( point.x * 2 ) - 1, - ( point.y * 2 ) + 1 );
raycaster.setFromCamera( mouse, camera );
return this.getIntersects( raycaster );
}
select( object ) {
if ( this.editor.selected === object ) return;
let uuid = null;
if ( object !== null ) {
uuid = object.uuid;
}
this.editor.selected = object;
this.editor.config.setKey( 'selected', uuid );
this.signals.objectSelected.dispatch( object );
}
deselect() {
this.select( null );
}
}
export { Selector };
import * as THREE from 'three';
import { UIDiv, UIRow, UIText, UINumber, UIInteger } from './libs/ui.js';
import { SetGeometryCommand } from './commands/SetGeometryCommand.js';
function GeometryParametersPanel( editor, object ) {
const strings = editor.strings;
const signals = editor.signals;
const container = new UIDiv();
const geometry = object.geometry;
const parameters = geometry.parameters;
// width
const widthRow = new UIRow();
const width = new UINumber().setPrecision( 3 ).setValue( parameters.width ).onChange( update );
widthRow.add( new UIText( strings.getKey( 'sidebar/geometry/box_geometry/width' ) ).setClass( 'Label' ) );
widthRow.add( width );
container.add( widthRow );
// height
const heightRow = new UIRow();
const height = new UINumber().setPrecision( 3 ).setValue( parameters.height ).onChange( update );
heightRow.add( new UIText( strings.getKey( 'sidebar/geometry/box_geometry/height' ) ).setClass( 'Label' ) );
heightRow.add( height );
container.add( heightRow );
// depth
const depthRow = new UIRow();
const depth = new UINumber().setPrecision( 3 ).setValue( parameters.depth ).onChange( update );
depthRow.add( new UIText( strings.getKey( 'sidebar/geometry/box_geometry/depth' ) ).setClass( 'Label' ) );
depthRow.add( depth );
container.add( depthRow );
// widthSegments
const widthSegmentsRow = new UIRow();
const widthSegments = new UIInteger( parameters.widthSegments ).setRange( 1, Infinity ).onChange( update );
widthSegmentsRow.add( new UIText( strings.getKey( 'sidebar/geometry/box_geometry/widthseg' ) ).setClass( 'Label' ) );
widthSegmentsRow.add( widthSegments );
container.add( widthSegmentsRow );
// heightSegments
const heightSegmentsRow = new UIRow();
const heightSegments = new UIInteger( parameters.heightSegments ).setRange( 1, Infinity ).onChange( update );
heightSegmentsRow.add( new UIText( strings.getKey( 'sidebar/geometry/box_geometry/heightseg' ) ).setClass( 'Label' ) );
heightSegmentsRow.add( heightSegments );
container.add( heightSegmentsRow );
// depthSegments
const depthSegmentsRow = new UIRow();
const depthSegments = new UIInteger( parameters.depthSegments ).setRange( 1, Infinity ).onChange( update );
depthSegmentsRow.add( new UIText( strings.getKey( 'sidebar/geometry/box_geometry/depthseg' ) ).setClass( 'Label' ) );
depthSegmentsRow.add( depthSegments );
container.add( depthSegmentsRow );
//
function refreshUI() {
const parameters = object.geometry.parameters;
width.setValue( parameters.width );
height.setValue( parameters.height );
depth.setValue( parameters.depth );
widthSegments.setValue( parameters.widthSegments );
heightSegments.setValue( parameters.heightSegments );
depthSegments.setValue( parameters.depthSegments );
}
signals.geometryChanged.add( function ( mesh ) {
if ( mesh === object ) {
refreshUI();
}
} );
//
function update() {
editor.execute( new SetGeometryCommand( editor, object, new THREE.BoxGeometry(
width.getValue(),
height.getValue(),
depth.getValue(),
widthSegments.getValue(),
heightSegments.getValue(),
depthSegments.getValue()
) ) );
}
return container;
}
export { GeometryParametersPanel };
import { UIRow, UIText, UISpan, UIBreak, UICheckbox } from './libs/ui.js';
function SidebarGeometryBufferGeometry( editor ) {
const strings = editor.strings;
const signals = editor.signals;
const container = new UIRow();
function update( object ) {
if ( object === null ) return; // objectSelected.dispatch( null )
if ( object === undefined ) return;
const geometry = object.geometry;
if ( geometry ) {
container.clear();
container.setDisplay( 'block' );
// attributes
const attributesRow = new UIRow();
const textAttributes = new UIText( strings.getKey( 'sidebar/geometry/buffer_geometry/attributes' ) ).setClass( 'Label' );
attributesRow.add( textAttributes );
const containerAttributes = new UISpan().setDisplay( 'inline-block' ).setVerticalAlign( 'middle' ).setWidth( '160px' );
attributesRow.add( containerAttributes );
const index = geometry.index;
if ( index !== null ) {
containerAttributes.add( new UIText( strings.getKey( 'sidebar/geometry/buffer_geometry/index' ) ).setWidth( '80px' ) );
containerAttributes.add( new UIText( editor.utils.formatNumber( index.count ) ).setFontSize( '12px' ) );
containerAttributes.add( new UIBreak() );
}
const attributes = geometry.attributes;
for ( const name in attributes ) {
const attribute = attributes[ name ];
containerAttributes.add( new UIText( name ).setWidth( '80px' ) );
containerAttributes.add( new UIText( editor.utils.formatNumber( attribute.count ) + ' (' + attribute.itemSize + ')' ).setFontSize( '12px' ) );
containerAttributes.add( new UIBreak() );
}
container.add( attributesRow );
// morph targets
const morphAttributes = geometry.morphAttributes;
const hasMorphTargets = Object.keys( morphAttributes ).length > 0;
if ( hasMorphTargets === true ) {
// morph attributes
const rowMorphAttributes = new UIRow();
const textMorphAttributes = new UIText( strings.getKey( 'sidebar/geometry/buffer_geometry/morphAttributes' ) ).setClass( 'Label' );
rowMorphAttributes.add( textMorphAttributes );
const containerMorphAttributes = new UISpan().setDisplay( 'inline-block' ).setVerticalAlign( 'middle' ).setWidth( '160px' );
rowMorphAttributes.add( containerMorphAttributes );
for ( const name in morphAttributes ) {
const morphTargets = morphAttributes[ name ];
containerMorphAttributes.add( new UIText( name ).setWidth( '80px' ) );
containerMorphAttributes.add( new UIText( editor.utils.formatNumber( morphTargets.length ) ).setFontSize( '12px' ) );
containerMorphAttributes.add( new UIBreak() );
}
container.add( rowMorphAttributes );
// morph relative
const rowMorphRelative = new UIRow();
const textMorphRelative = new UIText( strings.getKey( 'sidebar/geometry/buffer_geometry/morphRelative' ) ).setClass( 'Label' );
rowMorphRelative.add( textMorphRelative );
const checkboxMorphRelative = new UICheckbox().setValue( geometry.morphTargetsRelative ).setDisabled( true );
rowMorphRelative.add( checkboxMorphRelative );
container.add( rowMorphRelative );
}
} else {
container.setDisplay( 'none' );
}
}
signals.objectSelected.add( update );
signals.geometryChanged.add( update );
return container;
}
export { SidebarGeometryBufferGeometry };
import * as THREE from 'three';
import { UIDiv, UIRow, UIText, UINumber, UIInteger } from './libs/ui.js';
import { SetGeometryCommand } from './commands/SetGeometryCommand.js';
function GeometryParametersPanel( editor, object ) {
const strings = editor.strings;
const signals = editor.signals;
const container = new UIDiv();
const geometry = object.geometry;
const parameters = geometry.parameters;
// radius
const radiusRow = new UIRow();
const radius = new UINumber( parameters.radius ).onChange( update );
radiusRow.add( new UIText( strings.getKey( 'sidebar/geometry/capsule_geometry/radius' ) ).setClass( 'Label' ) );
radiusRow.add( radius );
container.add( radiusRow );
// length
const lengthRow = new UIRow();
const length = new UINumber( parameters.length ).onChange( update );
lengthRow.add( new UIText( strings.getKey( 'sidebar/geometry/capsule_geometry/length' ) ).setClass( 'Label' ) );
lengthRow.add( length );
container.add( lengthRow );
// capSegments
const capSegmentsRow = new UIRow();
const capSegments = new UIInteger( parameters.capSegments ).setRange( 1, Infinity ).onChange( update );
capSegmentsRow.add( new UIText( strings.getKey( 'sidebar/geometry/capsule_geometry/capseg' ) ).setClass( 'Label' ) );
capSegmentsRow.add( capSegments );
container.add( capSegmentsRow );
// radialSegments
const radialSegmentsRow = new UIRow();
const radialSegments = new UIInteger( parameters.radialSegments ).setRange( 1, Infinity ).onChange( update );
radialSegmentsRow.add( new UIText( strings.getKey( 'sidebar/geometry/capsule_geometry/radialseg' ) ).setClass( 'Label' ) );
radialSegmentsRow.add( radialSegments );
container.add( radialSegmentsRow );
//
function refreshUI() {
const parameters = object.geometry.parameters;
radius.setValue( parameters.radius );
length.setValue( parameters.length );
capSegments.setValue( parameters.capSegments );
radialSegments.setValue( parameters.radialSegments );
}
signals.geometryChanged.add( function ( mesh ) {
if ( mesh === object ) {
refreshUI();
}
} );
//
function update() {
editor.execute( new SetGeometryCommand( editor, object, new THREE.CapsuleGeometry(
radius.getValue(),
length.getValue(),
capSegments.getValue(),
radialSegments.getValue()
) ) );
}
return container;
}
export { GeometryParametersPanel };
import * as THREE from 'three';
import { UIDiv, UIRow, UIText, UIInteger, UINumber } from './libs/ui.js';
import { SetGeometryCommand } from './commands/SetGeometryCommand.js';
function GeometryParametersPanel( editor, object ) {
const strings = editor.strings;
const signals = editor.signals;
const container = new UIDiv();
const geometry = object.geometry;
const parameters = geometry.parameters;
// radius
const radiusRow = new UIRow();
const radius = new UINumber( parameters.radius ).onChange( update );
radiusRow.add( new UIText( strings.getKey( 'sidebar/geometry/circle_geometry/radius' ) ).setClass( 'Label' ) );
radiusRow.add( radius );
container.add( radiusRow );
// segments
const segmentsRow = new UIRow();
const segments = new UIInteger( parameters.segments ).setRange( 3, Infinity ).onChange( update );
segmentsRow.add( new UIText( strings.getKey( 'sidebar/geometry/circle_geometry/segments' ) ).setClass( 'Label' ) );
segmentsRow.add( segments );
container.add( segmentsRow );
// thetaStart
const thetaStartRow = new UIRow();
const thetaStart = new UINumber( parameters.thetaStart * THREE.MathUtils.RAD2DEG ).setUnit( '°' ).setStep( 10 ).onChange( update );
thetaStartRow.add( new UIText( strings.getKey( 'sidebar/geometry/circle_geometry/thetastart' ) ).setClass( 'Label' ) );
thetaStartRow.add( thetaStart );
container.add( thetaStartRow );
// thetaLength
const thetaLengthRow = new UIRow();
const thetaLength = new UINumber( parameters.thetaLength * THREE.MathUtils.RAD2DEG ).setUnit( '°' ).setStep( 10 ).onChange( update );
thetaLengthRow.add( new UIText( strings.getKey( 'sidebar/geometry/circle_geometry/thetalength' ) ).setClass( 'Label' ) );
thetaLengthRow.add( thetaLength );
container.add( thetaLengthRow );
//
function refreshUI() {
const parameters = object.geometry.parameters;
radius.setValue( parameters.radius );
segments.setValue( parameters.segments );
thetaStart.setValue( parameters.thetaStart * THREE.MathUtils.RAD2DEG );
thetaLength.setValue( parameters.thetaLength * THREE.MathUtils.RAD2DEG );
}
signals.geometryChanged.add( function ( mesh ) {
if ( mesh === object ) {
refreshUI();
}
} );
//
function update() {
editor.execute( new SetGeometryCommand( editor, object, new THREE.CircleGeometry(
radius.getValue(),
segments.getValue(),
thetaStart.getValue() * THREE.MathUtils.DEG2RAD,
thetaLength.getValue() * THREE.MathUtils.DEG2RAD
) ) );
}
return container;
}
export { GeometryParametersPanel };
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment