Initial commit: 大陆架零部件智能生产线数字孪生系统
- 实现TV1和TV2双大屏展示 - 集成ECharts数据可视化 - 实现实时数据刷新机制 - 添加刀具寿命管理模块 - 添加工单加工时长统计模块 - 添加加工中心7日工作时长统计模块 - 添加OEE分析、设备状态监控等功能 - 创建技术文档和使用说明书
24
src/App.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<keep-alive>
|
||||
<router-view v-if="this.$route.meta.keepAlive"/>
|
||||
</keep-alive>
|
||||
<router-view v-if="!this.$route.meta.keepAlive"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: MiSans Bold, serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
/*font-family: Microsoft YaHei!important;*/
|
||||
}
|
||||
.el-dialog__wrapper {
|
||||
pointer-events: none;
|
||||
}
|
||||
.el-dialog {
|
||||
pointer-events: auto;
|
||||
}
|
||||
</style>
|
||||
70
src/assets/font/Misans.css
Normal file
@@ -0,0 +1,70 @@
|
||||
@font-face {
|
||||
font-family: MiSans Demibold;
|
||||
src: url("MiSans-Demibold.ttf");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans Bold;
|
||||
src: url("MiSans-Bold.ttf");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans ExtraLight;
|
||||
src: url("MiSans-ExtraLight.ttf");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans Heavy;
|
||||
src: url("MiSans-Heavy.ttf");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans Light;
|
||||
src: url("MiSans-Light.ttf");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans Medium;
|
||||
src: url("MiSans-Medium.ttf");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans Normal;
|
||||
src: url("MiSans-Normal.ttf");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans Regular;
|
||||
src: url("MiSans-Regular.ttf");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans Semibold;
|
||||
src: url("MiSans-Semibold.ttf");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans Thin;
|
||||
src: url("MiSans-Thin.ttf");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
BIN
src/assets/img/jiegang1.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
155
src/components/DMT/VR/VRButton.js
Normal file
@@ -0,0 +1,155 @@
|
||||
class VRButton {
|
||||
static createButton(renderer, options) {
|
||||
if (options) {
|
||||
console.error('THREE.VRButton: The "options" parameter has been removed. Please set the reference space type via renderer.xr.setReferenceSpaceType() instead.')
|
||||
}
|
||||
|
||||
const button = document.createElement('button')
|
||||
|
||||
function showEnterVR(/* device*/) {
|
||||
let currentSession = null
|
||||
|
||||
async function onSessionStarted(session) {
|
||||
session.addEventListener('end', onSessionEnded)
|
||||
|
||||
await renderer.xr.setSession(session)
|
||||
button.textContent = 'EXIT VR'
|
||||
|
||||
currentSession = session
|
||||
}
|
||||
|
||||
function onSessionEnded(/* event*/) {
|
||||
currentSession.removeEventListener('end', onSessionEnded)
|
||||
|
||||
button.textContent = 'ENTER VR'
|
||||
|
||||
currentSession = null
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
button.style.display = ''
|
||||
|
||||
button.style.cursor = 'pointer'
|
||||
button.style.left = 'calc(50% - 50px)'
|
||||
button.style.width = '100px'
|
||||
|
||||
button.textContent = 'ENTER VR'
|
||||
|
||||
button.onmouseenter = function() {
|
||||
button.style.opacity = '1.0'
|
||||
}
|
||||
|
||||
button.onmouseleave = function() {
|
||||
button.style.opacity = '0.5'
|
||||
}
|
||||
|
||||
button.onclick = function() {
|
||||
if (currentSession === null) {
|
||||
// WebXR's requestReferenceSpace only works if the corresponding feature
|
||||
// was requested at session creation time. For simplicity, just ask for
|
||||
// the interesting ones as optional features, but be aware that the
|
||||
// requestReferenceSpace call will fail if it turns out to be unavailable.
|
||||
// ('local' is always available for immersive sessions and doesn't need to
|
||||
// be requested separately.)
|
||||
|
||||
const sessionInit = { optionalFeatures: ['local-floor', 'bounded-floor', 'hand-tracking', 'layers'] }
|
||||
navigator.xr.requestSession('immersive-vr', sessionInit).then(onSessionStarted)
|
||||
} else {
|
||||
currentSession.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function disableButton() {
|
||||
button.style.display = ''
|
||||
|
||||
button.style.cursor = 'auto'
|
||||
button.style.left = 'calc(50% - 75px)'
|
||||
button.style.width = '150px'
|
||||
|
||||
button.onmouseenter = null
|
||||
button.onmouseleave = null
|
||||
|
||||
button.onclick = null
|
||||
}
|
||||
|
||||
function showWebXRNotFound() {
|
||||
disableButton()
|
||||
|
||||
button.textContent = 'VR NOT SUPPORTED'
|
||||
}
|
||||
|
||||
function showVRNotAllowed(exception) {
|
||||
disableButton()
|
||||
|
||||
console.warn('Exception when trying to call xr.isSessionSupported', exception)
|
||||
|
||||
button.textContent = 'VR NOT ALLOWED'
|
||||
}
|
||||
|
||||
function stylizeElement(element) {
|
||||
element.style.position = 'absolute'
|
||||
element.style.bottom = '20px'
|
||||
element.style.padding = '12px 6px'
|
||||
element.style.border = '1px solid #fff'
|
||||
element.style.borderRadius = '4px'
|
||||
element.style.background = 'rgba(0,0,0,0.1)'
|
||||
element.style.color = '#fff'
|
||||
element.style.font = 'normal 13px sans-serif'
|
||||
element.style.textAlign = 'center'
|
||||
element.style.opacity = '0.5'
|
||||
element.style.outline = 'none'
|
||||
element.style.zIndex = '999'
|
||||
}
|
||||
|
||||
if ('xr' in navigator) {
|
||||
button.id = 'VRButton'
|
||||
button.style.display = 'none'
|
||||
|
||||
stylizeElement(button)
|
||||
|
||||
navigator.xr.isSessionSupported('immersive-vr').then(function(supported) {
|
||||
supported ? showEnterVR() : showWebXRNotFound()
|
||||
|
||||
if (supported && VRButton.xrSessionIsGranted) {
|
||||
button.click()
|
||||
}
|
||||
}).catch(showVRNotAllowed)
|
||||
|
||||
return button
|
||||
} else {
|
||||
const message = document.createElement('a')
|
||||
|
||||
if (window.isSecureContext === false) {
|
||||
message.href = document.location.href.replace(/^http:/, 'https:')
|
||||
message.innerHTML = 'WEBXR NEEDS HTTPS' // TODO Improve message
|
||||
} else {
|
||||
message.href = 'https://immersiveweb.dev/'
|
||||
message.innerHTML = 'WEBXR NOT AVAILABLE'
|
||||
}
|
||||
|
||||
message.style.left = 'calc(50% - 90px)'
|
||||
message.style.width = '180px'
|
||||
message.style.textDecoration = 'none'
|
||||
|
||||
stylizeElement(message)
|
||||
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
static xrSessionIsGranted = false;
|
||||
|
||||
static registerSessionGrantedListener() {
|
||||
if ('xr' in navigator) {
|
||||
navigator.xr.addEventListener('sessiongranted', () => {
|
||||
VRButton.xrSessionIsGranted = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VRButton.registerSessionGrantedListener()
|
||||
|
||||
export { VRButton }
|
||||
327
src/components/DMT/config.vue
Normal file
@@ -0,0 +1,327 @@
|
||||
<template>
|
||||
<div style="pointer-events: none">
|
||||
<el-dialog title="修改配置文件" :visible.sync="dialogFormVisible" width="1000px" class="myDia" :modal="false" :close-on-press-escape="false" v-el-drag-dialog>
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item title="通讯配置" name="1">
|
||||
<el-row class="row-Show">
|
||||
<span class="title-show">标题</span>
|
||||
<el-input size="mini" style="width: 300px" v-model="configInfo['title']"></el-input>
|
||||
<span class="title-show">工位和协议</span>
|
||||
<el-input size="mini" style="width: 120px" v-model="configInfo['webSocketOpName']"></el-input>
|
||||
<el-input size="mini" style="width: 180px" v-model="configInfo['webSocketBSport']"></el-input>
|
||||
</el-row>
|
||||
<el-row class="row-Show">
|
||||
<span class="title-show">通讯类型</span>
|
||||
<el-radio-group size="mini" v-model="configInfo['connectType']">
|
||||
<el-radio :label="1">WebSocket</el-radio>
|
||||
<el-radio :label="2">MQTT</el-radio>
|
||||
<el-radio :label="3">其他</el-radio>
|
||||
</el-radio-group>
|
||||
</el-row>
|
||||
<el-row class="row-Show">
|
||||
<span class="title-show">Socket_IP&Port</span>
|
||||
<el-input size="mini" style="width: 300px" v-model="configInfo['webSocketIp']"></el-input>
|
||||
<span class="title-show">MQTT_IP&Port</span>
|
||||
<el-input size="mini" style="width: 200px" v-model="configInfo['MQTTHost']"></el-input>
|
||||
<el-input size="mini" style="width: 100px" v-model="configInfo['MQTTPort']"></el-input>
|
||||
</el-row>
|
||||
<el-row class="row-Show">
|
||||
<span class="title-show">数据服务器</span>
|
||||
<el-input size="mini" style="width: 300px" v-model="configInfo['dataBaseURL']"></el-input>
|
||||
<span class="title-show">node服务</span>
|
||||
<el-input size="mini" style="width: 300px" v-model="configInfo['NodeRootIp']"></el-input>
|
||||
</el-row>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="2">
|
||||
<template slot="title">
|
||||
机器人/设备配置
|
||||
<el-button size="mini" type="warning" style="margin-left: 10px" @click.stop.prevent="dialogFormVisible = false">重置配置文件</el-button>
|
||||
<el-button size="mini" type="primary" style="margin-left: 10px" @click.stop.prevent="exportConfig">导出配置文件</el-button>
|
||||
{{ currentUUid }}
|
||||
</template>
|
||||
<el-col :span="6">
|
||||
<el-table
|
||||
:data="tableDataRobot"
|
||||
:highlight-current-row="false"
|
||||
element-loading-text="数据加载中"
|
||||
element-loading-spinner="el-icon-loading"
|
||||
element-loading-background="rgba(0, 0, 0, 0.2)"
|
||||
style="width: 100%"
|
||||
max-height="300"
|
||||
border
|
||||
size="mini"
|
||||
class="my-table-show"
|
||||
@cell-dblclick="showEdit"
|
||||
@row-click="rowClick">
|
||||
<el-table-column type="index" label=" " align="center" width="40"/>
|
||||
<!-- <el-table-column align="center" label="name">-->
|
||||
<!-- <template slot-scope="scope">-->
|
||||
<!-- {{ scope.row.name }}-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
<el-table-column align="center" label="机器人模型id">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-if="scope.row.edit" size="mini" v-model="scope.row.uuid"></el-input>
|
||||
<span v-else>{{ scope.row.uuid }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" width="50px" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" style="color: #00893d" @click="applyRobotConfig(scope.row)">应用</el-button>
|
||||
<br>
|
||||
<el-button size="mini" type="text" style="color: red">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<el-table
|
||||
:data="RobotChildrenData"
|
||||
:highlight-current-row="false"
|
||||
element-loading-text="数据加载中"
|
||||
element-loading-spinner="el-icon-loading"
|
||||
element-loading-background="rgba(0, 0, 0, 0.2)"
|
||||
style="width: 100%"
|
||||
max-height="300"
|
||||
border
|
||||
size="mini">
|
||||
<el-table-column type="index" label=" " align="center" width="50"/>
|
||||
<!-- <el-table-column align="center" label="关节名字">-->
|
||||
<!-- <template slot-scope="scope">-->
|
||||
<!-- {{ scope.row.name }}-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
<el-table-column align="center" label="关节ID" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-input size="mini" v-model="scope.row.ModelId "></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="type" width="110">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.type" size="mini">
|
||||
<el-option
|
||||
v-for="item in robotJointType"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="axis" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.axis" size="mini">
|
||||
<el-option
|
||||
v-for="item in robotJointAxis"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="flip" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.flip" size="mini">
|
||||
<el-option
|
||||
v-for="item in robotJointFlip"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="delta" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-input size="mini" v-model="scope.row.delta "></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="multiple" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-input size="mini" v-model="scope.row.multiple "></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" width="50px" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" style="color: #00893d" @click="applyRobotConfig(scope.row, scope.$index)">应用</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Mousetrap from './js/mousetrap.js'
|
||||
import axios from 'axios'
|
||||
export default {
|
||||
name: 'ConfigVue',
|
||||
data() {
|
||||
return {
|
||||
currentUUid: '',
|
||||
activeNames: '2',
|
||||
configInfo: {},
|
||||
tableDataRobot: [],
|
||||
tableDataRobotChildren: {},
|
||||
RobotChildrenData: [],
|
||||
dialogFormVisible: false,
|
||||
robotJointType: [
|
||||
{
|
||||
label: 'position',
|
||||
value: 'position'
|
||||
},
|
||||
{
|
||||
label: 'rotation',
|
||||
value: 'rotation'
|
||||
}
|
||||
],
|
||||
robotJointFlip: [
|
||||
{
|
||||
label: 1,
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: -1,
|
||||
value: -1
|
||||
}
|
||||
],
|
||||
robotJointAxis: [
|
||||
{
|
||||
label: 'x',
|
||||
value: 'x'
|
||||
},
|
||||
{
|
||||
label: 'y',
|
||||
value: 'y'
|
||||
},
|
||||
{
|
||||
label: 'z',
|
||||
value: 'z'
|
||||
}
|
||||
],
|
||||
form: {
|
||||
name: '',
|
||||
region: '',
|
||||
date1: '',
|
||||
date2: '',
|
||||
delivery: false,
|
||||
type: [],
|
||||
resource: '',
|
||||
desc: ''
|
||||
},
|
||||
formLabelWidth: '120px'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getConfig().then(res => {
|
||||
this.configInfo = res
|
||||
console.log(res)
|
||||
this.solveRobotInfoFirst()
|
||||
})
|
||||
const scope = this
|
||||
Mousetrap.bind('ctrl+c', function() {
|
||||
// scope.solveConfig()
|
||||
// return false
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
solveConfig() {
|
||||
console.log('ctrl + c')
|
||||
this.dialogFormVisible = !this.dialogFormVisible
|
||||
},
|
||||
getConfig() {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.get('./DMT_WEB/config/config.json').then(res => {
|
||||
const configData = res.data
|
||||
resolve(configData)
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
solveRobotInfoFirst() {
|
||||
this.tableDataRobot = []
|
||||
this.tableDataRobotChildren = {}
|
||||
for (const argumentsKey in this.configInfo.robotList) {
|
||||
this.tableDataRobot.push({
|
||||
name: '',
|
||||
uuid: argumentsKey,
|
||||
edit: false
|
||||
})
|
||||
this.tableDataRobotChildren[argumentsKey] = []
|
||||
for (let i = 0; i < this.configInfo.robotList[argumentsKey].length; i++) {
|
||||
this.tableDataRobotChildren[argumentsKey].push(this.configInfo.robotList[argumentsKey][i])
|
||||
}
|
||||
}
|
||||
},
|
||||
showEdit(row) {
|
||||
row.edit = !row.edit
|
||||
},
|
||||
rowClick(row) {
|
||||
this.currentUUid = row.uuid
|
||||
this.RobotChildrenData = this.tableDataRobotChildren[row.uuid]
|
||||
},
|
||||
exportConfig() {
|
||||
this.configInfo['robotList'] = this.tableDataRobotChildren
|
||||
const saveJsonStr = JSON.stringify(this.configInfo)
|
||||
const blob = new Blob([saveJsonStr], {
|
||||
type: 'application/json'
|
||||
})
|
||||
const objectUrl = URL.createObjectURL(blob)
|
||||
const aa = document.createElement('a')
|
||||
aa.href = objectUrl
|
||||
aa.download = 'config.json'
|
||||
aa.click()
|
||||
URL.revokeObjectURL(objectUrl)
|
||||
},
|
||||
applyRobotConfig(row, index) {
|
||||
const rootRobotJoint = window.editor.robotList[this.currentUUid][index]
|
||||
if (row.ModelId === rootRobotJoint.ModelId) {
|
||||
window.editor.robotList[this.currentUUid][index].axis = row.axis
|
||||
window.editor.robotList[this.currentUUid][index].delta = row.delta
|
||||
window.editor.robotList[this.currentUUid][index].flip = row.flip
|
||||
window.editor.robotList[this.currentUUid][index].type = row.type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.row-Show {
|
||||
margin: 10px 0
|
||||
}
|
||||
.title-show {
|
||||
font-weight: bolder;
|
||||
font-size: 16px;
|
||||
margin: 0 10px;
|
||||
width: 130px;
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.myDia .el-dialog {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.myDia .el-dialog__body {
|
||||
padding: 5px 15px;
|
||||
}
|
||||
.myDia .el-dialog--center .el-dialog__body {
|
||||
padding: 15px;
|
||||
}
|
||||
.myDia .el-dialog__title {
|
||||
font-size: 22px;
|
||||
color: #000000;
|
||||
}
|
||||
.myDia .el-dialog__header {
|
||||
padding: 15px;
|
||||
}
|
||||
.myDia .el-dialog__wrapper{
|
||||
pointer-events:none;
|
||||
}
|
||||
.myDia .el-dialog{
|
||||
pointer-events:auto;
|
||||
}
|
||||
</style>
|
||||
46
src/components/DMT/el-dragDialog/drag.js
Normal file
@@ -0,0 +1,46 @@
|
||||
export default{
|
||||
bind(el, binding) {
|
||||
const dialogHeaderEl = el.querySelector('.el-dialog__header')
|
||||
const dragDom = el.querySelector('.el-dialog')
|
||||
dialogHeaderEl.style = 'cursor:move;'
|
||||
|
||||
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
|
||||
const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null)
|
||||
|
||||
dialogHeaderEl.onmousedown = (e) => {
|
||||
// 鼠标按下,计算当前元素距离可视区的距离
|
||||
const disX = e.clientX - dialogHeaderEl.offsetLeft
|
||||
const disY = e.clientY - dialogHeaderEl.offsetTop
|
||||
|
||||
// 获取到的值带px 正则匹配替换
|
||||
let styL, styT
|
||||
|
||||
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
|
||||
if (sty.left.includes('%')) {
|
||||
styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100)
|
||||
styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100)
|
||||
} else {
|
||||
styL = +sty.left.replace(/\px/g, '')
|
||||
styT = +sty.top.replace(/\px/g, '')
|
||||
}
|
||||
|
||||
document.onmousemove = function(e) {
|
||||
// 通过事件委托,计算移动的距离
|
||||
const l = e.clientX - disX
|
||||
const t = e.clientY - disY
|
||||
|
||||
// 移动当前元素
|
||||
dragDom.style.left = `${l + styL}px`
|
||||
dragDom.style.top = `${t + styT}px`
|
||||
|
||||
// 将此时的位置传出去
|
||||
// binding.value({x:e.pageX,y:e.pageY})
|
||||
}
|
||||
|
||||
document.onmouseup = function(e) {
|
||||
document.onmousemove = null
|
||||
document.onmouseup = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/components/DMT/el-dragDialog/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import drag from './drag'
|
||||
|
||||
const install = function(Vue) {
|
||||
Vue.directive('el-drag-dialog', drag)
|
||||
}
|
||||
|
||||
if (window.Vue) {
|
||||
window['el-drag-dialog'] = drag
|
||||
Vue.use(install); // eslint-disable-line
|
||||
}
|
||||
|
||||
drag.install = install
|
||||
export default drag
|
||||
1402
src/components/DMT/index.vue
Normal file
1063
src/components/DMT/indexForDMT.vue
Normal file
1731
src/components/DMT/indexForDMTbak.vue
Normal file
900
src/components/DMT/indexForDMTbakbak.vue
Normal file
@@ -0,0 +1,900 @@
|
||||
<template>
|
||||
<div style="height: 100%">
|
||||
<div id="viewPort" style="width: 100%;height: 100%;display: flex;align-items: center;justify-content: center;"></div>
|
||||
<div ref="modelProcess" style="position: absolute;top: 10px;left: 2px;color: white">正在加载模型0%</div>
|
||||
<div style="display: none">
|
||||
<audio controls loop id="alarm">
|
||||
<source src="./libs/alarm.mp3" type="audio/mpeg">
|
||||
</audio>
|
||||
<audio controls id="clash">
|
||||
<source src="./libs/alarm.mp3" type="audio/mpeg">
|
||||
</audio>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as THREE from 'three'
|
||||
import axios from 'axios'
|
||||
import $ from 'jquery'
|
||||
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
|
||||
import * as TWEEN from 'three/examples/jsm/libs/tween.module.js'
|
||||
|
||||
import { DmtControl } from './libs/DmtControl.js'
|
||||
import { connect } from './js/websocketConnect.js'
|
||||
import { connectMqtt } from './js/MqttConnect.js'
|
||||
import { PlaySoundCommand } from './js/PlaySoundCommand.js'
|
||||
import SpriteText from './js/three-spritetext.module.js'
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let container, clock, scene, camera, axesHelper, renderer, Plane
|
||||
let modelCount = 0
|
||||
const editor = {
|
||||
cameraStateZoom: 25,
|
||||
addEventListener: THREE.EventDispatcher.prototype.addEventListener,
|
||||
removeEventListener: THREE.EventDispatcher.prototype.removeEventListener,
|
||||
dispatchEvent: THREE.EventDispatcher.prototype.dispatchEvent,
|
||||
hasEventListener: THREE.EventDispatcher.prototype.hasEventListener,
|
||||
showTagList: [{
|
||||
title: '工件:QY16KC',
|
||||
ModelId: 'A113AD62-9CE7-4ACF-8D0C-8E8A597A8768',
|
||||
object: null
|
||||
}],
|
||||
ip: '',
|
||||
WeldLight1: '',
|
||||
WeldLightVisible1: false,
|
||||
WeldLight2: '',
|
||||
WeldLightVisible2: false,
|
||||
scene: null,
|
||||
objectIdUUidMap: new Map(),
|
||||
opname: '',
|
||||
connectType: 1,
|
||||
BSport: '',
|
||||
IsAllowConnect: true,
|
||||
service: axios.create({
|
||||
baseURL: './submit/MESCommonBase.ashx',
|
||||
timeout: 1500000 // 请求超时时间
|
||||
}),
|
||||
selected: null,
|
||||
socket: null,
|
||||
mqttClient: {
|
||||
connected: false
|
||||
},
|
||||
ProjectCode: null,
|
||||
sceneConfig: null,
|
||||
resourcesTree: null,
|
||||
attachments: null,
|
||||
resources: null,
|
||||
cameraState: null,
|
||||
cameraStateAll: [],
|
||||
isOk: false,
|
||||
objectIdSet: new Map(),
|
||||
animOperations: null,
|
||||
CraftList: {},
|
||||
moveTrigger: null,
|
||||
camera: null,
|
||||
cameraPosition: 10,
|
||||
currentButton: 1,
|
||||
cameraControls: null,
|
||||
TagActList: {}, // 存放tag对应的类型
|
||||
worker: {}, // 播放工艺的线程
|
||||
RotationWorker: {}, // 自传工艺的线程
|
||||
ModelRotateWorker: {}, // 自传工艺的的状态数据
|
||||
rotateSpeed: 5 * Math.PI / 180,
|
||||
getObjectById: null, // 方法 根据ModelId获取模型
|
||||
solveAttachModel: null, // 方法 处理attach问题
|
||||
removeObject: null, // 方法 移除模型
|
||||
setModelWireFrame: null, // 方法 设置线框
|
||||
setTransparent: null, // 方法 设置透明
|
||||
showMessage: null, // 方法 显示消息
|
||||
drawPartTextInfo: null, // 方法 模型上显示文字
|
||||
ModelLabelList: new Map(), // 模型上显示列表
|
||||
playSoundCommand: new PlaySoundCommand(), // 方法 警告
|
||||
attachList: new Map(), // 记录attach列表
|
||||
objMaterial: new Map(),
|
||||
WareHouseList: [],
|
||||
WareHouse: new Map(), // 立库库存表
|
||||
WareHouseBasicModel: new Map(), // 立库库存基础 零件模型需要命名为warehousepart1-100 立库定位模型需要命名为warehouse1000-1009
|
||||
ProtocolIdentifiers: {
|
||||
Robot_7: 'Robot_7',
|
||||
Device_3: 'Device_3',
|
||||
Agv_3: 'Agv_3',
|
||||
Device_1: 'Device_1',
|
||||
FirstPosition: 'FirstPosition',
|
||||
WareHousePart: 'WareHousePart',
|
||||
Robot_6: 'Robot_6',
|
||||
RobotTcp: 'RobotTcp',
|
||||
DrawPath: 'DrawPath',
|
||||
ReLoadData: 'ReLoadData',
|
||||
PlayAct: 'PlayAct',
|
||||
CraftCurrOperation: 'CraftCurrOperation',
|
||||
Position: 'Position',
|
||||
Select: 'Select',
|
||||
DragMove: 'DragMove',
|
||||
ModelTransparent: 'ModelTransparent',
|
||||
Visible: 'Visible',
|
||||
Frames: 'Frames',
|
||||
ModelRotation: 'ModelRotation',
|
||||
GoHome: 'GoHome',
|
||||
GetJointAxisInfo: 'GetJointAxisInfo',
|
||||
GetDeviceBaseTool: 'GetDeviceBaseTool',
|
||||
GetJointValue: 'GetJointValue',
|
||||
C_B_ImportModel: 'C_B_ImportModel',
|
||||
SendURDF: 'SendURDF',
|
||||
SendDeviceJson: 'SendDeviceJson',
|
||||
GetDeviceJson: 'GetDeviceJson',
|
||||
SendMotionInfo: 'SendMotionInfo',
|
||||
C_B_ImportLayout: 'C_B_ImportLayout',
|
||||
GetPoseLocation: 'GetPoseLocation',
|
||||
C_B_SetProjectCode: 'C_B_SetProjectCode',
|
||||
C_B_TagIDTrigger: 'C_B_TagIDTrigger',
|
||||
C_B_ReLoad_TagIDHandler: 'C_B_ReLoad_TagIDHandler',
|
||||
C_B_SetCurrSignalList: 'C_B_SetCurrSignalList',
|
||||
SendClearModelInfo: 'SendClearModelInfo',
|
||||
C_B_SaveProjectSimulationData: 'C_B_SaveProjectSimulationData',
|
||||
C_B_GetProjectSimulationData: 'C_B_GetProjectSimulationData',
|
||||
C_B_KinematicsEditing: 'C_B_KinematicsEditing',
|
||||
C_B_SaveModel: 'C_B_SaveModel',
|
||||
C_B_ObjectAlarm: 'C_B_ObjectAlarm',
|
||||
C_B_JointMoveTo: 'C_B_JointMoveTo',
|
||||
JointMoveTo: 'JointMoveTo' // DT|JointMoveTo|modelCode|{"J1":"1","J2":"2","J3":"3","J4":"4","J5":"5","J6":"6","J7":"7","J8":"8","J9":"9"}
|
||||
},
|
||||
craftPlaySolveType: {
|
||||
visible: 'visible',
|
||||
rotation: 'rotation',
|
||||
craft: 'craft',
|
||||
signalwrite: 'signalwrite',
|
||||
wait: 'wait',
|
||||
FrameData: 'FrameData',
|
||||
attach: 'attach',
|
||||
warning: 'warning'
|
||||
},
|
||||
FrameOperation: {
|
||||
playCraft: true,
|
||||
visibleObject: true,
|
||||
rotationObject: true,
|
||||
writeSignal: true,
|
||||
waitSignal: true,
|
||||
attach: true,
|
||||
warning: true
|
||||
},
|
||||
signalSolveType: {
|
||||
visible: 'visible',
|
||||
rotation: 'rotation',
|
||||
craft: 'craft',
|
||||
targetLocation: 'targetlocation',
|
||||
attach: 'attach',
|
||||
warning: 'warning'
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let ModelListTransParentList = []
|
||||
const ModelList = []
|
||||
const ModelListId = []
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const timer = null
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const lastIntersectModel = null
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const onUpPosition = new THREE.Vector2()
|
||||
|
||||
var raycaster = new THREE.Raycaster()
|
||||
var mouse = new THREE.Vector2()
|
||||
|
||||
export default {
|
||||
components: {
|
||||
},
|
||||
name: 'DMT',
|
||||
props: {
|
||||
containerHeight: {
|
||||
type: String,
|
||||
default: '100vh'
|
||||
},
|
||||
containerWidth: {
|
||||
type: String,
|
||||
default: '100vw'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
checked: false,
|
||||
cameraData: [],
|
||||
object: null,
|
||||
s: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const scope = this
|
||||
editor.objectIdSet = new Map()
|
||||
editor.objMaterial = new Map()
|
||||
editor.attachList = new Map()
|
||||
this.getConfig().then(res => {
|
||||
if (Object.hasOwnProperty.call(res, 'robotList')) {
|
||||
editor.robotList = res.robotList
|
||||
}
|
||||
if (Object.hasOwnProperty.call(res, 'WareHouseList')) {
|
||||
editor.WareHouseList = res.WareHouseList
|
||||
}
|
||||
if (Object.hasOwnProperty.call(res, 'transParentList')) {
|
||||
ModelListTransParentList = res.transParentList
|
||||
}
|
||||
if (Object.hasOwnProperty.call(res, 'connectType')) {
|
||||
editor.connectType = res.connectType
|
||||
}
|
||||
editor.mqttHost = res.MQTTHost
|
||||
editor.mqttPort = res.MQTTPort
|
||||
editor.ip = res.webSocketIp
|
||||
editor.opname = res.webSocketOpName
|
||||
editor.BSport = res.webSocketBSport
|
||||
editor.rootIp = res.NodeRootIp
|
||||
this.getProjectConfig()
|
||||
})
|
||||
editor.showMessage = function(message, type, time = 2000) {
|
||||
scope.$message({
|
||||
message: message,
|
||||
type: type,
|
||||
duration: time,
|
||||
center: true
|
||||
})
|
||||
}
|
||||
editor.addEventListener('dmtModelDBClick', function(event) {
|
||||
console.log('dmtModelDBClick', event)
|
||||
})
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
getConfig() {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.get('./DMT_WEB/config/config.json').then(res => {
|
||||
const configData = res.data
|
||||
resolve(configData)
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取项目配置文件,用于初始隐藏模型
|
||||
*/
|
||||
getProjectConfig() {
|
||||
const P1 = new Promise(function(resolve, reject) {
|
||||
axios({
|
||||
method: 'get',
|
||||
url: './DMT_WEB/models/DTDATA.json'
|
||||
}).then((res) => {
|
||||
editor.animOperations = {}
|
||||
editor.moveTrigger = {}
|
||||
res.data.forEach(n => {
|
||||
Object.keys(n).forEach(function(key) {
|
||||
/**
|
||||
* 获取播放数据并解析数据
|
||||
*/
|
||||
if (key === 'OperationTree') {
|
||||
n[key].forEach(CraftData => {
|
||||
editor.CraftList[CraftData.CraftCode] = {}
|
||||
editor.CraftList[CraftData.CraftCode]['CraftName'] = CraftData.CraftName
|
||||
editor.CraftList[CraftData.CraftCode]['Varieties'] = CraftData.Varieties
|
||||
editor.CraftList[CraftData.CraftCode]['CurrPlay'] = CraftData.CurrPlay
|
||||
if (CraftData.CraftPlayData.length < 2) {
|
||||
editor.animOperations[CraftData.CraftCode] = ''
|
||||
} else {
|
||||
editor.animOperations[CraftData.CraftCode] = JSON.parse(CraftData.CraftPlayData)
|
||||
}
|
||||
editor.moveTrigger[CraftData.CraftCode] = {
|
||||
moveStatus: 0,
|
||||
myVarSetInterval: null,
|
||||
doAnimation: false,
|
||||
currentFrame: 0,
|
||||
pausedFrame: 0,
|
||||
animOperations: editor.animOperations[CraftData.CraftCode],
|
||||
simPaceTime: 20,
|
||||
moveStatusPosition: 0
|
||||
}
|
||||
})
|
||||
}
|
||||
/**
|
||||
* HandlerType: 1:显隐,2:自转,3行为
|
||||
* HandlerCode: 模型或者工艺代码
|
||||
* HandlerValue: 显隐:0/1 ,自传:正负转速&axis,行为暂停播放:0/1
|
||||
* TagValue:信号值
|
||||
*/
|
||||
if (key === 'TagTriggerHandler') {
|
||||
n[key].forEach(TagActListValue => {
|
||||
if (editor.TagActList[TagActListValue.TagID]) {
|
||||
editor.TagActList[TagActListValue.TagID].push({
|
||||
HandlerType: TagActListValue.HandlerType,
|
||||
HandlerCode: TagActListValue.HandlerCode,
|
||||
HandlerValue: TagActListValue.HandlerValue,
|
||||
TagValue: TagActListValue.TagValue,
|
||||
JointType: TagActListValue.JointType,
|
||||
Axis: TagActListValue.Axis,
|
||||
Flip: TagActListValue.Flip,
|
||||
Value: TagActListValue.Value,
|
||||
ValueFactor: TagActListValue.ValueFactor
|
||||
})
|
||||
} else {
|
||||
editor.TagActList[TagActListValue.TagID] = []
|
||||
editor.TagActList[TagActListValue.TagID].push({
|
||||
HandlerType: TagActListValue.HandlerType,
|
||||
HandlerCode: TagActListValue.HandlerCode,
|
||||
HandlerValue: TagActListValue.HandlerValue,
|
||||
TagValue: TagActListValue.TagValue,
|
||||
JointType: TagActListValue.JointType,
|
||||
Axis: TagActListValue.Axis,
|
||||
Flip: TagActListValue.Flip,
|
||||
Value: TagActListValue.Value,
|
||||
ValueFactor: TagActListValue.ValueFactor
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
resolve(1)
|
||||
})
|
||||
})
|
||||
const P2 = new Promise(function(resolve, reject) {
|
||||
axios({
|
||||
method: 'get',
|
||||
url: './DMT_WEB/models/RobotPlan_Process.json'
|
||||
}).then((res) => {
|
||||
editor.sceneConfig = res.data.sceneConfig // 场景配置
|
||||
editor.attachments = res.data.attachments // 附加物体
|
||||
editor.resources = res.data.resources // 场景模型资源
|
||||
editor.resourcesTree = res.data.resourcesTree // 模型树
|
||||
editor.cameraState = res.data.cameraState // 场景相机位置
|
||||
editor.ProjectCode = res.data.ProjectCode // 项目代码
|
||||
editor.resourcesTree.forEach(res => {
|
||||
if (res.treeType === 'OriginModel') {
|
||||
ModelList.push(res.name)
|
||||
}
|
||||
})
|
||||
editor.resources.forEach(res => {
|
||||
editor.objectIdUUidMap.set(res.id, res)
|
||||
})
|
||||
editor.attachments.forEach(res => {
|
||||
editor.attachList.set(res.RootId, res.attachList)
|
||||
})
|
||||
resolve(1)
|
||||
}).catch((error) => { console.log(error) })
|
||||
})
|
||||
Promise.all([P1, P2]).then(res => {
|
||||
this.init()
|
||||
// this.getCameraData()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 设定模型初始状态
|
||||
*/
|
||||
setModelSates() {
|
||||
// 模型的显示隐藏
|
||||
editor.resourcesTree.forEach(res => {
|
||||
if (res.visible === '0') {
|
||||
if (editor.getObjectById(res.id)) {
|
||||
editor.getObjectById(res.id).visible = !!(parseInt(res.visible))
|
||||
}
|
||||
}
|
||||
})
|
||||
const treeRes = editor.resourcesTree
|
||||
for (const treeResValue of treeRes) {
|
||||
if (treeResValue.treeType !== 'folder') {
|
||||
const currentModel = editor.getObjectById(treeResValue.id)
|
||||
|
||||
if (Object.hasOwnProperty.call(treeResValue, 'WireFrame') && treeResValue.WireFrame) {
|
||||
editor.setModelWireFrame(currentModel, true)
|
||||
}
|
||||
if (Object.hasOwnProperty.call(treeResValue, 'transParent') && treeResValue.transParent) {
|
||||
editor.setTransparent(treeResValue.id, 0.3)
|
||||
}
|
||||
if (currentModel) currentModel.visible = !!parseInt(treeResValue.visible)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 设定场景初始状态
|
||||
* @param type
|
||||
*/
|
||||
setSceneStates(type) {
|
||||
switch (type) {
|
||||
case '0':
|
||||
scene.background = new THREE.Color('#dcdcdc')
|
||||
break
|
||||
case '1':
|
||||
scene.background = new THREE.Color(editor.sceneConfig.RenderColorModel.color)
|
||||
break
|
||||
case '2':
|
||||
if (editor.sceneConfig.RenderColorModel.image === '') {
|
||||
editor.scene.background = new THREE.Color(editor.sceneConfig.RenderColorModel.color)
|
||||
return
|
||||
}
|
||||
new THREE.TextureLoader().load(editor.sceneConfig.RenderColorModel.image === '', function(texture) {
|
||||
scene.background = texture
|
||||
texture.dispose()
|
||||
})
|
||||
break
|
||||
default:
|
||||
scene.background = new THREE.Color('#dcdcdc')
|
||||
break
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 初始化场景信息
|
||||
*/
|
||||
init() {
|
||||
const scope = this
|
||||
container = document.getElementById('viewPort')
|
||||
clock = new THREE.Clock()
|
||||
scene = new THREE.Scene()
|
||||
editor.scene = scene
|
||||
const width = container.clientWidth
|
||||
const height = container.clientHeight
|
||||
const k = width / height
|
||||
const s = 100
|
||||
camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 0, 2000)
|
||||
camera.position.set(editor.cameraPosition, editor.cameraPosition, editor.cameraPosition)
|
||||
camera.lookAt(scene.position)
|
||||
camera.up.x = 0
|
||||
camera.up.y = 0
|
||||
camera.up.z = 1
|
||||
scene.add(camera)
|
||||
editor.camera = camera
|
||||
axesHelper = new THREE.AxesHelper(1)
|
||||
scene.add(axesHelper)
|
||||
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true })
|
||||
renderer.setPixelRatio(window.devicePixelRatio)
|
||||
renderer.setSize(container.clientWidth, container.clientHeight)
|
||||
renderer.setClearColor(0x666666, 0)
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping
|
||||
renderer.toneMappingExposure = 0.8
|
||||
renderer.shadowMap.enabled = true
|
||||
renderer.hadowMapEnabled = true
|
||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap
|
||||
renderer.outputColorSpace = THREE.SRGBColorSpace
|
||||
renderer.textureEncoding = THREE.sRGBEncoding
|
||||
|
||||
// editor.WeldLight1 = this.drawWeldLight()
|
||||
// editor.WeldLight2 = this.drawWeldLight()
|
||||
|
||||
const pmremGenerator = new THREE.PMREMGenerator(renderer)
|
||||
pmremGenerator.compileEquirectangularShader()
|
||||
const loader = new GLTFLoader().setPath('./DMT_WEB/models/')
|
||||
const dracoLoader = new DRACOLoader()
|
||||
new RGBELoader().setDataType(THREE.FloatType)
|
||||
.load('./DMT_WEB/assets/venice_sunset_1k.hdr', function(texture) {
|
||||
const envMap = pmremGenerator.fromEquirectangular(texture).texture
|
||||
scene.environment = envMap
|
||||
scene.background = new THREE.Color('#081f48')
|
||||
texture.dispose()
|
||||
pmremGenerator.dispose()
|
||||
})
|
||||
this.setSceneStates(editor.sceneConfig.RenderColorModel.model)
|
||||
dracoLoader.setDecoderPath('./DMT_WEB/draco/')
|
||||
loader.setDRACOLoader(dracoLoader)
|
||||
editor.getObjectById = function(ModelId) {
|
||||
let currentModel = null
|
||||
const currentModelData = editor.objectIdSet.get(ModelId)
|
||||
if (currentModelData) currentModel = currentModelData.object
|
||||
return currentModel
|
||||
}
|
||||
editor.solveAttachModel = function(ModelId, attach = true) {
|
||||
}
|
||||
editor.removeObject = function(object) {
|
||||
if (object === null) return
|
||||
if (object.parent === null) return
|
||||
object.parent.remove(object)
|
||||
}
|
||||
/**
|
||||
* 设置模型线框
|
||||
* @param currentObject
|
||||
* @param WireframeValue
|
||||
*/
|
||||
editor.setModelWireFrame = function(currentObject, WireframeValue) {
|
||||
currentObject.traverse(obj => {
|
||||
if (obj instanceof THREE.Mesh) {
|
||||
obj.material = editor.objMaterial.get(obj.id).clone()
|
||||
obj.material['wireframe'] = WireframeValue
|
||||
obj.material.needsUpdate = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型透明
|
||||
* @param ModelId
|
||||
* @param Opacity 透明度
|
||||
*/
|
||||
editor.setTransparent = function(ModelId, Opacity = 0.3) {
|
||||
new Promise((resolve, reject) => {
|
||||
const TransModel = editor.getObjectById(ModelId)
|
||||
if (!TransModel) {
|
||||
console.warn('模型不存在!(setTransparent)')
|
||||
reject(false)
|
||||
} else {
|
||||
TransModel.traverse(obj => {
|
||||
if (obj.material) {
|
||||
const material_yuan = editor.objMaterial.get(obj.id).clone()
|
||||
obj.material = material_yuan
|
||||
if (Opacity !== 1) {
|
||||
obj.material = new THREE.MeshStandardMaterial({
|
||||
color: material_yuan.color,
|
||||
side: 2,
|
||||
transparent: true,
|
||||
opacity: Opacity
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
resolve(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
editor.drawPartTextInfo = function(ModelId, TagValue) {
|
||||
new Promise((resolve, reject) => {
|
||||
let LabelInfo = editor.ModelLabelList.get(ModelId)
|
||||
const TransModel = editor.getObjectById(ModelId)
|
||||
if (TransModel) {
|
||||
if (!LabelInfo) {
|
||||
LabelInfo = {
|
||||
position: scope.getDeltaPosition(new THREE.Vector3(), { x: 0, y: 0, z: 0.5 }),
|
||||
label: TagValue,
|
||||
object: null
|
||||
}
|
||||
editor.ModelLabelList.set(ModelId, LabelInfo)
|
||||
}
|
||||
if (LabelInfo.object) editor.removeObject(LabelInfo.object)
|
||||
if (TransModel.visible) {
|
||||
LabelInfo.label = TagValue.replaceAll('\\n', '\n')
|
||||
const spriteLabel = new SpriteText(LabelInfo.label, 0.2, '#000000')
|
||||
// spriteLabel.borderColor = '#30ff00'
|
||||
// spriteLabel.borderWidth = [0.3, 0.2]
|
||||
spriteLabel.backgroundColor = 'rgb(255,183,0)'
|
||||
spriteLabel.position.copy(LabelInfo.position)
|
||||
LabelInfo.object = spriteLabel
|
||||
TransModel.add(spriteLabel)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
container.appendChild(renderer.domElement)
|
||||
container.addEventListener('dblclick', this.ondblclick, false)
|
||||
editor.cameraControls = new DmtControl(editor.camera, renderer.domElement)
|
||||
editor.cameraControls.listenToKeyEvents(window) // optional
|
||||
editor.cameraControls.update()
|
||||
|
||||
const light1 = new THREE.AmbientLight(new THREE.Color('#ffffff'), 1)
|
||||
light1.name = 'ambient_light'
|
||||
editor.cameraControls.object.add(light1)
|
||||
|
||||
const light2 = new THREE.DirectionalLight(new THREE.Color('#ffffff'), 1)
|
||||
light2.position.set(0.5, 0, 0.866) // ~60º
|
||||
light2.name = 'main_light'
|
||||
editor.cameraControls.object.add(light2)
|
||||
|
||||
// document.getElementById('orientCubeWrapperDiv').style.display = 'none'
|
||||
window.addEventListener('resize', this.onWindowResize, false)
|
||||
const encoding = THREE.sRGBEncoding
|
||||
editor.cameraControls.resetState(editor.cameraState.target, editor.cameraState.position, editor.cameraState.zoom0)
|
||||
// 加载模型
|
||||
for (let i = 0; i < ModelList.length; i++) {
|
||||
loader.load(ModelList[i] + '.glb', function(gltf) {
|
||||
const currentModel = gltf.scene.children[0]
|
||||
if (currentModel.name === 'Plane') {
|
||||
Plane = currentModel
|
||||
}
|
||||
currentModel.traverse(child => {
|
||||
if (child['material']) {
|
||||
child['material'].metalness = 1
|
||||
if (child['material'].map) child['material'].map.encoding = encoding
|
||||
if (child['material'].emissiveMap) child['material'].emissiveMap.encoding = encoding
|
||||
if (child['material'].map || child['material'].emissiveMap) child['material'].needsUpdate = true
|
||||
editor.objMaterial.set(child.id, child['material'])
|
||||
}
|
||||
})
|
||||
scene.add(currentModel)
|
||||
scope.setObjectByConfig(currentModel)
|
||||
ModelListId.push(gltf.scene.id)
|
||||
modelCount += 1
|
||||
scope.$refs.modelProcess.innerText = '正在加载模型' + (modelCount / ModelList.length * 100).toFixed(2) + '%'
|
||||
if (modelCount === ModelList.length) {
|
||||
scope.$refs.modelProcess.style.display = 'none'
|
||||
scope.lookObject(scene)
|
||||
scope.setModelSates()
|
||||
scope.connectService()
|
||||
}
|
||||
}, (xhr) => {
|
||||
// eslint-disable-next-line handle-callback-err
|
||||
}, (error) => {
|
||||
modelCount += 1
|
||||
console.log(`加载模型${ModelList[i].ModelName}出错!`)
|
||||
if (modelCount === ModelList.length) {
|
||||
scope.$refs.modelProcess.style.display = 'none'
|
||||
scope.lookObject(scene)
|
||||
scope.setModelSates()
|
||||
scope.connectService()
|
||||
}
|
||||
})
|
||||
}
|
||||
this.animate()
|
||||
},
|
||||
|
||||
/**
|
||||
* 自适应窗口尺寸
|
||||
*/
|
||||
onWindowResize() {
|
||||
renderer.setSize(container.clientWidth, container.clientHeight)
|
||||
const s = 100; const k = container.clientWidth / container.clientHeight // 窗口宽高比
|
||||
camera.left = -s * k
|
||||
camera.right = s * k
|
||||
camera.top = s
|
||||
camera.bottom = -s
|
||||
camera.updateProjectionMatrix()
|
||||
this.render()
|
||||
},
|
||||
/**
|
||||
* 根据有配置文件设置模型初始化属性
|
||||
* @param object
|
||||
*/
|
||||
setObjectByConfig(object) {
|
||||
const objectId = object.userData['ModelId']
|
||||
const objectResource = editor.objectIdUUidMap.get(objectId)
|
||||
if (objectResource) {
|
||||
object.position.copy(
|
||||
new THREE.Vector3(parseFloat(objectResource.location.position.x),
|
||||
parseFloat(objectResource.location.position.y),
|
||||
parseFloat(objectResource.location.position.z)
|
||||
))
|
||||
object.quaternion.copy(
|
||||
new THREE.Quaternion(parseFloat(objectResource.location.quaternion.x),
|
||||
parseFloat(objectResource.location.quaternion.y),
|
||||
parseFloat(objectResource.location.quaternion.z),
|
||||
parseFloat(objectResource.location.quaternion.w)
|
||||
))
|
||||
object.visible = Boolean(parseInt(objectResource.visible))
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 渲染
|
||||
*/
|
||||
animate() {
|
||||
this.render()
|
||||
requestAnimationFrame(this.animate)
|
||||
editor.attachList.forEach((attackList, ModelId) => {
|
||||
const ModelAttachParent = editor.getObjectById(ModelId)
|
||||
if (ModelAttachParent) {
|
||||
if (!ModelAttachParent.lastMatrixWorld.equals(ModelAttachParent.matrixWorld)) {
|
||||
for (const attackListElement of attackList) {
|
||||
const attachModel = editor.getObjectById(attackListElement.ModelId)
|
||||
if (attachModel) {
|
||||
const newMatrixWord = ModelAttachParent.matrixWorld.clone()
|
||||
const cubeTransWorld = newMatrixWord.clone().multiply(ModelAttachParent.lastMatrixWorld.clone().invert()).clone()
|
||||
const cube2OldWorld = attachModel.matrixWorld.clone()
|
||||
const cube2NewWorld = cubeTransWorld.clone().multiply(cube2OldWorld)
|
||||
const cube2ParentWorld = attachModel.parent.matrixWorld.clone()
|
||||
const cube2ParentNew = (cube2ParentWorld.invert()).multiply(cube2NewWorld)
|
||||
const position = new THREE.Vector3()
|
||||
const quaternion = new THREE.Quaternion()
|
||||
const scale = new THREE.Vector3()
|
||||
cube2ParentNew.decompose(position, quaternion, scale)
|
||||
attachModel.quaternion.copy(quaternion)
|
||||
attachModel.position.copy(position)
|
||||
attachModel.updateMatrixWorld()
|
||||
}
|
||||
}
|
||||
ModelAttachParent.lastMatrixWorld = ModelAttachParent.matrixWorld.clone()
|
||||
}
|
||||
}
|
||||
})
|
||||
TWEEN.update()
|
||||
},
|
||||
|
||||
/**
|
||||
* 渲染
|
||||
*/
|
||||
render() {
|
||||
renderer.render(scene, camera)
|
||||
this.sendCameraState(camera.zoom < editor.cameraStateZoom)
|
||||
this.updatePosition(editor.selected)
|
||||
},
|
||||
connectService() {
|
||||
switch (editor.connectType) {
|
||||
case 1:
|
||||
connect(editor)
|
||||
break
|
||||
case 2:
|
||||
connectMqtt(editor)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
// drawWeldLight() {
|
||||
// const geometry = new THREE.BufferGeometry()
|
||||
// const vertices = []
|
||||
// const textureLoader = new THREE.TextureLoader()
|
||||
// const sprite1 = textureLoader.load('./css/lensflare0.png')
|
||||
// const x = 0
|
||||
// const y = 0.3
|
||||
// const z = -0.07
|
||||
// const materials = []
|
||||
// vertices.push(x, y, z)
|
||||
// geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3))
|
||||
// const parameters = [
|
||||
// [[1.0, 1, 1], sprite1, 50]
|
||||
// ]
|
||||
// const sprite = parameters[ 0 ][ 1 ]
|
||||
// const size = parameters[ 0 ][ 2 ]
|
||||
//
|
||||
// materials[ 0 ] = new THREE.PointsMaterial({ size: size, map: sprite, blending: THREE.AdditiveBlending, depthTest: false, transparent: true })
|
||||
// return new THREE.Points(geometry, materials[0])
|
||||
// },
|
||||
/**
|
||||
* 解析模型
|
||||
* @param object
|
||||
*/
|
||||
lookObject(object) {
|
||||
object.traverse(function(child) {
|
||||
if (!child) return
|
||||
// 避免modelId重复
|
||||
if (Object.hasOwnProperty.call(child.userData, 'ModelId')) {
|
||||
if (editor.objectIdSet.has(child.userData['ModelId'])) {
|
||||
child.userData['ModelId'] = child.uuid
|
||||
editor.objectIdSet.set(child.uuid, {
|
||||
ModelId: child.id,
|
||||
Name: child.name,
|
||||
position: JSON.parse(JSON.stringify(child.position)),
|
||||
rotation: JSON.parse(JSON.stringify(child.rotation)),
|
||||
quaternion: JSON.parse(JSON.stringify(child.quaternion)),
|
||||
object: child,
|
||||
matrix: child.matrix.clone(),
|
||||
lastValue: null
|
||||
})
|
||||
} else {
|
||||
editor.objectIdSet.set(child.userData['ModelId'], {
|
||||
ModelId: child.id,
|
||||
Name: child.name,
|
||||
position: JSON.parse(JSON.stringify(child.position)),
|
||||
rotation: JSON.parse(JSON.stringify(child.rotation)),
|
||||
quaternion: JSON.parse(JSON.stringify(child.quaternion)),
|
||||
object: child,
|
||||
matrix: child.matrix.clone(),
|
||||
lastValue: null
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
for (const robotListInfo in editor.robotList) {
|
||||
for (const info of editor.robotList[robotListInfo]) {
|
||||
info.object = editor.getObjectById(info.ModelId)
|
||||
info.matrix = info.object ? info.object.matrix.clone() : null
|
||||
info.lastValue = null
|
||||
}
|
||||
}
|
||||
object.traverse(function(child) {
|
||||
child.lastMatrixWorld = child.matrixWorld.clone()
|
||||
})
|
||||
window.editor = editor
|
||||
},
|
||||
/**
|
||||
* 获取预置焦点
|
||||
*/
|
||||
getCameraData() {
|
||||
var param = []
|
||||
param[0] = ['ProjectCode', editor.ProjectCode]
|
||||
var Data = this.CreateData('3', 'select * from dbo.[基础数据_焦点预置]', param)
|
||||
this.ExecDatabase(Data).then(response => {
|
||||
this.cameraData = response.data
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 点击后 到达预置焦点
|
||||
*/
|
||||
setViewCamera() {
|
||||
if (this.cameraData.length === 0) return
|
||||
const scope = this
|
||||
const viewOperation = document.getElementById('viewOperationParent')
|
||||
const viewOperationChild = document.getElementById('viewOperationChild')
|
||||
viewOperation.onmouseover = function() {
|
||||
viewOperationChild.style.display = 'block'
|
||||
}
|
||||
viewOperation.onmouseout = function() {
|
||||
viewOperationChild.style.display = 'none'
|
||||
}
|
||||
setTimeout(() => {
|
||||
for (const info of this.cameraData) {
|
||||
$('#cameraViewBtn' + info.焦点代码).on('click', function(e) {
|
||||
const data = JSON.parse(info.焦点位置)
|
||||
const target = data.target
|
||||
const position = data.position
|
||||
const zoom0 = data.zoom0
|
||||
const id = info.焦点代码
|
||||
$('#cameraViewBtn' + info.焦点代码).css({ 'background': '#00893d', 'color': '#fff' })
|
||||
editor.cameraControls.resetState(target, position, zoom0)
|
||||
for (const infoColor of scope.cameraData) {
|
||||
if (id !== infoColor.焦点代码) {
|
||||
$('#cameraViewBtn' + infoColor.焦点代码).css({ 'background': '#fff', 'color': '#606266' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
getMousePosition(dom, x, y) {
|
||||
const rect = dom.getBoundingClientRect()
|
||||
return [(x - rect.left) / rect.width, (y - rect.top) / rect.height]
|
||||
},
|
||||
ondblclick(event) {
|
||||
try {
|
||||
const array = this.getMousePosition(container, event.clientX, event.clientY)
|
||||
onUpPosition.fromArray(array)
|
||||
let intersects = this.getIntersects(onUpPosition, editor.scene.children)
|
||||
intersects = intersects.filter(res => {
|
||||
return res.object.visible
|
||||
})
|
||||
if (intersects.length > 0) {
|
||||
const rootObjId = intersects[0].object.userData['RootId']
|
||||
editor.selected = editor.getObjectById(rootObjId)
|
||||
editor.dispatchEvent({
|
||||
type: 'dmtModelDBClick',
|
||||
ModelId: editor.selected.userData['ModelId'],
|
||||
ModelName: editor.selected.userData['name']
|
||||
})
|
||||
} else {
|
||||
editor.selected = null
|
||||
editor.dispatchEvent({
|
||||
type: 'dmtModelDBClick',
|
||||
ModelId: '',
|
||||
ModelName: ''
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
editor.selected = null
|
||||
editor.dispatchEvent({
|
||||
type: 'dmtModelDBClick',
|
||||
ModelId: '',
|
||||
ModelName: ''
|
||||
})
|
||||
}
|
||||
},
|
||||
getIntersects(point, objects) {
|
||||
mouse.set((point.x * 2) - 1, -(point.y * 2) + 1)
|
||||
raycaster.setFromCamera(mouse, camera)
|
||||
return raycaster.intersectObjects(objects, true)
|
||||
},
|
||||
sendCameraState(state) {
|
||||
this.$emit('changeCameraState', state)// 自定义事件 传递值“子向父组件传值”
|
||||
},
|
||||
updatePosition() {
|
||||
if (editor.selected) {
|
||||
const targetPosition = editor.selected.getWorldPosition(new THREE.Vector3()).clone().project(camera)
|
||||
const halfWidth = window.innerWidth / 2
|
||||
const halfHeight = window.innerHeight / 2
|
||||
const position = {
|
||||
left: targetPosition.x * halfWidth + halfWidth - 200,
|
||||
top: -targetPosition.y * halfHeight + halfHeight - 500
|
||||
}
|
||||
this.$emit('clickModel', editor.selected, position)// 自定义事件 传递值“子向父组件传值”
|
||||
}
|
||||
},
|
||||
getDeltaPosition(positionModel, positionLabel) {
|
||||
return new THREE.Vector3((positionLabel.x - positionModel.x), (positionLabel.y - positionModel.y), (positionLabel.z - positionModel.z))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
html{
|
||||
height: 99%;
|
||||
}
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
1508
src/components/DMT/indexLG.vue
Normal file
1250
src/components/DMT/indexVR.vue
Normal file
139
src/components/DMT/js/DmtControl.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
|
||||
import * as TWEEN from 'three/examples/jsm/libs/tween.module.js'
|
||||
|
||||
// import { viewCubeUpdate } from './DmtViewCube'
|
||||
const _changeEvent = { type: 'change' }
|
||||
class DmtControl extends OrbitControls {
|
||||
constructor(object, domElement) {
|
||||
super(object, domElement)
|
||||
this.cubeControlDirection = {
|
||||
'TOP': 'TOP',
|
||||
'BOTTOM': 'BOTTOM',
|
||||
'FRONT': 'FRONT',
|
||||
'BACK': 'BACK',
|
||||
'LEFT': 'LEFT',
|
||||
'RIGHT': 'RIGHT',
|
||||
|
||||
'BACK-LEFT': 'BACK-LEFT',
|
||||
'FRONT-LEFT': 'FRONT-LEFT',
|
||||
'TOP-LEFT': 'TOP-LEFT',
|
||||
'TOP-RIGHT': 'TOP-RIGHT',
|
||||
'RIGHT-BOTTOM': 'RIGHT-BOTTOM',
|
||||
'LEFT-BOTTOM': 'LEFT-BOTTOM',
|
||||
'BACK-BOTTOM': 'BACK-BOTTOM',
|
||||
'TOP-FRONT': 'TOP-FRONT',
|
||||
'FRONT-BOTTOM': 'FRONT-BOTTOM',
|
||||
'TOP-BACK': 'TOP-BACK',
|
||||
'RIGHT-FRONT': 'RIGHT-FRONT',
|
||||
'BACK-RIGHT': 'BACK-RIGHT',
|
||||
|
||||
'BACK-RIGHT-BOTTOM': 'BACK-RIGHT-BOTTOM',
|
||||
'TOP-RIGHT-BACK': 'TOP-RIGHT-BACK',
|
||||
'TOP-LEFT-BACK': 'TOP-LEFT-BACK',
|
||||
'TOP-LEFT-FRONT': 'TOP-LEFT-FRONT',
|
||||
'FRONT-BOTTOM-RIGHT': 'FRONT-BOTTOM-RIGHT',
|
||||
'TOP-RIGHT-FRONT': 'TOP-RIGHT-FRONT',
|
||||
'BACK-BOTTOM-LEFT': 'BACK-BOTTOM-LEFT',
|
||||
'FRONT-BOTTOM-LEFT': 'FRONT-BOTTOM-LEFT'
|
||||
}
|
||||
|
||||
const scope = this
|
||||
const STATE = {
|
||||
NONE: -1,
|
||||
ROTATE: 0,
|
||||
DOLLY: 1,
|
||||
PAN: 2,
|
||||
TOUCH_ROTATE: 3,
|
||||
TOUCH_PAN: 4,
|
||||
TOUCH_DOLLY_PAN: 5,
|
||||
TOUCH_DOLLY_ROTATE: 6
|
||||
}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let state = STATE.NONE
|
||||
this.resetState = function(target0, position0, zoom0) {
|
||||
scope.target.copy(target0)
|
||||
scope.object.position.copy(position0)
|
||||
if (scope.object.isOrthographicCamera) {
|
||||
scope.object.zoom = zoom0
|
||||
} else {
|
||||
scope.object.zoom = 1
|
||||
}
|
||||
scope.object.updateProjectionMatrix()
|
||||
scope.dispatchEvent(_changeEvent)
|
||||
scope.update()
|
||||
state = STATE.NONE
|
||||
// viewCubeUpdate()
|
||||
}
|
||||
|
||||
this.resetStateTween = function(target1, current1, target2, current2, duration) {
|
||||
const scope = this
|
||||
const positionVar = {
|
||||
x1: current1.x,
|
||||
y1: current1.y,
|
||||
z1: current1.z,
|
||||
x2: target1.x,
|
||||
y2: target1.y,
|
||||
z2: target1.z
|
||||
}
|
||||
// 关闭控制器
|
||||
scope.enabled = false
|
||||
var tween = new TWEEN.Tween(positionVar)
|
||||
tween.to({
|
||||
x1: current2.x,
|
||||
y1: current2.y,
|
||||
z1: current2.z,
|
||||
x2: target2.x,
|
||||
y2: target2.y,
|
||||
z2: target2.z
|
||||
}, duration)
|
||||
tween.onUpdate(function() {
|
||||
scope.object.position.x = positionVar.x1
|
||||
scope.object.position.y = positionVar.y1
|
||||
scope.object.position.z = positionVar.z1
|
||||
scope.target.x = positionVar.x2
|
||||
scope.target.y = positionVar.y2
|
||||
scope.target.z = positionVar.z2
|
||||
scope.object.updateProjectionMatrix()
|
||||
scope.update()
|
||||
})
|
||||
tween.start()
|
||||
tween.onComplete(function() {
|
||||
// /开启控制器
|
||||
scope.enabled = true
|
||||
})
|
||||
tween.easing(TWEEN.Easing.Cubic.InOut)
|
||||
}
|
||||
|
||||
this.setLookAt = function(position0, target0, zoom0) {
|
||||
scope.target.copy(target0)
|
||||
scope.object.position.copy(position0)
|
||||
scope.object.zoom = zoom0
|
||||
scope.object.updateProjectionMatrix()
|
||||
scope.dispatchEvent(_changeEvent)
|
||||
scope.update()
|
||||
state = STATE.NONE
|
||||
}
|
||||
this.setLookAt1 = function(target0, rotation) {
|
||||
scope.target.copy(this.target)
|
||||
scope.object.rotation.copy(rotation)
|
||||
scope.object.updateProjectionMatrix()
|
||||
scope.dispatchEvent(_changeEvent)
|
||||
scope.update()
|
||||
state = STATE.NONE
|
||||
}
|
||||
this.moveTo = function(x, y, z) {
|
||||
scope.target.set(x, y, z)
|
||||
}
|
||||
this.getState = function() {
|
||||
return {
|
||||
target: scope.target.clone(),
|
||||
position: scope.object.position.clone(),
|
||||
zoom: parseFloat(scope.object.zoom.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export {
|
||||
DmtControl
|
||||
}
|
||||
|
||||
78
src/components/DMT/js/MqttConnect.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import { WebsocketMessageReceive } from './WebSocketMessage.js'
|
||||
import mqtt from 'mqtt'
|
||||
|
||||
function connectMqtt(editor) {
|
||||
const connection = {
|
||||
host: editor.mqttHost,
|
||||
port: editor.mqttPort,
|
||||
endpoint: '/mqtt',
|
||||
clean: true, // 保留会话
|
||||
connectTimeout: 4000, // 超时时间
|
||||
reconnectPeriod: 4000, // 重连时间间隔
|
||||
// 认证信息
|
||||
clientId: 'mqttjs_3be2c321' + new Date().getTime(), // 同一个ID 无法二次连接
|
||||
username: '',
|
||||
password: ''
|
||||
}
|
||||
const subscription = {
|
||||
topic: editor.opname,
|
||||
qos: 0
|
||||
}
|
||||
const websocketMessageReceive = new WebsocketMessageReceive(editor)
|
||||
|
||||
const { host, port, endpoint, ...options } = connection
|
||||
const wsHeaderString = location.protocol === 'https:' ? 'wss' : 'ws'
|
||||
// const connectUrl = `${wsHeaderString}://${location.host}${endpoint}`
|
||||
const connectUrl = `${wsHeaderString}://${host}:${port}${endpoint}`
|
||||
console.log(connectUrl)
|
||||
try {
|
||||
editor.mqttClient = mqtt.connect(connectUrl, options)
|
||||
} catch (error) {
|
||||
console.log('MQTT connect error', error)
|
||||
}
|
||||
editor.mqttClient.on('connect', () => {
|
||||
editor.showMessage('加载完成,连接MQTT服务器成功!', 'success', 2000)
|
||||
console.log('MQTT Connection succeeded!')
|
||||
editor.isOk = true
|
||||
const { topic, qos } = subscription
|
||||
editor.mqttClient.subscribe(topic, { qos }, (error, res) => {
|
||||
if (error) {
|
||||
console.log('Subscribe to topics error', error)
|
||||
return
|
||||
}
|
||||
websocketMessageReceive.getOriginTagValue()
|
||||
})
|
||||
})
|
||||
editor.mqttClient.on('error', error => {
|
||||
console.log('MQTT Connection failed', error)
|
||||
try {
|
||||
editor.showMessage('MQTT服务器连接失败', 'error', 2000)
|
||||
editor.socket.close()
|
||||
if (editor.IsAllowConnect) {
|
||||
// 显示一下
|
||||
editor.showMessage('重新连接MQTT服务器', 'warning', 2000)
|
||||
console.log('重新连接MQTT服务器')
|
||||
// 重新连接,打开计时器
|
||||
setTimeout(function() {
|
||||
connectMqtt(editor)
|
||||
}, 5000)
|
||||
}
|
||||
} catch (exception) {
|
||||
const error = exception.toString()
|
||||
editor.showMessage.error('MQTT服务器出现错误,关闭连接' + error, 'error', 2000)
|
||||
}
|
||||
})
|
||||
// 接收消息
|
||||
editor.mqttClient.on('message', (topic, message) => {
|
||||
try {
|
||||
websocketMessageReceive.websocketMessage({ data: message.toString() })
|
||||
} catch (exception) {
|
||||
console.log(exception.toString())
|
||||
}
|
||||
})
|
||||
editor.mqttClient.on('close', () => {
|
||||
editor.mqttClient.end()
|
||||
})
|
||||
}
|
||||
export { connectMqtt }
|
||||
|
||||
40
src/components/DMT/js/ObjectAlarmCommand.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import * as THREE from 'three'
|
||||
|
||||
/**
|
||||
* @param editor Editor
|
||||
* @param object THREE.Object3D
|
||||
* @constructor
|
||||
*/
|
||||
class ObjectAlarmCommand {
|
||||
constructor(editor, ModelId) {
|
||||
this.type = 'ObjectAlarmCommand'
|
||||
this.ModelId = ModelId
|
||||
this.editor = editor
|
||||
this.object = editor.getObjectById(ModelId)
|
||||
}
|
||||
|
||||
exec(isAlarm = true) {
|
||||
if (!this.object) return
|
||||
if (isAlarm) {
|
||||
this.object.traverse(object => {
|
||||
if (object instanceof THREE.Mesh && !Object.hasOwnProperty.call(object.userData, 'isAxis')) {
|
||||
object.material.color = new THREE.Color('#ff0000')
|
||||
object.material.side = 2
|
||||
object.material.transparent = true
|
||||
object.material.opacity = 0.8
|
||||
}
|
||||
})
|
||||
// this.editor.playSoundCommand.exec('Alarm', true, this.ModelId)
|
||||
} else {
|
||||
this.object.traverse(object => {
|
||||
if (object instanceof THREE.Mesh) {
|
||||
const m = this.editor.objMaterial.get(object.id)
|
||||
if (m)object.material = m.clone()
|
||||
}
|
||||
})
|
||||
// this.editor.playSoundCommand.exec('Alarm', false, this.ModelId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { ObjectAlarmCommand }
|
||||
58
src/components/DMT/js/PlaySoundCommand.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @param editor Editor
|
||||
* @param object THREE.Object3D
|
||||
* @constructor
|
||||
*/
|
||||
class PlaySoundCommand {
|
||||
constructor() {
|
||||
this.type = 'PlaySoundCommand'
|
||||
this.PlayType = {
|
||||
Alarm: 'Alarm',
|
||||
Clash: 'Clash'
|
||||
}
|
||||
this.AlarmList = []
|
||||
this.isAlarm = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统声音
|
||||
* @param type 报警Alarm, 碰撞Clash 。。。。。。。
|
||||
* @param state 报警/解除报警
|
||||
* @param ModelId 模型ID
|
||||
*/
|
||||
exec(type, state = true, ModelId) {
|
||||
switch (type) {
|
||||
case this.PlayType.Alarm:
|
||||
if (state) {
|
||||
if (!this.isAlarm) this.playAlarmSound(true)
|
||||
if (this.AlarmList.indexOf(ModelId) === -1) this.AlarmList.push(ModelId)
|
||||
this.isAlarm = true
|
||||
} else {
|
||||
this.AlarmList = this.AlarmList.filter(res => { return res !== ModelId })
|
||||
if (this.AlarmList.length === 0) {
|
||||
this.playAlarmSound(false)
|
||||
this.isAlarm = false
|
||||
}
|
||||
}
|
||||
break
|
||||
case this.PlayType.Clash:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
playAlarmSound(play = true) {
|
||||
// const audio = document.getElementById('alarm')
|
||||
// play ? audio.play() : audio.pause()
|
||||
}
|
||||
playClashSound(play) {
|
||||
const audio = document.getElementById('clash')
|
||||
play ? audio.play() : audio.pause()
|
||||
}
|
||||
}
|
||||
|
||||
export { PlaySoundCommand }
|
||||
629
src/components/DMT/js/WebSocketMessage.js
Normal file
@@ -0,0 +1,629 @@
|
||||
import * as THREE from 'three'
|
||||
import { ObjectAlarmCommand } from './ObjectAlarmCommand.js'
|
||||
import * as TWEEN from 'three/examples/jsm/libs/tween.module.js'
|
||||
|
||||
const WebsocketMessageReceive = function(editor) {
|
||||
const scope = this
|
||||
this.websocketMessage = (msg) => {
|
||||
new Promise((resolve, reject) => {
|
||||
let returnMes
|
||||
const arrays_signal = msg.data.split('|')
|
||||
const m_SourceType = arrays_signal[0] // 数据类型:DT:数字孪生数据
|
||||
const m_DeviceType = arrays_signal[1] // 设备类型,Robot:机器人类型的设备,Device:普通设备
|
||||
const m_DeviceName = arrays_signal[2] // 设备名称
|
||||
const m_DevicePartName = arrays_signal[3] // 设备运动部件
|
||||
const m_DevicePartDate = arrays_signal[4] // 设备运动部件数据
|
||||
switch (m_SourceType) {
|
||||
case 'DT':
|
||||
switch (m_DeviceType) {
|
||||
case editor.ProtocolIdentifiers.Robot_7: // 接受机器人运动数据进行播放 --------------------------现场用----------------
|
||||
returnMes = scope.deviceMoveEvent.RobotTranslation7(m_DeviceName, m_DevicePartName, m_DevicePartDate)
|
||||
break
|
||||
case editor.ProtocolIdentifiers.Device_3:
|
||||
console.log('设备运行')// --------------------------现场用----------------
|
||||
returnMes = scope.deviceMoveEvent.DeviceThreeAxis(m_DeviceName, m_DevicePartName, m_DevicePartDate)
|
||||
break
|
||||
case editor.ProtocolIdentifiers.Agv_3:// --------------------------现场用----------------
|
||||
console.log('AGV运行')
|
||||
returnMes = scope.deviceMoveEvent.AGVThreeAxis(m_DeviceName, m_DevicePartName, m_DevicePartDate)
|
||||
break
|
||||
case editor.ProtocolIdentifiers.Device_1: // 接受设备运动数据进行播放,已更新
|
||||
break
|
||||
case editor.ProtocolIdentifiers.FirstPosition: // 接受第一个点的数据,瞬间跳到该点,已更新
|
||||
break
|
||||
case editor.ProtocolIdentifiers.WareHousePart: //
|
||||
returnMes = scope.drawWareHousePartOne(m_DeviceName)
|
||||
break
|
||||
case editor.ProtocolIdentifiers.Robot_6: // 接受机器人运动数据进行播放
|
||||
returnMes = scope.deviceMoveEvent.RobotTranslation(m_DeviceName, m_DevicePartName, m_DevicePartDate)
|
||||
break
|
||||
case editor.ProtocolIdentifiers.ReLoadData: // 加载所有要播放的数据,避免资源浪费 ***********************UUID测试好用了******************************
|
||||
break
|
||||
case editor.ProtocolIdentifiers.PlayAct: // 根据ID 播放数据 状态为1 0暂停 ***********************UUID测试好用了******************************
|
||||
scope.playActData(m_DeviceName, parseInt(m_DevicePartName))
|
||||
break
|
||||
// case editor.ProtocolIdentifiers.ModelTransparent: // 根据传过来的模型名字和透明度 改变模型的透明度 ***********************UUID测试好用了******************************
|
||||
// returnMes = scope.robotPlan_ModelOperation.modelTrans(m_DeviceName, m_DevicePartName)
|
||||
// break
|
||||
case editor.ProtocolIdentifiers.Visible: // 接受CS指令显示、隐藏模型 ***********************UUID测试好用了******************************
|
||||
returnMes = scope.visibleObjectToBS(m_DeviceName, m_DevicePartName)
|
||||
break
|
||||
case 'JointMoveTo':
|
||||
if (!JSON.parse(m_DevicePartName)) console.log('JointMoveTo数据不对' + m_DevicePartName)
|
||||
this.JointMoveTo(m_DeviceName, JSON.parse(m_DevicePartName))
|
||||
break
|
||||
case editor.ProtocolIdentifiers.C_B_TagIDTrigger:
|
||||
{
|
||||
const tag = JSON.parse(m_DeviceName)
|
||||
scope.solveTagValue(tag.TagID, tag.TagValue)
|
||||
}
|
||||
break
|
||||
case editor.ProtocolIdentifiers.C_B_ObjectAlarm:
|
||||
{
|
||||
const Info = JSON.parse(m_DeviceName)
|
||||
console.log(Info)
|
||||
for (const infoElement of Info) {
|
||||
new ObjectAlarmCommand(editor, infoElement.ModelId).exec(!!parseInt(infoElement.value))
|
||||
}
|
||||
}
|
||||
break
|
||||
case editor.ProtocolIdentifiers.C_B_JointMoveTo:
|
||||
{
|
||||
const MoveValueList = JSON.parse(m_DeviceName)
|
||||
scope.ModelJointMoveTo(MoveValueList)
|
||||
}
|
||||
break
|
||||
case 'showWeld':
|
||||
this.showWeld(m_DeviceName, m_DevicePartName)
|
||||
break
|
||||
case 'showCheckOk':
|
||||
this.showCheckOk(m_DeviceName, m_DevicePartName)
|
||||
break
|
||||
default:
|
||||
returnMes = '不存在协议:' + msg
|
||||
break
|
||||
}
|
||||
break
|
||||
default:
|
||||
returnMes = '不存在协议:' + msg
|
||||
break
|
||||
}
|
||||
resolve(returnMes)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* HandlerType: 1:显隐,2:自转,3:行为,4:私服位置,5:附加拆离指令
|
||||
* HandlerCode: 模型或者工艺代码
|
||||
* HandlerValue: 显隐:0/1 ,自传:正负转速&axis,行为暂停播放:0/1
|
||||
* TagValueTagValue: 信号值
|
||||
* @param tagId
|
||||
* @param tagValue
|
||||
*/
|
||||
this.solveTagValue = (tagId, tagValue) => {
|
||||
const scope = this
|
||||
const actionInfo = editor.TagActList[tagId]
|
||||
if (!actionInfo) return
|
||||
for (const actionInfoElement of actionInfo) {
|
||||
switch (actionInfoElement.HandlerType) {
|
||||
case editor.signalSolveType.warning: // 报警
|
||||
if (tagValue === actionInfoElement.TagValue) {
|
||||
new ObjectAlarmCommand(editor, actionInfoElement.HandlerCode).exec(!!parseInt(actionInfoElement.HandlerValue))
|
||||
}
|
||||
break
|
||||
case editor.signalSolveType.visible: // 显隐
|
||||
if (tagValue === actionInfoElement.TagValue) {
|
||||
scope.visibleObjectToBS(actionInfoElement.HandlerCode, parseInt(actionInfoElement.HandlerValue))
|
||||
}
|
||||
break
|
||||
case editor.signalSolveType.rotation: // 自转
|
||||
if (tagValue === actionInfoElement.TagValue) {
|
||||
const ValueFactor = actionInfoElement.ValueFactor
|
||||
const Axis = actionInfoElement.Axis
|
||||
// const JointType = actionInfoElement.JointType
|
||||
const Flip = actionInfoElement.Flip
|
||||
const ModelCode = actionInfoElement.HandlerCode
|
||||
scope.modelRotation(ModelCode, Axis, Flip * parseFloat(ValueFactor))
|
||||
}
|
||||
break
|
||||
case editor.signalSolveType.craft: // 行为
|
||||
if (tagValue === actionInfoElement.TagValue) {
|
||||
scope.playActData(actionInfoElement.HandlerCode, parseInt(actionInfoElement.HandlerValue))
|
||||
}
|
||||
break
|
||||
case editor.signalSolveType.targetLocation: // 伺服位置
|
||||
scope.solveServoSignal(actionInfoElement, tagValue)
|
||||
break
|
||||
case editor.signalSolveType.attach: // 附加功能
|
||||
console.log('这个地方的附加功能没用写')
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 根据ACTid播放动作,开启线程
|
||||
* @param actID
|
||||
* @param state
|
||||
*/
|
||||
this.playActData = (actID, state) => {
|
||||
const scope = this
|
||||
if (!editor.isOk) return
|
||||
if (!editor.worker[actID]) editor.worker[actID] = new Worker('./DMT_WEB/webWorker/playFunction.js')
|
||||
if (editor.moveTrigger[actID].moveStatus !== state) {
|
||||
editor.worker[actID].postMessage({
|
||||
ActID: actID,
|
||||
State: state,
|
||||
isOk: editor.isOk,
|
||||
craftPlaySolveType: editor.craftPlaySolveType,
|
||||
moveTrigger: JSON.stringify(editor.moveTrigger[actID])
|
||||
})
|
||||
editor.moveTrigger[actID].moveStatus = state
|
||||
}
|
||||
editor.worker[actID].onmessage = function(event) {
|
||||
editor.moveTrigger[actID].myVarSetInterval = event.data.myVarSetInterval
|
||||
editor.moveTrigger[actID].currentFrame = event.data.pausedFrame
|
||||
const currentFrameData = event.data.currentFrameData
|
||||
const frameType = event.data.frameType
|
||||
editor.solveAttachModel('', true)
|
||||
switch (frameType) {
|
||||
case editor.craftPlaySolveType.attach:
|
||||
if (editor.FrameOperation.attach) {
|
||||
const position = currentFrameData['AttachToModelCode']
|
||||
const List_AttachModel = currentFrameData['List_AttachModel']
|
||||
const IsTwoWay = currentFrameData['IsTwoWay']
|
||||
const IsAttach = currentFrameData['IsAttach']
|
||||
if (IsAttach) {
|
||||
const attachData = []
|
||||
for (const editorElement of List_AttachModel) {
|
||||
attachData.push({
|
||||
ModelId: editorElement.ModelCode,
|
||||
ModelName: editorElement.ModelName,
|
||||
RootId: position,
|
||||
IsTwoWay: IsTwoWay
|
||||
})
|
||||
}
|
||||
editor.attachList.set(position, attachData)
|
||||
} else {
|
||||
for (const detachData of List_AttachModel) {
|
||||
const detachModelCode = detachData.ModelCode
|
||||
editor.scene.attach(editor.getObjectById(detachModelCode))
|
||||
editor.attachList.forEach((value, key) => {
|
||||
const attachList = value.filter(res => { return res.ModelId !== detachModelCode })
|
||||
editor.attachList.set(key, attachList)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
case editor.craftPlaySolveType.visible:
|
||||
if (editor.FrameOperation.visibleObject) {
|
||||
const position = currentFrameData['ModelCode']
|
||||
const Visible = parseInt(currentFrameData['Visible'])
|
||||
scope.visibleObjectToBS(position, Visible)
|
||||
}
|
||||
break
|
||||
case editor.craftPlaySolveType.rotation:
|
||||
if (editor.FrameOperation.rotationObject) {
|
||||
const position = currentFrameData['ModelCode']
|
||||
const Axis = currentFrameData['Axis']
|
||||
const Rate = currentFrameData['Rate']
|
||||
scope.modelRotation(position, Axis, Rate)
|
||||
}
|
||||
break
|
||||
case editor.craftPlaySolveType.craft:
|
||||
if (editor.FrameOperation.playCraft) {
|
||||
const position = currentFrameData['CraftCode']
|
||||
const craftState = parseInt(currentFrameData['CraftValue'])
|
||||
scope.playActData(position, craftState)
|
||||
}
|
||||
break
|
||||
case editor.craftPlaySolveType.FrameData:
|
||||
{
|
||||
const position = currentFrameData['i']
|
||||
const objectToMove = editor.getObjectById(position)
|
||||
if (objectToMove) {
|
||||
if (currentFrameData.tx === 'NaN' || currentFrameData.ty === 'NaN' || currentFrameData.tz === 'NaN' || currentFrameData.qx === 'NaN' || currentFrameData.qy === 'NaN' || currentFrameData.qz === 'NaN' || currentFrameData.qw === 'NaN') {
|
||||
console.log('craftDataError')
|
||||
// editor.robotPlan_MessageList.execute(position, 'craftDataError')
|
||||
} else {
|
||||
const movePosition = new THREE.Vector3(parseFloat(currentFrameData.tx), parseFloat(currentFrameData.ty), parseFloat(currentFrameData.tz))
|
||||
const nq = new THREE.Quaternion(parseFloat(currentFrameData.qx), parseFloat(currentFrameData.qy), parseFloat(currentFrameData.qz), parseFloat(currentFrameData.qw))
|
||||
objectToMove.quaternion.copy(nq)
|
||||
objectToMove.position.copy(movePosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
case editor.craftPlaySolveType.warning: // 报警
|
||||
if (editor.FrameOperation.warning) {
|
||||
new ObjectAlarmCommand(scope.editor, currentFrameData['ModelCode']).exec(!!parseInt(currentFrameData['Visible']))
|
||||
}
|
||||
break
|
||||
default:
|
||||
delete editor.worker[event.data.ActID]
|
||||
editor.moveTrigger[actID].moveStatus = 0
|
||||
editor.moveTrigger[actID].myVarSetInterval = null
|
||||
editor.moveTrigger[actID].currentFrame = 0
|
||||
editor.moveTrigger[actID].pausedFrame = 0
|
||||
break
|
||||
}
|
||||
editor.solveAttachModel('', false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 接受CS模型自转的指令
|
||||
* 接受CS模型自转的指令
|
||||
* @param objectNames
|
||||
* @param values
|
||||
* @param axis
|
||||
*/
|
||||
this.modelRotation = (objectNames, axis, values) => {
|
||||
const scope = this
|
||||
const EP = 0.000001
|
||||
let dir = 0
|
||||
if (!editor.getObjectById(objectNames)) return
|
||||
const currentModel = editor.getObjectById(objectNames)
|
||||
if ((parseFloat(values) >= -EP) && (parseFloat(values) <= EP)) {
|
||||
if (editor.RotationWorker[objectNames]) {
|
||||
editor.RotationWorker[objectNames].terminate()
|
||||
delete editor.RotationWorker[objectNames]
|
||||
delete editor.ModelRotateWorker[objectNames]
|
||||
}
|
||||
return
|
||||
} else {
|
||||
dir = 1
|
||||
if (editor.RotationWorker[objectNames]) {
|
||||
editor.RotationWorker[objectNames].terminate()
|
||||
delete editor.RotationWorker[objectNames]
|
||||
delete editor.ModelRotateWorker[objectNames]
|
||||
}
|
||||
}
|
||||
editor.RotationWorker[objectNames] = new Worker('webWorker/modelRotation.js')
|
||||
|
||||
if (!editor.ModelRotateWorker[objectNames]) {
|
||||
editor.ModelRotateWorker[objectNames] = {
|
||||
state: 0,
|
||||
myVarSetInterval: null,
|
||||
doAnimation: false,
|
||||
ModelCode: objectNames
|
||||
}
|
||||
}
|
||||
|
||||
editor.RotationWorker[objectNames].postMessage({
|
||||
ObjectNames: objectNames,
|
||||
Values: values,
|
||||
Dir: dir,
|
||||
ModelRotateWorker: editor.ModelRotateWorker[objectNames],
|
||||
RotateSpeed: editor.rotateSpeed,
|
||||
RotationValue: currentModel.rotation[axis]
|
||||
})
|
||||
|
||||
editor.RotationWorker[objectNames].onmessage = (event) => {
|
||||
scope.modelSelfRotation(currentModel, event.data.rotation, axis)
|
||||
}
|
||||
}
|
||||
this.modelSelfRotation = (currentModel, rotation, axis) => {
|
||||
currentModel.rotation[axis] = rotation
|
||||
}
|
||||
this.ModelJointMoveTo = (MoveValueList) => {
|
||||
// TOBS?WEB?SCADAMain?DT|C_B_JointMoveTo|[{"ModelCode":"5E6D91B8-B44D-4C44-AAEF-10492E548A1A","JointType":"rotation","Axis":"x","Flip":1,"Value":0,"ValueFactor":0.017453292519943295,"Delta":0}]
|
||||
// TOBS?WEB?SCADAMain?DT|C_B_JointMoveTo|[{"ModelCode":"644506B1-A05A-4E57-BBA2-C63635586C63","JointType":"position","Axis":"y","Flip":-1,"Value":-15.0,"ValueFactor":0.001,"Delta":-655.0}]
|
||||
new Promise(resolve => {
|
||||
for (const MoveValue of MoveValueList) {
|
||||
const ModelCode = MoveValue.ModelCode
|
||||
const ValueFactor = parseFloat(MoveValue.ValueFactor)
|
||||
const type = MoveValue.JointType
|
||||
const Value = parseFloat(MoveValue.Value)
|
||||
const Flip = parseFloat(MoveValue.Flip)
|
||||
const axis = MoveValue.Axis
|
||||
const Delta = parseFloat(MoveValue.Delta)
|
||||
const object = editor.getObjectById(ModelCode)
|
||||
const originInfo = editor.objectIdSet.get(ModelCode)
|
||||
if (!originInfo) return
|
||||
const matrix = originInfo['matrix']
|
||||
const originValue = type === 'rotation' ? originInfo[type]['_' + axis] : originInfo[type][axis]
|
||||
const lastValue = originInfo['lastValue'] === null ? originValue : originInfo['lastValue']
|
||||
const moveValue = Flip * (Value - Delta) * ValueFactor
|
||||
|
||||
if (object) {
|
||||
editor.solveAttachModel('', true)
|
||||
const JointValue = originValue + moveValue
|
||||
new TWEEN.Tween({
|
||||
moveValue: lastValue
|
||||
}).to({ moveValue: JointValue }, 100)
|
||||
.onStart(function() {
|
||||
originInfo['lastValue'] = JointValue
|
||||
}).onUpdate(function() {
|
||||
switch (type) {
|
||||
case 'position':
|
||||
object[type][axis] = this._object.moveValue
|
||||
break
|
||||
case 'rotation':
|
||||
{
|
||||
const moveAxis = {
|
||||
x: new THREE.Vector3(1, 1, 1),
|
||||
y: new THREE.Vector3(1, 1, 1),
|
||||
z: new THREE.Vector3(1, 1, 1)
|
||||
}
|
||||
matrix.extractBasis(moveAxis.x, moveAxis.y, moveAxis.z)
|
||||
const nM = new THREE.Matrix4()
|
||||
nM.makeRotationAxis(moveAxis[axis], this._object.moveValue)
|
||||
nM.multiply(matrix)
|
||||
object.rotation.setFromRotationMatrix(nM)
|
||||
object.updateWorldMatrix()
|
||||
}
|
||||
break
|
||||
}
|
||||
}).start()
|
||||
editor.solveAttachModel('', false)
|
||||
} else {
|
||||
console.log('模型不存在')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 接伺服类的数据
|
||||
* @param ModelInfo
|
||||
* @param tagValue
|
||||
*/
|
||||
this.solveServoSignal = (ModelInfo, tagValue) => {
|
||||
try {
|
||||
const type = ModelInfo.JointType
|
||||
const axis = ModelInfo.Axis
|
||||
const flip = ModelInfo.Flip
|
||||
const Value = ModelInfo.Value
|
||||
const ValueFactor = ModelInfo.ValueFactor // 倍率
|
||||
const ModelId = ModelInfo.HandlerCode // 模型代码
|
||||
const object = editor.getObjectById(ModelId)
|
||||
const originInfo = editor.objectIdSet.get(ModelId)
|
||||
if (!originInfo) return
|
||||
const matrix = originInfo['matrix']
|
||||
const originValue = type === 'rotation' ? originInfo[type]['_' + axis] : originInfo[type][axis]
|
||||
const lastValue = originInfo['lastValue'] === null ? originValue : originInfo['lastValue']
|
||||
const moveValue = flip * (parseFloat(tagValue) - Value) * ValueFactor
|
||||
|
||||
if (object) {
|
||||
editor.solveAttachModel('', true)
|
||||
const JointValue = originValue + moveValue
|
||||
new TWEEN.Tween({
|
||||
moveValue: lastValue
|
||||
}).to({ moveValue: JointValue }, 100)
|
||||
.onStart(function() {
|
||||
originInfo['lastValue'] = JointValue
|
||||
})
|
||||
.onUpdate(function() {
|
||||
switch (type) {
|
||||
case 'position':
|
||||
object[type][axis] = this._object.moveValue
|
||||
break
|
||||
case 'rotation':
|
||||
{
|
||||
const moveAxis = {
|
||||
x: new THREE.Vector3(1, 1, 1),
|
||||
y: new THREE.Vector3(1, 1, 1),
|
||||
z: new THREE.Vector3(1, 1, 1)
|
||||
}
|
||||
matrix.extractBasis(moveAxis.x, moveAxis.y, moveAxis.z)
|
||||
const nM = new THREE.Matrix4()
|
||||
nM.makeRotationAxis(moveAxis[axis], this._object.moveValue)
|
||||
// nM.makeRotationAxis(moveAxis[axis], JointValue)
|
||||
nM.multiply(matrix)
|
||||
object.rotation.setFromRotationMatrix(nM)
|
||||
object.updateWorldMatrix()
|
||||
}
|
||||
break
|
||||
}
|
||||
}).start()
|
||||
editor.solveAttachModel('', false)
|
||||
}
|
||||
//
|
||||
// if (object) {
|
||||
// editor.solveAttachModel('', true)
|
||||
// object[type][axis] = originValue + (ValueFactor * flip * parseFloat(tagValue) + Value)
|
||||
// editor.solveAttachModel('', false)
|
||||
// }
|
||||
} catch (e) {
|
||||
console.log('solveServoSignal错误')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 接受机器人类的数据
|
||||
* @param ModelId
|
||||
* @param JointData
|
||||
* @constructor
|
||||
*/
|
||||
this.JointMoveTo = (ModelId, JointData = {}) => {
|
||||
if (editor.robotList[ModelId]) {
|
||||
const ModelList = editor.robotList[ModelId]
|
||||
// window.updateRobotData(ModelId, JointData)
|
||||
// window.updateLabelInfoJoint(ModelId, JointData)
|
||||
for (let i = 0; i < ModelList.length; i++) {
|
||||
if (ModelList[i].ModelId === '') return
|
||||
this.jointMoveToPromise(JointData, ModelList[i], i, ModelId)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.jointMoveToPromise = (JointData, ModelListData, i, ModelId) => {
|
||||
new Promise(resolve => {
|
||||
const ModelIdJoint = ModelListData.ModelId
|
||||
const object = ModelListData.object
|
||||
const type = ModelListData.type
|
||||
const axis = ModelListData.axis
|
||||
const matrix = ModelListData.matrix
|
||||
const flip = ModelListData.flip
|
||||
const delta = parseFloat(ModelListData.delta)
|
||||
const multiple = ModelListData.multiple
|
||||
const originInfo = editor.objectIdSet.get(ModelIdJoint)
|
||||
if (!originInfo) return
|
||||
const originValue = type === 'rotation' ? originInfo[type]['_' + axis] : originInfo[type][axis]
|
||||
const lastValue = ModelListData.lastValue === null ? originValue : ModelListData.lastValue
|
||||
const moveValue = flip * (parseFloat(JointData['J' + (i + 1)]) - delta) * multiple
|
||||
if (object) {
|
||||
const JointValue = originValue + moveValue
|
||||
new TWEEN.Tween({
|
||||
moveValue: lastValue
|
||||
}).to({ moveValue: JointValue }, 200)
|
||||
.onStart(function() {
|
||||
editor.robotList[ModelId][i]['lastValue'] = JointValue
|
||||
})
|
||||
.onUpdate(function() {
|
||||
switch (type) {
|
||||
case 'position':
|
||||
object[type][axis] = this._object.moveValue
|
||||
break
|
||||
case 'rotation':
|
||||
{
|
||||
const moveAxis = {
|
||||
x: new THREE.Vector3(1, 1, 1),
|
||||
y: new THREE.Vector3(1, 1, 1),
|
||||
z: new THREE.Vector3(1, 1, 1)
|
||||
}
|
||||
matrix.extractBasis(moveAxis.x, moveAxis.y, moveAxis.z)
|
||||
const nM = new THREE.Matrix4()
|
||||
nM.makeRotationAxis(moveAxis[axis], this._object.moveValue)
|
||||
// nM.makeRotationAxis(moveAxis[axis], JointValue)
|
||||
nM.multiply(matrix)
|
||||
object.rotation.setFromRotationMatrix(nM)
|
||||
object.updateWorldMatrix()
|
||||
}
|
||||
break
|
||||
}
|
||||
}).start()
|
||||
}
|
||||
})
|
||||
}
|
||||
this.getAxisValue = (axis, value) => {
|
||||
return JSON.parse('{"' + axis + '":' + value + '}')
|
||||
}
|
||||
|
||||
this.getAxisValue = (axis, value) => {
|
||||
return JSON.parse('{"' + axis + '":' + value + '}')
|
||||
}
|
||||
|
||||
this.showWeld = (ModelId, Welding) => {
|
||||
// 徐工专用
|
||||
if (ModelId === '46DDBCD8-D775-49A4-BB14-30015FF87595') editor.WeldLightVisible1 = !!parseInt(Welding)
|
||||
if (ModelId === '1E96DED2-6EEF-469F-BFE6-8F52FB62C2B0') editor.WeldLightVisible2 = !!parseInt(Welding)
|
||||
}
|
||||
this.showCheckOk = (check) => {
|
||||
// 徐工专用
|
||||
if (parseInt(check)) {
|
||||
editor.showTagList[3].title = '工件:QY16KC, 检测OK'
|
||||
} else {
|
||||
editor.showTagList[3].title = '工件:QY16KC'
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 显示隐藏物体
|
||||
* @param m_DeviceName
|
||||
* @param isVisible
|
||||
*/
|
||||
this.visibleObjectToBS = (m_DeviceName, isVisible) => {
|
||||
const currentModel = editor.getObjectById(m_DeviceName)
|
||||
if (currentModel) {
|
||||
currentModel.visible = !!parseInt(isVisible)
|
||||
currentModel.traverse(res => {
|
||||
res.visible = !!parseInt(isVisible)
|
||||
})
|
||||
currentModel.userData['visible'] = !!parseInt(isVisible)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化获取TagValue
|
||||
*/
|
||||
this.getOriginTagValue = () => {
|
||||
let msg = ''
|
||||
let socketMsg = ''
|
||||
const keyWord = 'B_C_DT_WebLoaded'
|
||||
msg = 'DT|' + keyWord
|
||||
socketMsg = editor.BSport + msg
|
||||
const topic = editor.BSport.split('?')[2]
|
||||
switch (editor.connectType) {
|
||||
case 1:
|
||||
editor.socket.send(socketMsg)
|
||||
break
|
||||
case 2:
|
||||
editor.mqttClient.publish(topic, msg, 0, error => {
|
||||
if (error) {
|
||||
console.log('Publish error', error)
|
||||
}
|
||||
})
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 更新库存状态
|
||||
* @param dataInfo
|
||||
*/
|
||||
this.drawWareHousePartOne = (dataInfo) => {
|
||||
const scope = this
|
||||
try {
|
||||
const whInfo = JSON.parse(dataInfo)
|
||||
for (const whStoreInfo of whInfo) {
|
||||
const whStoreNum = whStoreInfo.Key // 库位号 唯一值
|
||||
const whStoreState = whStoreInfo.Value // 立库状态
|
||||
const whNum = whStoreNum.toString().substring(0, 4) // 立库号 1000一期立库 1003刀具库 1002三坐标测量库 1001机床线旁库
|
||||
const currentStoreInfo = editor.WareHouse.get(whStoreNum)
|
||||
if (currentStoreInfo) {
|
||||
if (parseInt(whStoreState) === 0) {
|
||||
// 0的时候直接移除库存
|
||||
editor.removeObject(currentStoreInfo.object)
|
||||
editor.WareHouse.delete(whStoreNum)
|
||||
} else {
|
||||
// 不一样的状态 更新
|
||||
if (currentStoreInfo.whStoreState !== whStoreState) {
|
||||
// 移除零件,更新库存信息信息
|
||||
editor.removeObject(currentStoreInfo.object)
|
||||
scope.updateStoreModel(whNum, whStoreNum, whStoreState)
|
||||
currentStoreInfo.whStoreState = whStoreState
|
||||
currentStoreInfo.object = null
|
||||
}
|
||||
}
|
||||
} else {
|
||||
editor.WareHouse.set(whStoreNum, {
|
||||
whNum: whNum,
|
||||
whStoreNum: whStoreNum,
|
||||
whStoreState: whStoreState,
|
||||
object: null
|
||||
})
|
||||
scope.updateStoreModel(whNum, whStoreNum, whStoreState)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('库存解析错误')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新零件
|
||||
* @param whNum
|
||||
* @param whStoreNum
|
||||
* @param whStoreState
|
||||
*/
|
||||
this.updateStoreModel = (whNum, whStoreNum, whStoreState) => {
|
||||
try {
|
||||
const basicPartModel = editor.WareHouseBasicModel.get('warehousepart' + whStoreState)
|
||||
const basicWareHouseModelData = editor.WareHouseBasicModel.get('warehouse' + whNum)
|
||||
if (basicPartModel && basicWareHouseModelData) {
|
||||
const basicPartModelClone = basicPartModel.clone()
|
||||
const basicPartModelPosition = basicWareHouseModelData[whStoreNum]
|
||||
basicPartModelClone.position.copy(basicPartModelPosition.getWorldPosition(new THREE.Vector3()))
|
||||
editor.scene.add(basicPartModelClone)
|
||||
editor.WareHouse.get(whStoreNum).object = basicPartModelClone
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('库存模型解析错误')
|
||||
}
|
||||
}
|
||||
}
|
||||
export {
|
||||
WebsocketMessageReceive
|
||||
}
|
||||
1049
src/components/DMT/js/mousetrap.js
Normal file
535
src/components/DMT/js/three-spritetext.module.js
Normal file
@@ -0,0 +1,535 @@
|
||||
import { LinearFilter, Sprite, SpriteMaterial, Texture } from 'three'
|
||||
|
||||
function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError('Cannot call a class as a function')
|
||||
}
|
||||
}
|
||||
|
||||
function _defineProperties(target, props) {
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
var descriptor = props[i]
|
||||
descriptor.enumerable = descriptor.enumerable || false
|
||||
descriptor.configurable = true
|
||||
if ('value' in descriptor) descriptor.writable = true
|
||||
Object.defineProperty(target, descriptor.key, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
function _createClass(Constructor, protoProps, staticProps) {
|
||||
if (protoProps) _defineProperties(Constructor.prototype, protoProps)
|
||||
if (staticProps) _defineProperties(Constructor, staticProps)
|
||||
Object.defineProperty(Constructor, 'prototype', {
|
||||
writable: false
|
||||
})
|
||||
return Constructor
|
||||
}
|
||||
|
||||
function _inherits(subClass, superClass) {
|
||||
if (typeof superClass !== 'function' && superClass !== null) {
|
||||
throw new TypeError('Super expression must either be null or a function')
|
||||
}
|
||||
|
||||
subClass.prototype = Object.create(superClass && superClass.prototype, {
|
||||
constructor: {
|
||||
value: subClass,
|
||||
writable: true,
|
||||
configurable: true
|
||||
}
|
||||
})
|
||||
Object.defineProperty(subClass, 'prototype', {
|
||||
writable: false
|
||||
})
|
||||
if (superClass) _setPrototypeOf(subClass, superClass)
|
||||
}
|
||||
|
||||
function _getPrototypeOf(o) {
|
||||
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
|
||||
return o.__proto__ || Object.getPrototypeOf(o)
|
||||
}
|
||||
return _getPrototypeOf(o)
|
||||
}
|
||||
|
||||
function _setPrototypeOf(o, p) {
|
||||
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
|
||||
o.__proto__ = p
|
||||
return o
|
||||
}
|
||||
|
||||
return _setPrototypeOf(o, p)
|
||||
}
|
||||
|
||||
function _isNativeReflectConstruct() {
|
||||
if (typeof Reflect === 'undefined' || !Reflect.construct) return false
|
||||
if (Reflect.construct.sham) return false
|
||||
if (typeof Proxy === 'function') return true
|
||||
|
||||
try {
|
||||
Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {}))
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function _assertThisInitialized(self) {
|
||||
if (self === void 0) {
|
||||
throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
function _possibleConstructorReturn(self, call) {
|
||||
if (call && (typeof call === 'object' || typeof call === 'function')) {
|
||||
return call
|
||||
} else if (call !== void 0) {
|
||||
throw new TypeError('Derived constructors may only return object or undefined')
|
||||
}
|
||||
|
||||
return _assertThisInitialized(self)
|
||||
}
|
||||
|
||||
function _createSuper(Derived) {
|
||||
var hasNativeReflectConstruct = _isNativeReflectConstruct()
|
||||
|
||||
return function _createSuperInternal() {
|
||||
var Super = _getPrototypeOf(Derived)
|
||||
var result
|
||||
|
||||
if (hasNativeReflectConstruct) {
|
||||
var NewTarget = _getPrototypeOf(this).constructor
|
||||
|
||||
result = Reflect.construct(Super, arguments, NewTarget)
|
||||
} else {
|
||||
result = Super.apply(this, arguments)
|
||||
}
|
||||
|
||||
return _possibleConstructorReturn(this, result)
|
||||
}
|
||||
}
|
||||
|
||||
function _slicedToArray(arr, i) {
|
||||
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest()
|
||||
}
|
||||
|
||||
function _toConsumableArray(arr) {
|
||||
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread()
|
||||
}
|
||||
|
||||
function _arrayWithoutHoles(arr) {
|
||||
if (Array.isArray(arr)) return _arrayLikeToArray(arr)
|
||||
}
|
||||
|
||||
function _arrayWithHoles(arr) {
|
||||
if (Array.isArray(arr)) return arr
|
||||
}
|
||||
|
||||
function _iterableToArray(iter) {
|
||||
if (typeof Symbol !== 'undefined' && iter[Symbol.iterator] != null || iter['@@iterator'] != null) return Array.from(iter)
|
||||
}
|
||||
|
||||
function _iterableToArrayLimit(arr, i) {
|
||||
var _i = arr == null ? null : typeof Symbol !== 'undefined' && arr[Symbol.iterator] || arr['@@iterator']
|
||||
|
||||
if (_i == null) return
|
||||
var _arr = []
|
||||
var _n = true
|
||||
var _d = false
|
||||
|
||||
var _s, _e
|
||||
|
||||
try {
|
||||
for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) {
|
||||
_arr.push(_s.value)
|
||||
|
||||
if (i && _arr.length === i) break
|
||||
}
|
||||
} catch (err) {
|
||||
_d = true
|
||||
_e = err
|
||||
} finally {
|
||||
try {
|
||||
if (!_n && _i['return'] != null) _i['return']()
|
||||
} finally {
|
||||
if (_d) throw _e
|
||||
}
|
||||
}
|
||||
|
||||
return _arr
|
||||
}
|
||||
|
||||
function _unsupportedIterableToArray(o, minLen) {
|
||||
if (!o) return
|
||||
if (typeof o === 'string') return _arrayLikeToArray(o, minLen)
|
||||
var n = Object.prototype.toString.call(o).slice(8, -1)
|
||||
if (n === 'Object' && o.constructor) n = o.constructor.name
|
||||
if (n === 'Map' || n === 'Set') return Array.from(o)
|
||||
if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen)
|
||||
}
|
||||
|
||||
function _arrayLikeToArray(arr, len) {
|
||||
if (len == null || len > arr.length) len = arr.length
|
||||
|
||||
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]
|
||||
|
||||
return arr2
|
||||
}
|
||||
|
||||
function _nonIterableSpread() {
|
||||
throw new TypeError('Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.')
|
||||
}
|
||||
|
||||
function _nonIterableRest() {
|
||||
throw new TypeError('Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.')
|
||||
}
|
||||
|
||||
var three = typeof window !== 'undefined' && window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
|
||||
: {
|
||||
LinearFilter: LinearFilter,
|
||||
Sprite: Sprite,
|
||||
SpriteMaterial: SpriteMaterial,
|
||||
Texture: Texture
|
||||
}
|
||||
|
||||
var _default = /* #__PURE__*/(function(_three$Sprite) {
|
||||
_inherits(_default, _three$Sprite)
|
||||
|
||||
var _super = _createSuper(_default)
|
||||
|
||||
function _default() {
|
||||
var _this
|
||||
|
||||
var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''
|
||||
var textHeight = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 10
|
||||
var color = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'rgba(255, 255, 255, 1)'
|
||||
|
||||
_classCallCheck(this, _default)
|
||||
|
||||
_this = _super.call(this, new three.SpriteMaterial())
|
||||
_this._text = ''.concat(text)
|
||||
_this._textHeight = textHeight
|
||||
_this._color = color
|
||||
_this._backgroundColor = false // no background color
|
||||
|
||||
_this._padding = 0
|
||||
_this._borderWidth = 0
|
||||
_this._borderRadius = 0
|
||||
_this._borderColor = 'white'
|
||||
_this._strokeWidth = 0
|
||||
_this._strokeColor = 'white'
|
||||
_this._fontFace = 'Arial'
|
||||
_this._fontSize = 90 // defines text resolution
|
||||
|
||||
_this._fontWeight = 'normal'
|
||||
_this._canvas = document.createElement('canvas')
|
||||
_this.material.depthTest = false
|
||||
_this._genCanvas()
|
||||
|
||||
return _this
|
||||
}
|
||||
|
||||
_createClass(_default, [{
|
||||
key: 'text',
|
||||
get: function get() {
|
||||
return this._text
|
||||
},
|
||||
set: function set(text) {
|
||||
this._text = text
|
||||
|
||||
this._genCanvas()
|
||||
}
|
||||
}, {
|
||||
key: 'textHeight',
|
||||
get: function get() {
|
||||
return this._textHeight
|
||||
},
|
||||
set: function set(textHeight) {
|
||||
this._textHeight = textHeight
|
||||
|
||||
this._genCanvas()
|
||||
}
|
||||
}, {
|
||||
key: 'color',
|
||||
get: function get() {
|
||||
return this._color
|
||||
},
|
||||
set: function set(color) {
|
||||
this._color = color
|
||||
|
||||
this._genCanvas()
|
||||
}
|
||||
}, {
|
||||
key: 'backgroundColor',
|
||||
get: function get() {
|
||||
return this._backgroundColor
|
||||
},
|
||||
set: function set(color) {
|
||||
this._backgroundColor = color
|
||||
|
||||
this._genCanvas()
|
||||
}
|
||||
}, {
|
||||
key: 'padding',
|
||||
get: function get() {
|
||||
return this._padding
|
||||
},
|
||||
set: function set(padding) {
|
||||
this._padding = padding
|
||||
|
||||
this._genCanvas()
|
||||
}
|
||||
}, {
|
||||
key: 'borderWidth',
|
||||
get: function get() {
|
||||
return this._borderWidth
|
||||
},
|
||||
set: function set(borderWidth) {
|
||||
this._borderWidth = borderWidth
|
||||
|
||||
this._genCanvas()
|
||||
}
|
||||
}, {
|
||||
key: 'borderRadius',
|
||||
get: function get() {
|
||||
return this._borderRadius
|
||||
},
|
||||
set: function set(borderRadius) {
|
||||
this._borderRadius = borderRadius
|
||||
|
||||
this._genCanvas()
|
||||
}
|
||||
}, {
|
||||
key: 'borderColor',
|
||||
get: function get() {
|
||||
return this._borderColor
|
||||
},
|
||||
set: function set(borderColor) {
|
||||
this._borderColor = borderColor
|
||||
|
||||
this._genCanvas()
|
||||
}
|
||||
}, {
|
||||
key: 'fontFace',
|
||||
get: function get() {
|
||||
return this._fontFace
|
||||
},
|
||||
set: function set(fontFace) {
|
||||
this._fontFace = fontFace
|
||||
|
||||
this._genCanvas()
|
||||
}
|
||||
}, {
|
||||
key: 'fontSize',
|
||||
get: function get() {
|
||||
return this._fontSize
|
||||
},
|
||||
set: function set(fontSize) {
|
||||
this._fontSize = fontSize
|
||||
|
||||
this._genCanvas()
|
||||
}
|
||||
}, {
|
||||
key: 'fontWeight',
|
||||
get: function get() {
|
||||
return this._fontWeight
|
||||
},
|
||||
set: function set(fontWeight) {
|
||||
this._fontWeight = fontWeight
|
||||
|
||||
this._genCanvas()
|
||||
}
|
||||
}, {
|
||||
key: 'strokeWidth',
|
||||
get: function get() {
|
||||
return this._strokeWidth
|
||||
},
|
||||
set: function set(strokeWidth) {
|
||||
this._strokeWidth = strokeWidth
|
||||
|
||||
this._genCanvas()
|
||||
}
|
||||
}, {
|
||||
key: 'strokeColor',
|
||||
get: function get() {
|
||||
return this._strokeColor
|
||||
},
|
||||
set: function set(strokeColor) {
|
||||
this._strokeColor = strokeColor
|
||||
|
||||
this._genCanvas()
|
||||
}
|
||||
}, {
|
||||
key: '_genCanvas',
|
||||
value: function _genCanvas() {
|
||||
var _this2 = this
|
||||
|
||||
var canvas = this._canvas
|
||||
var ctx = canvas.getContext('2d')
|
||||
var border = Array.isArray(this.borderWidth) ? this.borderWidth : [this.borderWidth, this.borderWidth] // x,y border
|
||||
|
||||
var relBorder = border.map(function(b) {
|
||||
return b * _this2.fontSize * 0.1
|
||||
}) // border in canvas units
|
||||
|
||||
var borderRadius = Array.isArray(this.borderRadius) ? this.borderRadius : [this.borderRadius, this.borderRadius, this.borderRadius, this.borderRadius] // tl tr br bl corners
|
||||
|
||||
var relBorderRadius = borderRadius.map(function(b) {
|
||||
return b * _this2.fontSize * 0.1
|
||||
}) // border radius in canvas units
|
||||
|
||||
var padding = Array.isArray(this.padding) ? this.padding : [this.padding, this.padding] // x,y padding
|
||||
|
||||
var relPadding = padding.map(function(p) {
|
||||
return p * _this2.fontSize * 0.1
|
||||
}) // padding in canvas units
|
||||
|
||||
var lines = this.text.split('\n')
|
||||
var font = ''.concat(this.fontWeight, ' ').concat(this.fontSize, 'px ').concat(this.fontFace)
|
||||
ctx.font = font // measure canvas with appropriate font
|
||||
|
||||
var innerWidth = Math.max.apply(Math, _toConsumableArray(lines.map(function(line) {
|
||||
return ctx.measureText(line).width
|
||||
})))
|
||||
var innerHeight = this.fontSize * lines.length
|
||||
canvas.width = innerWidth + relBorder[0] * 2 + relPadding[0] * 2
|
||||
canvas.height = innerHeight + relBorder[1] * 2 + relPadding[1] * 2 // paint border
|
||||
|
||||
if (this.borderWidth) {
|
||||
ctx.strokeStyle = this.borderColor
|
||||
|
||||
if (relBorder[0]) {
|
||||
// left + right borders
|
||||
var hb = relBorder[0] / 2
|
||||
ctx.lineWidth = relBorder[0]
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(hb, relBorderRadius[0])
|
||||
ctx.lineTo(hb, canvas.height - relBorderRadius[3])
|
||||
ctx.moveTo(canvas.width - hb, relBorderRadius[1])
|
||||
ctx.lineTo(canvas.width - hb, canvas.height - relBorderRadius[2])
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
if (relBorder[1]) {
|
||||
// top + bottom borders
|
||||
var _hb = relBorder[1] / 2
|
||||
|
||||
ctx.lineWidth = relBorder[1]
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(Math.max(relBorder[0], relBorderRadius[0]), _hb)
|
||||
ctx.lineTo(canvas.width - Math.max(relBorder[0], relBorderRadius[1]), _hb)
|
||||
ctx.moveTo(Math.max(relBorder[0], relBorderRadius[3]), canvas.height - _hb)
|
||||
ctx.lineTo(canvas.width - Math.max(relBorder[0], relBorderRadius[2]), canvas.height - _hb)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
if (this.borderRadius) {
|
||||
// strike rounded corners
|
||||
var cornerWidth = Math.max.apply(Math, _toConsumableArray(relBorder))
|
||||
|
||||
var _hb2 = cornerWidth / 2
|
||||
|
||||
ctx.lineWidth = cornerWidth
|
||||
ctx.beginPath();
|
||||
[!!relBorderRadius[0] && [relBorderRadius[0], _hb2, _hb2, relBorderRadius[0]], !!relBorderRadius[1] && [canvas.width - relBorderRadius[1], canvas.width - _hb2, _hb2, relBorderRadius[1]], !!relBorderRadius[2] && [canvas.width - relBorderRadius[2], canvas.width - _hb2, canvas.height - _hb2, canvas.height - relBorderRadius[2]], !!relBorderRadius[3] && [relBorderRadius[3], _hb2, canvas.height - _hb2, canvas.height - relBorderRadius[3]]].filter(function(d) {
|
||||
return d
|
||||
}).forEach(function(_ref) {
|
||||
var _ref2 = _slicedToArray(_ref, 4)
|
||||
var x0 = _ref2[0]
|
||||
var x1 = _ref2[1]
|
||||
var y0 = _ref2[2]
|
||||
var y1 = _ref2[3]
|
||||
|
||||
ctx.moveTo(x0, y0)
|
||||
ctx.quadraticCurveTo(x1, y0, x1, y1)
|
||||
})
|
||||
ctx.stroke()
|
||||
}
|
||||
} // paint background
|
||||
|
||||
if (this.backgroundColor) {
|
||||
ctx.fillStyle = this.backgroundColor
|
||||
|
||||
if (!this.borderRadius) {
|
||||
ctx.fillRect(relBorder[0], relBorder[1], canvas.width - relBorder[0] * 2, canvas.height - relBorder[1] * 2)
|
||||
} else {
|
||||
// fill with rounded corners
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(relBorder[0], relBorderRadius[0]);
|
||||
[[relBorder[0], relBorderRadius[0], canvas.width - relBorderRadius[1], relBorder[1], relBorder[1], relBorder[1]], // t
|
||||
[canvas.width - relBorder[0], canvas.width - relBorder[0], canvas.width - relBorder[0], relBorder[1], relBorderRadius[1], canvas.height - relBorderRadius[2]], // r
|
||||
[canvas.width - relBorder[0], canvas.width - relBorderRadius[2], relBorderRadius[3], canvas.height - relBorder[1], canvas.height - relBorder[1], canvas.height - relBorder[1]], // b
|
||||
[relBorder[0], relBorder[0], relBorder[0], canvas.height - relBorder[1], canvas.height - relBorderRadius[3], relBorderRadius[0]] // t
|
||||
].forEach(function(_ref3) {
|
||||
var _ref4 = _slicedToArray(_ref3, 6)
|
||||
var x0 = _ref4[0]
|
||||
var x1 = _ref4[1]
|
||||
var x2 = _ref4[2]
|
||||
var y0 = _ref4[3]
|
||||
var y1 = _ref4[4]
|
||||
var y2 = _ref4[5]
|
||||
|
||||
ctx.quadraticCurveTo(x0, y0, x1, y1)
|
||||
ctx.lineTo(x2, y2)
|
||||
})
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
|
||||
ctx.translate.apply(ctx, _toConsumableArray(relBorder))
|
||||
ctx.translate.apply(ctx, _toConsumableArray(relPadding)) // paint text
|
||||
|
||||
ctx.font = font // Set font again after canvas is resized, as context properties are reset
|
||||
|
||||
ctx.fillStyle = this.color
|
||||
ctx.textBaseline = 'bottom'
|
||||
var drawTextStroke = this.strokeWidth > 0
|
||||
|
||||
if (drawTextStroke) {
|
||||
ctx.lineWidth = this.strokeWidth * this.fontSize / 10
|
||||
ctx.strokeStyle = this.strokeColor
|
||||
}
|
||||
|
||||
lines.forEach(function(line, index) {
|
||||
var lineX = (innerWidth - ctx.measureText(line).width) / 2
|
||||
var lineY = (index + 1) * _this2.fontSize
|
||||
drawTextStroke && ctx.strokeText(line, lineX, lineY)
|
||||
ctx.fillText(line, lineX, lineY)
|
||||
}) // Inject canvas into sprite
|
||||
|
||||
if (this.material.map) this.material.map.dispose() // gc previous texture
|
||||
|
||||
var texture = this.material.map = new three.Texture(canvas)
|
||||
texture.minFilter = three.LinearFilter
|
||||
texture.needsUpdate = true
|
||||
var yScale = this.textHeight * lines.length + border[1] * 2 + padding[1] * 2
|
||||
this.scale.set(yScale * canvas.width / canvas.height, yScale, 0.001)
|
||||
}
|
||||
}, {
|
||||
key: 'clone',
|
||||
value: function clone() {
|
||||
return new this.constructor(this.text, this.textHeight, this.color).copy(this)
|
||||
}
|
||||
}, {
|
||||
key: 'copy',
|
||||
value: function copy(source) {
|
||||
three.Sprite.prototype.copy.call(this, source)
|
||||
this.color = source.color
|
||||
this.backgroundColor = source.backgroundColor
|
||||
this.padding = source.padding
|
||||
this.borderWidth = source.borderWidth
|
||||
this.borderColor = source.borderColor
|
||||
this.fontFace = source.fontFace
|
||||
this.fontSize = source.fontSize
|
||||
this.fontWeight = source.fontWeight
|
||||
this.strokeWidth = source.strokeWidth
|
||||
this.strokeColor = source.strokeColor
|
||||
return this
|
||||
}
|
||||
}])
|
||||
|
||||
return _default
|
||||
}(three.Sprite))
|
||||
|
||||
export { _default as default }
|
||||
74
src/components/DMT/js/websocketConnect.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import { WebsocketMessageReceive } from './WebSocketMessage.js'
|
||||
|
||||
function connect(editor) {
|
||||
const host = 'ws://' + editor.ip + '//WebModule//websocket'
|
||||
let timeout = null
|
||||
const websocketMessageReceive = new WebsocketMessageReceive(editor)
|
||||
// 尝试连接至服务器
|
||||
try {
|
||||
if ('WebSocket' in window) {
|
||||
editor.socket = new WebSocket(host)
|
||||
} else {
|
||||
editor.showMessage('浏览器不支持WebSocket', 'error', 2000)
|
||||
}
|
||||
} catch (exception) {
|
||||
editor.showMessage('与服务器断开连接', 'error', 2000)
|
||||
return
|
||||
}
|
||||
// 连接成功
|
||||
editor.socket.onopen = function() {
|
||||
// 关闭timer
|
||||
try {
|
||||
clearInterval(timeout)
|
||||
} catch (exception) {
|
||||
console.log(exception)
|
||||
}
|
||||
const name = editor.opname
|
||||
editor.socket.send('LIN,' + name)
|
||||
editor.showMessage('加载完成,连接服务器成功!', 'success', 2000)
|
||||
editor.isOk = true
|
||||
websocketMessageReceive.getOriginTagValue()
|
||||
// let a = 0
|
||||
// setInterval(() => {
|
||||
// a += 0.5
|
||||
// const message = `DT|JointMoveTo|0f26fc6e-1caf-4f57-beb3-d8a65872fbdd|{"J1":"${a}","J2":"0","J3":"0","J4":"0","J5":"0","J6":"0","J7":"0","J8":"0","J9":"0","J10":"0","J11":"0","J12":"0"}`
|
||||
// websocketMessageReceive.websocketMessage({ data: message })
|
||||
// }, 10)
|
||||
}
|
||||
// 收到消息
|
||||
editor.socket.onmessage = function(msg) {
|
||||
try {
|
||||
websocketMessageReceive.websocketMessage(msg)
|
||||
} catch (exception) {
|
||||
const error = exception.toString()
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
// 连接断开
|
||||
editor.socket.onclose = function(event) {
|
||||
try {
|
||||
editor.showMessage('onclose' + '服务器连接失败', 'error', 2000)
|
||||
editor.socket.close()
|
||||
if (editor.IsAllowConnect) {
|
||||
// 显示一下
|
||||
editor.showMessage('重新连接服务器', 'warning', 2000)
|
||||
console.log('onclose' + '重新连接服务器')
|
||||
// 重新连接,打开计时器
|
||||
timeout = setTimeout(function() {
|
||||
connect(editor)
|
||||
}, 5000)
|
||||
}
|
||||
} catch (exception) {
|
||||
const error = exception.toString()
|
||||
editor.showMessage.error('服务器出现错误,关闭连接' + error, 'error', 2000)
|
||||
}
|
||||
}
|
||||
// 出现错误
|
||||
editor.socket.onerror = function(event) {
|
||||
console.log('onerror_' + '服务器连接失败')
|
||||
editor.showMessage.error('服务器连接失败', 'error', 2000)
|
||||
}
|
||||
}
|
||||
|
||||
export { connect }
|
||||
|
||||
BIN
src/components/DMT/libs/alarm.mp3
Normal file
BIN
src/components/DMT/libs/bgc.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
27
src/components/DMT/libs/bvh/core/Constants.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// Split strategy constants
|
||||
export const CENTER = 0;
|
||||
export const AVERAGE = 1;
|
||||
export const SAH = 2;
|
||||
|
||||
// Traversal constants
|
||||
export const NOT_INTERSECTED = 0;
|
||||
export const INTERSECTED = 1;
|
||||
export const CONTAINED = 2;
|
||||
|
||||
// SAH cost constants
|
||||
// TODO: hone these costs more. The relative difference between them should be the
|
||||
// difference in measured time to perform a triangle intersection vs traversing
|
||||
// bounds.
|
||||
export const TRIANGLE_INTERSECT_COST = 1.25;
|
||||
export const TRAVERSAL_COST = 1;
|
||||
|
||||
|
||||
// Build constants
|
||||
export const BYTES_PER_NODE = 6 * 4 + 4 + 4;
|
||||
export const IS_LEAFNODE_FLAG = 0xFFFF;
|
||||
|
||||
// EPSILON for computing floating point error during build
|
||||
// https://en.wikipedia.org/wiki/Machine_epsilon#Values_for_standard_hardware_floating_point_arithmetics
|
||||
export const FLOAT32_EPSILON = Math.pow( 2, - 24 );
|
||||
|
||||
export const SKIP_GENERATION = Symbol( 'SKIP_GENERATION' );
|
||||
548
src/components/DMT/libs/bvh/core/MeshBVH.js
Normal file
@@ -0,0 +1,548 @@
|
||||
import { BufferAttribute, Box3, FrontSide } from 'three'
|
||||
import { CENTER, BYTES_PER_NODE, IS_LEAFNODE_FLAG, SKIP_GENERATION } from './Constants.js';
|
||||
import { buildPackedTree } from './build/buildTree.js';
|
||||
import { OrientedBox } from '../math/OrientedBox.js';
|
||||
import { arrayToBox } from '../utils/ArrayBoxUtilities.js';
|
||||
import { ExtendedTrianglePool } from '../utils/ExtendedTrianglePool.js';
|
||||
import { shapecast } from './cast/shapecast.js';
|
||||
import { closestPointToPoint } from './cast/closestPointToPoint.js';
|
||||
|
||||
import { iterateOverTriangles } from './utils/iterationUtils.generated.js';
|
||||
import { refit } from './cast/refit.generated.js';
|
||||
import { raycast } from './cast/raycast.generated.js';
|
||||
import { raycastFirst } from './cast/raycastFirst.generated.js';
|
||||
import { intersectsGeometry } from './cast/intersectsGeometry.generated.js';
|
||||
import { closestPointToGeometry } from './cast/closestPointToGeometry.generated.js';
|
||||
|
||||
import { iterateOverTriangles_indirect } from './utils/iterationUtils_indirect.generated.js';
|
||||
import { refit_indirect } from './cast/refit_indirect.generated.js';
|
||||
import { raycast_indirect } from './cast/raycast_indirect.generated.js';
|
||||
import { raycastFirst_indirect } from './cast/raycastFirst_indirect.generated.js';
|
||||
import { intersectsGeometry_indirect } from './cast/intersectsGeometry_indirect.generated.js';
|
||||
import { closestPointToGeometry_indirect } from './cast/closestPointToGeometry_indirect.generated.js';
|
||||
import { isSharedArrayBufferSupported } from '../utils/BufferUtils.js';
|
||||
import { setTriangle } from '../utils/TriangleUtilities.js';
|
||||
import { bvhcast } from './cast/bvhcast.js';
|
||||
|
||||
const obb = /* @__PURE__ */ new OrientedBox();
|
||||
const tempBox = /* @__PURE__ */ new Box3();
|
||||
export const DEFAULT_OPTIONS = {
|
||||
strategy: CENTER,
|
||||
maxDepth: 40,
|
||||
maxLeafTris: 10,
|
||||
useSharedArrayBuffer: false,
|
||||
setBoundingBox: true,
|
||||
onProgress: null,
|
||||
indirect: false,
|
||||
verbose: true,
|
||||
};
|
||||
|
||||
export class MeshBVH {
|
||||
|
||||
static serialize( bvh, options = {} ) {
|
||||
|
||||
options = {
|
||||
cloneBuffers: true,
|
||||
...options,
|
||||
};
|
||||
|
||||
const geometry = bvh.geometry;
|
||||
const rootData = bvh._roots;
|
||||
const indirectBuffer = bvh._indirectBuffer;
|
||||
const indexAttribute = geometry.getIndex();
|
||||
let result;
|
||||
if ( options.cloneBuffers ) {
|
||||
|
||||
result = {
|
||||
roots: rootData.map( root => root.slice() ),
|
||||
index: indexAttribute ? indexAttribute.array.slice() : null,
|
||||
indirectBuffer: indirectBuffer ? indirectBuffer.slice() : null,
|
||||
};
|
||||
|
||||
} else {
|
||||
|
||||
result = {
|
||||
roots: rootData,
|
||||
index: indexAttribute ? indexAttribute.array : null,
|
||||
indirectBuffer: indirectBuffer,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
static deserialize( data, geometry, options = {} ) {
|
||||
|
||||
options = {
|
||||
setIndex: true,
|
||||
indirect: Boolean( data.indirectBuffer ),
|
||||
...options,
|
||||
};
|
||||
|
||||
const { index, roots, indirectBuffer } = data;
|
||||
const bvh = new MeshBVH( geometry, { ...options, [ SKIP_GENERATION ]: true } );
|
||||
bvh._roots = roots;
|
||||
bvh._indirectBuffer = indirectBuffer || null;
|
||||
|
||||
if ( options.setIndex ) {
|
||||
|
||||
const indexAttribute = geometry.getIndex();
|
||||
if ( indexAttribute === null ) {
|
||||
|
||||
const newIndex = new BufferAttribute( data.index, 1, false );
|
||||
geometry.setIndex( newIndex );
|
||||
|
||||
} else if ( indexAttribute.array !== index ) {
|
||||
|
||||
indexAttribute.array.set( index );
|
||||
indexAttribute.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return bvh;
|
||||
|
||||
}
|
||||
|
||||
get indirect() {
|
||||
|
||||
return ! ! this._indirectBuffer;
|
||||
|
||||
}
|
||||
|
||||
constructor( geometry, options = {} ) {
|
||||
|
||||
if ( ! geometry.isBufferGeometry ) {
|
||||
|
||||
throw new Error( 'MeshBVH: Only BufferGeometries are supported.' );
|
||||
|
||||
} else if ( geometry.index && geometry.index.isInterleavedBufferAttribute ) {
|
||||
|
||||
throw new Error( 'MeshBVH: InterleavedBufferAttribute is not supported for the index attribute.' );
|
||||
|
||||
}
|
||||
|
||||
// default options
|
||||
options = Object.assign( {
|
||||
|
||||
...DEFAULT_OPTIONS,
|
||||
|
||||
// undocumented options
|
||||
|
||||
// Whether to skip generating the tree. Used for deserialization.
|
||||
[ SKIP_GENERATION ]: false,
|
||||
|
||||
}, options );
|
||||
|
||||
if ( options.useSharedArrayBuffer && ! isSharedArrayBufferSupported() ) {
|
||||
|
||||
throw new Error( 'MeshBVH: SharedArrayBuffer is not available.' );
|
||||
|
||||
}
|
||||
|
||||
// retain references to the geometry so we can use them it without having to
|
||||
// take a geometry reference in every function.
|
||||
this.geometry = geometry;
|
||||
this._roots = null;
|
||||
this._indirectBuffer = null;
|
||||
if ( ! options[ SKIP_GENERATION ] ) {
|
||||
|
||||
buildPackedTree( this, options );
|
||||
|
||||
if ( ! geometry.boundingBox && options.setBoundingBox ) {
|
||||
|
||||
geometry.boundingBox = this.getBoundingBox( new Box3() );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const { _indirectBuffer } = this;
|
||||
this.resolveTriangleIndex = options.indirect ? i => _indirectBuffer[ i ] : i => i;
|
||||
|
||||
}
|
||||
|
||||
refit( nodeIndices = null ) {
|
||||
|
||||
const refitFunc = this.indirect ? refit_indirect : refit;
|
||||
return refitFunc( this, nodeIndices );
|
||||
|
||||
}
|
||||
|
||||
traverse( callback, rootIndex = 0 ) {
|
||||
|
||||
const buffer = this._roots[ rootIndex ];
|
||||
const uint32Array = new Uint32Array( buffer );
|
||||
const uint16Array = new Uint16Array( buffer );
|
||||
_traverse( 0 );
|
||||
|
||||
function _traverse( node32Index, depth = 0 ) {
|
||||
|
||||
const node16Index = node32Index * 2;
|
||||
const isLeaf = uint16Array[ node16Index + 15 ] === IS_LEAFNODE_FLAG;
|
||||
if ( isLeaf ) {
|
||||
|
||||
const offset = uint32Array[ node32Index + 6 ];
|
||||
const count = uint16Array[ node16Index + 14 ];
|
||||
callback( depth, isLeaf, new Float32Array( buffer, node32Index * 4, 6 ), offset, count );
|
||||
|
||||
} else {
|
||||
|
||||
// TODO: use node functions here
|
||||
const left = node32Index + BYTES_PER_NODE / 4;
|
||||
const right = uint32Array[ node32Index + 6 ];
|
||||
const splitAxis = uint32Array[ node32Index + 7 ];
|
||||
const stopTraversal = callback( depth, isLeaf, new Float32Array( buffer, node32Index * 4, 6 ), splitAxis );
|
||||
|
||||
if ( ! stopTraversal ) {
|
||||
|
||||
_traverse( left, depth + 1 );
|
||||
_traverse( right, depth + 1 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Core Cast Functions */
|
||||
raycast( ray, materialOrSide = FrontSide ) {
|
||||
|
||||
const roots = this._roots;
|
||||
const geometry = this.geometry;
|
||||
const intersects = [];
|
||||
const isMaterial = materialOrSide.isMaterial;
|
||||
const isArrayMaterial = Array.isArray( materialOrSide );
|
||||
|
||||
const groups = geometry.groups;
|
||||
const side = isMaterial ? materialOrSide.side : materialOrSide;
|
||||
const raycastFunc = this.indirect ? raycast_indirect : raycast;
|
||||
for ( let i = 0, l = roots.length; i < l; i ++ ) {
|
||||
|
||||
const materialSide = isArrayMaterial ? materialOrSide[ groups[ i ].materialIndex ].side : side;
|
||||
const startCount = intersects.length;
|
||||
|
||||
raycastFunc( this, i, materialSide, ray, intersects );
|
||||
|
||||
if ( isArrayMaterial ) {
|
||||
|
||||
const materialIndex = groups[ i ].materialIndex;
|
||||
for ( let j = startCount, jl = intersects.length; j < jl; j ++ ) {
|
||||
|
||||
intersects[ j ].face.materialIndex = materialIndex;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return intersects;
|
||||
|
||||
}
|
||||
|
||||
raycastFirst( ray, materialOrSide = FrontSide ) {
|
||||
|
||||
const roots = this._roots;
|
||||
const geometry = this.geometry;
|
||||
const isMaterial = materialOrSide.isMaterial;
|
||||
const isArrayMaterial = Array.isArray( materialOrSide );
|
||||
|
||||
let closestResult = null;
|
||||
|
||||
const groups = geometry.groups;
|
||||
const side = isMaterial ? materialOrSide.side : materialOrSide;
|
||||
const raycastFirstFunc = this.indirect ? raycastFirst_indirect : raycastFirst;
|
||||
for ( let i = 0, l = roots.length; i < l; i ++ ) {
|
||||
|
||||
const materialSide = isArrayMaterial ? materialOrSide[ groups[ i ].materialIndex ].side : side;
|
||||
const result = raycastFirstFunc( this, i, materialSide, ray );
|
||||
if ( result != null && ( closestResult == null || result.distance < closestResult.distance ) ) {
|
||||
|
||||
closestResult = result;
|
||||
if ( isArrayMaterial ) {
|
||||
|
||||
result.face.materialIndex = groups[ i ].materialIndex;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return closestResult;
|
||||
|
||||
}
|
||||
|
||||
intersectsGeometry( otherGeometry, geomToMesh ) {
|
||||
|
||||
let result = false;
|
||||
const roots = this._roots;
|
||||
const intersectsGeometryFunc = this.indirect ? intersectsGeometry_indirect : intersectsGeometry;
|
||||
for ( let i = 0, l = roots.length; i < l; i ++ ) {
|
||||
|
||||
result = intersectsGeometryFunc( this, i, otherGeometry, geomToMesh );
|
||||
|
||||
if ( result ) {
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
shapecast( callbacks ) {
|
||||
|
||||
const triangle = ExtendedTrianglePool.getPrimitive();
|
||||
const iterateFunc = this.indirect ? iterateOverTriangles_indirect : iterateOverTriangles;
|
||||
let {
|
||||
boundsTraverseOrder,
|
||||
intersectsBounds,
|
||||
intersectsRange,
|
||||
intersectsTriangle,
|
||||
} = callbacks;
|
||||
|
||||
// wrap the intersectsRange function
|
||||
if ( intersectsRange && intersectsTriangle ) {
|
||||
|
||||
const originalIntersectsRange = intersectsRange;
|
||||
intersectsRange = ( offset, count, contained, depth, nodeIndex ) => {
|
||||
|
||||
if ( ! originalIntersectsRange( offset, count, contained, depth, nodeIndex ) ) {
|
||||
|
||||
return iterateFunc( offset, count, this, intersectsTriangle, contained, depth, triangle );
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
};
|
||||
|
||||
} else if ( ! intersectsRange ) {
|
||||
|
||||
if ( intersectsTriangle ) {
|
||||
|
||||
intersectsRange = ( offset, count, contained, depth ) => {
|
||||
|
||||
return iterateFunc( offset, count, this, intersectsTriangle, contained, depth, triangle );
|
||||
|
||||
};
|
||||
|
||||
} else {
|
||||
|
||||
intersectsRange = ( offset, count, contained ) => {
|
||||
|
||||
return contained;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// run shapecast
|
||||
let result = false;
|
||||
let byteOffset = 0;
|
||||
const roots = this._roots;
|
||||
for ( let i = 0, l = roots.length; i < l; i ++ ) {
|
||||
|
||||
const root = roots[ i ];
|
||||
result = shapecast( this, i, intersectsBounds, intersectsRange, boundsTraverseOrder, byteOffset );
|
||||
|
||||
if ( result ) {
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
byteOffset += root.byteLength;
|
||||
|
||||
}
|
||||
|
||||
ExtendedTrianglePool.releasePrimitive( triangle );
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
bvhcast( otherBvh, matrixToLocal, callbacks ) {
|
||||
|
||||
let {
|
||||
intersectsRanges,
|
||||
intersectsTriangles,
|
||||
} = callbacks;
|
||||
|
||||
const triangle1 = ExtendedTrianglePool.getPrimitive();
|
||||
const indexAttr1 = this.geometry.index;
|
||||
const positionAttr1 = this.geometry.attributes.position;
|
||||
const assignTriangle1 = this.indirect ?
|
||||
i1 => {
|
||||
|
||||
|
||||
const ti = this.resolveTriangleIndex( i1 );
|
||||
setTriangle( triangle1, ti * 3, indexAttr1, positionAttr1 );
|
||||
|
||||
} :
|
||||
i1 => {
|
||||
|
||||
setTriangle( triangle1, i1 * 3, indexAttr1, positionAttr1 );
|
||||
|
||||
};
|
||||
|
||||
const triangle2 = ExtendedTrianglePool.getPrimitive();
|
||||
const indexAttr2 = otherBvh.geometry.index;
|
||||
const positionAttr2 = otherBvh.geometry.attributes.position;
|
||||
const assignTriangle2 = otherBvh.indirect ?
|
||||
i2 => {
|
||||
|
||||
const ti2 = otherBvh.resolveTriangleIndex( i2 );
|
||||
setTriangle( triangle2, ti2 * 3, indexAttr2, positionAttr2 );
|
||||
|
||||
} :
|
||||
i2 => {
|
||||
|
||||
setTriangle( triangle2, i2 * 3, indexAttr2, positionAttr2 );
|
||||
|
||||
};
|
||||
|
||||
// generate triangle callback if needed
|
||||
if ( intersectsTriangles ) {
|
||||
|
||||
const iterateOverDoubleTriangles = ( offset1, count1, offset2, count2, depth1, index1, depth2, index2 ) => {
|
||||
|
||||
for ( let i2 = offset2, l2 = offset2 + count2; i2 < l2; i2 ++ ) {
|
||||
|
||||
assignTriangle2( i2 );
|
||||
|
||||
triangle2.a.applyMatrix4( matrixToLocal );
|
||||
triangle2.b.applyMatrix4( matrixToLocal );
|
||||
triangle2.c.applyMatrix4( matrixToLocal );
|
||||
triangle2.needsUpdate = true;
|
||||
|
||||
for ( let i1 = offset1, l1 = offset1 + count1; i1 < l1; i1 ++ ) {
|
||||
|
||||
assignTriangle1( i1 );
|
||||
|
||||
triangle1.needsUpdate = true;
|
||||
|
||||
if ( intersectsTriangles( triangle1, triangle2, i1, i2, depth1, index1, depth2, index2 ) ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
if ( intersectsRanges ) {
|
||||
|
||||
const originalIntersectsRanges = intersectsRanges;
|
||||
intersectsRanges = function ( offset1, count1, offset2, count2, depth1, index1, depth2, index2 ) {
|
||||
|
||||
if ( ! originalIntersectsRanges( offset1, count1, offset2, count2, depth1, index1, depth2, index2 ) ) {
|
||||
|
||||
return iterateOverDoubleTriangles( offset1, count1, offset2, count2, depth1, index1, depth2, index2 );
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
};
|
||||
|
||||
} else {
|
||||
|
||||
intersectsRanges = iterateOverDoubleTriangles;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return bvhcast( this, otherBvh, matrixToLocal, intersectsRanges );
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Derived Cast Functions */
|
||||
intersectsBox( box, boxToMesh ) {
|
||||
|
||||
obb.set( box.min, box.max, boxToMesh );
|
||||
obb.needsUpdate = true;
|
||||
|
||||
return this.shapecast(
|
||||
{
|
||||
intersectsBounds: box => obb.intersectsBox( box ),
|
||||
intersectsTriangle: tri => obb.intersectsTriangle( tri )
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
intersectsSphere( sphere ) {
|
||||
|
||||
return this.shapecast(
|
||||
{
|
||||
intersectsBounds: box => sphere.intersectsBox( box ),
|
||||
intersectsTriangle: tri => tri.intersectsSphere( sphere )
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
closestPointToGeometry( otherGeometry, geometryToBvh, target1 = { }, target2 = { }, minThreshold = 0, maxThreshold = Infinity ) {
|
||||
|
||||
const closestPointToGeometryFunc = this.indirect ? closestPointToGeometry_indirect : closestPointToGeometry;
|
||||
return closestPointToGeometryFunc(
|
||||
this,
|
||||
otherGeometry,
|
||||
geometryToBvh,
|
||||
target1,
|
||||
target2,
|
||||
minThreshold,
|
||||
maxThreshold,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
closestPointToPoint( point, target = { }, minThreshold = 0, maxThreshold = Infinity ) {
|
||||
|
||||
return closestPointToPoint(
|
||||
this,
|
||||
point,
|
||||
target,
|
||||
minThreshold,
|
||||
maxThreshold,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
getBoundingBox( target ) {
|
||||
|
||||
target.makeEmpty();
|
||||
|
||||
const roots = this._roots;
|
||||
roots.forEach( buffer => {
|
||||
|
||||
arrayToBox( 0, new Float32Array( buffer ), tempBox );
|
||||
target.union( tempBox );
|
||||
|
||||
} );
|
||||
|
||||
return target;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
12
src/components/DMT/libs/bvh/core/MeshBVHNode.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export class MeshBVHNode {
|
||||
|
||||
constructor() {
|
||||
|
||||
// internal nodes have boundingData, left, right, and splitAxis
|
||||
// leaf nodes have offset and count (referring to primitives in the mesh geometry)
|
||||
|
||||
this.boundingData = new Float32Array( 6 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
314
src/components/DMT/libs/bvh/core/cast/bvhcast.js
Normal file
@@ -0,0 +1,314 @@
|
||||
import { Box3, Matrix4 } from 'three'
|
||||
import { BufferStack } from '../utils/BufferStack.js';
|
||||
import { BOUNDING_DATA_INDEX, COUNT, IS_LEAF, LEFT_NODE, OFFSET, RIGHT_NODE } from '../utils/nodeBufferUtils.js';
|
||||
import { arrayToBox } from '../../utils/ArrayBoxUtilities.js';
|
||||
import { PrimitivePool } from '../../utils/PrimitivePool.js';
|
||||
|
||||
const _bufferStack1 = new BufferStack.constructor();
|
||||
const _bufferStack2 = new BufferStack.constructor();
|
||||
const _boxPool = new PrimitivePool( () => new Box3() );
|
||||
const _leftBox1 = new Box3();
|
||||
const _rightBox1 = new Box3();
|
||||
|
||||
const _leftBox2 = new Box3();
|
||||
const _rightBox2 = new Box3();
|
||||
|
||||
let _active = false;
|
||||
|
||||
export function bvhcast( bvh, otherBvh, matrixToLocal, intersectsRanges ) {
|
||||
|
||||
if ( _active ) {
|
||||
|
||||
throw new Error( 'MeshBVH: Recursive calls to bvhcast not supported.' );
|
||||
|
||||
}
|
||||
|
||||
_active = true;
|
||||
|
||||
const roots = bvh._roots;
|
||||
const otherRoots = otherBvh._roots;
|
||||
let result;
|
||||
let offset1 = 0;
|
||||
let offset2 = 0;
|
||||
const invMat = new Matrix4().copy( matrixToLocal ).invert();
|
||||
|
||||
// iterate over the first set of roots
|
||||
for ( let i = 0, il = roots.length; i < il; i ++ ) {
|
||||
|
||||
_bufferStack1.setBuffer( roots[ i ] );
|
||||
offset2 = 0;
|
||||
|
||||
// prep the initial root box
|
||||
const localBox = _boxPool.getPrimitive();
|
||||
arrayToBox( BOUNDING_DATA_INDEX( 0 ), _bufferStack1.float32Array, localBox );
|
||||
localBox.applyMatrix4( invMat );
|
||||
|
||||
// iterate over the second set of roots
|
||||
for ( let j = 0, jl = otherRoots.length; j < jl; j ++ ) {
|
||||
|
||||
_bufferStack2.setBuffer( otherRoots[ i ] );
|
||||
|
||||
result = _traverse(
|
||||
0, 0, matrixToLocal, invMat, intersectsRanges,
|
||||
offset1, offset2, 0, 0,
|
||||
localBox,
|
||||
);
|
||||
|
||||
_bufferStack2.clearBuffer();
|
||||
offset2 += otherRoots[ j ].length;
|
||||
|
||||
if ( result ) {
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// release stack info
|
||||
_boxPool.releasePrimitive( localBox );
|
||||
_bufferStack1.clearBuffer();
|
||||
offset1 += roots[ i ].length;
|
||||
|
||||
if ( result ) {
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_active = false;
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
function _traverse(
|
||||
node1Index32,
|
||||
node2Index32,
|
||||
matrix2to1,
|
||||
matrix1to2,
|
||||
intersectsRangesFunc,
|
||||
|
||||
// offsets for ids
|
||||
node1IndexByteOffset = 0,
|
||||
node2IndexByteOffset = 0,
|
||||
|
||||
// tree depth
|
||||
depth1 = 0,
|
||||
depth2 = 0,
|
||||
|
||||
currBox = null,
|
||||
reversed = false,
|
||||
|
||||
) {
|
||||
|
||||
// get the buffer stacks associated with the current indices
|
||||
let bufferStack1, bufferStack2;
|
||||
if ( reversed ) {
|
||||
|
||||
bufferStack1 = _bufferStack2;
|
||||
bufferStack2 = _bufferStack1;
|
||||
|
||||
} else {
|
||||
|
||||
bufferStack1 = _bufferStack1;
|
||||
bufferStack2 = _bufferStack2;
|
||||
|
||||
}
|
||||
|
||||
// get the local instances of the typed buffers
|
||||
const
|
||||
float32Array1 = bufferStack1.float32Array,
|
||||
uint32Array1 = bufferStack1.uint32Array,
|
||||
uint16Array1 = bufferStack1.uint16Array,
|
||||
float32Array2 = bufferStack2.float32Array,
|
||||
uint32Array2 = bufferStack2.uint32Array,
|
||||
uint16Array2 = bufferStack2.uint16Array;
|
||||
|
||||
const node1Index16 = node1Index32 * 2;
|
||||
const node2Index16 = node2Index32 * 2;
|
||||
const isLeaf1 = IS_LEAF( node1Index16, uint16Array1 );
|
||||
const isLeaf2 = IS_LEAF( node2Index16, uint16Array2 );
|
||||
let result = false;
|
||||
if ( isLeaf2 && isLeaf1 ) {
|
||||
|
||||
// if both bounds are leaf nodes then fire the callback if the boxes intersect
|
||||
if ( reversed ) {
|
||||
|
||||
result = intersectsRangesFunc(
|
||||
OFFSET( node2Index32, uint32Array2 ), COUNT( node2Index32 * 2, uint16Array2 ),
|
||||
OFFSET( node1Index32, uint32Array1 ), COUNT( node1Index32 * 2, uint16Array1 ),
|
||||
depth2, node2IndexByteOffset + node2Index32,
|
||||
depth1, node1IndexByteOffset + node1Index32,
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
result = intersectsRangesFunc(
|
||||
OFFSET( node1Index32, uint32Array1 ), COUNT( node1Index32 * 2, uint16Array1 ),
|
||||
OFFSET( node2Index32, uint32Array2 ), COUNT( node2Index32 * 2, uint16Array2 ),
|
||||
depth1, node1IndexByteOffset + node1Index32,
|
||||
depth2, node2IndexByteOffset + node2Index32,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
} else if ( isLeaf2 ) {
|
||||
|
||||
// SWAP
|
||||
// If we've traversed to the leaf node on the other bvh then we need to swap over
|
||||
// to traverse down the first one
|
||||
|
||||
// get the new box to use
|
||||
const newBox = _boxPool.getPrimitive();
|
||||
arrayToBox( BOUNDING_DATA_INDEX( node2Index32 ), float32Array2, newBox );
|
||||
newBox.applyMatrix4( matrix2to1 );
|
||||
|
||||
// get the child bounds to check before traversal
|
||||
const cl1 = LEFT_NODE( node1Index32 );
|
||||
const cr1 = RIGHT_NODE( node1Index32, uint32Array1 );
|
||||
arrayToBox( BOUNDING_DATA_INDEX( cl1 ), float32Array1, _leftBox1 );
|
||||
arrayToBox( BOUNDING_DATA_INDEX( cr1 ), float32Array1, _rightBox1 );
|
||||
|
||||
// precompute the intersections otherwise the global boxes will be modified during traversal
|
||||
const intersectCl1 = newBox.intersectsBox( _leftBox1 );
|
||||
const intersectCr1 = newBox.intersectsBox( _rightBox1 );
|
||||
result = (
|
||||
intersectCl1 && _traverse(
|
||||
node2Index32, cl1, matrix1to2, matrix2to1, intersectsRangesFunc,
|
||||
node2IndexByteOffset, node1IndexByteOffset, depth2, depth1 + 1,
|
||||
newBox, ! reversed,
|
||||
)
|
||||
) || (
|
||||
intersectCr1 && _traverse(
|
||||
node2Index32, cr1, matrix1to2, matrix2to1, intersectsRangesFunc,
|
||||
node2IndexByteOffset, node1IndexByteOffset, depth2, depth1 + 1,
|
||||
newBox, ! reversed,
|
||||
)
|
||||
);
|
||||
|
||||
_boxPool.releasePrimitive( newBox );
|
||||
|
||||
} else {
|
||||
|
||||
// if neither are leaves then we should swap if one of the children does not
|
||||
// intersect with the current bounds
|
||||
|
||||
// get the child bounds to check
|
||||
const cl2 = LEFT_NODE( node2Index32 );
|
||||
const cr2 = RIGHT_NODE( node2Index32, uint32Array2 );
|
||||
arrayToBox( BOUNDING_DATA_INDEX( cl2 ), float32Array2, _leftBox2 );
|
||||
arrayToBox( BOUNDING_DATA_INDEX( cr2 ), float32Array2, _rightBox2 );
|
||||
|
||||
const leftIntersects = currBox.intersectsBox( _leftBox2 );
|
||||
const rightIntersects = currBox.intersectsBox( _rightBox2 );
|
||||
if ( leftIntersects && rightIntersects ) {
|
||||
|
||||
// continue to traverse both children if they both intersect
|
||||
result = _traverse(
|
||||
node1Index32, cl2, matrix2to1, matrix1to2, intersectsRangesFunc,
|
||||
node1IndexByteOffset, node2IndexByteOffset, depth1, depth2 + 1,
|
||||
currBox, reversed,
|
||||
) || _traverse(
|
||||
node1Index32, cr2, matrix2to1, matrix1to2, intersectsRangesFunc,
|
||||
node1IndexByteOffset, node2IndexByteOffset, depth1, depth2 + 1,
|
||||
currBox, reversed,
|
||||
);
|
||||
|
||||
} else if ( leftIntersects ) {
|
||||
|
||||
if ( isLeaf1 ) {
|
||||
|
||||
// if the current box is a leaf then just continue
|
||||
result = _traverse(
|
||||
node1Index32, cl2, matrix2to1, matrix1to2, intersectsRangesFunc,
|
||||
node1IndexByteOffset, node2IndexByteOffset, depth1, depth2 + 1,
|
||||
currBox, reversed,
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
// SWAP
|
||||
// if only one box intersects then we have to swap to the other bvh to continue
|
||||
const newBox = _boxPool.getPrimitive();
|
||||
newBox.copy( _leftBox2 ).applyMatrix4( matrix2to1 );
|
||||
|
||||
const cl1 = LEFT_NODE( node1Index32 );
|
||||
const cr1 = RIGHT_NODE( node1Index32, uint32Array1 );
|
||||
arrayToBox( BOUNDING_DATA_INDEX( cl1 ), float32Array1, _leftBox1 );
|
||||
arrayToBox( BOUNDING_DATA_INDEX( cr1 ), float32Array1, _rightBox1 );
|
||||
|
||||
// precompute the intersections otherwise the global boxes will be modified during traversal
|
||||
const intersectCl1 = newBox.intersectsBox( _leftBox1 );
|
||||
const intersectCr1 = newBox.intersectsBox( _rightBox1 );
|
||||
result = (
|
||||
intersectCl1 && _traverse(
|
||||
cl2, cl1, matrix1to2, matrix2to1, intersectsRangesFunc,
|
||||
node2IndexByteOffset, node1IndexByteOffset, depth2, depth1 + 1,
|
||||
newBox, ! reversed,
|
||||
)
|
||||
) || (
|
||||
intersectCr1 && _traverse(
|
||||
cl2, cr1, matrix1to2, matrix2to1, intersectsRangesFunc,
|
||||
node2IndexByteOffset, node1IndexByteOffset, depth2, depth1 + 1,
|
||||
newBox, ! reversed,
|
||||
)
|
||||
);
|
||||
|
||||
_boxPool.releasePrimitive( newBox );
|
||||
|
||||
}
|
||||
|
||||
} else if ( rightIntersects ) {
|
||||
|
||||
if ( isLeaf1 ) {
|
||||
|
||||
// if the current box is a leaf then just continue
|
||||
result = _traverse(
|
||||
node1Index32, cr2, matrix2to1, matrix1to2, intersectsRangesFunc,
|
||||
node1IndexByteOffset, node2IndexByteOffset, depth1, depth2 + 1,
|
||||
currBox, reversed,
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
// SWAP
|
||||
// if only one box intersects then we have to swap to the other bvh to continue
|
||||
const newBox = _boxPool.getPrimitive();
|
||||
newBox.copy( _rightBox2 ).applyMatrix4( matrix2to1 );
|
||||
|
||||
const cl1 = LEFT_NODE( node1Index32 );
|
||||
const cr1 = RIGHT_NODE( node1Index32, uint32Array1 );
|
||||
arrayToBox( BOUNDING_DATA_INDEX( cl1 ), float32Array1, _leftBox1 );
|
||||
arrayToBox( BOUNDING_DATA_INDEX( cr1 ), float32Array1, _rightBox1 );
|
||||
|
||||
// precompute the intersections otherwise the global boxes will be modified during traversal
|
||||
const intersectCl1 = newBox.intersectsBox( _leftBox1 );
|
||||
const intersectCr1 = newBox.intersectsBox( _rightBox1 );
|
||||
result = (
|
||||
intersectCl1 && _traverse(
|
||||
cr2, cl1, matrix1to2, matrix2to1, intersectsRangesFunc,
|
||||
node2IndexByteOffset, node1IndexByteOffset, depth2, depth1 + 1,
|
||||
newBox, ! reversed,
|
||||
)
|
||||
) || (
|
||||
intersectCr1 && _traverse(
|
||||
cr2, cr1, matrix1to2, matrix2to1, intersectsRangesFunc,
|
||||
node2IndexByteOffset, node1IndexByteOffset, depth2, depth1 + 1,
|
||||
newBox, ! reversed,
|
||||
)
|
||||
);
|
||||
|
||||
_boxPool.releasePrimitive( newBox );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
import { Matrix4, Vector3 } from 'three'
|
||||
import { OrientedBox } from '../../math/OrientedBox.js';
|
||||
import { setTriangle } from '../../utils/TriangleUtilities.js';
|
||||
import { getTriCount } from '../build/geometryUtils.js';
|
||||
import { ExtendedTrianglePool } from '../../utils/ExtendedTrianglePool.js';
|
||||
|
||||
/*********************************************************************/
|
||||
/* This file is generated from "closestPointToGeometry.template.js". */
|
||||
/*********************************************************************/
|
||||
|
||||
const tempMatrix = /* @__PURE__ */ new Matrix4();
|
||||
const obb = /* @__PURE__ */ new OrientedBox();
|
||||
const obb2 = /* @__PURE__ */ new OrientedBox();
|
||||
const temp1 = /* @__PURE__ */ new Vector3();
|
||||
const temp2 = /* @__PURE__ */ new Vector3();
|
||||
const temp3 = /* @__PURE__ */ new Vector3();
|
||||
const temp4 = /* @__PURE__ */ new Vector3();
|
||||
|
||||
function closestPointToGeometry(
|
||||
bvh,
|
||||
otherGeometry,
|
||||
geometryToBvh,
|
||||
target1 = { },
|
||||
target2 = { },
|
||||
minThreshold = 0,
|
||||
maxThreshold = Infinity,
|
||||
) {
|
||||
|
||||
if ( ! otherGeometry.boundingBox ) {
|
||||
|
||||
otherGeometry.computeBoundingBox();
|
||||
|
||||
}
|
||||
|
||||
obb.set( otherGeometry.boundingBox.min, otherGeometry.boundingBox.max, geometryToBvh );
|
||||
obb.needsUpdate = true;
|
||||
|
||||
const geometry = bvh.geometry;
|
||||
const pos = geometry.attributes.position;
|
||||
const index = geometry.index;
|
||||
const otherPos = otherGeometry.attributes.position;
|
||||
const otherIndex = otherGeometry.index;
|
||||
const triangle = ExtendedTrianglePool.getPrimitive();
|
||||
const triangle2 = ExtendedTrianglePool.getPrimitive();
|
||||
|
||||
let tempTarget1 = temp1;
|
||||
let tempTargetDest1 = temp2;
|
||||
let tempTarget2 = null;
|
||||
let tempTargetDest2 = null;
|
||||
|
||||
if ( target2 ) {
|
||||
|
||||
tempTarget2 = temp3;
|
||||
tempTargetDest2 = temp4;
|
||||
|
||||
}
|
||||
|
||||
let closestDistance = Infinity;
|
||||
let closestDistanceTriIndex = null;
|
||||
let closestDistanceOtherTriIndex = null;
|
||||
tempMatrix.copy( geometryToBvh ).invert();
|
||||
obb2.matrix.copy( tempMatrix );
|
||||
bvh.shapecast(
|
||||
{
|
||||
|
||||
boundsTraverseOrder: box => {
|
||||
|
||||
return obb.distanceToBox( box );
|
||||
|
||||
},
|
||||
|
||||
intersectsBounds: ( box, isLeaf, score ) => {
|
||||
|
||||
if ( score < closestDistance && score < maxThreshold ) {
|
||||
|
||||
// if we know the triangles of this bounds will be intersected next then
|
||||
// save the bounds to use during triangle checks.
|
||||
if ( isLeaf ) {
|
||||
|
||||
obb2.min.copy( box.min );
|
||||
obb2.max.copy( box.max );
|
||||
obb2.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
intersectsRange: ( offset, count ) => {
|
||||
|
||||
if ( otherGeometry.boundsTree ) {
|
||||
|
||||
// if the other geometry has a bvh then use the accelerated path where we use shapecast to find
|
||||
// the closest bounds in the other geometry to check.
|
||||
const otherBvh = otherGeometry.boundsTree;
|
||||
return otherBvh.shapecast( {
|
||||
boundsTraverseOrder: box => {
|
||||
|
||||
return obb2.distanceToBox( box );
|
||||
|
||||
},
|
||||
|
||||
intersectsBounds: ( box, isLeaf, score ) => {
|
||||
|
||||
return score < closestDistance && score < maxThreshold;
|
||||
|
||||
},
|
||||
|
||||
intersectsRange: ( otherOffset, otherCount ) => {
|
||||
|
||||
for ( let i2 = otherOffset, l2 = otherOffset + otherCount; i2 < l2; i2 ++ ) {
|
||||
|
||||
|
||||
setTriangle( triangle2, 3 * i2, otherIndex, otherPos );
|
||||
|
||||
triangle2.a.applyMatrix4( geometryToBvh );
|
||||
triangle2.b.applyMatrix4( geometryToBvh );
|
||||
triangle2.c.applyMatrix4( geometryToBvh );
|
||||
triangle2.needsUpdate = true;
|
||||
|
||||
for ( let i = offset, l = offset + count; i < l; i ++ ) {
|
||||
|
||||
|
||||
setTriangle( triangle, 3 * i, index, pos );
|
||||
|
||||
triangle.needsUpdate = true;
|
||||
|
||||
const dist = triangle.distanceToTriangle( triangle2, tempTarget1, tempTarget2 );
|
||||
if ( dist < closestDistance ) {
|
||||
|
||||
tempTargetDest1.copy( tempTarget1 );
|
||||
|
||||
if ( tempTargetDest2 ) {
|
||||
|
||||
tempTargetDest2.copy( tempTarget2 );
|
||||
|
||||
}
|
||||
|
||||
closestDistance = dist;
|
||||
closestDistanceTriIndex = i;
|
||||
closestDistanceOtherTriIndex = i2;
|
||||
|
||||
}
|
||||
|
||||
// stop traversal if we find a point that's under the given threshold
|
||||
if ( dist < minThreshold ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
} );
|
||||
|
||||
} else {
|
||||
|
||||
// If no bounds tree then we'll just check every triangle.
|
||||
const triCount = getTriCount( otherGeometry );
|
||||
for ( let i2 = 0, l2 = triCount; i2 < l2; i2 ++ ) {
|
||||
|
||||
setTriangle( triangle2, 3 * i2, otherIndex, otherPos );
|
||||
triangle2.a.applyMatrix4( geometryToBvh );
|
||||
triangle2.b.applyMatrix4( geometryToBvh );
|
||||
triangle2.c.applyMatrix4( geometryToBvh );
|
||||
triangle2.needsUpdate = true;
|
||||
|
||||
for ( let i = offset, l = offset + count; i < l; i ++ ) {
|
||||
|
||||
|
||||
setTriangle( triangle, 3 * i, index, pos );
|
||||
|
||||
triangle.needsUpdate = true;
|
||||
|
||||
const dist = triangle.distanceToTriangle( triangle2, tempTarget1, tempTarget2 );
|
||||
if ( dist < closestDistance ) {
|
||||
|
||||
tempTargetDest1.copy( tempTarget1 );
|
||||
|
||||
if ( tempTargetDest2 ) {
|
||||
|
||||
tempTargetDest2.copy( tempTarget2 );
|
||||
|
||||
}
|
||||
|
||||
closestDistance = dist;
|
||||
closestDistanceTriIndex = i;
|
||||
closestDistanceOtherTriIndex = i2;
|
||||
|
||||
}
|
||||
|
||||
// stop traversal if we find a point that's under the given threshold
|
||||
if ( dist < minThreshold ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
ExtendedTrianglePool.releasePrimitive( triangle );
|
||||
ExtendedTrianglePool.releasePrimitive( triangle2 );
|
||||
|
||||
if ( closestDistance === Infinity ) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
if ( ! target1.point ) {
|
||||
|
||||
target1.point = tempTargetDest1.clone();
|
||||
|
||||
} else {
|
||||
|
||||
target1.point.copy( tempTargetDest1 );
|
||||
|
||||
}
|
||||
|
||||
target1.distance = closestDistance,
|
||||
target1.faceIndex = closestDistanceTriIndex;
|
||||
|
||||
if ( target2 ) {
|
||||
|
||||
if ( ! target2.point ) target2.point = tempTargetDest2.clone();
|
||||
else target2.point.copy( tempTargetDest2 );
|
||||
target2.point.applyMatrix4( tempMatrix );
|
||||
tempTargetDest1.applyMatrix4( tempMatrix );
|
||||
target2.distance = tempTargetDest1.sub( target2.point ).length();
|
||||
target2.faceIndex = closestDistanceOtherTriIndex;
|
||||
|
||||
}
|
||||
|
||||
return target1;
|
||||
|
||||
}
|
||||
|
||||
export { closestPointToGeometry };
|
||||
@@ -0,0 +1,271 @@
|
||||
import { Vector3, Matrix4 } from 'three'
|
||||
import { OrientedBox } from '../../math/OrientedBox.js';
|
||||
import { setTriangle } from '../../utils/TriangleUtilities.js';
|
||||
import { getTriCount } from '../build/geometryUtils.js';
|
||||
import { ExtendedTrianglePool } from '../../utils/ExtendedTrianglePool.js';
|
||||
|
||||
const tempMatrix = /* @__PURE__ */ new Matrix4();
|
||||
const obb = /* @__PURE__ */ new OrientedBox();
|
||||
const obb2 = /* @__PURE__ */ new OrientedBox();
|
||||
const temp1 = /* @__PURE__ */ new Vector3();
|
||||
const temp2 = /* @__PURE__ */ new Vector3();
|
||||
const temp3 = /* @__PURE__ */ new Vector3();
|
||||
const temp4 = /* @__PURE__ */ new Vector3();
|
||||
|
||||
export function closestPointToGeometry/* @echo INDIRECT_STRING */(
|
||||
bvh,
|
||||
otherGeometry,
|
||||
geometryToBvh,
|
||||
target1 = { },
|
||||
target2 = { },
|
||||
minThreshold = 0,
|
||||
maxThreshold = Infinity,
|
||||
) {
|
||||
|
||||
if ( ! otherGeometry.boundingBox ) {
|
||||
|
||||
otherGeometry.computeBoundingBox();
|
||||
|
||||
}
|
||||
|
||||
obb.set( otherGeometry.boundingBox.min, otherGeometry.boundingBox.max, geometryToBvh );
|
||||
obb.needsUpdate = true;
|
||||
|
||||
const geometry = bvh.geometry;
|
||||
const pos = geometry.attributes.position;
|
||||
const index = geometry.index;
|
||||
const otherPos = otherGeometry.attributes.position;
|
||||
const otherIndex = otherGeometry.index;
|
||||
const triangle = ExtendedTrianglePool.getPrimitive();
|
||||
const triangle2 = ExtendedTrianglePool.getPrimitive();
|
||||
|
||||
let tempTarget1 = temp1;
|
||||
let tempTargetDest1 = temp2;
|
||||
let tempTarget2 = null;
|
||||
let tempTargetDest2 = null;
|
||||
|
||||
if ( target2 ) {
|
||||
|
||||
tempTarget2 = temp3;
|
||||
tempTargetDest2 = temp4;
|
||||
|
||||
}
|
||||
|
||||
let closestDistance = Infinity;
|
||||
let closestDistanceTriIndex = null;
|
||||
let closestDistanceOtherTriIndex = null;
|
||||
tempMatrix.copy( geometryToBvh ).invert();
|
||||
obb2.matrix.copy( tempMatrix );
|
||||
bvh.shapecast(
|
||||
{
|
||||
|
||||
boundsTraverseOrder: box => {
|
||||
|
||||
return obb.distanceToBox( box );
|
||||
|
||||
},
|
||||
|
||||
intersectsBounds: ( box, isLeaf, score ) => {
|
||||
|
||||
if ( score < closestDistance && score < maxThreshold ) {
|
||||
|
||||
// if we know the triangles of this bounds will be intersected next then
|
||||
// save the bounds to use during triangle checks.
|
||||
if ( isLeaf ) {
|
||||
|
||||
obb2.min.copy( box.min );
|
||||
obb2.max.copy( box.max );
|
||||
obb2.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
intersectsRange: ( offset, count ) => {
|
||||
|
||||
if ( otherGeometry.boundsTree ) {
|
||||
|
||||
// if the other geometry has a bvh then use the accelerated path where we use shapecast to find
|
||||
// the closest bounds in the other geometry to check.
|
||||
const otherBvh = otherGeometry.boundsTree;
|
||||
return otherBvh.shapecast( {
|
||||
boundsTraverseOrder: box => {
|
||||
|
||||
return obb2.distanceToBox( box );
|
||||
|
||||
},
|
||||
|
||||
intersectsBounds: ( box, isLeaf, score ) => {
|
||||
|
||||
return score < closestDistance && score < maxThreshold;
|
||||
|
||||
},
|
||||
|
||||
intersectsRange: ( otherOffset, otherCount ) => {
|
||||
|
||||
for ( let i2 = otherOffset, l2 = otherOffset + otherCount; i2 < l2; i2 ++ ) {
|
||||
|
||||
/* @if INDIRECT */
|
||||
|
||||
const ti2 = otherBvh.resolveTriangleIndex( i2 );
|
||||
setTriangle( triangle2, 3 * ti2, otherIndex, otherPos );
|
||||
|
||||
/* @else */
|
||||
|
||||
setTriangle( triangle2, 3 * i2, otherIndex, otherPos );
|
||||
|
||||
/* @endif */
|
||||
triangle2.a.applyMatrix4( geometryToBvh );
|
||||
triangle2.b.applyMatrix4( geometryToBvh );
|
||||
triangle2.c.applyMatrix4( geometryToBvh );
|
||||
triangle2.needsUpdate = true;
|
||||
|
||||
for ( let i = offset, l = offset + count; i < l; i ++ ) {
|
||||
|
||||
/* @if INDIRECT */
|
||||
|
||||
const ti = bvh.resolveTriangleIndex( i );
|
||||
setTriangle( triangle, 3 * ti, index, pos );
|
||||
|
||||
/* @else */
|
||||
|
||||
setTriangle( triangle, 3 * i, index, pos );
|
||||
|
||||
/* @endif */
|
||||
triangle.needsUpdate = true;
|
||||
|
||||
const dist = triangle.distanceToTriangle( triangle2, tempTarget1, tempTarget2 );
|
||||
if ( dist < closestDistance ) {
|
||||
|
||||
tempTargetDest1.copy( tempTarget1 );
|
||||
|
||||
if ( tempTargetDest2 ) {
|
||||
|
||||
tempTargetDest2.copy( tempTarget2 );
|
||||
|
||||
}
|
||||
|
||||
closestDistance = dist;
|
||||
closestDistanceTriIndex = i;
|
||||
closestDistanceOtherTriIndex = i2;
|
||||
|
||||
}
|
||||
|
||||
// stop traversal if we find a point that's under the given threshold
|
||||
if ( dist < minThreshold ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
} );
|
||||
|
||||
} else {
|
||||
|
||||
// If no bounds tree then we'll just check every triangle.
|
||||
const triCount = getTriCount( otherGeometry );
|
||||
for ( let i2 = 0, l2 = triCount; i2 < l2; i2 ++ ) {
|
||||
|
||||
setTriangle( triangle2, 3 * i2, otherIndex, otherPos );
|
||||
triangle2.a.applyMatrix4( geometryToBvh );
|
||||
triangle2.b.applyMatrix4( geometryToBvh );
|
||||
triangle2.c.applyMatrix4( geometryToBvh );
|
||||
triangle2.needsUpdate = true;
|
||||
|
||||
for ( let i = offset, l = offset + count; i < l; i ++ ) {
|
||||
|
||||
/* @if INDIRECT */
|
||||
|
||||
const ti = bvh.resolveTriangleIndex( i );
|
||||
setTriangle( triangle, 3 * ti, index, pos );
|
||||
|
||||
/* @else */
|
||||
|
||||
setTriangle( triangle, 3 * i, index, pos );
|
||||
|
||||
/* @endif */
|
||||
triangle.needsUpdate = true;
|
||||
|
||||
const dist = triangle.distanceToTriangle( triangle2, tempTarget1, tempTarget2 );
|
||||
if ( dist < closestDistance ) {
|
||||
|
||||
tempTargetDest1.copy( tempTarget1 );
|
||||
|
||||
if ( tempTargetDest2 ) {
|
||||
|
||||
tempTargetDest2.copy( tempTarget2 );
|
||||
|
||||
}
|
||||
|
||||
closestDistance = dist;
|
||||
closestDistanceTriIndex = i;
|
||||
closestDistanceOtherTriIndex = i2;
|
||||
|
||||
}
|
||||
|
||||
// stop traversal if we find a point that's under the given threshold
|
||||
if ( dist < minThreshold ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
ExtendedTrianglePool.releasePrimitive( triangle );
|
||||
ExtendedTrianglePool.releasePrimitive( triangle2 );
|
||||
|
||||
if ( closestDistance === Infinity ) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
if ( ! target1.point ) {
|
||||
|
||||
target1.point = tempTargetDest1.clone();
|
||||
|
||||
} else {
|
||||
|
||||
target1.point.copy( tempTargetDest1 );
|
||||
|
||||
}
|
||||
|
||||
target1.distance = closestDistance,
|
||||
target1.faceIndex = closestDistanceTriIndex;
|
||||
|
||||
if ( target2 ) {
|
||||
|
||||
if ( ! target2.point ) target2.point = tempTargetDest2.clone();
|
||||
else target2.point.copy( tempTargetDest2 );
|
||||
target2.point.applyMatrix4( tempMatrix );
|
||||
tempTargetDest1.applyMatrix4( tempMatrix );
|
||||
target2.distance = tempTargetDest1.sub( target2.point ).length();
|
||||
target2.faceIndex = closestDistanceOtherTriIndex;
|
||||
|
||||
}
|
||||
|
||||
return target1;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
import { Matrix4, Vector3 } from 'three'
|
||||
import { OrientedBox } from '../../math/OrientedBox.js';
|
||||
import { setTriangle } from '../../utils/TriangleUtilities.js';
|
||||
import { getTriCount } from '../build/geometryUtils.js';
|
||||
import { ExtendedTrianglePool } from '../../utils/ExtendedTrianglePool.js';
|
||||
|
||||
/*********************************************************************/
|
||||
/* This file is generated from "closestPointToGeometry.template.js". */
|
||||
/*********************************************************************/
|
||||
|
||||
const tempMatrix = /* @__PURE__ */ new Matrix4();
|
||||
const obb = /* @__PURE__ */ new OrientedBox();
|
||||
const obb2 = /* @__PURE__ */ new OrientedBox();
|
||||
const temp1 = /* @__PURE__ */ new Vector3();
|
||||
const temp2 = /* @__PURE__ */ new Vector3();
|
||||
const temp3 = /* @__PURE__ */ new Vector3();
|
||||
const temp4 = /* @__PURE__ */ new Vector3();
|
||||
|
||||
function closestPointToGeometry_indirect(
|
||||
bvh,
|
||||
otherGeometry,
|
||||
geometryToBvh,
|
||||
target1 = { },
|
||||
target2 = { },
|
||||
minThreshold = 0,
|
||||
maxThreshold = Infinity,
|
||||
) {
|
||||
|
||||
if ( ! otherGeometry.boundingBox ) {
|
||||
|
||||
otherGeometry.computeBoundingBox();
|
||||
|
||||
}
|
||||
|
||||
obb.set( otherGeometry.boundingBox.min, otherGeometry.boundingBox.max, geometryToBvh );
|
||||
obb.needsUpdate = true;
|
||||
|
||||
const geometry = bvh.geometry;
|
||||
const pos = geometry.attributes.position;
|
||||
const index = geometry.index;
|
||||
const otherPos = otherGeometry.attributes.position;
|
||||
const otherIndex = otherGeometry.index;
|
||||
const triangle = ExtendedTrianglePool.getPrimitive();
|
||||
const triangle2 = ExtendedTrianglePool.getPrimitive();
|
||||
|
||||
let tempTarget1 = temp1;
|
||||
let tempTargetDest1 = temp2;
|
||||
let tempTarget2 = null;
|
||||
let tempTargetDest2 = null;
|
||||
|
||||
if ( target2 ) {
|
||||
|
||||
tempTarget2 = temp3;
|
||||
tempTargetDest2 = temp4;
|
||||
|
||||
}
|
||||
|
||||
let closestDistance = Infinity;
|
||||
let closestDistanceTriIndex = null;
|
||||
let closestDistanceOtherTriIndex = null;
|
||||
tempMatrix.copy( geometryToBvh ).invert();
|
||||
obb2.matrix.copy( tempMatrix );
|
||||
bvh.shapecast(
|
||||
{
|
||||
|
||||
boundsTraverseOrder: box => {
|
||||
|
||||
return obb.distanceToBox( box );
|
||||
|
||||
},
|
||||
|
||||
intersectsBounds: ( box, isLeaf, score ) => {
|
||||
|
||||
if ( score < closestDistance && score < maxThreshold ) {
|
||||
|
||||
// if we know the triangles of this bounds will be intersected next then
|
||||
// save the bounds to use during triangle checks.
|
||||
if ( isLeaf ) {
|
||||
|
||||
obb2.min.copy( box.min );
|
||||
obb2.max.copy( box.max );
|
||||
obb2.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
intersectsRange: ( offset, count ) => {
|
||||
|
||||
if ( otherGeometry.boundsTree ) {
|
||||
|
||||
// if the other geometry has a bvh then use the accelerated path where we use shapecast to find
|
||||
// the closest bounds in the other geometry to check.
|
||||
const otherBvh = otherGeometry.boundsTree;
|
||||
return otherBvh.shapecast( {
|
||||
boundsTraverseOrder: box => {
|
||||
|
||||
return obb2.distanceToBox( box );
|
||||
|
||||
},
|
||||
|
||||
intersectsBounds: ( box, isLeaf, score ) => {
|
||||
|
||||
return score < closestDistance && score < maxThreshold;
|
||||
|
||||
},
|
||||
|
||||
intersectsRange: ( otherOffset, otherCount ) => {
|
||||
|
||||
for ( let i2 = otherOffset, l2 = otherOffset + otherCount; i2 < l2; i2 ++ ) {
|
||||
|
||||
const ti2 = otherBvh.resolveTriangleIndex( i2 );
|
||||
setTriangle( triangle2, 3 * ti2, otherIndex, otherPos );
|
||||
|
||||
triangle2.a.applyMatrix4( geometryToBvh );
|
||||
triangle2.b.applyMatrix4( geometryToBvh );
|
||||
triangle2.c.applyMatrix4( geometryToBvh );
|
||||
triangle2.needsUpdate = true;
|
||||
|
||||
for ( let i = offset, l = offset + count; i < l; i ++ ) {
|
||||
|
||||
const ti = bvh.resolveTriangleIndex( i );
|
||||
setTriangle( triangle, 3 * ti, index, pos );
|
||||
|
||||
triangle.needsUpdate = true;
|
||||
|
||||
const dist = triangle.distanceToTriangle( triangle2, tempTarget1, tempTarget2 );
|
||||
if ( dist < closestDistance ) {
|
||||
|
||||
tempTargetDest1.copy( tempTarget1 );
|
||||
|
||||
if ( tempTargetDest2 ) {
|
||||
|
||||
tempTargetDest2.copy( tempTarget2 );
|
||||
|
||||
}
|
||||
|
||||
closestDistance = dist;
|
||||
closestDistanceTriIndex = i;
|
||||
closestDistanceOtherTriIndex = i2;
|
||||
|
||||
}
|
||||
|
||||
// stop traversal if we find a point that's under the given threshold
|
||||
if ( dist < minThreshold ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
} );
|
||||
|
||||
} else {
|
||||
|
||||
// If no bounds tree then we'll just check every triangle.
|
||||
const triCount = getTriCount( otherGeometry );
|
||||
for ( let i2 = 0, l2 = triCount; i2 < l2; i2 ++ ) {
|
||||
|
||||
setTriangle( triangle2, 3 * i2, otherIndex, otherPos );
|
||||
triangle2.a.applyMatrix4( geometryToBvh );
|
||||
triangle2.b.applyMatrix4( geometryToBvh );
|
||||
triangle2.c.applyMatrix4( geometryToBvh );
|
||||
triangle2.needsUpdate = true;
|
||||
|
||||
for ( let i = offset, l = offset + count; i < l; i ++ ) {
|
||||
|
||||
const ti = bvh.resolveTriangleIndex( i );
|
||||
setTriangle( triangle, 3 * ti, index, pos );
|
||||
|
||||
triangle.needsUpdate = true;
|
||||
|
||||
const dist = triangle.distanceToTriangle( triangle2, tempTarget1, tempTarget2 );
|
||||
if ( dist < closestDistance ) {
|
||||
|
||||
tempTargetDest1.copy( tempTarget1 );
|
||||
|
||||
if ( tempTargetDest2 ) {
|
||||
|
||||
tempTargetDest2.copy( tempTarget2 );
|
||||
|
||||
}
|
||||
|
||||
closestDistance = dist;
|
||||
closestDistanceTriIndex = i;
|
||||
closestDistanceOtherTriIndex = i2;
|
||||
|
||||
}
|
||||
|
||||
// stop traversal if we find a point that's under the given threshold
|
||||
if ( dist < minThreshold ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
ExtendedTrianglePool.releasePrimitive( triangle );
|
||||
ExtendedTrianglePool.releasePrimitive( triangle2 );
|
||||
|
||||
if ( closestDistance === Infinity ) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
if ( ! target1.point ) {
|
||||
|
||||
target1.point = tempTargetDest1.clone();
|
||||
|
||||
} else {
|
||||
|
||||
target1.point.copy( tempTargetDest1 );
|
||||
|
||||
}
|
||||
|
||||
target1.distance = closestDistance,
|
||||
target1.faceIndex = closestDistanceTriIndex;
|
||||
|
||||
if ( target2 ) {
|
||||
|
||||
if ( ! target2.point ) target2.point = tempTargetDest2.clone();
|
||||
else target2.point.copy( tempTargetDest2 );
|
||||
target2.point.applyMatrix4( tempMatrix );
|
||||
tempTargetDest1.applyMatrix4( tempMatrix );
|
||||
target2.distance = tempTargetDest1.sub( target2.point ).length();
|
||||
target2.faceIndex = closestDistanceOtherTriIndex;
|
||||
|
||||
}
|
||||
|
||||
return target1;
|
||||
|
||||
}
|
||||
|
||||
export { closestPointToGeometry_indirect };
|
||||
78
src/components/DMT/libs/bvh/core/cast/closestPointToPoint.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Vector3 } from 'three'
|
||||
|
||||
const temp = /* @__PURE__ */ new Vector3();
|
||||
const temp1 = /* @__PURE__ */ new Vector3();
|
||||
|
||||
export function closestPointToPoint(
|
||||
bvh,
|
||||
point,
|
||||
target = { },
|
||||
minThreshold = 0,
|
||||
maxThreshold = Infinity,
|
||||
) {
|
||||
|
||||
// early out if under minThreshold
|
||||
// skip checking if over maxThreshold
|
||||
// set minThreshold = maxThreshold to quickly check if a point is within a threshold
|
||||
// returns Infinity if no value found
|
||||
const minThresholdSq = minThreshold * minThreshold;
|
||||
const maxThresholdSq = maxThreshold * maxThreshold;
|
||||
let closestDistanceSq = Infinity;
|
||||
let closestDistanceTriIndex = null;
|
||||
bvh.shapecast(
|
||||
|
||||
{
|
||||
|
||||
boundsTraverseOrder: box => {
|
||||
|
||||
temp.copy( point ).clamp( box.min, box.max );
|
||||
return temp.distanceToSquared( point );
|
||||
|
||||
},
|
||||
|
||||
intersectsBounds: ( box, isLeaf, score ) => {
|
||||
|
||||
return score < closestDistanceSq && score < maxThresholdSq;
|
||||
|
||||
},
|
||||
|
||||
intersectsTriangle: ( tri, triIndex ) => {
|
||||
|
||||
tri.closestPointToPoint( point, temp );
|
||||
const distSq = point.distanceToSquared( temp );
|
||||
if ( distSq < closestDistanceSq ) {
|
||||
|
||||
temp1.copy( temp );
|
||||
closestDistanceSq = distSq;
|
||||
closestDistanceTriIndex = triIndex;
|
||||
|
||||
}
|
||||
|
||||
if ( distSq < minThresholdSq ) {
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
if ( closestDistanceSq === Infinity ) return null;
|
||||
|
||||
const closestDistance = Math.sqrt( closestDistanceSq );
|
||||
|
||||
if ( ! target.point ) target.point = temp1.clone();
|
||||
else target.point.copy( temp1 );
|
||||
target.distance = closestDistance,
|
||||
target.faceIndex = closestDistanceTriIndex;
|
||||
|
||||
return target;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import { Box3, Matrix4 } from 'three'
|
||||
import { OrientedBox } from '../../math/OrientedBox.js';
|
||||
import { ExtendedTriangle } from '../../math/ExtendedTriangle.js';
|
||||
import { setTriangle } from '../../utils/TriangleUtilities.js';
|
||||
import { arrayToBox } from '../../utils/ArrayBoxUtilities.js';
|
||||
import { IS_LEAF, OFFSET, COUNT, BOUNDING_DATA_INDEX } from '../utils/nodeBufferUtils.js';
|
||||
import { BufferStack } from '../utils/BufferStack.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/* This file is generated from "intersectsGeometry.template.js". */
|
||||
/*****************************************************************/
|
||||
/* eslint-disable indent */
|
||||
|
||||
const boundingBox = /* @__PURE__ */ new Box3();
|
||||
const triangle = /* @__PURE__ */ new ExtendedTriangle();
|
||||
const triangle2 = /* @__PURE__ */ new ExtendedTriangle();
|
||||
const invertedMat = /* @__PURE__ */ new Matrix4();
|
||||
|
||||
const obb = /* @__PURE__ */ new OrientedBox();
|
||||
const obb2 = /* @__PURE__ */ new OrientedBox();
|
||||
|
||||
function intersectsGeometry( bvh, root, otherGeometry, geometryToBvh ) {
|
||||
|
||||
BufferStack.setBuffer( bvh._roots[ root ] );
|
||||
const result = _intersectsGeometry( 0, bvh, otherGeometry, geometryToBvh );
|
||||
BufferStack.clearBuffer();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
function _intersectsGeometry( nodeIndex32, bvh, otherGeometry, geometryToBvh, cachedObb = null ) {
|
||||
|
||||
const { float32Array, uint16Array, uint32Array } = BufferStack;
|
||||
let nodeIndex16 = nodeIndex32 * 2;
|
||||
|
||||
if ( cachedObb === null ) {
|
||||
|
||||
if ( ! otherGeometry.boundingBox ) {
|
||||
|
||||
otherGeometry.computeBoundingBox();
|
||||
|
||||
}
|
||||
|
||||
obb.set( otherGeometry.boundingBox.min, otherGeometry.boundingBox.max, geometryToBvh );
|
||||
cachedObb = obb;
|
||||
|
||||
}
|
||||
|
||||
const isLeaf = IS_LEAF( nodeIndex16, uint16Array );
|
||||
if ( isLeaf ) {
|
||||
|
||||
const thisGeometry = bvh.geometry;
|
||||
const thisIndex = thisGeometry.index;
|
||||
const thisPos = thisGeometry.attributes.position;
|
||||
|
||||
const index = otherGeometry.index;
|
||||
const pos = otherGeometry.attributes.position;
|
||||
|
||||
const offset = OFFSET( nodeIndex32, uint32Array );
|
||||
const count = COUNT( nodeIndex16, uint16Array );
|
||||
|
||||
// get the inverse of the geometry matrix so we can transform our triangles into the
|
||||
// geometry space we're trying to test. We assume there are fewer triangles being checked
|
||||
// here.
|
||||
invertedMat.copy( geometryToBvh ).invert();
|
||||
|
||||
if ( otherGeometry.boundsTree ) {
|
||||
|
||||
// if there's a bounds tree
|
||||
arrayToBox( BOUNDING_DATA_INDEX( nodeIndex32 ), float32Array, obb2 );
|
||||
obb2.matrix.copy( invertedMat );
|
||||
obb2.needsUpdate = true;
|
||||
|
||||
// TODO: use a triangle iteration function here
|
||||
const res = otherGeometry.boundsTree.shapecast( {
|
||||
|
||||
intersectsBounds: box => obb2.intersectsBox( box ),
|
||||
|
||||
intersectsTriangle: tri => {
|
||||
|
||||
tri.a.applyMatrix4( geometryToBvh );
|
||||
tri.b.applyMatrix4( geometryToBvh );
|
||||
tri.c.applyMatrix4( geometryToBvh );
|
||||
tri.needsUpdate = true;
|
||||
|
||||
|
||||
for ( let i = offset * 3, l = ( count + offset ) * 3; i < l; i += 3 ) {
|
||||
|
||||
// this triangle needs to be transformed into the current BVH coordinate frame
|
||||
setTriangle( triangle2, i, thisIndex, thisPos );
|
||||
triangle2.needsUpdate = true;
|
||||
if ( tri.intersectsTriangle( triangle2 ) ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
return res;
|
||||
|
||||
} else {
|
||||
|
||||
// if we're just dealing with raw geometry
|
||||
|
||||
for ( let i = offset * 3, l = ( count + offset ) * 3; i < l; i += 3 ) {
|
||||
|
||||
// this triangle needs to be transformed into the current BVH coordinate frame
|
||||
setTriangle( triangle, i, thisIndex, thisPos );
|
||||
|
||||
|
||||
triangle.a.applyMatrix4( invertedMat );
|
||||
triangle.b.applyMatrix4( invertedMat );
|
||||
triangle.c.applyMatrix4( invertedMat );
|
||||
triangle.needsUpdate = true;
|
||||
|
||||
for ( let i2 = 0, l2 = index.count; i2 < l2; i2 += 3 ) {
|
||||
|
||||
setTriangle( triangle2, i2, index, pos );
|
||||
triangle2.needsUpdate = true;
|
||||
|
||||
if ( triangle.intersectsTriangle( triangle2 ) ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
const left = nodeIndex32 + 8;
|
||||
const right = uint32Array[ nodeIndex32 + 6 ];
|
||||
|
||||
arrayToBox( BOUNDING_DATA_INDEX( left ), float32Array, boundingBox );
|
||||
const leftIntersection =
|
||||
cachedObb.intersectsBox( boundingBox ) &&
|
||||
_intersectsGeometry( left, bvh, otherGeometry, geometryToBvh, cachedObb );
|
||||
|
||||
if ( leftIntersection ) return true;
|
||||
|
||||
arrayToBox( BOUNDING_DATA_INDEX( right ), float32Array, boundingBox );
|
||||
const rightIntersection =
|
||||
cachedObb.intersectsBox( boundingBox ) &&
|
||||
_intersectsGeometry( right, bvh, otherGeometry, geometryToBvh, cachedObb );
|
||||
|
||||
if ( rightIntersection ) return true;
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { intersectsGeometry };
|
||||
@@ -0,0 +1,196 @@
|
||||
/* eslint-disable indent */
|
||||
import { Box3, Matrix4 } from 'three'
|
||||
import { OrientedBox } from '../../math/OrientedBox.js';
|
||||
import { ExtendedTriangle } from '../../math/ExtendedTriangle.js';
|
||||
import { setTriangle } from '../../utils/TriangleUtilities.js';
|
||||
import { arrayToBox } from '../../utils/ArrayBoxUtilities.js';
|
||||
import { COUNT, OFFSET, IS_LEAF, BOUNDING_DATA_INDEX } from '../utils/nodeBufferUtils.js';
|
||||
import { BufferStack } from '../utils/BufferStack.js';
|
||||
|
||||
const boundingBox = /* @__PURE__ */ new Box3();
|
||||
const triangle = /* @__PURE__ */ new ExtendedTriangle();
|
||||
const triangle2 = /* @__PURE__ */ new ExtendedTriangle();
|
||||
const invertedMat = /* @__PURE__ */ new Matrix4();
|
||||
|
||||
const obb = /* @__PURE__ */ new OrientedBox();
|
||||
const obb2 = /* @__PURE__ */ new OrientedBox();
|
||||
|
||||
export function intersectsGeometry/* @echo INDIRECT_STRING */( bvh, root, otherGeometry, geometryToBvh ) {
|
||||
|
||||
BufferStack.setBuffer( bvh._roots[ root ] );
|
||||
const result = _intersectsGeometry( 0, bvh, otherGeometry, geometryToBvh );
|
||||
BufferStack.clearBuffer();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
function _intersectsGeometry( nodeIndex32, bvh, otherGeometry, geometryToBvh, cachedObb = null ) {
|
||||
|
||||
const { float32Array, uint16Array, uint32Array } = BufferStack;
|
||||
let nodeIndex16 = nodeIndex32 * 2;
|
||||
|
||||
if ( cachedObb === null ) {
|
||||
|
||||
if ( ! otherGeometry.boundingBox ) {
|
||||
|
||||
otherGeometry.computeBoundingBox();
|
||||
|
||||
}
|
||||
|
||||
obb.set( otherGeometry.boundingBox.min, otherGeometry.boundingBox.max, geometryToBvh );
|
||||
cachedObb = obb;
|
||||
|
||||
}
|
||||
|
||||
const isLeaf = IS_LEAF( nodeIndex16, uint16Array );
|
||||
if ( isLeaf ) {
|
||||
|
||||
const thisGeometry = bvh.geometry;
|
||||
const thisIndex = thisGeometry.index;
|
||||
const thisPos = thisGeometry.attributes.position;
|
||||
|
||||
const index = otherGeometry.index;
|
||||
const pos = otherGeometry.attributes.position;
|
||||
|
||||
const offset = OFFSET( nodeIndex32, uint32Array );
|
||||
const count = COUNT( nodeIndex16, uint16Array );
|
||||
|
||||
// get the inverse of the geometry matrix so we can transform our triangles into the
|
||||
// geometry space we're trying to test. We assume there are fewer triangles being checked
|
||||
// here.
|
||||
invertedMat.copy( geometryToBvh ).invert();
|
||||
|
||||
if ( otherGeometry.boundsTree ) {
|
||||
|
||||
// if there's a bounds tree
|
||||
arrayToBox( BOUNDING_DATA_INDEX( nodeIndex32 ), float32Array, obb2 );
|
||||
obb2.matrix.copy( invertedMat );
|
||||
obb2.needsUpdate = true;
|
||||
|
||||
// TODO: use a triangle iteration function here
|
||||
const res = otherGeometry.boundsTree.shapecast( {
|
||||
|
||||
intersectsBounds: box => obb2.intersectsBox( box ),
|
||||
|
||||
intersectsTriangle: tri => {
|
||||
|
||||
tri.a.applyMatrix4( geometryToBvh );
|
||||
tri.b.applyMatrix4( geometryToBvh );
|
||||
tri.c.applyMatrix4( geometryToBvh );
|
||||
tri.needsUpdate = true;
|
||||
|
||||
/* @if INDIRECT */
|
||||
|
||||
for ( let i = offset, l = count + offset; i < l; i ++ ) {
|
||||
|
||||
// this triangle needs to be transformed into the current BVH coordinate frame
|
||||
setTriangle( triangle2, 3 * bvh.resolveTriangleIndex( i ), thisIndex, thisPos );
|
||||
triangle2.needsUpdate = true;
|
||||
if ( tri.intersectsTriangle( triangle2 ) ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* @else */
|
||||
|
||||
for ( let i = offset * 3, l = ( count + offset ) * 3; i < l; i += 3 ) {
|
||||
|
||||
// this triangle needs to be transformed into the current BVH coordinate frame
|
||||
setTriangle( triangle2, i, thisIndex, thisPos );
|
||||
triangle2.needsUpdate = true;
|
||||
if ( tri.intersectsTriangle( triangle2 ) ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* @endif */
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
return res;
|
||||
|
||||
} else {
|
||||
|
||||
// if we're just dealing with raw geometry
|
||||
/* @if INDIRECT */
|
||||
|
||||
for ( let i = offset, l = count + offset; i < l; i ++ ) {
|
||||
|
||||
// this triangle needs to be transformed into the current BVH coordinate frame
|
||||
const ti = bvh.resolveTriangleIndex( i );
|
||||
setTriangle( triangle, 3 * ti, thisIndex, thisPos );
|
||||
|
||||
/* @else */
|
||||
|
||||
for ( let i = offset * 3, l = ( count + offset ) * 3; i < l; i += 3 ) {
|
||||
|
||||
// this triangle needs to be transformed into the current BVH coordinate frame
|
||||
setTriangle( triangle, i, thisIndex, thisPos );
|
||||
|
||||
/* @endif */
|
||||
|
||||
triangle.a.applyMatrix4( invertedMat );
|
||||
triangle.b.applyMatrix4( invertedMat );
|
||||
triangle.c.applyMatrix4( invertedMat );
|
||||
triangle.needsUpdate = true;
|
||||
|
||||
for ( let i2 = 0, l2 = index.count; i2 < l2; i2 += 3 ) {
|
||||
|
||||
setTriangle( triangle2, i2, index, pos );
|
||||
triangle2.needsUpdate = true;
|
||||
|
||||
if ( triangle.intersectsTriangle( triangle2 ) ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* @if INDIRECT */
|
||||
|
||||
}
|
||||
|
||||
/* @else */
|
||||
|
||||
}
|
||||
|
||||
/* @endif */
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
const left = nodeIndex32 + 8;
|
||||
const right = uint32Array[ nodeIndex32 + 6 ];
|
||||
|
||||
arrayToBox( BOUNDING_DATA_INDEX( left ), float32Array, boundingBox );
|
||||
const leftIntersection =
|
||||
cachedObb.intersectsBox( boundingBox ) &&
|
||||
_intersectsGeometry( left, bvh, otherGeometry, geometryToBvh, cachedObb );
|
||||
|
||||
if ( leftIntersection ) return true;
|
||||
|
||||
arrayToBox( BOUNDING_DATA_INDEX( right ), float32Array, boundingBox );
|
||||
const rightIntersection =
|
||||
cachedObb.intersectsBox( boundingBox ) &&
|
||||
_intersectsGeometry( right, bvh, otherGeometry, geometryToBvh, cachedObb );
|
||||
|
||||
if ( rightIntersection ) return true;
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import { Box3, Matrix4 } from 'three'
|
||||
import { OrientedBox } from '../../math/OrientedBox.js';
|
||||
import { ExtendedTriangle } from '../../math/ExtendedTriangle.js';
|
||||
import { setTriangle } from '../../utils/TriangleUtilities.js';
|
||||
import { arrayToBox } from '../../utils/ArrayBoxUtilities.js';
|
||||
import { IS_LEAF, OFFSET, COUNT, BOUNDING_DATA_INDEX } from '../utils/nodeBufferUtils.js';
|
||||
import { BufferStack } from '../utils/BufferStack.js';
|
||||
|
||||
/*****************************************************************/
|
||||
/* This file is generated from "intersectsGeometry.template.js". */
|
||||
/*****************************************************************/
|
||||
/* eslint-disable indent */
|
||||
|
||||
const boundingBox = /* @__PURE__ */ new Box3();
|
||||
const triangle = /* @__PURE__ */ new ExtendedTriangle();
|
||||
const triangle2 = /* @__PURE__ */ new ExtendedTriangle();
|
||||
const invertedMat = /* @__PURE__ */ new Matrix4();
|
||||
|
||||
const obb = /* @__PURE__ */ new OrientedBox();
|
||||
const obb2 = /* @__PURE__ */ new OrientedBox();
|
||||
|
||||
function intersectsGeometry_indirect( bvh, root, otherGeometry, geometryToBvh ) {
|
||||
|
||||
BufferStack.setBuffer( bvh._roots[ root ] );
|
||||
const result = _intersectsGeometry( 0, bvh, otherGeometry, geometryToBvh );
|
||||
BufferStack.clearBuffer();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
function _intersectsGeometry( nodeIndex32, bvh, otherGeometry, geometryToBvh, cachedObb = null ) {
|
||||
|
||||
const { float32Array, uint16Array, uint32Array } = BufferStack;
|
||||
let nodeIndex16 = nodeIndex32 * 2;
|
||||
|
||||
if ( cachedObb === null ) {
|
||||
|
||||
if ( ! otherGeometry.boundingBox ) {
|
||||
|
||||
otherGeometry.computeBoundingBox();
|
||||
|
||||
}
|
||||
|
||||
obb.set( otherGeometry.boundingBox.min, otherGeometry.boundingBox.max, geometryToBvh );
|
||||
cachedObb = obb;
|
||||
|
||||
}
|
||||
|
||||
const isLeaf = IS_LEAF( nodeIndex16, uint16Array );
|
||||
if ( isLeaf ) {
|
||||
|
||||
const thisGeometry = bvh.geometry;
|
||||
const thisIndex = thisGeometry.index;
|
||||
const thisPos = thisGeometry.attributes.position;
|
||||
|
||||
const index = otherGeometry.index;
|
||||
const pos = otherGeometry.attributes.position;
|
||||
|
||||
const offset = OFFSET( nodeIndex32, uint32Array );
|
||||
const count = COUNT( nodeIndex16, uint16Array );
|
||||
|
||||
// get the inverse of the geometry matrix so we can transform our triangles into the
|
||||
// geometry space we're trying to test. We assume there are fewer triangles being checked
|
||||
// here.
|
||||
invertedMat.copy( geometryToBvh ).invert();
|
||||
|
||||
if ( otherGeometry.boundsTree ) {
|
||||
|
||||
// if there's a bounds tree
|
||||
arrayToBox( BOUNDING_DATA_INDEX( nodeIndex32 ), float32Array, obb2 );
|
||||
obb2.matrix.copy( invertedMat );
|
||||
obb2.needsUpdate = true;
|
||||
|
||||
// TODO: use a triangle iteration function here
|
||||
const res = otherGeometry.boundsTree.shapecast( {
|
||||
|
||||
intersectsBounds: box => obb2.intersectsBox( box ),
|
||||
|
||||
intersectsTriangle: tri => {
|
||||
|
||||
tri.a.applyMatrix4( geometryToBvh );
|
||||
tri.b.applyMatrix4( geometryToBvh );
|
||||
tri.c.applyMatrix4( geometryToBvh );
|
||||
tri.needsUpdate = true;
|
||||
|
||||
for ( let i = offset, l = count + offset; i < l; i ++ ) {
|
||||
|
||||
// this triangle needs to be transformed into the current BVH coordinate frame
|
||||
setTriangle( triangle2, 3 * bvh.resolveTriangleIndex( i ), thisIndex, thisPos );
|
||||
triangle2.needsUpdate = true;
|
||||
if ( tri.intersectsTriangle( triangle2 ) ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
return res;
|
||||
|
||||
} else {
|
||||
|
||||
// if we're just dealing with raw geometry
|
||||
for ( let i = offset, l = count + offset; i < l; i ++ ) {
|
||||
|
||||
// this triangle needs to be transformed into the current BVH coordinate frame
|
||||
const ti = bvh.resolveTriangleIndex( i );
|
||||
setTriangle( triangle, 3 * ti, thisIndex, thisPos );
|
||||
|
||||
|
||||
triangle.a.applyMatrix4( invertedMat );
|
||||
triangle.b.applyMatrix4( invertedMat );
|
||||
triangle.c.applyMatrix4( invertedMat );
|
||||
triangle.needsUpdate = true;
|
||||
|
||||
for ( let i2 = 0, l2 = index.count; i2 < l2; i2 += 3 ) {
|
||||
|
||||
setTriangle( triangle2, i2, index, pos );
|
||||
triangle2.needsUpdate = true;
|
||||
|
||||
if ( triangle.intersectsTriangle( triangle2 ) ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
const left = nodeIndex32 + 8;
|
||||
const right = uint32Array[ nodeIndex32 + 6 ];
|
||||
|
||||
arrayToBox( BOUNDING_DATA_INDEX( left ), float32Array, boundingBox );
|
||||
const leftIntersection =
|
||||
cachedObb.intersectsBox( boundingBox ) &&
|
||||
_intersectsGeometry( left, bvh, otherGeometry, geometryToBvh, cachedObb );
|
||||
|
||||
if ( leftIntersection ) return true;
|
||||
|
||||
arrayToBox( BOUNDING_DATA_INDEX( right ), float32Array, boundingBox );
|
||||
const rightIntersection =
|
||||
cachedObb.intersectsBox( boundingBox ) &&
|
||||
_intersectsGeometry( right, bvh, otherGeometry, geometryToBvh, cachedObb );
|
||||
|
||||
if ( rightIntersection ) return true;
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { intersectsGeometry_indirect };
|
||||
53
src/components/DMT/libs/bvh/core/cast/raycast.generated.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { intersectRay } from '../utils/intersectUtils.js';
|
||||
import { IS_LEAF, OFFSET, COUNT, LEFT_NODE, RIGHT_NODE } from '../utils/nodeBufferUtils.js';
|
||||
import { BufferStack } from '../utils/BufferStack.js';
|
||||
import { intersectTris } from '../utils/iterationUtils.generated.js';
|
||||
import '../utils/iterationUtils_indirect.generated.js';
|
||||
|
||||
/******************************************************/
|
||||
/* This file is generated from "raycast.template.js". */
|
||||
/******************************************************/
|
||||
|
||||
function raycast( bvh, root, side, ray, intersects ) {
|
||||
|
||||
BufferStack.setBuffer( bvh._roots[ root ] );
|
||||
_raycast( 0, bvh, side, ray, intersects );
|
||||
BufferStack.clearBuffer();
|
||||
|
||||
}
|
||||
|
||||
function _raycast( nodeIndex32, bvh, side, ray, intersects ) {
|
||||
|
||||
const { float32Array, uint16Array, uint32Array } = BufferStack;
|
||||
const nodeIndex16 = nodeIndex32 * 2;
|
||||
const isLeaf = IS_LEAF( nodeIndex16, uint16Array );
|
||||
if ( isLeaf ) {
|
||||
|
||||
const offset = OFFSET( nodeIndex32, uint32Array );
|
||||
const count = COUNT( nodeIndex16, uint16Array );
|
||||
|
||||
|
||||
intersectTris( bvh, side, ray, offset, count, intersects );
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
const leftIndex = LEFT_NODE( nodeIndex32 );
|
||||
if ( intersectRay( leftIndex, float32Array, ray ) ) {
|
||||
|
||||
_raycast( leftIndex, bvh, side, ray, intersects );
|
||||
|
||||
}
|
||||
|
||||
const rightIndex = RIGHT_NODE( nodeIndex32, uint32Array );
|
||||
if ( intersectRay( rightIndex, float32Array, ray ) ) {
|
||||
|
||||
_raycast( rightIndex, bvh, side, ray, intersects );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { raycast };
|
||||
53
src/components/DMT/libs/bvh/core/cast/raycast.template.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { intersectRay } from '../utils/intersectUtils.js';
|
||||
import { COUNT, OFFSET, LEFT_NODE, RIGHT_NODE, IS_LEAF } from '../utils/nodeBufferUtils.js';
|
||||
import { BufferStack } from '../utils/BufferStack.js';
|
||||
import { intersectTris } from '../utils/iterationUtils.generated.js';
|
||||
import { intersectTris_indirect } from '../utils/iterationUtils_indirect.generated.js';
|
||||
|
||||
export function raycast/* @echo INDIRECT_STRING */( bvh, root, side, ray, intersects ) {
|
||||
|
||||
BufferStack.setBuffer( bvh._roots[ root ] );
|
||||
_raycast( 0, bvh, side, ray, intersects );
|
||||
BufferStack.clearBuffer();
|
||||
|
||||
}
|
||||
|
||||
function _raycast( nodeIndex32, bvh, side, ray, intersects ) {
|
||||
|
||||
const { float32Array, uint16Array, uint32Array } = BufferStack;
|
||||
const nodeIndex16 = nodeIndex32 * 2;
|
||||
const isLeaf = IS_LEAF( nodeIndex16, uint16Array );
|
||||
if ( isLeaf ) {
|
||||
|
||||
const offset = OFFSET( nodeIndex32, uint32Array );
|
||||
const count = COUNT( nodeIndex16, uint16Array );
|
||||
|
||||
/* @if INDIRECT */
|
||||
|
||||
intersectTris_indirect( bvh, side, ray, offset, count, intersects );
|
||||
|
||||
/* @else */
|
||||
|
||||
intersectTris( bvh, side, ray, offset, count, intersects );
|
||||
|
||||
/* @endif */
|
||||
|
||||
} else {
|
||||
|
||||
const leftIndex = LEFT_NODE( nodeIndex32 );
|
||||
if ( intersectRay( leftIndex, float32Array, ray ) ) {
|
||||
|
||||
_raycast( leftIndex, bvh, side, ray, intersects );
|
||||
|
||||
}
|
||||
|
||||
const rightIndex = RIGHT_NODE( nodeIndex32, uint32Array );
|
||||
if ( intersectRay( rightIndex, float32Array, ray ) ) {
|
||||
|
||||
_raycast( rightIndex, bvh, side, ray, intersects );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
102
src/components/DMT/libs/bvh/core/cast/raycastFirst.generated.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import { IS_LEAF, OFFSET, COUNT, SPLIT_AXIS, LEFT_NODE, RIGHT_NODE } from '../utils/nodeBufferUtils.js';
|
||||
import { BufferStack } from '../utils/BufferStack.js';
|
||||
import { intersectRay } from '../utils/intersectUtils.js';
|
||||
import { intersectClosestTri } from '../utils/iterationUtils.generated.js';
|
||||
import '../utils/iterationUtils_indirect.generated.js';
|
||||
|
||||
/***********************************************************/
|
||||
/* This file is generated from "raycastFirst.template.js". */
|
||||
/***********************************************************/
|
||||
|
||||
const _xyzFields = [ 'x', 'y', 'z' ];
|
||||
|
||||
function raycastFirst( bvh, root, side, ray ) {
|
||||
|
||||
BufferStack.setBuffer( bvh._roots[ root ] );
|
||||
const result = _raycastFirst( 0, bvh, side, ray );
|
||||
BufferStack.clearBuffer();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
function _raycastFirst( nodeIndex32, bvh, side, ray ) {
|
||||
|
||||
const { float32Array, uint16Array, uint32Array } = BufferStack;
|
||||
let nodeIndex16 = nodeIndex32 * 2;
|
||||
|
||||
const isLeaf = IS_LEAF( nodeIndex16, uint16Array );
|
||||
if ( isLeaf ) {
|
||||
|
||||
const offset = OFFSET( nodeIndex32, uint32Array );
|
||||
const count = COUNT( nodeIndex16, uint16Array );
|
||||
|
||||
|
||||
return intersectClosestTri( bvh, side, ray, offset, count );
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
// consider the position of the split plane with respect to the oncoming ray; whichever direction
|
||||
// the ray is coming from, look for an intersection among that side of the tree first
|
||||
const splitAxis = SPLIT_AXIS( nodeIndex32, uint32Array );
|
||||
const xyzAxis = _xyzFields[ splitAxis ];
|
||||
const rayDir = ray.direction[ xyzAxis ];
|
||||
const leftToRight = rayDir >= 0;
|
||||
|
||||
// c1 is the child to check first
|
||||
let c1, c2;
|
||||
if ( leftToRight ) {
|
||||
|
||||
c1 = LEFT_NODE( nodeIndex32 );
|
||||
c2 = RIGHT_NODE( nodeIndex32, uint32Array );
|
||||
|
||||
} else {
|
||||
|
||||
c1 = RIGHT_NODE( nodeIndex32, uint32Array );
|
||||
c2 = LEFT_NODE( nodeIndex32 );
|
||||
|
||||
}
|
||||
|
||||
const c1Intersection = intersectRay( c1, float32Array, ray );
|
||||
const c1Result = c1Intersection ? _raycastFirst( c1, bvh, side, ray ) : null;
|
||||
|
||||
// if we got an intersection in the first node and it's closer than the second node's bounding
|
||||
// box, we don't need to consider the second node because it couldn't possibly be a better result
|
||||
if ( c1Result ) {
|
||||
|
||||
// check if the point is within the second bounds
|
||||
// "point" is in the local frame of the bvh
|
||||
const point = c1Result.point[ xyzAxis ];
|
||||
const isOutside = leftToRight ?
|
||||
point <= float32Array[ c2 + splitAxis ] : // min bounding data
|
||||
point >= float32Array[ c2 + splitAxis + 3 ]; // max bounding data
|
||||
|
||||
if ( isOutside ) {
|
||||
|
||||
return c1Result;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// either there was no intersection in the first node, or there could still be a closer
|
||||
// intersection in the second, so check the second node and then take the better of the two
|
||||
const c2Intersection = intersectRay( c2, float32Array, ray );
|
||||
const c2Result = c2Intersection ? _raycastFirst( c2, bvh, side, ray ) : null;
|
||||
|
||||
if ( c1Result && c2Result ) {
|
||||
|
||||
return c1Result.distance <= c2Result.distance ? c1Result : c2Result;
|
||||
|
||||
} else {
|
||||
|
||||
return c1Result || c2Result || null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { raycastFirst };
|
||||
102
src/components/DMT/libs/bvh/core/cast/raycastFirst.template.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import { COUNT, OFFSET, LEFT_NODE, RIGHT_NODE, IS_LEAF, SPLIT_AXIS } from '../utils/nodeBufferUtils.js';
|
||||
import { BufferStack } from '../utils/BufferStack.js';
|
||||
import { intersectRay } from '../utils/intersectUtils.js';
|
||||
import { intersectClosestTri } from '../utils/iterationUtils.generated.js';
|
||||
import { intersectClosestTri_indirect } from '../utils/iterationUtils_indirect.generated.js';
|
||||
|
||||
const _xyzFields = [ 'x', 'y', 'z' ];
|
||||
|
||||
export function raycastFirst/* @echo INDIRECT_STRING */( bvh, root, side, ray ) {
|
||||
|
||||
BufferStack.setBuffer( bvh._roots[ root ] );
|
||||
const result = _raycastFirst( 0, bvh, side, ray );
|
||||
BufferStack.clearBuffer();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
function _raycastFirst( nodeIndex32, bvh, side, ray ) {
|
||||
|
||||
const { float32Array, uint16Array, uint32Array } = BufferStack;
|
||||
let nodeIndex16 = nodeIndex32 * 2;
|
||||
|
||||
const isLeaf = IS_LEAF( nodeIndex16, uint16Array );
|
||||
if ( isLeaf ) {
|
||||
|
||||
const offset = OFFSET( nodeIndex32, uint32Array );
|
||||
const count = COUNT( nodeIndex16, uint16Array );
|
||||
|
||||
/* @if INDIRECT */
|
||||
|
||||
return intersectClosestTri_indirect( bvh, side, ray, offset, count );
|
||||
|
||||
/* @else */
|
||||
|
||||
return intersectClosestTri( bvh, side, ray, offset, count );
|
||||
|
||||
/* @endif */
|
||||
|
||||
} else {
|
||||
|
||||
// consider the position of the split plane with respect to the oncoming ray; whichever direction
|
||||
// the ray is coming from, look for an intersection among that side of the tree first
|
||||
const splitAxis = SPLIT_AXIS( nodeIndex32, uint32Array );
|
||||
const xyzAxis = _xyzFields[ splitAxis ];
|
||||
const rayDir = ray.direction[ xyzAxis ];
|
||||
const leftToRight = rayDir >= 0;
|
||||
|
||||
// c1 is the child to check first
|
||||
let c1, c2;
|
||||
if ( leftToRight ) {
|
||||
|
||||
c1 = LEFT_NODE( nodeIndex32 );
|
||||
c2 = RIGHT_NODE( nodeIndex32, uint32Array );
|
||||
|
||||
} else {
|
||||
|
||||
c1 = RIGHT_NODE( nodeIndex32, uint32Array );
|
||||
c2 = LEFT_NODE( nodeIndex32 );
|
||||
|
||||
}
|
||||
|
||||
const c1Intersection = intersectRay( c1, float32Array, ray );
|
||||
const c1Result = c1Intersection ? _raycastFirst( c1, bvh, side, ray ) : null;
|
||||
|
||||
// if we got an intersection in the first node and it's closer than the second node's bounding
|
||||
// box, we don't need to consider the second node because it couldn't possibly be a better result
|
||||
if ( c1Result ) {
|
||||
|
||||
// check if the point is within the second bounds
|
||||
// "point" is in the local frame of the bvh
|
||||
const point = c1Result.point[ xyzAxis ];
|
||||
const isOutside = leftToRight ?
|
||||
point <= float32Array[ c2 + splitAxis ] : // min bounding data
|
||||
point >= float32Array[ c2 + splitAxis + 3 ]; // max bounding data
|
||||
|
||||
if ( isOutside ) {
|
||||
|
||||
return c1Result;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// either there was no intersection in the first node, or there could still be a closer
|
||||
// intersection in the second, so check the second node and then take the better of the two
|
||||
const c2Intersection = intersectRay( c2, float32Array, ray );
|
||||
const c2Result = c2Intersection ? _raycastFirst( c2, bvh, side, ray ) : null;
|
||||
|
||||
if ( c1Result && c2Result ) {
|
||||
|
||||
return c1Result.distance <= c2Result.distance ? c1Result : c2Result;
|
||||
|
||||
} else {
|
||||
|
||||
return c1Result || c2Result || null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { IS_LEAF, OFFSET, COUNT, SPLIT_AXIS, LEFT_NODE, RIGHT_NODE } from '../utils/nodeBufferUtils.js';
|
||||
import { BufferStack } from '../utils/BufferStack.js';
|
||||
import { intersectRay } from '../utils/intersectUtils.js';
|
||||
import '../utils/iterationUtils.generated.js';
|
||||
import { intersectClosestTri_indirect } from '../utils/iterationUtils_indirect.generated.js';
|
||||
|
||||
/***********************************************************/
|
||||
/* This file is generated from "raycastFirst.template.js". */
|
||||
/***********************************************************/
|
||||
|
||||
const _xyzFields = [ 'x', 'y', 'z' ];
|
||||
|
||||
function raycastFirst_indirect( bvh, root, side, ray ) {
|
||||
|
||||
BufferStack.setBuffer( bvh._roots[ root ] );
|
||||
const result = _raycastFirst( 0, bvh, side, ray );
|
||||
BufferStack.clearBuffer();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
function _raycastFirst( nodeIndex32, bvh, side, ray ) {
|
||||
|
||||
const { float32Array, uint16Array, uint32Array } = BufferStack;
|
||||
let nodeIndex16 = nodeIndex32 * 2;
|
||||
|
||||
const isLeaf = IS_LEAF( nodeIndex16, uint16Array );
|
||||
if ( isLeaf ) {
|
||||
|
||||
const offset = OFFSET( nodeIndex32, uint32Array );
|
||||
const count = COUNT( nodeIndex16, uint16Array );
|
||||
|
||||
return intersectClosestTri_indirect( bvh, side, ray, offset, count );
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
// consider the position of the split plane with respect to the oncoming ray; whichever direction
|
||||
// the ray is coming from, look for an intersection among that side of the tree first
|
||||
const splitAxis = SPLIT_AXIS( nodeIndex32, uint32Array );
|
||||
const xyzAxis = _xyzFields[ splitAxis ];
|
||||
const rayDir = ray.direction[ xyzAxis ];
|
||||
const leftToRight = rayDir >= 0;
|
||||
|
||||
// c1 is the child to check first
|
||||
let c1, c2;
|
||||
if ( leftToRight ) {
|
||||
|
||||
c1 = LEFT_NODE( nodeIndex32 );
|
||||
c2 = RIGHT_NODE( nodeIndex32, uint32Array );
|
||||
|
||||
} else {
|
||||
|
||||
c1 = RIGHT_NODE( nodeIndex32, uint32Array );
|
||||
c2 = LEFT_NODE( nodeIndex32 );
|
||||
|
||||
}
|
||||
|
||||
const c1Intersection = intersectRay( c1, float32Array, ray );
|
||||
const c1Result = c1Intersection ? _raycastFirst( c1, bvh, side, ray ) : null;
|
||||
|
||||
// if we got an intersection in the first node and it's closer than the second node's bounding
|
||||
// box, we don't need to consider the second node because it couldn't possibly be a better result
|
||||
if ( c1Result ) {
|
||||
|
||||
// check if the point is within the second bounds
|
||||
// "point" is in the local frame of the bvh
|
||||
const point = c1Result.point[ xyzAxis ];
|
||||
const isOutside = leftToRight ?
|
||||
point <= float32Array[ c2 + splitAxis ] : // min bounding data
|
||||
point >= float32Array[ c2 + splitAxis + 3 ]; // max bounding data
|
||||
|
||||
if ( isOutside ) {
|
||||
|
||||
return c1Result;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// either there was no intersection in the first node, or there could still be a closer
|
||||
// intersection in the second, so check the second node and then take the better of the two
|
||||
const c2Intersection = intersectRay( c2, float32Array, ray );
|
||||
const c2Result = c2Intersection ? _raycastFirst( c2, bvh, side, ray ) : null;
|
||||
|
||||
if ( c1Result && c2Result ) {
|
||||
|
||||
return c1Result.distance <= c2Result.distance ? c1Result : c2Result;
|
||||
|
||||
} else {
|
||||
|
||||
return c1Result || c2Result || null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { raycastFirst_indirect };
|
||||
@@ -0,0 +1,52 @@
|
||||
import { intersectRay } from '../utils/intersectUtils.js';
|
||||
import { IS_LEAF, OFFSET, COUNT, LEFT_NODE, RIGHT_NODE } from '../utils/nodeBufferUtils.js';
|
||||
import { BufferStack } from '../utils/BufferStack.js';
|
||||
import '../utils/iterationUtils.generated.js';
|
||||
import { intersectTris_indirect } from '../utils/iterationUtils_indirect.generated.js';
|
||||
|
||||
/******************************************************/
|
||||
/* This file is generated from "raycast.template.js". */
|
||||
/******************************************************/
|
||||
|
||||
function raycast_indirect( bvh, root, side, ray, intersects ) {
|
||||
|
||||
BufferStack.setBuffer( bvh._roots[ root ] );
|
||||
_raycast( 0, bvh, side, ray, intersects );
|
||||
BufferStack.clearBuffer();
|
||||
|
||||
}
|
||||
|
||||
function _raycast( nodeIndex32, bvh, side, ray, intersects ) {
|
||||
|
||||
const { float32Array, uint16Array, uint32Array } = BufferStack;
|
||||
const nodeIndex16 = nodeIndex32 * 2;
|
||||
const isLeaf = IS_LEAF( nodeIndex16, uint16Array );
|
||||
if ( isLeaf ) {
|
||||
|
||||
const offset = OFFSET( nodeIndex32, uint32Array );
|
||||
const count = COUNT( nodeIndex16, uint16Array );
|
||||
|
||||
intersectTris_indirect( bvh, side, ray, offset, count, intersects );
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
const leftIndex = LEFT_NODE( nodeIndex32 );
|
||||
if ( intersectRay( leftIndex, float32Array, ray ) ) {
|
||||
|
||||
_raycast( leftIndex, bvh, side, ray, intersects );
|
||||
|
||||
}
|
||||
|
||||
const rightIndex = RIGHT_NODE( nodeIndex32, uint32Array );
|
||||
if ( intersectRay( rightIndex, float32Array, ray ) ) {
|
||||
|
||||
_raycast( rightIndex, bvh, side, ray, intersects );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { raycast_indirect };
|
||||
172
src/components/DMT/libs/bvh/core/cast/refit.generated.js
Normal file
@@ -0,0 +1,172 @@
|
||||
import { IS_LEAFNODE_FLAG } from '../Constants.js';
|
||||
|
||||
/****************************************************/
|
||||
/* This file is generated from "refit.template.js". */
|
||||
/****************************************************/
|
||||
|
||||
function refit( bvh, nodeIndices = null ) {
|
||||
|
||||
if ( nodeIndices && Array.isArray( nodeIndices ) ) {
|
||||
|
||||
nodeIndices = new Set( nodeIndices );
|
||||
|
||||
}
|
||||
|
||||
const geometry = bvh.geometry;
|
||||
const indexArr = geometry.index ? geometry.index.array : null;
|
||||
const posAttr = geometry.attributes.position;
|
||||
|
||||
let buffer, uint32Array, uint16Array, float32Array;
|
||||
let byteOffset = 0;
|
||||
const roots = bvh._roots;
|
||||
for ( let i = 0, l = roots.length; i < l; i ++ ) {
|
||||
|
||||
buffer = roots[ i ];
|
||||
uint32Array = new Uint32Array( buffer );
|
||||
uint16Array = new Uint16Array( buffer );
|
||||
float32Array = new Float32Array( buffer );
|
||||
|
||||
_traverse( 0, byteOffset );
|
||||
byteOffset += buffer.byteLength;
|
||||
|
||||
}
|
||||
|
||||
function _traverse( node32Index, byteOffset, force = false ) {
|
||||
|
||||
const node16Index = node32Index * 2;
|
||||
const isLeaf = uint16Array[ node16Index + 15 ] === IS_LEAFNODE_FLAG;
|
||||
if ( isLeaf ) {
|
||||
|
||||
const offset = uint32Array[ node32Index + 6 ];
|
||||
const count = uint16Array[ node16Index + 14 ];
|
||||
|
||||
let minx = Infinity;
|
||||
let miny = Infinity;
|
||||
let minz = Infinity;
|
||||
let maxx = - Infinity;
|
||||
let maxy = - Infinity;
|
||||
let maxz = - Infinity;
|
||||
|
||||
|
||||
for ( let i = 3 * offset, l = 3 * ( offset + count ); i < l; i ++ ) {
|
||||
|
||||
let index = indexArr[ i ];
|
||||
const x = posAttr.getX( index );
|
||||
const y = posAttr.getY( index );
|
||||
const z = posAttr.getZ( index );
|
||||
|
||||
if ( x < minx ) minx = x;
|
||||
if ( x > maxx ) maxx = x;
|
||||
|
||||
if ( y < miny ) miny = y;
|
||||
if ( y > maxy ) maxy = y;
|
||||
|
||||
if ( z < minz ) minz = z;
|
||||
if ( z > maxz ) maxz = z;
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
float32Array[ node32Index + 0 ] !== minx ||
|
||||
float32Array[ node32Index + 1 ] !== miny ||
|
||||
float32Array[ node32Index + 2 ] !== minz ||
|
||||
|
||||
float32Array[ node32Index + 3 ] !== maxx ||
|
||||
float32Array[ node32Index + 4 ] !== maxy ||
|
||||
float32Array[ node32Index + 5 ] !== maxz
|
||||
) {
|
||||
|
||||
float32Array[ node32Index + 0 ] = minx;
|
||||
float32Array[ node32Index + 1 ] = miny;
|
||||
float32Array[ node32Index + 2 ] = minz;
|
||||
|
||||
float32Array[ node32Index + 3 ] = maxx;
|
||||
float32Array[ node32Index + 4 ] = maxy;
|
||||
float32Array[ node32Index + 5 ] = maxz;
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
const left = node32Index + 8;
|
||||
const right = uint32Array[ node32Index + 6 ];
|
||||
|
||||
// the identifying node indices provided by the shapecast function include offsets of all
|
||||
// root buffers to guarantee they're unique between roots so offset left and right indices here.
|
||||
const offsetLeft = left + byteOffset;
|
||||
const offsetRight = right + byteOffset;
|
||||
let forceChildren = force;
|
||||
let includesLeft = false;
|
||||
let includesRight = false;
|
||||
|
||||
if ( nodeIndices ) {
|
||||
|
||||
// if we see that neither the left or right child are included in the set that need to be updated
|
||||
// then we assume that all children need to be updated.
|
||||
if ( ! forceChildren ) {
|
||||
|
||||
includesLeft = nodeIndices.has( offsetLeft );
|
||||
includesRight = nodeIndices.has( offsetRight );
|
||||
forceChildren = ! includesLeft && ! includesRight;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
includesLeft = true;
|
||||
includesRight = true;
|
||||
|
||||
}
|
||||
|
||||
const traverseLeft = forceChildren || includesLeft;
|
||||
const traverseRight = forceChildren || includesRight;
|
||||
|
||||
let leftChange = false;
|
||||
if ( traverseLeft ) {
|
||||
|
||||
leftChange = _traverse( left, byteOffset, forceChildren );
|
||||
|
||||
}
|
||||
|
||||
let rightChange = false;
|
||||
if ( traverseRight ) {
|
||||
|
||||
rightChange = _traverse( right, byteOffset, forceChildren );
|
||||
|
||||
}
|
||||
|
||||
const didChange = leftChange || rightChange;
|
||||
if ( didChange ) {
|
||||
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
const lefti = left + i;
|
||||
const righti = right + i;
|
||||
const minLeftValue = float32Array[ lefti ];
|
||||
const maxLeftValue = float32Array[ lefti + 3 ];
|
||||
const minRightValue = float32Array[ righti ];
|
||||
const maxRightValue = float32Array[ righti + 3 ];
|
||||
|
||||
float32Array[ node32Index + i ] = minLeftValue < minRightValue ? minLeftValue : minRightValue;
|
||||
float32Array[ node32Index + i + 3 ] = maxLeftValue > maxRightValue ? maxLeftValue : maxRightValue;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return didChange;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { refit };
|
||||
196
src/components/DMT/libs/bvh/core/cast/refit.template.js
Normal file
@@ -0,0 +1,196 @@
|
||||
import { IS_LEAFNODE_FLAG } from '../Constants.js';
|
||||
|
||||
export function refit/* @echo INDIRECT_STRING */( bvh, nodeIndices = null ) {
|
||||
|
||||
if ( nodeIndices && Array.isArray( nodeIndices ) ) {
|
||||
|
||||
nodeIndices = new Set( nodeIndices );
|
||||
|
||||
}
|
||||
|
||||
const geometry = bvh.geometry;
|
||||
const indexArr = geometry.index ? geometry.index.array : null;
|
||||
const posAttr = geometry.attributes.position;
|
||||
|
||||
let buffer, uint32Array, uint16Array, float32Array;
|
||||
let byteOffset = 0;
|
||||
const roots = bvh._roots;
|
||||
for ( let i = 0, l = roots.length; i < l; i ++ ) {
|
||||
|
||||
buffer = roots[ i ];
|
||||
uint32Array = new Uint32Array( buffer );
|
||||
uint16Array = new Uint16Array( buffer );
|
||||
float32Array = new Float32Array( buffer );
|
||||
|
||||
_traverse( 0, byteOffset );
|
||||
byteOffset += buffer.byteLength;
|
||||
|
||||
}
|
||||
|
||||
function _traverse( node32Index, byteOffset, force = false ) {
|
||||
|
||||
const node16Index = node32Index * 2;
|
||||
const isLeaf = uint16Array[ node16Index + 15 ] === IS_LEAFNODE_FLAG;
|
||||
if ( isLeaf ) {
|
||||
|
||||
const offset = uint32Array[ node32Index + 6 ];
|
||||
const count = uint16Array[ node16Index + 14 ];
|
||||
|
||||
let minx = Infinity;
|
||||
let miny = Infinity;
|
||||
let minz = Infinity;
|
||||
let maxx = - Infinity;
|
||||
let maxy = - Infinity;
|
||||
let maxz = - Infinity;
|
||||
|
||||
/* @if INDIRECT */
|
||||
|
||||
for ( let i = offset, l = offset + count; i < l; i ++ ) {
|
||||
|
||||
const t = 3 * bvh.resolveTriangleIndex( i );
|
||||
for ( let j = 0; j < 3; j ++ ) {
|
||||
|
||||
let index = t + j;
|
||||
index = indexArr ? indexArr[ index ] : index;
|
||||
|
||||
const x = posAttr.getX( index );
|
||||
const y = posAttr.getY( index );
|
||||
const z = posAttr.getZ( index );
|
||||
|
||||
if ( x < minx ) minx = x;
|
||||
if ( x > maxx ) maxx = x;
|
||||
|
||||
if ( y < miny ) miny = y;
|
||||
if ( y > maxy ) maxy = y;
|
||||
|
||||
if ( z < minz ) minz = z;
|
||||
if ( z > maxz ) maxz = z;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* @else */
|
||||
|
||||
for ( let i = 3 * offset, l = 3 * ( offset + count ); i < l; i ++ ) {
|
||||
|
||||
let index = indexArr[ i ];
|
||||
const x = posAttr.getX( index );
|
||||
const y = posAttr.getY( index );
|
||||
const z = posAttr.getZ( index );
|
||||
|
||||
if ( x < minx ) minx = x;
|
||||
if ( x > maxx ) maxx = x;
|
||||
|
||||
if ( y < miny ) miny = y;
|
||||
if ( y > maxy ) maxy = y;
|
||||
|
||||
if ( z < minz ) minz = z;
|
||||
if ( z > maxz ) maxz = z;
|
||||
|
||||
}
|
||||
|
||||
/* @endif */
|
||||
|
||||
if (
|
||||
float32Array[ node32Index + 0 ] !== minx ||
|
||||
float32Array[ node32Index + 1 ] !== miny ||
|
||||
float32Array[ node32Index + 2 ] !== minz ||
|
||||
|
||||
float32Array[ node32Index + 3 ] !== maxx ||
|
||||
float32Array[ node32Index + 4 ] !== maxy ||
|
||||
float32Array[ node32Index + 5 ] !== maxz
|
||||
) {
|
||||
|
||||
float32Array[ node32Index + 0 ] = minx;
|
||||
float32Array[ node32Index + 1 ] = miny;
|
||||
float32Array[ node32Index + 2 ] = minz;
|
||||
|
||||
float32Array[ node32Index + 3 ] = maxx;
|
||||
float32Array[ node32Index + 4 ] = maxy;
|
||||
float32Array[ node32Index + 5 ] = maxz;
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
const left = node32Index + 8;
|
||||
const right = uint32Array[ node32Index + 6 ];
|
||||
|
||||
// the identifying node indices provided by the shapecast function include offsets of all
|
||||
// root buffers to guarantee they're unique between roots so offset left and right indices here.
|
||||
const offsetLeft = left + byteOffset;
|
||||
const offsetRight = right + byteOffset;
|
||||
let forceChildren = force;
|
||||
let includesLeft = false;
|
||||
let includesRight = false;
|
||||
|
||||
if ( nodeIndices ) {
|
||||
|
||||
// if we see that neither the left or right child are included in the set that need to be updated
|
||||
// then we assume that all children need to be updated.
|
||||
if ( ! forceChildren ) {
|
||||
|
||||
includesLeft = nodeIndices.has( offsetLeft );
|
||||
includesRight = nodeIndices.has( offsetRight );
|
||||
forceChildren = ! includesLeft && ! includesRight;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
includesLeft = true;
|
||||
includesRight = true;
|
||||
|
||||
}
|
||||
|
||||
const traverseLeft = forceChildren || includesLeft;
|
||||
const traverseRight = forceChildren || includesRight;
|
||||
|
||||
let leftChange = false;
|
||||
if ( traverseLeft ) {
|
||||
|
||||
leftChange = _traverse( left, byteOffset, forceChildren );
|
||||
|
||||
}
|
||||
|
||||
let rightChange = false;
|
||||
if ( traverseRight ) {
|
||||
|
||||
rightChange = _traverse( right, byteOffset, forceChildren );
|
||||
|
||||
}
|
||||
|
||||
const didChange = leftChange || rightChange;
|
||||
if ( didChange ) {
|
||||
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
const lefti = left + i;
|
||||
const righti = right + i;
|
||||
const minLeftValue = float32Array[ lefti ];
|
||||
const maxLeftValue = float32Array[ lefti + 3 ];
|
||||
const minRightValue = float32Array[ righti ];
|
||||
const maxRightValue = float32Array[ righti + 3 ];
|
||||
|
||||
float32Array[ node32Index + i ] = minLeftValue < minRightValue ? minLeftValue : minRightValue;
|
||||
float32Array[ node32Index + i + 3 ] = maxLeftValue > maxRightValue ? maxLeftValue : maxRightValue;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return didChange;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import { IS_LEAFNODE_FLAG } from '../Constants.js';
|
||||
|
||||
/****************************************************/
|
||||
/* This file is generated from "refit.template.js". */
|
||||
/****************************************************/
|
||||
|
||||
function refit_indirect( bvh, nodeIndices = null ) {
|
||||
|
||||
if ( nodeIndices && Array.isArray( nodeIndices ) ) {
|
||||
|
||||
nodeIndices = new Set( nodeIndices );
|
||||
|
||||
}
|
||||
|
||||
const geometry = bvh.geometry;
|
||||
const indexArr = geometry.index ? geometry.index.array : null;
|
||||
const posAttr = geometry.attributes.position;
|
||||
|
||||
let buffer, uint32Array, uint16Array, float32Array;
|
||||
let byteOffset = 0;
|
||||
const roots = bvh._roots;
|
||||
for ( let i = 0, l = roots.length; i < l; i ++ ) {
|
||||
|
||||
buffer = roots[ i ];
|
||||
uint32Array = new Uint32Array( buffer );
|
||||
uint16Array = new Uint16Array( buffer );
|
||||
float32Array = new Float32Array( buffer );
|
||||
|
||||
_traverse( 0, byteOffset );
|
||||
byteOffset += buffer.byteLength;
|
||||
|
||||
}
|
||||
|
||||
function _traverse( node32Index, byteOffset, force = false ) {
|
||||
|
||||
const node16Index = node32Index * 2;
|
||||
const isLeaf = uint16Array[ node16Index + 15 ] === IS_LEAFNODE_FLAG;
|
||||
if ( isLeaf ) {
|
||||
|
||||
const offset = uint32Array[ node32Index + 6 ];
|
||||
const count = uint16Array[ node16Index + 14 ];
|
||||
|
||||
let minx = Infinity;
|
||||
let miny = Infinity;
|
||||
let minz = Infinity;
|
||||
let maxx = - Infinity;
|
||||
let maxy = - Infinity;
|
||||
let maxz = - Infinity;
|
||||
|
||||
for ( let i = offset, l = offset + count; i < l; i ++ ) {
|
||||
|
||||
const t = 3 * bvh.resolveTriangleIndex( i );
|
||||
for ( let j = 0; j < 3; j ++ ) {
|
||||
|
||||
let index = t + j;
|
||||
index = indexArr ? indexArr[ index ] : index;
|
||||
|
||||
const x = posAttr.getX( index );
|
||||
const y = posAttr.getY( index );
|
||||
const z = posAttr.getZ( index );
|
||||
|
||||
if ( x < minx ) minx = x;
|
||||
if ( x > maxx ) maxx = x;
|
||||
|
||||
if ( y < miny ) miny = y;
|
||||
if ( y > maxy ) maxy = y;
|
||||
|
||||
if ( z < minz ) minz = z;
|
||||
if ( z > maxz ) maxz = z;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
float32Array[ node32Index + 0 ] !== minx ||
|
||||
float32Array[ node32Index + 1 ] !== miny ||
|
||||
float32Array[ node32Index + 2 ] !== minz ||
|
||||
|
||||
float32Array[ node32Index + 3 ] !== maxx ||
|
||||
float32Array[ node32Index + 4 ] !== maxy ||
|
||||
float32Array[ node32Index + 5 ] !== maxz
|
||||
) {
|
||||
|
||||
float32Array[ node32Index + 0 ] = minx;
|
||||
float32Array[ node32Index + 1 ] = miny;
|
||||
float32Array[ node32Index + 2 ] = minz;
|
||||
|
||||
float32Array[ node32Index + 3 ] = maxx;
|
||||
float32Array[ node32Index + 4 ] = maxy;
|
||||
float32Array[ node32Index + 5 ] = maxz;
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
const left = node32Index + 8;
|
||||
const right = uint32Array[ node32Index + 6 ];
|
||||
|
||||
// the identifying node indices provided by the shapecast function include offsets of all
|
||||
// root buffers to guarantee they're unique between roots so offset left and right indices here.
|
||||
const offsetLeft = left + byteOffset;
|
||||
const offsetRight = right + byteOffset;
|
||||
let forceChildren = force;
|
||||
let includesLeft = false;
|
||||
let includesRight = false;
|
||||
|
||||
if ( nodeIndices ) {
|
||||
|
||||
// if we see that neither the left or right child are included in the set that need to be updated
|
||||
// then we assume that all children need to be updated.
|
||||
if ( ! forceChildren ) {
|
||||
|
||||
includesLeft = nodeIndices.has( offsetLeft );
|
||||
includesRight = nodeIndices.has( offsetRight );
|
||||
forceChildren = ! includesLeft && ! includesRight;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
includesLeft = true;
|
||||
includesRight = true;
|
||||
|
||||
}
|
||||
|
||||
const traverseLeft = forceChildren || includesLeft;
|
||||
const traverseRight = forceChildren || includesRight;
|
||||
|
||||
let leftChange = false;
|
||||
if ( traverseLeft ) {
|
||||
|
||||
leftChange = _traverse( left, byteOffset, forceChildren );
|
||||
|
||||
}
|
||||
|
||||
let rightChange = false;
|
||||
if ( traverseRight ) {
|
||||
|
||||
rightChange = _traverse( right, byteOffset, forceChildren );
|
||||
|
||||
}
|
||||
|
||||
const didChange = leftChange || rightChange;
|
||||
if ( didChange ) {
|
||||
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
const lefti = left + i;
|
||||
const righti = right + i;
|
||||
const minLeftValue = float32Array[ lefti ];
|
||||
const maxLeftValue = float32Array[ lefti + 3 ];
|
||||
const minRightValue = float32Array[ righti ];
|
||||
const maxRightValue = float32Array[ righti + 3 ];
|
||||
|
||||
float32Array[ node32Index + i ] = minLeftValue < minRightValue ? minLeftValue : minRightValue;
|
||||
float32Array[ node32Index + i + 3 ] = maxLeftValue > maxRightValue ? maxLeftValue : maxRightValue;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return didChange;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { refit_indirect };
|
||||
214
src/components/DMT/libs/bvh/core/cast/shapecast.js
Normal file
@@ -0,0 +1,214 @@
|
||||
import { Box3 } from 'three'
|
||||
import { CONTAINED } from '../Constants.js';
|
||||
import { arrayToBox } from '../../utils/ArrayBoxUtilities.js';
|
||||
import { PrimitivePool } from '../../utils/PrimitivePool.js';
|
||||
import { COUNT, OFFSET, LEFT_NODE, RIGHT_NODE, IS_LEAF, BOUNDING_DATA_INDEX } from '../utils/nodeBufferUtils.js';
|
||||
import { BufferStack } from '../utils/BufferStack.js';
|
||||
|
||||
let _box1, _box2;
|
||||
const boxStack = [];
|
||||
const boxPool = /* @__PURE__ */ new PrimitivePool( () => new Box3() );
|
||||
|
||||
export function shapecast( bvh, root, intersectsBounds, intersectsRange, boundsTraverseOrder, byteOffset ) {
|
||||
|
||||
// setup
|
||||
_box1 = boxPool.getPrimitive();
|
||||
_box2 = boxPool.getPrimitive();
|
||||
boxStack.push( _box1, _box2 );
|
||||
BufferStack.setBuffer( bvh._roots[ root ] );
|
||||
|
||||
const result = shapecastTraverse( 0, bvh.geometry, intersectsBounds, intersectsRange, boundsTraverseOrder, byteOffset );
|
||||
|
||||
// cleanup
|
||||
BufferStack.clearBuffer();
|
||||
boxPool.releasePrimitive( _box1 );
|
||||
boxPool.releasePrimitive( _box2 );
|
||||
boxStack.pop();
|
||||
boxStack.pop();
|
||||
|
||||
const length = boxStack.length;
|
||||
if ( length > 0 ) {
|
||||
|
||||
_box2 = boxStack[ length - 1 ];
|
||||
_box1 = boxStack[ length - 2 ];
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
function shapecastTraverse(
|
||||
nodeIndex32,
|
||||
geometry,
|
||||
intersectsBoundsFunc,
|
||||
intersectsRangeFunc,
|
||||
nodeScoreFunc = null,
|
||||
nodeIndexByteOffset = 0, // offset for unique node identifier
|
||||
depth = 0
|
||||
) {
|
||||
|
||||
const { float32Array, uint16Array, uint32Array } = BufferStack;
|
||||
let nodeIndex16 = nodeIndex32 * 2;
|
||||
|
||||
const isLeaf = IS_LEAF( nodeIndex16, uint16Array );
|
||||
if ( isLeaf ) {
|
||||
|
||||
const offset = OFFSET( nodeIndex32, uint32Array );
|
||||
const count = COUNT( nodeIndex16, uint16Array );
|
||||
arrayToBox( BOUNDING_DATA_INDEX( nodeIndex32 ), float32Array, _box1 );
|
||||
return intersectsRangeFunc( offset, count, false, depth, nodeIndexByteOffset + nodeIndex32, _box1 );
|
||||
|
||||
} else {
|
||||
|
||||
const left = LEFT_NODE( nodeIndex32 );
|
||||
const right = RIGHT_NODE( nodeIndex32, uint32Array );
|
||||
let c1 = left;
|
||||
let c2 = right;
|
||||
|
||||
let score1, score2;
|
||||
let box1, box2;
|
||||
if ( nodeScoreFunc ) {
|
||||
|
||||
box1 = _box1;
|
||||
box2 = _box2;
|
||||
|
||||
// bounding data is not offset
|
||||
arrayToBox( BOUNDING_DATA_INDEX( c1 ), float32Array, box1 );
|
||||
arrayToBox( BOUNDING_DATA_INDEX( c2 ), float32Array, box2 );
|
||||
|
||||
score1 = nodeScoreFunc( box1 );
|
||||
score2 = nodeScoreFunc( box2 );
|
||||
|
||||
if ( score2 < score1 ) {
|
||||
|
||||
c1 = right;
|
||||
c2 = left;
|
||||
|
||||
const temp = score1;
|
||||
score1 = score2;
|
||||
score2 = temp;
|
||||
|
||||
box1 = box2;
|
||||
// box2 is always set before use below
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Check box 1 intersection
|
||||
if ( ! box1 ) {
|
||||
|
||||
box1 = _box1;
|
||||
arrayToBox( BOUNDING_DATA_INDEX( c1 ), float32Array, box1 );
|
||||
|
||||
}
|
||||
|
||||
const isC1Leaf = IS_LEAF( c1 * 2, uint16Array );
|
||||
const c1Intersection = intersectsBoundsFunc( box1, isC1Leaf, score1, depth + 1, nodeIndexByteOffset + c1 );
|
||||
|
||||
let c1StopTraversal;
|
||||
if ( c1Intersection === CONTAINED ) {
|
||||
|
||||
const offset = getLeftOffset( c1 );
|
||||
const end = getRightEndOffset( c1 );
|
||||
const count = end - offset;
|
||||
|
||||
c1StopTraversal = intersectsRangeFunc( offset, count, true, depth + 1, nodeIndexByteOffset + c1, box1 );
|
||||
|
||||
} else {
|
||||
|
||||
c1StopTraversal =
|
||||
c1Intersection &&
|
||||
shapecastTraverse(
|
||||
c1,
|
||||
geometry,
|
||||
intersectsBoundsFunc,
|
||||
intersectsRangeFunc,
|
||||
nodeScoreFunc,
|
||||
nodeIndexByteOffset,
|
||||
depth + 1
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if ( c1StopTraversal ) return true;
|
||||
|
||||
// Check box 2 intersection
|
||||
// cached box2 will have been overwritten by previous traversal
|
||||
box2 = _box2;
|
||||
arrayToBox( BOUNDING_DATA_INDEX( c2 ), float32Array, box2 );
|
||||
|
||||
const isC2Leaf = IS_LEAF( c2 * 2, uint16Array );
|
||||
const c2Intersection = intersectsBoundsFunc( box2, isC2Leaf, score2, depth + 1, nodeIndexByteOffset + c2 );
|
||||
|
||||
let c2StopTraversal;
|
||||
if ( c2Intersection === CONTAINED ) {
|
||||
|
||||
const offset = getLeftOffset( c2 );
|
||||
const end = getRightEndOffset( c2 );
|
||||
const count = end - offset;
|
||||
|
||||
c2StopTraversal = intersectsRangeFunc( offset, count, true, depth + 1, nodeIndexByteOffset + c2, box2 );
|
||||
|
||||
} else {
|
||||
|
||||
c2StopTraversal =
|
||||
c2Intersection &&
|
||||
shapecastTraverse(
|
||||
c2,
|
||||
geometry,
|
||||
intersectsBoundsFunc,
|
||||
intersectsRangeFunc,
|
||||
nodeScoreFunc,
|
||||
nodeIndexByteOffset,
|
||||
depth + 1
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if ( c2StopTraversal ) return true;
|
||||
|
||||
return false;
|
||||
|
||||
// Define these inside the function so it has access to the local variables needed
|
||||
// when converting to the buffer equivalents
|
||||
function getLeftOffset( nodeIndex32 ) {
|
||||
|
||||
const { uint16Array, uint32Array } = BufferStack;
|
||||
let nodeIndex16 = nodeIndex32 * 2;
|
||||
|
||||
// traverse until we find a leaf
|
||||
while ( ! IS_LEAF( nodeIndex16, uint16Array ) ) {
|
||||
|
||||
nodeIndex32 = LEFT_NODE( nodeIndex32 );
|
||||
nodeIndex16 = nodeIndex32 * 2;
|
||||
|
||||
}
|
||||
|
||||
return OFFSET( nodeIndex32, uint32Array );
|
||||
|
||||
}
|
||||
|
||||
function getRightEndOffset( nodeIndex32 ) {
|
||||
|
||||
const { uint16Array, uint32Array } = BufferStack;
|
||||
let nodeIndex16 = nodeIndex32 * 2;
|
||||
|
||||
// traverse until we find a leaf
|
||||
while ( ! IS_LEAF( nodeIndex16, uint16Array ) ) {
|
||||
|
||||
// adjust offset to point to the right node
|
||||
nodeIndex32 = RIGHT_NODE( nodeIndex32, uint32Array );
|
||||
nodeIndex16 = nodeIndex32 * 2;
|
||||
|
||||
}
|
||||
|
||||
// return the end offset of the triangle range
|
||||
return OFFSET( nodeIndex32, uint32Array ) + COUNT( nodeIndex16, uint16Array );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
45
src/components/DMT/libs/bvh/core/utils/BufferStack.js
Normal file
@@ -0,0 +1,45 @@
|
||||
class _BufferStack {
|
||||
|
||||
constructor() {
|
||||
|
||||
this.float32Array = null;
|
||||
this.uint16Array = null;
|
||||
this.uint32Array = null;
|
||||
|
||||
const stack = [];
|
||||
let prevBuffer = null;
|
||||
this.setBuffer = buffer => {
|
||||
|
||||
if ( prevBuffer ) {
|
||||
|
||||
stack.push( prevBuffer );
|
||||
|
||||
}
|
||||
|
||||
prevBuffer = buffer;
|
||||
this.float32Array = new Float32Array( buffer );
|
||||
this.uint16Array = new Uint16Array( buffer );
|
||||
this.uint32Array = new Uint32Array( buffer );
|
||||
|
||||
};
|
||||
|
||||
this.clearBuffer = () => {
|
||||
|
||||
prevBuffer = null;
|
||||
this.float32Array = null;
|
||||
this.uint16Array = null;
|
||||
this.uint32Array = null;
|
||||
|
||||
if ( stack.length !== 0 ) {
|
||||
|
||||
this.setBuffer( stack.pop() );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const BufferStack = new _BufferStack();
|
||||
80
src/components/DMT/libs/bvh/core/utils/intersectUtils.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* This function performs intersection tests similar to Ray.intersectBox in three.js,
|
||||
* with the difference that the box values are read from an array to improve performance.
|
||||
*/
|
||||
export function intersectRay( nodeIndex32, array, ray ) {
|
||||
|
||||
let tmin, tmax, tymin, tymax, tzmin, tzmax;
|
||||
|
||||
const invdirx = 1 / ray.direction.x,
|
||||
invdiry = 1 / ray.direction.y,
|
||||
invdirz = 1 / ray.direction.z;
|
||||
|
||||
const ox = ray.origin.x;
|
||||
const oy = ray.origin.y;
|
||||
const oz = ray.origin.z;
|
||||
|
||||
let minx = array[ nodeIndex32 ];
|
||||
let maxx = array[ nodeIndex32 + 3 ];
|
||||
|
||||
let miny = array[ nodeIndex32 + 1 ];
|
||||
let maxy = array[ nodeIndex32 + 3 + 1 ];
|
||||
|
||||
let minz = array[ nodeIndex32 + 2 ];
|
||||
let maxz = array[ nodeIndex32 + 3 + 2 ];
|
||||
|
||||
if ( invdirx >= 0 ) {
|
||||
|
||||
tmin = ( minx - ox ) * invdirx;
|
||||
tmax = ( maxx - ox ) * invdirx;
|
||||
|
||||
} else {
|
||||
|
||||
tmin = ( maxx - ox ) * invdirx;
|
||||
tmax = ( minx - ox ) * invdirx;
|
||||
|
||||
}
|
||||
|
||||
if ( invdiry >= 0 ) {
|
||||
|
||||
tymin = ( miny - oy ) * invdiry;
|
||||
tymax = ( maxy - oy ) * invdiry;
|
||||
|
||||
} else {
|
||||
|
||||
tymin = ( maxy - oy ) * invdiry;
|
||||
tymax = ( miny - oy ) * invdiry;
|
||||
|
||||
}
|
||||
|
||||
if ( ( tmin > tymax ) || ( tymin > tmax ) ) return false;
|
||||
|
||||
if ( tymin > tmin || isNaN( tmin ) ) tmin = tymin;
|
||||
|
||||
if ( tymax < tmax || isNaN( tmax ) ) tmax = tymax;
|
||||
|
||||
if ( invdirz >= 0 ) {
|
||||
|
||||
tzmin = ( minz - oz ) * invdirz;
|
||||
tzmax = ( maxz - oz ) * invdirz;
|
||||
|
||||
} else {
|
||||
|
||||
tzmin = ( maxz - oz ) * invdirz;
|
||||
tzmax = ( minz - oz ) * invdirz;
|
||||
|
||||
}
|
||||
|
||||
if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return false;
|
||||
|
||||
// if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin; // Uncomment this line if add the distance check
|
||||
|
||||
if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax;
|
||||
|
||||
//return point closest to the ray (positive side)
|
||||
|
||||
if ( tmax < 0 ) return false;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { intersectTri } from '../../utils/ThreeRayIntersectUtilities.js';
|
||||
import { setTriangle } from '../../utils/TriangleUtilities.js';
|
||||
|
||||
/*************************************************************/
|
||||
/* This file is generated from "iterationUtils.template.js". */
|
||||
/*************************************************************/
|
||||
/* eslint-disable indent */
|
||||
|
||||
function intersectTris( bvh, side, ray, offset, count, intersections ) {
|
||||
|
||||
const { geometry, _indirectBuffer } = bvh;
|
||||
for ( let i = offset, end = offset + count; i < end; i ++ ) {
|
||||
|
||||
|
||||
intersectTri( geometry, side, ray, i, intersections );
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function intersectClosestTri( bvh, side, ray, offset, count ) {
|
||||
|
||||
const { geometry, _indirectBuffer } = bvh;
|
||||
let dist = Infinity;
|
||||
let res = null;
|
||||
for ( let i = offset, end = offset + count; i < end; i ++ ) {
|
||||
|
||||
let intersection;
|
||||
|
||||
intersection = intersectTri( geometry, side, ray, i );
|
||||
|
||||
|
||||
if ( intersection && intersection.distance < dist ) {
|
||||
|
||||
res = intersection;
|
||||
dist = intersection.distance;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
function iterateOverTriangles(
|
||||
offset,
|
||||
count,
|
||||
bvh,
|
||||
intersectsTriangleFunc,
|
||||
contained,
|
||||
depth,
|
||||
triangle
|
||||
) {
|
||||
|
||||
const { geometry } = bvh;
|
||||
const { index } = geometry;
|
||||
const pos = geometry.attributes.position;
|
||||
for ( let i = offset, l = count + offset; i < l; i ++ ) {
|
||||
|
||||
let tri;
|
||||
|
||||
tri = i;
|
||||
|
||||
setTriangle( triangle, tri * 3, index, pos );
|
||||
triangle.needsUpdate = true;
|
||||
|
||||
if ( intersectsTriangleFunc( triangle, tri, contained, depth ) ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
export { intersectClosestTri, intersectTris, iterateOverTriangles };
|
||||
@@ -0,0 +1,94 @@
|
||||
/* eslint-disable indent */
|
||||
import { intersectTri } from '../../utils/ThreeRayIntersectUtilities.js';
|
||||
import { setTriangle } from '../../utils/TriangleUtilities.js';
|
||||
|
||||
export function intersectTris/* @echo INDIRECT_STRING */( bvh, side, ray, offset, count, intersections ) {
|
||||
|
||||
const { geometry, _indirectBuffer } = bvh;
|
||||
for ( let i = offset, end = offset + count; i < end; i ++ ) {
|
||||
|
||||
/* @if INDIRECT */
|
||||
|
||||
let vi = _indirectBuffer ? _indirectBuffer[ i ] : i;
|
||||
intersectTri( geometry, side, ray, vi, intersections );
|
||||
|
||||
/* @else */
|
||||
|
||||
intersectTri( geometry, side, ray, i, intersections );
|
||||
|
||||
/* @endif */
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function intersectClosestTri/* @echo INDIRECT_STRING */( bvh, side, ray, offset, count ) {
|
||||
|
||||
const { geometry, _indirectBuffer } = bvh;
|
||||
let dist = Infinity;
|
||||
let res = null;
|
||||
for ( let i = offset, end = offset + count; i < end; i ++ ) {
|
||||
|
||||
let intersection;
|
||||
/* @if INDIRECT */
|
||||
|
||||
intersection = intersectTri( geometry, side, ray, _indirectBuffer ? _indirectBuffer[ i ] : i );
|
||||
|
||||
/* @else */
|
||||
|
||||
intersection = intersectTri( geometry, side, ray, i );
|
||||
|
||||
/* @endif */
|
||||
|
||||
if ( intersection && intersection.distance < dist ) {
|
||||
|
||||
res = intersection;
|
||||
dist = intersection.distance;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
export function iterateOverTriangles/* @echo INDIRECT_STRING */(
|
||||
offset,
|
||||
count,
|
||||
bvh,
|
||||
intersectsTriangleFunc,
|
||||
contained,
|
||||
depth,
|
||||
triangle
|
||||
) {
|
||||
|
||||
const { geometry } = bvh;
|
||||
const { index } = geometry;
|
||||
const pos = geometry.attributes.position;
|
||||
for ( let i = offset, l = count + offset; i < l; i ++ ) {
|
||||
|
||||
let tri;
|
||||
/* @if INDIRECT */
|
||||
|
||||
tri = bvh.resolveTriangleIndex( i );
|
||||
|
||||
/* @else */
|
||||
|
||||
tri = i;
|
||||
|
||||
/* @endif */
|
||||
setTriangle( triangle, tri * 3, index, pos );
|
||||
triangle.needsUpdate = true;
|
||||
|
||||
if ( intersectsTriangleFunc( triangle, tri, contained, depth ) ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { intersectTri } from '../../utils/ThreeRayIntersectUtilities.js';
|
||||
import { setTriangle } from '../../utils/TriangleUtilities.js';
|
||||
|
||||
/*************************************************************/
|
||||
/* This file is generated from "iterationUtils.template.js". */
|
||||
/*************************************************************/
|
||||
/* eslint-disable indent */
|
||||
|
||||
function intersectTris_indirect( bvh, side, ray, offset, count, intersections ) {
|
||||
|
||||
const { geometry, _indirectBuffer } = bvh;
|
||||
for ( let i = offset, end = offset + count; i < end; i ++ ) {
|
||||
|
||||
let vi = _indirectBuffer ? _indirectBuffer[ i ] : i;
|
||||
intersectTri( geometry, side, ray, vi, intersections );
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function intersectClosestTri_indirect( bvh, side, ray, offset, count ) {
|
||||
|
||||
const { geometry, _indirectBuffer } = bvh;
|
||||
let dist = Infinity;
|
||||
let res = null;
|
||||
for ( let i = offset, end = offset + count; i < end; i ++ ) {
|
||||
|
||||
let intersection;
|
||||
intersection = intersectTri( geometry, side, ray, _indirectBuffer ? _indirectBuffer[ i ] : i );
|
||||
|
||||
|
||||
if ( intersection && intersection.distance < dist ) {
|
||||
|
||||
res = intersection;
|
||||
dist = intersection.distance;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
function iterateOverTriangles_indirect(
|
||||
offset,
|
||||
count,
|
||||
bvh,
|
||||
intersectsTriangleFunc,
|
||||
contained,
|
||||
depth,
|
||||
triangle
|
||||
) {
|
||||
|
||||
const { geometry } = bvh;
|
||||
const { index } = geometry;
|
||||
const pos = geometry.attributes.position;
|
||||
for ( let i = offset, l = count + offset; i < l; i ++ ) {
|
||||
|
||||
let tri;
|
||||
tri = bvh.resolveTriangleIndex( i );
|
||||
|
||||
setTriangle( triangle, tri * 3, index, pos );
|
||||
triangle.needsUpdate = true;
|
||||
|
||||
if ( intersectsTriangleFunc( triangle, tri, contained, depth ) ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
export { intersectClosestTri_indirect, intersectTris_indirect, iterateOverTriangles_indirect };
|
||||
41
src/components/DMT/libs/bvh/core/utils/nodeBufferUtils.js
Normal file
@@ -0,0 +1,41 @@
|
||||
export function IS_LEAF( n16, uint16Array ) {
|
||||
|
||||
return uint16Array[ n16 + 15 ] === 0xFFFF;
|
||||
|
||||
}
|
||||
|
||||
export function OFFSET( n32, uint32Array ) {
|
||||
|
||||
return uint32Array[ n32 + 6 ];
|
||||
|
||||
}
|
||||
|
||||
export function COUNT( n16, uint16Array ) {
|
||||
|
||||
return uint16Array[ n16 + 14 ];
|
||||
|
||||
}
|
||||
|
||||
export function LEFT_NODE( n32 ) {
|
||||
|
||||
return n32 + 8;
|
||||
|
||||
}
|
||||
|
||||
export function RIGHT_NODE( n32, uint32Array ) {
|
||||
|
||||
return uint32Array[ n32 + 6 ];
|
||||
|
||||
}
|
||||
|
||||
export function SPLIT_AXIS( n32, uint32Array ) {
|
||||
|
||||
return uint32Array[ n32 + 7 ];
|
||||
|
||||
}
|
||||
|
||||
export function BOUNDING_DATA_INDEX( n32 ) {
|
||||
|
||||
return n32;
|
||||
|
||||
}
|
||||
292
src/components/DMT/libs/bvh/debug/Debug.js
Normal file
@@ -0,0 +1,292 @@
|
||||
import { Box3, Vector3 } from 'three'
|
||||
import { TRAVERSAL_COST, TRIANGLE_INTERSECT_COST } from '../core/Constants.js';
|
||||
import { arrayToBox } from '../utils/ArrayBoxUtilities.js';
|
||||
import { isSharedArrayBufferSupported } from '../utils/BufferUtils.js';
|
||||
|
||||
const _box1 = /* @__PURE__ */ new Box3();
|
||||
const _box2 = /* @__PURE__ */ new Box3();
|
||||
const _vec = /* @__PURE__ */ new Vector3();
|
||||
|
||||
// https://stackoverflow.com/questions/1248302/how-to-get-the-size-of-a-javascript-object
|
||||
function getPrimitiveSize( el ) {
|
||||
|
||||
switch ( typeof el ) {
|
||||
|
||||
case 'number':
|
||||
return 8;
|
||||
case 'string':
|
||||
return el.length * 2;
|
||||
case 'boolean':
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function isTypedArray( arr ) {
|
||||
|
||||
const regex = /(Uint|Int|Float)(8|16|32)Array/;
|
||||
return regex.test( arr.constructor.name );
|
||||
|
||||
}
|
||||
|
||||
function getRootExtremes( bvh, group ) {
|
||||
|
||||
const result = {
|
||||
nodeCount: 0,
|
||||
leafNodeCount: 0,
|
||||
|
||||
depth: {
|
||||
min: Infinity, max: - Infinity
|
||||
},
|
||||
tris: {
|
||||
min: Infinity, max: - Infinity
|
||||
},
|
||||
splits: [ 0, 0, 0 ],
|
||||
surfaceAreaScore: 0,
|
||||
};
|
||||
|
||||
bvh.traverse( ( depth, isLeaf, boundingData, offsetOrSplit, count ) => {
|
||||
|
||||
const l0 = boundingData[ 0 + 3 ] - boundingData[ 0 ];
|
||||
const l1 = boundingData[ 1 + 3 ] - boundingData[ 1 ];
|
||||
const l2 = boundingData[ 2 + 3 ] - boundingData[ 2 ];
|
||||
|
||||
const surfaceArea = 2 * ( l0 * l1 + l1 * l2 + l2 * l0 );
|
||||
|
||||
result.nodeCount ++;
|
||||
if ( isLeaf ) {
|
||||
|
||||
result.leafNodeCount ++;
|
||||
|
||||
result.depth.min = Math.min( depth, result.depth.min );
|
||||
result.depth.max = Math.max( depth, result.depth.max );
|
||||
|
||||
result.tris.min = Math.min( count, result.tris.min );
|
||||
result.tris.max = Math.max( count, result.tris.max );
|
||||
|
||||
result.surfaceAreaScore += surfaceArea * TRIANGLE_INTERSECT_COST * count;
|
||||
|
||||
} else {
|
||||
|
||||
result.splits[ offsetOrSplit ] ++;
|
||||
|
||||
result.surfaceAreaScore += surfaceArea * TRAVERSAL_COST;
|
||||
|
||||
}
|
||||
|
||||
}, group );
|
||||
|
||||
// If there are no leaf nodes because the tree hasn't finished generating yet.
|
||||
if ( result.tris.min === Infinity ) {
|
||||
|
||||
result.tris.min = 0;
|
||||
result.tris.max = 0;
|
||||
|
||||
}
|
||||
|
||||
if ( result.depth.min === Infinity ) {
|
||||
|
||||
result.depth.min = 0;
|
||||
result.depth.max = 0;
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
function getBVHExtremes( bvh ) {
|
||||
|
||||
return bvh._roots.map( ( root, i ) => getRootExtremes( bvh, i ) );
|
||||
|
||||
}
|
||||
|
||||
function estimateMemoryInBytes( obj ) {
|
||||
|
||||
const traversed = new Set();
|
||||
const stack = [ obj ];
|
||||
let bytes = 0;
|
||||
|
||||
while ( stack.length ) {
|
||||
|
||||
const curr = stack.pop();
|
||||
if ( traversed.has( curr ) ) {
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
traversed.add( curr );
|
||||
|
||||
for ( let key in curr ) {
|
||||
|
||||
if ( ! curr.hasOwnProperty( key ) ) {
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
bytes += getPrimitiveSize( key );
|
||||
|
||||
const value = curr[ key ];
|
||||
if ( value && ( typeof value === 'object' || typeof value === 'function' ) ) {
|
||||
|
||||
if ( isTypedArray( value ) ) {
|
||||
|
||||
bytes += value.byteLength;
|
||||
|
||||
} else if ( isSharedArrayBufferSupported() && value instanceof SharedArrayBuffer ) {
|
||||
|
||||
bytes += value.byteLength;
|
||||
|
||||
} else if ( value instanceof ArrayBuffer ) {
|
||||
|
||||
bytes += value.byteLength;
|
||||
|
||||
} else {
|
||||
|
||||
stack.push( value );
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
bytes += getPrimitiveSize( value );
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return bytes;
|
||||
|
||||
}
|
||||
|
||||
function validateBounds( bvh ) {
|
||||
|
||||
const geometry = bvh.geometry;
|
||||
const depthStack = [];
|
||||
const index = geometry.index;
|
||||
const position = geometry.getAttribute( 'position' );
|
||||
let passes = true;
|
||||
|
||||
bvh.traverse( ( depth, isLeaf, boundingData, offset, count ) => {
|
||||
|
||||
const info = {
|
||||
depth,
|
||||
isLeaf,
|
||||
boundingData,
|
||||
offset,
|
||||
count,
|
||||
};
|
||||
depthStack[ depth ] = info;
|
||||
|
||||
arrayToBox( 0, boundingData, _box1 );
|
||||
const parent = depthStack[ depth - 1 ];
|
||||
|
||||
if ( isLeaf ) {
|
||||
|
||||
// check triangles
|
||||
for ( let i = offset, l = offset + count; i < l; i ++ ) {
|
||||
|
||||
const triIndex = bvh.resolveTriangleIndex( i );
|
||||
let i0 = 3 * triIndex;
|
||||
let i1 = 3 * triIndex + 1;
|
||||
let i2 = 3 * triIndex + 2;
|
||||
|
||||
if ( index ) {
|
||||
|
||||
i0 = index.getX( i0 );
|
||||
i1 = index.getX( i1 );
|
||||
i2 = index.getX( i2 );
|
||||
|
||||
}
|
||||
|
||||
let isContained;
|
||||
|
||||
_vec.fromBufferAttribute( position, i0 );
|
||||
isContained = _box1.containsPoint( _vec );
|
||||
|
||||
_vec.fromBufferAttribute( position, i1 );
|
||||
isContained = isContained && _box1.containsPoint( _vec );
|
||||
|
||||
_vec.fromBufferAttribute( position, i2 );
|
||||
isContained = isContained && _box1.containsPoint( _vec );
|
||||
|
||||
console.assert( isContained, 'Leaf bounds does not fully contain triangle.' );
|
||||
passes = passes && isContained;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( parent ) {
|
||||
|
||||
// check if my bounds fit in my parents
|
||||
arrayToBox( 0, boundingData, _box2 );
|
||||
|
||||
const isContained = _box2.containsBox( _box1 );
|
||||
console.assert( isContained, 'Parent bounds does not fully contain child.' );
|
||||
passes = passes && isContained;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
return passes;
|
||||
|
||||
}
|
||||
|
||||
// Returns a simple, human readable object that represents the BVH.
|
||||
function getJSONStructure( bvh ) {
|
||||
|
||||
const depthStack = [];
|
||||
|
||||
bvh.traverse( ( depth, isLeaf, boundingData, offset, count ) => {
|
||||
|
||||
const info = {
|
||||
bounds: arrayToBox( 0, boundingData, new Box3() ),
|
||||
};
|
||||
|
||||
if ( isLeaf ) {
|
||||
|
||||
info.count = count;
|
||||
info.offset = offset;
|
||||
|
||||
} else {
|
||||
|
||||
info.left = null;
|
||||
info.right = null;
|
||||
|
||||
}
|
||||
|
||||
depthStack[ depth ] = info;
|
||||
|
||||
// traversal hits the left then right node
|
||||
const parent = depthStack[ depth - 1 ];
|
||||
if ( parent ) {
|
||||
|
||||
if ( parent.left === null ) {
|
||||
|
||||
parent.left = info;
|
||||
|
||||
} else {
|
||||
|
||||
parent.right = info;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
return depthStack[ 0 ];
|
||||
|
||||
}
|
||||
|
||||
export { estimateMemoryInBytes, getBVHExtremes, validateBounds, getJSONStructure };
|
||||
4
src/components/DMT/libs/bvh/gpu/BVHShaderGLSL.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './glsl/common_functions.glsl.js';
|
||||
export * from './glsl/bvh_distance_functions.glsl.js';
|
||||
export * from './glsl/bvh_ray_functions.glsl.js';
|
||||
export * from './glsl/bvh_struct_definitions.glsl.js';
|
||||
191
src/components/DMT/libs/bvh/gpu/MeshBVHUniformStruct.js
Normal file
@@ -0,0 +1,191 @@
|
||||
import {
|
||||
DataTexture,
|
||||
FloatType,
|
||||
UnsignedIntType,
|
||||
RGBAFormat,
|
||||
RGIntegerFormat,
|
||||
NearestFilter,
|
||||
BufferAttribute,
|
||||
} from 'three'
|
||||
import {
|
||||
FloatVertexAttributeTexture,
|
||||
UIntVertexAttributeTexture,
|
||||
} from './VertexAttributeTexture.js';
|
||||
import { BYTES_PER_NODE } from '../core/Constants.js';
|
||||
import {
|
||||
BOUNDING_DATA_INDEX,
|
||||
COUNT,
|
||||
IS_LEAF,
|
||||
RIGHT_NODE,
|
||||
OFFSET,
|
||||
SPLIT_AXIS,
|
||||
} from '../core/utils/nodeBufferUtils.js';
|
||||
import { getIndexArray, getVertexCount } from '../core/build/geometryUtils.js';
|
||||
|
||||
export class MeshBVHUniformStruct {
|
||||
|
||||
constructor() {
|
||||
|
||||
this.index = new UIntVertexAttributeTexture();
|
||||
this.position = new FloatVertexAttributeTexture();
|
||||
this.bvhBounds = new DataTexture();
|
||||
this.bvhContents = new DataTexture();
|
||||
this._cachedIndexAttr = null;
|
||||
|
||||
this.index.overrideItemSize = 3;
|
||||
|
||||
}
|
||||
|
||||
updateFrom( bvh ) {
|
||||
|
||||
const { geometry } = bvh;
|
||||
bvhToTextures( bvh, this.bvhBounds, this.bvhContents );
|
||||
|
||||
this.position.updateFrom( geometry.attributes.position );
|
||||
|
||||
// dereference a new index attribute if we're using indirect storage
|
||||
if ( bvh.indirect ) {
|
||||
|
||||
const indirectBuffer = bvh._indirectBuffer;
|
||||
if (
|
||||
this._cachedIndexAttr === null ||
|
||||
this._cachedIndexAttr.count !== indirectBuffer.length
|
||||
) {
|
||||
|
||||
if ( geometry.index ) {
|
||||
|
||||
this._cachedIndexAttr = geometry.index.clone();
|
||||
|
||||
} else {
|
||||
|
||||
const array = getIndexArray( getVertexCount( geometry ) );
|
||||
this._cachedIndexAttr = new BufferAttribute( array, 1, false );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dereferenceIndex( geometry, indirectBuffer, this._cachedIndexAttr );
|
||||
this.index.updateFrom( this._cachedIndexAttr );
|
||||
|
||||
} else {
|
||||
|
||||
this.index.updateFrom( geometry.index );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
const { index, position, bvhBounds, bvhContents } = this;
|
||||
|
||||
if ( index ) index.dispose();
|
||||
if ( position ) position.dispose();
|
||||
if ( bvhBounds ) bvhBounds.dispose();
|
||||
if ( bvhContents ) bvhContents.dispose();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function dereferenceIndex( geometry, indirectBuffer, target ) {
|
||||
|
||||
const unpacked = target.array;
|
||||
const indexArray = geometry.index ? geometry.index.array : null;
|
||||
for ( let i = 0, l = indirectBuffer.length; i < l; i ++ ) {
|
||||
|
||||
const i3 = 3 * i;
|
||||
const v3 = 3 * indirectBuffer[ i ];
|
||||
for ( let c = 0; c < 3; c ++ ) {
|
||||
|
||||
unpacked[ i3 + c ] = indexArray ? indexArray[ v3 + c ] : v3 + c;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function bvhToTextures( bvh, boundsTexture, contentsTexture ) {
|
||||
|
||||
const roots = bvh._roots;
|
||||
|
||||
if ( roots.length !== 1 ) {
|
||||
|
||||
throw new Error( 'MeshBVHUniformStruct: Multi-root BVHs not supported.' );
|
||||
|
||||
}
|
||||
|
||||
const root = roots[ 0 ];
|
||||
const uint16Array = new Uint16Array( root );
|
||||
const uint32Array = new Uint32Array( root );
|
||||
const float32Array = new Float32Array( root );
|
||||
|
||||
// Both bounds need two elements per node so compute the height so it's twice as long as
|
||||
// the width so we can expand the row by two and still have a square texture
|
||||
const nodeCount = root.byteLength / BYTES_PER_NODE;
|
||||
const boundsDimension = 2 * Math.ceil( Math.sqrt( nodeCount / 2 ) );
|
||||
const boundsArray = new Float32Array( 4 * boundsDimension * boundsDimension );
|
||||
|
||||
const contentsDimension = Math.ceil( Math.sqrt( nodeCount ) );
|
||||
const contentsArray = new Uint32Array( 2 * contentsDimension * contentsDimension );
|
||||
|
||||
for ( let i = 0; i < nodeCount; i ++ ) {
|
||||
|
||||
const nodeIndex32 = i * BYTES_PER_NODE / 4;
|
||||
const nodeIndex16 = nodeIndex32 * 2;
|
||||
const boundsIndex = BOUNDING_DATA_INDEX( nodeIndex32 );
|
||||
for ( let b = 0; b < 3; b ++ ) {
|
||||
|
||||
boundsArray[ 8 * i + 0 + b ] = float32Array[ boundsIndex + 0 + b ];
|
||||
boundsArray[ 8 * i + 4 + b ] = float32Array[ boundsIndex + 3 + b ];
|
||||
|
||||
}
|
||||
|
||||
if ( IS_LEAF( nodeIndex16, uint16Array ) ) {
|
||||
|
||||
const count = COUNT( nodeIndex16, uint16Array );
|
||||
const offset = OFFSET( nodeIndex32, uint32Array );
|
||||
|
||||
const mergedLeafCount = 0xffff0000 | count;
|
||||
contentsArray[ i * 2 + 0 ] = mergedLeafCount;
|
||||
contentsArray[ i * 2 + 1 ] = offset;
|
||||
|
||||
} else {
|
||||
|
||||
const rightIndex = 4 * RIGHT_NODE( nodeIndex32, uint32Array ) / BYTES_PER_NODE;
|
||||
const splitAxis = SPLIT_AXIS( nodeIndex32, uint32Array );
|
||||
|
||||
contentsArray[ i * 2 + 0 ] = splitAxis;
|
||||
contentsArray[ i * 2 + 1 ] = rightIndex;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boundsTexture.image.data = boundsArray;
|
||||
boundsTexture.image.width = boundsDimension;
|
||||
boundsTexture.image.height = boundsDimension;
|
||||
boundsTexture.format = RGBAFormat;
|
||||
boundsTexture.type = FloatType;
|
||||
boundsTexture.internalFormat = 'RGBA32F';
|
||||
boundsTexture.minFilter = NearestFilter;
|
||||
boundsTexture.magFilter = NearestFilter;
|
||||
boundsTexture.generateMipmaps = false;
|
||||
boundsTexture.needsUpdate = true;
|
||||
boundsTexture.dispose();
|
||||
|
||||
contentsTexture.image.data = contentsArray;
|
||||
contentsTexture.image.width = contentsDimension;
|
||||
contentsTexture.image.height = contentsDimension;
|
||||
contentsTexture.format = RGIntegerFormat;
|
||||
contentsTexture.type = UnsignedIntType;
|
||||
contentsTexture.internalFormat = 'RG32UI';
|
||||
contentsTexture.minFilter = NearestFilter;
|
||||
contentsTexture.magFilter = NearestFilter;
|
||||
contentsTexture.generateMipmaps = false;
|
||||
contentsTexture.needsUpdate = true;
|
||||
contentsTexture.dispose();
|
||||
|
||||
}
|
||||
309
src/components/DMT/libs/bvh/gpu/VertexAttributeTexture.js
Normal file
@@ -0,0 +1,309 @@
|
||||
import {
|
||||
DataTexture,
|
||||
FloatType,
|
||||
IntType,
|
||||
UnsignedIntType,
|
||||
ByteType,
|
||||
UnsignedByteType,
|
||||
ShortType,
|
||||
UnsignedShortType,
|
||||
|
||||
RedFormat,
|
||||
RGFormat,
|
||||
RGBAFormat,
|
||||
|
||||
RedIntegerFormat,
|
||||
RGIntegerFormat,
|
||||
RGBAIntegerFormat,
|
||||
|
||||
NearestFilter,
|
||||
} from 'three'
|
||||
|
||||
function countToStringFormat( count ) {
|
||||
|
||||
switch ( count ) {
|
||||
|
||||
case 1: return 'R';
|
||||
case 2: return 'RG';
|
||||
case 3: return 'RGBA';
|
||||
case 4: return 'RGBA';
|
||||
|
||||
}
|
||||
|
||||
throw new Error();
|
||||
|
||||
}
|
||||
|
||||
function countToFormat( count ) {
|
||||
|
||||
switch ( count ) {
|
||||
|
||||
case 1: return RedFormat;
|
||||
case 2: return RGFormat;
|
||||
case 3: return RGBAFormat;
|
||||
case 4: return RGBAFormat;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function countToIntFormat( count ) {
|
||||
|
||||
switch ( count ) {
|
||||
|
||||
case 1: return RedIntegerFormat;
|
||||
case 2: return RGIntegerFormat;
|
||||
case 3: return RGBAIntegerFormat;
|
||||
case 4: return RGBAIntegerFormat;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class VertexAttributeTexture extends DataTexture {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
this.minFilter = NearestFilter;
|
||||
this.magFilter = NearestFilter;
|
||||
this.generateMipmaps = false;
|
||||
this.overrideItemSize = null;
|
||||
this._forcedType = null;
|
||||
|
||||
}
|
||||
|
||||
updateFrom( attr ) {
|
||||
|
||||
const overrideItemSize = this.overrideItemSize;
|
||||
const originalItemSize = attr.itemSize;
|
||||
const originalCount = attr.count;
|
||||
if ( overrideItemSize !== null ) {
|
||||
|
||||
if ( ( originalItemSize * originalCount ) % overrideItemSize !== 0.0 ) {
|
||||
|
||||
throw new Error( 'VertexAttributeTexture: overrideItemSize must divide evenly into buffer length.' );
|
||||
|
||||
}
|
||||
|
||||
attr.itemSize = overrideItemSize;
|
||||
attr.count = originalCount * originalItemSize / overrideItemSize;
|
||||
|
||||
}
|
||||
|
||||
const itemSize = attr.itemSize;
|
||||
const count = attr.count;
|
||||
const normalized = attr.normalized;
|
||||
const originalBufferCons = attr.array.constructor;
|
||||
const byteCount = originalBufferCons.BYTES_PER_ELEMENT;
|
||||
let targetType = this._forcedType;
|
||||
let finalStride = itemSize;
|
||||
|
||||
// derive the type of texture this should be in the shader
|
||||
if ( targetType === null ) {
|
||||
|
||||
switch ( originalBufferCons ) {
|
||||
|
||||
case Float32Array:
|
||||
targetType = FloatType;
|
||||
break;
|
||||
|
||||
case Uint8Array:
|
||||
case Uint16Array:
|
||||
case Uint32Array:
|
||||
targetType = UnsignedIntType;
|
||||
break;
|
||||
|
||||
case Int8Array:
|
||||
case Int16Array:
|
||||
case Int32Array:
|
||||
targetType = IntType;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// get the target format to store the texture as
|
||||
let type, format, normalizeValue, targetBufferCons;
|
||||
let internalFormat = countToStringFormat( itemSize );
|
||||
switch ( targetType ) {
|
||||
|
||||
case FloatType:
|
||||
normalizeValue = 1.0;
|
||||
format = countToFormat( itemSize );
|
||||
|
||||
if ( normalized && byteCount === 1 ) {
|
||||
|
||||
targetBufferCons = originalBufferCons;
|
||||
internalFormat += '8';
|
||||
|
||||
if ( originalBufferCons === Uint8Array ) {
|
||||
|
||||
type = UnsignedByteType;
|
||||
|
||||
} else {
|
||||
|
||||
type = ByteType;
|
||||
internalFormat += '_SNORM';
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
targetBufferCons = Float32Array;
|
||||
internalFormat += '32F';
|
||||
type = FloatType;
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case IntType:
|
||||
internalFormat += byteCount * 8 + 'I';
|
||||
normalizeValue = normalized ? Math.pow( 2, originalBufferCons.BYTES_PER_ELEMENT * 8 - 1 ) : 1.0;
|
||||
format = countToIntFormat( itemSize );
|
||||
|
||||
if ( byteCount === 1 ) {
|
||||
|
||||
targetBufferCons = Int8Array;
|
||||
type = ByteType;
|
||||
|
||||
} else if ( byteCount === 2 ) {
|
||||
|
||||
targetBufferCons = Int16Array;
|
||||
type = ShortType;
|
||||
|
||||
} else {
|
||||
|
||||
targetBufferCons = Int32Array;
|
||||
type = IntType;
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case UnsignedIntType:
|
||||
internalFormat += byteCount * 8 + 'UI';
|
||||
normalizeValue = normalized ? Math.pow( 2, originalBufferCons.BYTES_PER_ELEMENT * 8 - 1 ) : 1.0;
|
||||
format = countToIntFormat( itemSize );
|
||||
|
||||
if ( byteCount === 1 ) {
|
||||
|
||||
targetBufferCons = Uint8Array;
|
||||
type = UnsignedByteType;
|
||||
|
||||
} else if ( byteCount === 2 ) {
|
||||
|
||||
targetBufferCons = Uint16Array;
|
||||
type = UnsignedShortType;
|
||||
|
||||
} else {
|
||||
|
||||
targetBufferCons = Uint32Array;
|
||||
type = UnsignedIntType;
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// there will be a mismatch between format length and final length because
|
||||
// RGBFormat and RGBIntegerFormat was removed
|
||||
if ( finalStride === 3 && ( format === RGBAFormat || format === RGBAIntegerFormat ) ) {
|
||||
|
||||
finalStride = 4;
|
||||
|
||||
}
|
||||
|
||||
// copy the data over to the new texture array
|
||||
const dimension = Math.ceil( Math.sqrt( count ) ) || 1;
|
||||
const length = finalStride * dimension * dimension;
|
||||
const dataArray = new targetBufferCons( length );
|
||||
|
||||
// temporarily set the normalized state to false since we have custom normalization logic
|
||||
const originalNormalized = attr.normalized;
|
||||
attr.normalized = false;
|
||||
for ( let i = 0; i < count; i ++ ) {
|
||||
|
||||
const ii = finalStride * i;
|
||||
dataArray[ ii ] = attr.getX( i ) / normalizeValue;
|
||||
|
||||
if ( itemSize >= 2 ) {
|
||||
|
||||
dataArray[ ii + 1 ] = attr.getY( i ) / normalizeValue;
|
||||
|
||||
}
|
||||
|
||||
if ( itemSize >= 3 ) {
|
||||
|
||||
dataArray[ ii + 2 ] = attr.getZ( i ) / normalizeValue;
|
||||
|
||||
if ( finalStride === 4 ) {
|
||||
|
||||
dataArray[ ii + 3 ] = 1.0;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( itemSize >= 4 ) {
|
||||
|
||||
dataArray[ ii + 3 ] = attr.getW( i ) / normalizeValue;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
attr.normalized = originalNormalized;
|
||||
|
||||
this.internalFormat = internalFormat;
|
||||
this.format = format;
|
||||
this.type = type;
|
||||
this.image.width = dimension;
|
||||
this.image.height = dimension;
|
||||
this.image.data = dataArray;
|
||||
this.needsUpdate = true;
|
||||
this.dispose();
|
||||
|
||||
attr.itemSize = originalItemSize;
|
||||
attr.count = originalCount;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class UIntVertexAttributeTexture extends VertexAttributeTexture {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
this._forcedType = UnsignedIntType;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class IntVertexAttributeTexture extends VertexAttributeTexture {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
this._forcedType = IntType;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export class FloatVertexAttributeTexture extends VertexAttributeTexture {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
this._forcedType = FloatType;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
// Distance to Point
|
||||
export const bvh_distance_functions = /* glsl */`
|
||||
|
||||
float dot2( vec3 v ) {
|
||||
|
||||
return dot( v, v );
|
||||
|
||||
}
|
||||
|
||||
// https://www.shadertoy.com/view/ttfGWl
|
||||
vec3 closestPointToTriangle( vec3 p, vec3 v0, vec3 v1, vec3 v2, out vec3 barycoord ) {
|
||||
|
||||
vec3 v10 = v1 - v0;
|
||||
vec3 v21 = v2 - v1;
|
||||
vec3 v02 = v0 - v2;
|
||||
|
||||
vec3 p0 = p - v0;
|
||||
vec3 p1 = p - v1;
|
||||
vec3 p2 = p - v2;
|
||||
|
||||
vec3 nor = cross( v10, v02 );
|
||||
|
||||
// method 2, in barycentric space
|
||||
vec3 q = cross( nor, p0 );
|
||||
float d = 1.0 / dot2( nor );
|
||||
float u = d * dot( q, v02 );
|
||||
float v = d * dot( q, v10 );
|
||||
float w = 1.0 - u - v;
|
||||
|
||||
if( u < 0.0 ) {
|
||||
|
||||
w = clamp( dot( p2, v02 ) / dot2( v02 ), 0.0, 1.0 );
|
||||
u = 0.0;
|
||||
v = 1.0 - w;
|
||||
|
||||
} else if( v < 0.0 ) {
|
||||
|
||||
u = clamp( dot( p0, v10 ) / dot2( v10 ), 0.0, 1.0 );
|
||||
v = 0.0;
|
||||
w = 1.0 - u;
|
||||
|
||||
} else if( w < 0.0 ) {
|
||||
|
||||
v = clamp( dot( p1, v21 ) / dot2( v21 ), 0.0, 1.0 );
|
||||
w = 0.0;
|
||||
u = 1.0-v;
|
||||
|
||||
}
|
||||
|
||||
barycoord = vec3( u, v, w );
|
||||
return u * v1 + v * v2 + w * v0;
|
||||
|
||||
}
|
||||
|
||||
float distanceToTriangles(
|
||||
// geometry info and triangle range
|
||||
sampler2D positionAttr, usampler2D indexAttr, uint offset, uint count,
|
||||
|
||||
// point and cut off range
|
||||
vec3 point, float closestDistanceSquared,
|
||||
|
||||
// outputs
|
||||
inout uvec4 faceIndices, inout vec3 faceNormal, inout vec3 barycoord, inout float side, inout vec3 outPoint
|
||||
) {
|
||||
|
||||
bool found = false;
|
||||
vec3 localBarycoord;
|
||||
for ( uint i = offset, l = offset + count; i < l; i ++ ) {
|
||||
|
||||
uvec3 indices = uTexelFetch1D( indexAttr, i ).xyz;
|
||||
vec3 a = texelFetch1D( positionAttr, indices.x ).rgb;
|
||||
vec3 b = texelFetch1D( positionAttr, indices.y ).rgb;
|
||||
vec3 c = texelFetch1D( positionAttr, indices.z ).rgb;
|
||||
|
||||
// get the closest point and barycoord
|
||||
vec3 closestPoint = closestPointToTriangle( point, a, b, c, localBarycoord );
|
||||
vec3 delta = point - closestPoint;
|
||||
float sqDist = dot2( delta );
|
||||
if ( sqDist < closestDistanceSquared ) {
|
||||
|
||||
// set the output results
|
||||
closestDistanceSquared = sqDist;
|
||||
faceIndices = uvec4( indices.xyz, i );
|
||||
faceNormal = normalize( cross( a - b, b - c ) );
|
||||
barycoord = localBarycoord;
|
||||
outPoint = closestPoint;
|
||||
side = sign( dot( faceNormal, delta ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return closestDistanceSquared;
|
||||
|
||||
}
|
||||
|
||||
float distanceSqToBounds( vec3 point, vec3 boundsMin, vec3 boundsMax ) {
|
||||
|
||||
vec3 clampedPoint = clamp( point, boundsMin, boundsMax );
|
||||
vec3 delta = point - clampedPoint;
|
||||
return dot( delta, delta );
|
||||
|
||||
}
|
||||
|
||||
float distanceSqToBVHNodeBoundsPoint( vec3 point, sampler2D bvhBounds, uint currNodeIndex ) {
|
||||
|
||||
uint cni2 = currNodeIndex * 2u;
|
||||
vec3 boundsMin = texelFetch1D( bvhBounds, cni2 ).xyz;
|
||||
vec3 boundsMax = texelFetch1D( bvhBounds, cni2 + 1u ).xyz;
|
||||
return distanceSqToBounds( point, boundsMin, boundsMax );
|
||||
|
||||
}
|
||||
|
||||
// use a macro to hide the fact that we need to expand the struct into separate fields
|
||||
#define\
|
||||
bvhClosestPointToPoint(\
|
||||
bvh,\
|
||||
point, faceIndices, faceNormal, barycoord, side, outPoint\
|
||||
)\
|
||||
_bvhClosestPointToPoint(\
|
||||
bvh.position, bvh.index, bvh.bvhBounds, bvh.bvhContents,\
|
||||
point, faceIndices, faceNormal, barycoord, side, outPoint\
|
||||
)
|
||||
|
||||
float _bvhClosestPointToPoint(
|
||||
// bvh info
|
||||
sampler2D bvh_position, usampler2D bvh_index, sampler2D bvh_bvhBounds, usampler2D bvh_bvhContents,
|
||||
|
||||
// point to check
|
||||
vec3 point,
|
||||
|
||||
// output variables
|
||||
inout uvec4 faceIndices, inout vec3 faceNormal, inout vec3 barycoord,
|
||||
inout float side, inout vec3 outPoint
|
||||
) {
|
||||
|
||||
// stack needs to be twice as long as the deepest tree we expect because
|
||||
// we push both the left and right child onto the stack every traversal
|
||||
int ptr = 0;
|
||||
uint stack[ BVH_STACK_DEPTH ];
|
||||
stack[ 0 ] = 0u;
|
||||
|
||||
float closestDistanceSquared = pow( 100000.0, 2.0 );
|
||||
bool found = false;
|
||||
while ( ptr > - 1 && ptr < BVH_STACK_DEPTH ) {
|
||||
|
||||
uint currNodeIndex = stack[ ptr ];
|
||||
ptr --;
|
||||
|
||||
// check if we intersect the current bounds
|
||||
float boundsHitDistance = distanceSqToBVHNodeBoundsPoint( point, bvh_bvhBounds, currNodeIndex );
|
||||
if ( boundsHitDistance > closestDistanceSquared ) {
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
uvec2 boundsInfo = uTexelFetch1D( bvh_bvhContents, currNodeIndex ).xy;
|
||||
bool isLeaf = bool( boundsInfo.x & 0xffff0000u );
|
||||
if ( isLeaf ) {
|
||||
|
||||
uint count = boundsInfo.x & 0x0000ffffu;
|
||||
uint offset = boundsInfo.y;
|
||||
closestDistanceSquared = distanceToTriangles(
|
||||
bvh_position, bvh_index, offset, count, point, closestDistanceSquared,
|
||||
|
||||
// outputs
|
||||
faceIndices, faceNormal, barycoord, side, outPoint
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
uint leftIndex = currNodeIndex + 1u;
|
||||
uint splitAxis = boundsInfo.x & 0x0000ffffu;
|
||||
uint rightIndex = boundsInfo.y;
|
||||
bool leftToRight = distanceSqToBVHNodeBoundsPoint( point, bvh_bvhBounds, leftIndex ) < distanceSqToBVHNodeBoundsPoint( point, bvh_bvhBounds, rightIndex );//rayDirection[ splitAxis ] >= 0.0;
|
||||
uint c1 = leftToRight ? leftIndex : rightIndex;
|
||||
uint c2 = leftToRight ? rightIndex : leftIndex;
|
||||
|
||||
// set c2 in the stack so we traverse it later. We need to keep track of a pointer in
|
||||
// the stack while we traverse. The second pointer added is the one that will be
|
||||
// traversed first
|
||||
ptr ++;
|
||||
stack[ ptr ] = c2;
|
||||
ptr ++;
|
||||
stack[ ptr ] = c1;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sqrt( closestDistanceSquared );
|
||||
|
||||
}
|
||||
`;
|
||||
213
src/components/DMT/libs/bvh/gpu/glsl/bvh_ray_functions.glsl.js
Normal file
@@ -0,0 +1,213 @@
|
||||
export const bvh_ray_functions = /* glsl */`
|
||||
|
||||
#ifndef TRI_INTERSECT_EPSILON
|
||||
#define TRI_INTERSECT_EPSILON 1e-5
|
||||
#endif
|
||||
|
||||
// Raycasting
|
||||
bool intersectsBounds( vec3 rayOrigin, vec3 rayDirection, vec3 boundsMin, vec3 boundsMax, out float dist ) {
|
||||
|
||||
// https://www.reddit.com/r/opengl/comments/8ntzz5/fast_glsl_ray_box_intersection/
|
||||
// https://tavianator.com/2011/ray_box.html
|
||||
vec3 invDir = 1.0 / rayDirection;
|
||||
|
||||
// find intersection distances for each plane
|
||||
vec3 tMinPlane = invDir * ( boundsMin - rayOrigin );
|
||||
vec3 tMaxPlane = invDir * ( boundsMax - rayOrigin );
|
||||
|
||||
// get the min and max distances from each intersection
|
||||
vec3 tMinHit = min( tMaxPlane, tMinPlane );
|
||||
vec3 tMaxHit = max( tMaxPlane, tMinPlane );
|
||||
|
||||
// get the furthest hit distance
|
||||
vec2 t = max( tMinHit.xx, tMinHit.yz );
|
||||
float t0 = max( t.x, t.y );
|
||||
|
||||
// get the minimum hit distance
|
||||
t = min( tMaxHit.xx, tMaxHit.yz );
|
||||
float t1 = min( t.x, t.y );
|
||||
|
||||
// set distance to 0.0 if the ray starts inside the box
|
||||
dist = max( t0, 0.0 );
|
||||
|
||||
return t1 >= dist;
|
||||
|
||||
}
|
||||
|
||||
bool intersectsTriangle(
|
||||
vec3 rayOrigin, vec3 rayDirection, vec3 a, vec3 b, vec3 c,
|
||||
out vec3 barycoord, out vec3 norm, out float dist, out float side
|
||||
) {
|
||||
|
||||
// https://stackoverflow.com/questions/42740765/intersection-between-line-and-triangle-in-3d
|
||||
vec3 edge1 = b - a;
|
||||
vec3 edge2 = c - a;
|
||||
norm = cross( edge1, edge2 );
|
||||
|
||||
float det = - dot( rayDirection, norm );
|
||||
float invdet = 1.0 / det;
|
||||
|
||||
vec3 AO = rayOrigin - a;
|
||||
vec3 DAO = cross( AO, rayDirection );
|
||||
|
||||
vec4 uvt;
|
||||
uvt.x = dot( edge2, DAO ) * invdet;
|
||||
uvt.y = - dot( edge1, DAO ) * invdet;
|
||||
uvt.z = dot( AO, norm ) * invdet;
|
||||
uvt.w = 1.0 - uvt.x - uvt.y;
|
||||
|
||||
// set the hit information
|
||||
barycoord = uvt.wxy; // arranged in A, B, C order
|
||||
dist = uvt.z;
|
||||
side = sign( det );
|
||||
norm = side * normalize( norm );
|
||||
|
||||
// add an epsilon to avoid misses between triangles
|
||||
uvt += vec4( TRI_INTERSECT_EPSILON );
|
||||
|
||||
return all( greaterThanEqual( uvt, vec4( 0.0 ) ) );
|
||||
|
||||
}
|
||||
|
||||
bool intersectTriangles(
|
||||
// geometry info and triangle range
|
||||
sampler2D positionAttr, usampler2D indexAttr, uint offset, uint count,
|
||||
|
||||
// ray
|
||||
vec3 rayOrigin, vec3 rayDirection,
|
||||
|
||||
// outputs
|
||||
inout float minDistance, inout uvec4 faceIndices, inout vec3 faceNormal, inout vec3 barycoord,
|
||||
inout float side, inout float dist
|
||||
) {
|
||||
|
||||
bool found = false;
|
||||
vec3 localBarycoord, localNormal;
|
||||
float localDist, localSide;
|
||||
for ( uint i = offset, l = offset + count; i < l; i ++ ) {
|
||||
|
||||
uvec3 indices = uTexelFetch1D( indexAttr, i ).xyz;
|
||||
vec3 a = texelFetch1D( positionAttr, indices.x ).rgb;
|
||||
vec3 b = texelFetch1D( positionAttr, indices.y ).rgb;
|
||||
vec3 c = texelFetch1D( positionAttr, indices.z ).rgb;
|
||||
|
||||
if (
|
||||
intersectsTriangle( rayOrigin, rayDirection, a, b, c, localBarycoord, localNormal, localDist, localSide )
|
||||
&& localDist < minDistance
|
||||
) {
|
||||
|
||||
found = true;
|
||||
minDistance = localDist;
|
||||
|
||||
faceIndices = uvec4( indices.xyz, i );
|
||||
faceNormal = localNormal;
|
||||
|
||||
side = localSide;
|
||||
barycoord = localBarycoord;
|
||||
dist = localDist;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return found;
|
||||
|
||||
}
|
||||
|
||||
bool intersectsBVHNodeBounds( vec3 rayOrigin, vec3 rayDirection, sampler2D bvhBounds, uint currNodeIndex, out float dist ) {
|
||||
|
||||
uint cni2 = currNodeIndex * 2u;
|
||||
vec3 boundsMin = texelFetch1D( bvhBounds, cni2 ).xyz;
|
||||
vec3 boundsMax = texelFetch1D( bvhBounds, cni2 + 1u ).xyz;
|
||||
return intersectsBounds( rayOrigin, rayDirection, boundsMin, boundsMax, dist );
|
||||
|
||||
}
|
||||
|
||||
// use a macro to hide the fact that we need to expand the struct into separate fields
|
||||
#define\
|
||||
bvhIntersectFirstHit(\
|
||||
bvh,\
|
||||
rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist\
|
||||
)\
|
||||
_bvhIntersectFirstHit(\
|
||||
bvh.position, bvh.index, bvh.bvhBounds, bvh.bvhContents,\
|
||||
rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist\
|
||||
)
|
||||
|
||||
bool _bvhIntersectFirstHit(
|
||||
// bvh info
|
||||
sampler2D bvh_position, usampler2D bvh_index, sampler2D bvh_bvhBounds, usampler2D bvh_bvhContents,
|
||||
|
||||
// ray
|
||||
vec3 rayOrigin, vec3 rayDirection,
|
||||
|
||||
// output variables split into separate variables due to output precision
|
||||
inout uvec4 faceIndices, inout vec3 faceNormal, inout vec3 barycoord,
|
||||
inout float side, inout float dist
|
||||
) {
|
||||
|
||||
// stack needs to be twice as long as the deepest tree we expect because
|
||||
// we push both the left and right child onto the stack every traversal
|
||||
int ptr = 0;
|
||||
uint stack[ BVH_STACK_DEPTH ];
|
||||
stack[ 0 ] = 0u;
|
||||
|
||||
float triangleDistance = INFINITY;
|
||||
bool found = false;
|
||||
while ( ptr > - 1 && ptr < BVH_STACK_DEPTH ) {
|
||||
|
||||
uint currNodeIndex = stack[ ptr ];
|
||||
ptr --;
|
||||
|
||||
// check if we intersect the current bounds
|
||||
float boundsHitDistance;
|
||||
if (
|
||||
! intersectsBVHNodeBounds( rayOrigin, rayDirection, bvh_bvhBounds, currNodeIndex, boundsHitDistance )
|
||||
|| boundsHitDistance > triangleDistance
|
||||
) {
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
uvec2 boundsInfo = uTexelFetch1D( bvh_bvhContents, currNodeIndex ).xy;
|
||||
bool isLeaf = bool( boundsInfo.x & 0xffff0000u );
|
||||
|
||||
if ( isLeaf ) {
|
||||
|
||||
uint count = boundsInfo.x & 0x0000ffffu;
|
||||
uint offset = boundsInfo.y;
|
||||
|
||||
found = intersectTriangles(
|
||||
bvh_position, bvh_index, offset, count,
|
||||
rayOrigin, rayDirection, triangleDistance,
|
||||
faceIndices, faceNormal, barycoord, side, dist
|
||||
) || found;
|
||||
|
||||
} else {
|
||||
|
||||
uint leftIndex = currNodeIndex + 1u;
|
||||
uint splitAxis = boundsInfo.x & 0x0000ffffu;
|
||||
uint rightIndex = boundsInfo.y;
|
||||
|
||||
bool leftToRight = rayDirection[ splitAxis ] >= 0.0;
|
||||
uint c1 = leftToRight ? leftIndex : rightIndex;
|
||||
uint c2 = leftToRight ? rightIndex : leftIndex;
|
||||
|
||||
// set c2 in the stack so we traverse it later. We need to keep track of a pointer in
|
||||
// the stack while we traverse. The second pointer added is the one that will be
|
||||
// traversed first
|
||||
ptr ++;
|
||||
stack[ ptr ] = c2;
|
||||
|
||||
ptr ++;
|
||||
stack[ ptr ] = c1;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return found;
|
||||
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,14 @@
|
||||
// Note that a struct cannot be used for the hit record including faceIndices, faceNormal, barycoord,
|
||||
// side, and dist because on some mobile GPUS (such as Adreno) numbers are afforded less precision specifically
|
||||
// when in a struct leading to inaccurate hit results. See KhronosGroup/WebGL#3351 for more details.
|
||||
export const bvh_struct_definitions = /* glsl */`
|
||||
struct BVH {
|
||||
|
||||
usampler2D index;
|
||||
sampler2D position;
|
||||
|
||||
sampler2D bvhBounds;
|
||||
usampler2D bvhContents;
|
||||
|
||||
};
|
||||
`;
|
||||
@@ -0,0 +1,83 @@
|
||||
export const common_functions = /* glsl */`
|
||||
|
||||
// A stack of uint32 indices can can store the indices for
|
||||
// a perfectly balanced tree with a depth up to 31. Lower stack
|
||||
// depth gets higher performance.
|
||||
//
|
||||
// However not all trees are balanced. Best value to set this to
|
||||
// is the trees max depth.
|
||||
#ifndef BVH_STACK_DEPTH
|
||||
#define BVH_STACK_DEPTH 60
|
||||
#endif
|
||||
|
||||
#ifndef INFINITY
|
||||
#define INFINITY 1e20
|
||||
#endif
|
||||
|
||||
// Utilities
|
||||
uvec4 uTexelFetch1D( usampler2D tex, uint index ) {
|
||||
|
||||
uint width = uint( textureSize( tex, 0 ).x );
|
||||
uvec2 uv;
|
||||
uv.x = index % width;
|
||||
uv.y = index / width;
|
||||
|
||||
return texelFetch( tex, ivec2( uv ), 0 );
|
||||
|
||||
}
|
||||
|
||||
ivec4 iTexelFetch1D( isampler2D tex, uint index ) {
|
||||
|
||||
uint width = uint( textureSize( tex, 0 ).x );
|
||||
uvec2 uv;
|
||||
uv.x = index % width;
|
||||
uv.y = index / width;
|
||||
|
||||
return texelFetch( tex, ivec2( uv ), 0 );
|
||||
|
||||
}
|
||||
|
||||
vec4 texelFetch1D( sampler2D tex, uint index ) {
|
||||
|
||||
uint width = uint( textureSize( tex, 0 ).x );
|
||||
uvec2 uv;
|
||||
uv.x = index % width;
|
||||
uv.y = index / width;
|
||||
|
||||
return texelFetch( tex, ivec2( uv ), 0 );
|
||||
|
||||
}
|
||||
|
||||
vec4 textureSampleBarycoord( sampler2D tex, vec3 barycoord, uvec3 faceIndices ) {
|
||||
|
||||
return
|
||||
barycoord.x * texelFetch1D( tex, faceIndices.x ) +
|
||||
barycoord.y * texelFetch1D( tex, faceIndices.y ) +
|
||||
barycoord.z * texelFetch1D( tex, faceIndices.z );
|
||||
|
||||
}
|
||||
|
||||
void ndcToCameraRay(
|
||||
vec2 coord, mat4 cameraWorld, mat4 invProjectionMatrix,
|
||||
out vec3 rayOrigin, out vec3 rayDirection
|
||||
) {
|
||||
|
||||
// get camera look direction and near plane for camera clipping
|
||||
vec4 lookDirection = cameraWorld * vec4( 0.0, 0.0, - 1.0, 0.0 );
|
||||
vec4 nearVector = invProjectionMatrix * vec4( 0.0, 0.0, - 1.0, 1.0 );
|
||||
float near = abs( nearVector.z / nearVector.w );
|
||||
|
||||
// get the camera direction and position from camera matrices
|
||||
vec4 origin = cameraWorld * vec4( 0.0, 0.0, 0.0, 1.0 );
|
||||
vec4 direction = invProjectionMatrix * vec4( coord, 0.5, 1.0 );
|
||||
direction /= direction.w;
|
||||
direction = cameraWorld * direction - origin;
|
||||
|
||||
// slide the origin along the ray until it sits at the near clip plane position
|
||||
origin.xyz += direction.xyz * near / dot( direction, lookDirection );
|
||||
|
||||
rayOrigin = origin.xyz;
|
||||
rayDirection = direction.xyz;
|
||||
|
||||
}
|
||||
`;
|
||||
22
src/components/DMT/libs/bvh/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
export { MeshBVH } from './core/MeshBVH.js'
|
||||
export { MeshBVHHelper } from './objects/MeshBVHHelper.js'
|
||||
export { CENTER, AVERAGE, SAH, NOT_INTERSECTED, INTERSECTED, CONTAINED } from './core/Constants.js'
|
||||
export { getBVHExtremes, estimateMemoryInBytes, getJSONStructure, validateBounds } from './debug/Debug.js'
|
||||
export { acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from './utils/ExtensionUtilities.js'
|
||||
export { getTriangleHitPointInfo } from './utils/TriangleUtilities.js'
|
||||
export * from './math/ExtendedTriangle.js'
|
||||
export * from './math/OrientedBox.js'
|
||||
export * from './gpu/MeshBVHUniformStruct.js'
|
||||
export * from './gpu/VertexAttributeTexture.js'
|
||||
export * from './utils/StaticGeometryGenerator.js'
|
||||
export * as BVHShaderGLSL from './gpu/BVHShaderGLSL.js'
|
||||
|
||||
// backwards compatibility
|
||||
import * as BVHShaderGLSL from './gpu/BVHShaderGLSL.js'
|
||||
export const shaderStructs = BVHShaderGLSL.bvh_struct_definitions
|
||||
export const shaderDistanceFunction = BVHShaderGLSL.bvh_distance_functions
|
||||
export const shaderIntersectFunction = `
|
||||
${BVHShaderGLSL.common_functions}
|
||||
${BVHShaderGLSL.bvh_ray_functions}
|
||||
`
|
||||
// version: 0.7.4
|
||||
514
src/components/DMT/libs/bvh/math/ExtendedTriangle.js
Normal file
@@ -0,0 +1,514 @@
|
||||
import { Triangle, Vector3, Line3, Sphere, Plane } from 'three'
|
||||
import { SeparatingAxisBounds } from './SeparatingAxisBounds.js';
|
||||
import { closestPointsSegmentToSegment, sphereIntersectTriangle } from './MathUtilities.js';
|
||||
|
||||
const ZERO_EPSILON = 1e-15;
|
||||
function isNearZero( value ) {
|
||||
|
||||
return Math.abs( value ) < ZERO_EPSILON;
|
||||
|
||||
}
|
||||
|
||||
export class ExtendedTriangle extends Triangle {
|
||||
|
||||
constructor( ...args ) {
|
||||
|
||||
super( ...args );
|
||||
|
||||
this.isExtendedTriangle = true;
|
||||
this.satAxes = new Array( 4 ).fill().map( () => new Vector3() );
|
||||
this.satBounds = new Array( 4 ).fill().map( () => new SeparatingAxisBounds() );
|
||||
this.points = [ this.a, this.b, this.c ];
|
||||
this.sphere = new Sphere();
|
||||
this.plane = new Plane();
|
||||
this.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
intersectsSphere( sphere ) {
|
||||
|
||||
return sphereIntersectTriangle( sphere, this );
|
||||
|
||||
}
|
||||
|
||||
update() {
|
||||
|
||||
const a = this.a;
|
||||
const b = this.b;
|
||||
const c = this.c;
|
||||
const points = this.points;
|
||||
|
||||
const satAxes = this.satAxes;
|
||||
const satBounds = this.satBounds;
|
||||
|
||||
const axis0 = satAxes[ 0 ];
|
||||
const sab0 = satBounds[ 0 ];
|
||||
this.getNormal( axis0 );
|
||||
sab0.setFromPoints( axis0, points );
|
||||
|
||||
const axis1 = satAxes[ 1 ];
|
||||
const sab1 = satBounds[ 1 ];
|
||||
axis1.subVectors( a, b );
|
||||
sab1.setFromPoints( axis1, points );
|
||||
|
||||
const axis2 = satAxes[ 2 ];
|
||||
const sab2 = satBounds[ 2 ];
|
||||
axis2.subVectors( b, c );
|
||||
sab2.setFromPoints( axis2, points );
|
||||
|
||||
const axis3 = satAxes[ 3 ];
|
||||
const sab3 = satBounds[ 3 ];
|
||||
axis3.subVectors( c, a );
|
||||
sab3.setFromPoints( axis3, points );
|
||||
|
||||
this.sphere.setFromPoints( this.points );
|
||||
this.plane.setFromNormalAndCoplanarPoint( axis0, a );
|
||||
this.needsUpdate = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ExtendedTriangle.prototype.closestPointToSegment = ( function () {
|
||||
|
||||
const point1 = new Vector3();
|
||||
const point2 = new Vector3();
|
||||
const edge = new Line3();
|
||||
|
||||
return function distanceToSegment( segment, target1 = null, target2 = null ) {
|
||||
|
||||
const { start, end } = segment;
|
||||
const points = this.points;
|
||||
let distSq;
|
||||
let closestDistanceSq = Infinity;
|
||||
|
||||
// check the triangle edges
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
const nexti = ( i + 1 ) % 3;
|
||||
edge.start.copy( points[ i ] );
|
||||
edge.end.copy( points[ nexti ] );
|
||||
|
||||
closestPointsSegmentToSegment( edge, segment, point1, point2 );
|
||||
|
||||
distSq = point1.distanceToSquared( point2 );
|
||||
if ( distSq < closestDistanceSq ) {
|
||||
|
||||
closestDistanceSq = distSq;
|
||||
if ( target1 ) target1.copy( point1 );
|
||||
if ( target2 ) target2.copy( point2 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// check end points
|
||||
this.closestPointToPoint( start, point1 );
|
||||
distSq = start.distanceToSquared( point1 );
|
||||
if ( distSq < closestDistanceSq ) {
|
||||
|
||||
closestDistanceSq = distSq;
|
||||
if ( target1 ) target1.copy( point1 );
|
||||
if ( target2 ) target2.copy( start );
|
||||
|
||||
}
|
||||
|
||||
this.closestPointToPoint( end, point1 );
|
||||
distSq = end.distanceToSquared( point1 );
|
||||
if ( distSq < closestDistanceSq ) {
|
||||
|
||||
closestDistanceSq = distSq;
|
||||
if ( target1 ) target1.copy( point1 );
|
||||
if ( target2 ) target2.copy( end );
|
||||
|
||||
}
|
||||
|
||||
return Math.sqrt( closestDistanceSq );
|
||||
|
||||
};
|
||||
|
||||
} )();
|
||||
|
||||
ExtendedTriangle.prototype.intersectsTriangle = ( function () {
|
||||
|
||||
const saTri2 = new ExtendedTriangle();
|
||||
const arr1 = new Array( 3 );
|
||||
const arr2 = new Array( 3 );
|
||||
const cachedSatBounds = new SeparatingAxisBounds();
|
||||
const cachedSatBounds2 = new SeparatingAxisBounds();
|
||||
const cachedAxis = new Vector3();
|
||||
const dir = new Vector3();
|
||||
const dir1 = new Vector3();
|
||||
const dir2 = new Vector3();
|
||||
const tempDir = new Vector3();
|
||||
const edge = new Line3();
|
||||
const edge1 = new Line3();
|
||||
const edge2 = new Line3();
|
||||
const tempPoint = new Vector3();
|
||||
|
||||
function triIntersectPlane( tri, plane, targetEdge ) {
|
||||
|
||||
// find the edge that intersects the other triangle plane
|
||||
const points = tri.points;
|
||||
let count = 0;
|
||||
let startPointIntersection = - 1;
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
const { start, end } = edge;
|
||||
start.copy( points[ i ] );
|
||||
end.copy( points[ ( i + 1 ) % 3 ] );
|
||||
edge.delta( dir );
|
||||
|
||||
const startIntersects = isNearZero( plane.distanceToPoint( start ) );
|
||||
if ( isNearZero( plane.normal.dot( dir ) ) && startIntersects ) {
|
||||
|
||||
// if the edge lies on the plane then take the line
|
||||
targetEdge.copy( edge );
|
||||
count = 2;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// check if the start point is near the plane because "intersectLine" is not robust to that case
|
||||
const doesIntersect = plane.intersectLine( edge, tempPoint );
|
||||
if ( ! doesIntersect && startIntersects ) {
|
||||
|
||||
tempPoint.copy( start );
|
||||
|
||||
}
|
||||
|
||||
// ignore the end point
|
||||
if ( ( doesIntersect || startIntersects ) && ! isNearZero( tempPoint.distanceTo( end ) ) ) {
|
||||
|
||||
if ( count <= 1 ) {
|
||||
|
||||
// assign to the start or end point and save which index was snapped to
|
||||
// the start point if necessary
|
||||
const point = count === 1 ? targetEdge.start : targetEdge.end;
|
||||
point.copy( tempPoint );
|
||||
if ( startIntersects ) {
|
||||
|
||||
startPointIntersection = count;
|
||||
|
||||
}
|
||||
|
||||
} else if ( count >= 2 ) {
|
||||
|
||||
// if we're here that means that there must have been one point that had
|
||||
// snapped to the start point so replace it here
|
||||
const point = startPointIntersection === 1 ? targetEdge.start : targetEdge.end;
|
||||
point.copy( tempPoint );
|
||||
count = 2;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
count ++;
|
||||
if ( count === 2 && startPointIntersection === - 1 ) {
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return count;
|
||||
|
||||
}
|
||||
|
||||
// TODO: If the triangles are coplanar and intersecting the target is nonsensical. It should at least
|
||||
// be a line contained by both triangles if not a different special case somehow represented in the return result.
|
||||
return function intersectsTriangle( other, target = null, suppressLog = false ) {
|
||||
|
||||
if ( this.needsUpdate ) {
|
||||
|
||||
this.update();
|
||||
|
||||
}
|
||||
|
||||
if ( ! other.isExtendedTriangle ) {
|
||||
|
||||
saTri2.copy( other );
|
||||
saTri2.update();
|
||||
other = saTri2;
|
||||
|
||||
} else if ( other.needsUpdate ) {
|
||||
|
||||
other.update();
|
||||
|
||||
}
|
||||
|
||||
const plane1 = this.plane;
|
||||
const plane2 = other.plane;
|
||||
|
||||
if ( Math.abs( plane1.normal.dot( plane2.normal ) ) > 1.0 - 1e-10 ) {
|
||||
|
||||
// perform separating axis intersection test only for coplanar triangles
|
||||
const satBounds1 = this.satBounds;
|
||||
const satAxes1 = this.satAxes;
|
||||
arr2[ 0 ] = other.a;
|
||||
arr2[ 1 ] = other.b;
|
||||
arr2[ 2 ] = other.c;
|
||||
for ( let i = 0; i < 4; i ++ ) {
|
||||
|
||||
const sb = satBounds1[ i ];
|
||||
const sa = satAxes1[ i ];
|
||||
cachedSatBounds.setFromPoints( sa, arr2 );
|
||||
if ( sb.isSeparated( cachedSatBounds ) ) return false;
|
||||
|
||||
}
|
||||
|
||||
const satBounds2 = other.satBounds;
|
||||
const satAxes2 = other.satAxes;
|
||||
arr1[ 0 ] = this.a;
|
||||
arr1[ 1 ] = this.b;
|
||||
arr1[ 2 ] = this.c;
|
||||
for ( let i = 0; i < 4; i ++ ) {
|
||||
|
||||
const sb = satBounds2[ i ];
|
||||
const sa = satAxes2[ i ];
|
||||
cachedSatBounds.setFromPoints( sa, arr1 );
|
||||
if ( sb.isSeparated( cachedSatBounds ) ) return false;
|
||||
|
||||
}
|
||||
|
||||
// check crossed axes
|
||||
for ( let i = 0; i < 4; i ++ ) {
|
||||
|
||||
const sa1 = satAxes1[ i ];
|
||||
for ( let i2 = 0; i2 < 4; i2 ++ ) {
|
||||
|
||||
const sa2 = satAxes2[ i2 ];
|
||||
cachedAxis.crossVectors( sa1, sa2 );
|
||||
cachedSatBounds.setFromPoints( cachedAxis, arr1 );
|
||||
cachedSatBounds2.setFromPoints( cachedAxis, arr2 );
|
||||
if ( cachedSatBounds.isSeparated( cachedSatBounds2 ) ) return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( target ) {
|
||||
|
||||
// TODO find two points that intersect on the edges and make that the result
|
||||
if ( ! suppressLog ) {
|
||||
|
||||
console.warn( 'ExtendedTriangle.intersectsTriangle: Triangles are coplanar which does not support an output edge. Setting edge to 0, 0, 0.' );
|
||||
|
||||
}
|
||||
|
||||
target.start.set( 0, 0, 0 );
|
||||
target.end.set( 0, 0, 0 );
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
// find the edge that intersects the other triangle plane
|
||||
const count1 = triIntersectPlane( this, plane2, edge1 );
|
||||
if ( count1 === 1 && other.containsPoint( edge1.end ) ) {
|
||||
|
||||
if ( target ) {
|
||||
|
||||
target.start.copy( edge1.end );
|
||||
target.end.copy( edge1.end );
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} else if ( count1 !== 2 ) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// find the other triangles edge that intersects this plane
|
||||
const count2 = triIntersectPlane( other, plane1, edge2 );
|
||||
if ( count2 === 1 && this.containsPoint( edge2.end ) ) {
|
||||
|
||||
if ( target ) {
|
||||
|
||||
target.start.copy( edge2.end );
|
||||
target.end.copy( edge2.end );
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} else if ( count2 !== 2 ) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// find swap the second edge so both lines are running the same direction
|
||||
edge1.delta( dir1 );
|
||||
edge2.delta( dir2 );
|
||||
|
||||
if ( dir1.dot( dir2 ) < 0 ) {
|
||||
|
||||
let tmp = edge2.start;
|
||||
edge2.start = edge2.end;
|
||||
edge2.end = tmp;
|
||||
|
||||
}
|
||||
|
||||
// check if the edges are overlapping
|
||||
const s1 = edge1.start.dot( dir1 );
|
||||
const e1 = edge1.end.dot( dir1 );
|
||||
const s2 = edge2.start.dot( dir1 );
|
||||
const e2 = edge2.end.dot( dir1 );
|
||||
const separated1 = e1 < s2;
|
||||
const separated2 = s1 < e2;
|
||||
|
||||
if ( s1 !== e2 && s2 !== e1 && separated1 === separated2 ) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// assign the target output
|
||||
if ( target ) {
|
||||
|
||||
tempDir.subVectors( edge1.start, edge2.start );
|
||||
if ( tempDir.dot( dir1 ) > 0 ) {
|
||||
|
||||
target.start.copy( edge1.start );
|
||||
|
||||
} else {
|
||||
|
||||
target.start.copy( edge2.start );
|
||||
|
||||
}
|
||||
|
||||
tempDir.subVectors( edge1.end, edge2.end );
|
||||
if ( tempDir.dot( dir1 ) < 0 ) {
|
||||
|
||||
target.end.copy( edge1.end );
|
||||
|
||||
} else {
|
||||
|
||||
target.end.copy( edge2.end );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} )();
|
||||
|
||||
|
||||
ExtendedTriangle.prototype.distanceToPoint = ( function () {
|
||||
|
||||
const target = new Vector3();
|
||||
return function distanceToPoint( point ) {
|
||||
|
||||
this.closestPointToPoint( point, target );
|
||||
return point.distanceTo( target );
|
||||
|
||||
};
|
||||
|
||||
} )();
|
||||
|
||||
|
||||
ExtendedTriangle.prototype.distanceToTriangle = ( function () {
|
||||
|
||||
const point = new Vector3();
|
||||
const point2 = new Vector3();
|
||||
const cornerFields = [ 'a', 'b', 'c' ];
|
||||
const line1 = new Line3();
|
||||
const line2 = new Line3();
|
||||
|
||||
return function distanceToTriangle( other, target1 = null, target2 = null ) {
|
||||
|
||||
const lineTarget = target1 || target2 ? line1 : null;
|
||||
if ( this.intersectsTriangle( other, lineTarget ) ) {
|
||||
|
||||
if ( target1 || target2 ) {
|
||||
|
||||
if ( target1 ) lineTarget.getCenter( target1 );
|
||||
if ( target2 ) lineTarget.getCenter( target2 );
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
let closestDistanceSq = Infinity;
|
||||
|
||||
// check all point distances
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
let dist;
|
||||
const field = cornerFields[ i ];
|
||||
const otherVec = other[ field ];
|
||||
this.closestPointToPoint( otherVec, point );
|
||||
|
||||
dist = otherVec.distanceToSquared( point );
|
||||
|
||||
if ( dist < closestDistanceSq ) {
|
||||
|
||||
closestDistanceSq = dist;
|
||||
if ( target1 ) target1.copy( point );
|
||||
if ( target2 ) target2.copy( otherVec );
|
||||
|
||||
}
|
||||
|
||||
|
||||
const thisVec = this[ field ];
|
||||
other.closestPointToPoint( thisVec, point );
|
||||
|
||||
dist = thisVec.distanceToSquared( point );
|
||||
|
||||
if ( dist < closestDistanceSq ) {
|
||||
|
||||
closestDistanceSq = dist;
|
||||
if ( target1 ) target1.copy( thisVec );
|
||||
if ( target2 ) target2.copy( point );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
const f11 = cornerFields[ i ];
|
||||
const f12 = cornerFields[ ( i + 1 ) % 3 ];
|
||||
line1.set( this[ f11 ], this[ f12 ] );
|
||||
for ( let i2 = 0; i2 < 3; i2 ++ ) {
|
||||
|
||||
const f21 = cornerFields[ i2 ];
|
||||
const f22 = cornerFields[ ( i2 + 1 ) % 3 ];
|
||||
line2.set( other[ f21 ], other[ f22 ] );
|
||||
|
||||
closestPointsSegmentToSegment( line1, line2, point, point2 );
|
||||
|
||||
const dist = point.distanceToSquared( point2 );
|
||||
if ( dist < closestDistanceSq ) {
|
||||
|
||||
closestDistanceSq = dist;
|
||||
if ( target1 ) target1.copy( point );
|
||||
if ( target2 ) target2.copy( point2 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return Math.sqrt( closestDistanceSq );
|
||||
|
||||
};
|
||||
|
||||
} )();
|
||||
203
src/components/DMT/libs/bvh/math/MathUtilities.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import { Vector3, Vector2, Plane, Line3 } from 'three'
|
||||
|
||||
export const closestPointLineToLine = ( function () {
|
||||
|
||||
// https://github.com/juj/MathGeoLib/blob/master/src/Geometry/Line.cpp#L56
|
||||
const dir1 = new Vector3();
|
||||
const dir2 = new Vector3();
|
||||
const v02 = new Vector3();
|
||||
return function closestPointLineToLine( l1, l2, result ) {
|
||||
|
||||
const v0 = l1.start;
|
||||
const v10 = dir1;
|
||||
const v2 = l2.start;
|
||||
const v32 = dir2;
|
||||
|
||||
v02.subVectors( v0, v2 );
|
||||
dir1.subVectors( l1.end, l1.start );
|
||||
dir2.subVectors( l2.end, l2.start );
|
||||
|
||||
// float d0232 = v02.Dot(v32);
|
||||
const d0232 = v02.dot( v32 );
|
||||
|
||||
// float d3210 = v32.Dot(v10);
|
||||
const d3210 = v32.dot( v10 );
|
||||
|
||||
// float d3232 = v32.Dot(v32);
|
||||
const d3232 = v32.dot( v32 );
|
||||
|
||||
// float d0210 = v02.Dot(v10);
|
||||
const d0210 = v02.dot( v10 );
|
||||
|
||||
// float d1010 = v10.Dot(v10);
|
||||
const d1010 = v10.dot( v10 );
|
||||
|
||||
// float denom = d1010*d3232 - d3210*d3210;
|
||||
const denom = d1010 * d3232 - d3210 * d3210;
|
||||
|
||||
let d, d2;
|
||||
if ( denom !== 0 ) {
|
||||
|
||||
d = ( d0232 * d3210 - d0210 * d3232 ) / denom;
|
||||
|
||||
} else {
|
||||
|
||||
d = 0;
|
||||
|
||||
}
|
||||
|
||||
d2 = ( d0232 + d * d3210 ) / d3232;
|
||||
|
||||
result.x = d;
|
||||
result.y = d2;
|
||||
|
||||
};
|
||||
|
||||
} )();
|
||||
|
||||
export const closestPointsSegmentToSegment = ( function () {
|
||||
|
||||
// https://github.com/juj/MathGeoLib/blob/master/src/Geometry/LineSegment.cpp#L187
|
||||
const paramResult = new Vector2();
|
||||
const temp1 = new Vector3();
|
||||
const temp2 = new Vector3();
|
||||
return function closestPointsSegmentToSegment( l1, l2, target1, target2 ) {
|
||||
|
||||
closestPointLineToLine( l1, l2, paramResult );
|
||||
|
||||
let d = paramResult.x;
|
||||
let d2 = paramResult.y;
|
||||
if ( d >= 0 && d <= 1 && d2 >= 0 && d2 <= 1 ) {
|
||||
|
||||
l1.at( d, target1 );
|
||||
l2.at( d2, target2 );
|
||||
|
||||
return;
|
||||
|
||||
} else if ( d >= 0 && d <= 1 ) {
|
||||
|
||||
// Only d2 is out of bounds.
|
||||
if ( d2 < 0 ) {
|
||||
|
||||
l2.at( 0, target2 );
|
||||
|
||||
} else {
|
||||
|
||||
l2.at( 1, target2 );
|
||||
|
||||
}
|
||||
|
||||
l1.closestPointToPoint( target2, true, target1 );
|
||||
return;
|
||||
|
||||
} else if ( d2 >= 0 && d2 <= 1 ) {
|
||||
|
||||
// Only d is out of bounds.
|
||||
if ( d < 0 ) {
|
||||
|
||||
l1.at( 0, target1 );
|
||||
|
||||
} else {
|
||||
|
||||
l1.at( 1, target1 );
|
||||
|
||||
}
|
||||
|
||||
l2.closestPointToPoint( target1, true, target2 );
|
||||
return;
|
||||
|
||||
} else {
|
||||
|
||||
// Both u and u2 are out of bounds.
|
||||
let p;
|
||||
if ( d < 0 ) {
|
||||
|
||||
p = l1.start;
|
||||
|
||||
} else {
|
||||
|
||||
p = l1.end;
|
||||
|
||||
}
|
||||
|
||||
let p2;
|
||||
if ( d2 < 0 ) {
|
||||
|
||||
p2 = l2.start;
|
||||
|
||||
} else {
|
||||
|
||||
p2 = l2.end;
|
||||
|
||||
}
|
||||
|
||||
const closestPoint = temp1;
|
||||
const closestPoint2 = temp2;
|
||||
l1.closestPointToPoint( p2, true, temp1 );
|
||||
l2.closestPointToPoint( p, true, temp2 );
|
||||
|
||||
if ( closestPoint.distanceToSquared( p2 ) <= closestPoint2.distanceToSquared( p ) ) {
|
||||
|
||||
target1.copy( closestPoint );
|
||||
target2.copy( p2 );
|
||||
return;
|
||||
|
||||
} else {
|
||||
|
||||
target1.copy( p );
|
||||
target2.copy( closestPoint2 );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} )();
|
||||
|
||||
|
||||
export const sphereIntersectTriangle = ( function () {
|
||||
|
||||
// https://stackoverflow.com/questions/34043955/detect-collision-between-sphere-and-triangle-in-three-js
|
||||
const closestPointTemp = new Vector3();
|
||||
const projectedPointTemp = new Vector3();
|
||||
const planeTemp = new Plane();
|
||||
const lineTemp = new Line3();
|
||||
return function sphereIntersectTriangle( sphere, triangle ) {
|
||||
|
||||
const { radius, center } = sphere;
|
||||
const { a, b, c } = triangle;
|
||||
|
||||
// phase 1
|
||||
lineTemp.start = a;
|
||||
lineTemp.end = b;
|
||||
const closestPoint1 = lineTemp.closestPointToPoint( center, true, closestPointTemp );
|
||||
if ( closestPoint1.distanceTo( center ) <= radius ) return true;
|
||||
|
||||
lineTemp.start = a;
|
||||
lineTemp.end = c;
|
||||
const closestPoint2 = lineTemp.closestPointToPoint( center, true, closestPointTemp );
|
||||
if ( closestPoint2.distanceTo( center ) <= radius ) return true;
|
||||
|
||||
lineTemp.start = b;
|
||||
lineTemp.end = c;
|
||||
const closestPoint3 = lineTemp.closestPointToPoint( center, true, closestPointTemp );
|
||||
if ( closestPoint3.distanceTo( center ) <= radius ) return true;
|
||||
|
||||
// phase 2
|
||||
const plane = triangle.getPlane( planeTemp );
|
||||
const dp = Math.abs( plane.distanceToPoint( center ) );
|
||||
if ( dp <= radius ) {
|
||||
|
||||
const pp = plane.projectPoint( center, projectedPointTemp );
|
||||
const cp = triangle.containsPoint( pp );
|
||||
if ( cp ) return true;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
} )();
|
||||
421
src/components/DMT/libs/bvh/math/OrientedBox.js
Normal file
@@ -0,0 +1,421 @@
|
||||
import { Vector3, Matrix4, Line3 } from 'three'
|
||||
import { SeparatingAxisBounds } from './SeparatingAxisBounds.js';
|
||||
import { ExtendedTriangle } from './ExtendedTriangle.js';
|
||||
import { closestPointsSegmentToSegment } from './MathUtilities.js';
|
||||
|
||||
export class OrientedBox {
|
||||
|
||||
constructor( min, max, matrix ) {
|
||||
|
||||
this.isOrientedBox = true;
|
||||
this.min = new Vector3();
|
||||
this.max = new Vector3();
|
||||
this.matrix = new Matrix4();
|
||||
this.invMatrix = new Matrix4();
|
||||
this.points = new Array( 8 ).fill().map( () => new Vector3() );
|
||||
this.satAxes = new Array( 3 ).fill().map( () => new Vector3() );
|
||||
this.satBounds = new Array( 3 ).fill().map( () => new SeparatingAxisBounds() );
|
||||
this.alignedSatBounds = new Array( 3 ).fill().map( () => new SeparatingAxisBounds() );
|
||||
this.needsUpdate = false;
|
||||
|
||||
if ( min ) this.min.copy( min );
|
||||
if ( max ) this.max.copy( max );
|
||||
if ( matrix ) this.matrix.copy( matrix );
|
||||
|
||||
}
|
||||
|
||||
set( min, max, matrix ) {
|
||||
|
||||
this.min.copy( min );
|
||||
this.max.copy( max );
|
||||
this.matrix.copy( matrix );
|
||||
this.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
copy( other ) {
|
||||
|
||||
this.min.copy( other.min );
|
||||
this.max.copy( other.max );
|
||||
this.matrix.copy( other.matrix );
|
||||
this.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
OrientedBox.prototype.update = ( function () {
|
||||
|
||||
return function update() {
|
||||
|
||||
const matrix = this.matrix;
|
||||
const min = this.min;
|
||||
const max = this.max;
|
||||
|
||||
const points = this.points;
|
||||
for ( let x = 0; x <= 1; x ++ ) {
|
||||
|
||||
for ( let y = 0; y <= 1; y ++ ) {
|
||||
|
||||
for ( let z = 0; z <= 1; z ++ ) {
|
||||
|
||||
const i = ( ( 1 << 0 ) * x ) | ( ( 1 << 1 ) * y ) | ( ( 1 << 2 ) * z );
|
||||
const v = points[ i ];
|
||||
v.x = x ? max.x : min.x;
|
||||
v.y = y ? max.y : min.y;
|
||||
v.z = z ? max.z : min.z;
|
||||
|
||||
v.applyMatrix4( matrix );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const satBounds = this.satBounds;
|
||||
const satAxes = this.satAxes;
|
||||
const minVec = points[ 0 ];
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
const axis = satAxes[ i ];
|
||||
const sb = satBounds[ i ];
|
||||
const index = 1 << i;
|
||||
const pi = points[ index ];
|
||||
|
||||
axis.subVectors( minVec, pi );
|
||||
sb.setFromPoints( axis, points );
|
||||
|
||||
}
|
||||
|
||||
const alignedSatBounds = this.alignedSatBounds;
|
||||
alignedSatBounds[ 0 ].setFromPointsField( points, 'x' );
|
||||
alignedSatBounds[ 1 ].setFromPointsField( points, 'y' );
|
||||
alignedSatBounds[ 2 ].setFromPointsField( points, 'z' );
|
||||
|
||||
this.invMatrix.copy( this.matrix ).invert();
|
||||
this.needsUpdate = false;
|
||||
|
||||
};
|
||||
|
||||
} )();
|
||||
|
||||
OrientedBox.prototype.intersectsBox = ( function () {
|
||||
|
||||
const aabbBounds = new SeparatingAxisBounds();
|
||||
return function intersectsBox( box ) {
|
||||
|
||||
// TODO: should this be doing SAT against the AABB?
|
||||
if ( this.needsUpdate ) {
|
||||
|
||||
this.update();
|
||||
|
||||
}
|
||||
|
||||
const min = box.min;
|
||||
const max = box.max;
|
||||
const satBounds = this.satBounds;
|
||||
const satAxes = this.satAxes;
|
||||
const alignedSatBounds = this.alignedSatBounds;
|
||||
|
||||
aabbBounds.min = min.x;
|
||||
aabbBounds.max = max.x;
|
||||
if ( alignedSatBounds[ 0 ].isSeparated( aabbBounds ) ) return false;
|
||||
|
||||
aabbBounds.min = min.y;
|
||||
aabbBounds.max = max.y;
|
||||
if ( alignedSatBounds[ 1 ].isSeparated( aabbBounds ) ) return false;
|
||||
|
||||
aabbBounds.min = min.z;
|
||||
aabbBounds.max = max.z;
|
||||
if ( alignedSatBounds[ 2 ].isSeparated( aabbBounds ) ) return false;
|
||||
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
const axis = satAxes[ i ];
|
||||
const sb = satBounds[ i ];
|
||||
aabbBounds.setFromBox( axis, box );
|
||||
if ( sb.isSeparated( aabbBounds ) ) return false;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
};
|
||||
|
||||
} )();
|
||||
|
||||
OrientedBox.prototype.intersectsTriangle = ( function () {
|
||||
|
||||
const saTri = new ExtendedTriangle();
|
||||
const pointsArr = new Array( 3 );
|
||||
const cachedSatBounds = new SeparatingAxisBounds();
|
||||
const cachedSatBounds2 = new SeparatingAxisBounds();
|
||||
const cachedAxis = new Vector3();
|
||||
return function intersectsTriangle( triangle ) {
|
||||
|
||||
if ( this.needsUpdate ) {
|
||||
|
||||
this.update();
|
||||
|
||||
}
|
||||
|
||||
if ( ! triangle.isExtendedTriangle ) {
|
||||
|
||||
saTri.copy( triangle );
|
||||
saTri.update();
|
||||
triangle = saTri;
|
||||
|
||||
} else if ( triangle.needsUpdate ) {
|
||||
|
||||
triangle.update();
|
||||
|
||||
}
|
||||
|
||||
const satBounds = this.satBounds;
|
||||
const satAxes = this.satAxes;
|
||||
|
||||
pointsArr[ 0 ] = triangle.a;
|
||||
pointsArr[ 1 ] = triangle.b;
|
||||
pointsArr[ 2 ] = triangle.c;
|
||||
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
const sb = satBounds[ i ];
|
||||
const sa = satAxes[ i ];
|
||||
cachedSatBounds.setFromPoints( sa, pointsArr );
|
||||
if ( sb.isSeparated( cachedSatBounds ) ) return false;
|
||||
|
||||
}
|
||||
|
||||
const triSatBounds = triangle.satBounds;
|
||||
const triSatAxes = triangle.satAxes;
|
||||
const points = this.points;
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
const sb = triSatBounds[ i ];
|
||||
const sa = triSatAxes[ i ];
|
||||
cachedSatBounds.setFromPoints( sa, points );
|
||||
if ( sb.isSeparated( cachedSatBounds ) ) return false;
|
||||
|
||||
}
|
||||
|
||||
// check crossed axes
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
const sa1 = satAxes[ i ];
|
||||
for ( let i2 = 0; i2 < 4; i2 ++ ) {
|
||||
|
||||
const sa2 = triSatAxes[ i2 ];
|
||||
cachedAxis.crossVectors( sa1, sa2 );
|
||||
cachedSatBounds.setFromPoints( cachedAxis, pointsArr );
|
||||
cachedSatBounds2.setFromPoints( cachedAxis, points );
|
||||
if ( cachedSatBounds.isSeparated( cachedSatBounds2 ) ) return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
};
|
||||
|
||||
} )();
|
||||
|
||||
OrientedBox.prototype.closestPointToPoint = ( function () {
|
||||
|
||||
return function closestPointToPoint( point, target1 ) {
|
||||
|
||||
if ( this.needsUpdate ) {
|
||||
|
||||
this.update();
|
||||
|
||||
}
|
||||
|
||||
target1
|
||||
.copy( point )
|
||||
.applyMatrix4( this.invMatrix )
|
||||
.clamp( this.min, this.max )
|
||||
.applyMatrix4( this.matrix );
|
||||
|
||||
return target1;
|
||||
|
||||
};
|
||||
|
||||
} )();
|
||||
|
||||
OrientedBox.prototype.distanceToPoint = ( function () {
|
||||
|
||||
const target = new Vector3();
|
||||
return function distanceToPoint( point ) {
|
||||
|
||||
this.closestPointToPoint( point, target );
|
||||
return point.distanceTo( target );
|
||||
|
||||
};
|
||||
|
||||
} )();
|
||||
|
||||
OrientedBox.prototype.distanceToBox = ( function () {
|
||||
|
||||
const xyzFields = [ 'x', 'y', 'z' ];
|
||||
const segments1 = new Array( 12 ).fill().map( () => new Line3() );
|
||||
const segments2 = new Array( 12 ).fill().map( () => new Line3() );
|
||||
|
||||
const point1 = new Vector3();
|
||||
const point2 = new Vector3();
|
||||
|
||||
// early out if we find a value below threshold
|
||||
return function distanceToBox( box, threshold = 0, target1 = null, target2 = null ) {
|
||||
|
||||
if ( this.needsUpdate ) {
|
||||
|
||||
this.update();
|
||||
|
||||
}
|
||||
|
||||
if ( this.intersectsBox( box ) ) {
|
||||
|
||||
if ( target1 || target2 ) {
|
||||
|
||||
box.getCenter( point2 );
|
||||
this.closestPointToPoint( point2, point1 );
|
||||
box.closestPointToPoint( point1, point2 );
|
||||
|
||||
if ( target1 ) target1.copy( point1 );
|
||||
if ( target2 ) target2.copy( point2 );
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
const threshold2 = threshold * threshold;
|
||||
const min = box.min;
|
||||
const max = box.max;
|
||||
const points = this.points;
|
||||
|
||||
|
||||
// iterate over every edge and compare distances
|
||||
let closestDistanceSq = Infinity;
|
||||
|
||||
// check over all these points
|
||||
for ( let i = 0; i < 8; i ++ ) {
|
||||
|
||||
const p = points[ i ];
|
||||
point2.copy( p ).clamp( min, max );
|
||||
|
||||
const dist = p.distanceToSquared( point2 );
|
||||
if ( dist < closestDistanceSq ) {
|
||||
|
||||
closestDistanceSq = dist;
|
||||
if ( target1 ) target1.copy( p );
|
||||
if ( target2 ) target2.copy( point2 );
|
||||
|
||||
if ( dist < threshold2 ) return Math.sqrt( dist );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// generate and check all line segment distances
|
||||
let count = 0;
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
for ( let i1 = 0; i1 <= 1; i1 ++ ) {
|
||||
|
||||
for ( let i2 = 0; i2 <= 1; i2 ++ ) {
|
||||
|
||||
const nextIndex = ( i + 1 ) % 3;
|
||||
const nextIndex2 = ( i + 2 ) % 3;
|
||||
|
||||
// get obb line segments
|
||||
const index = i1 << nextIndex | i2 << nextIndex2;
|
||||
const index2 = 1 << i | i1 << nextIndex | i2 << nextIndex2;
|
||||
const p1 = points[ index ];
|
||||
const p2 = points[ index2 ];
|
||||
const line1 = segments1[ count ];
|
||||
line1.set( p1, p2 );
|
||||
|
||||
|
||||
// get aabb line segments
|
||||
const f1 = xyzFields[ i ];
|
||||
const f2 = xyzFields[ nextIndex ];
|
||||
const f3 = xyzFields[ nextIndex2 ];
|
||||
const line2 = segments2[ count ];
|
||||
const start = line2.start;
|
||||
const end = line2.end;
|
||||
|
||||
start[ f1 ] = min[ f1 ];
|
||||
start[ f2 ] = i1 ? min[ f2 ] : max[ f2 ];
|
||||
start[ f3 ] = i2 ? min[ f3 ] : max[ f2 ];
|
||||
|
||||
end[ f1 ] = max[ f1 ];
|
||||
end[ f2 ] = i1 ? min[ f2 ] : max[ f2 ];
|
||||
end[ f3 ] = i2 ? min[ f3 ] : max[ f2 ];
|
||||
|
||||
count ++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// check all the other boxes point
|
||||
for ( let x = 0; x <= 1; x ++ ) {
|
||||
|
||||
for ( let y = 0; y <= 1; y ++ ) {
|
||||
|
||||
for ( let z = 0; z <= 1; z ++ ) {
|
||||
|
||||
point2.x = x ? max.x : min.x;
|
||||
point2.y = y ? max.y : min.y;
|
||||
point2.z = z ? max.z : min.z;
|
||||
|
||||
this.closestPointToPoint( point2, point1 );
|
||||
const dist = point2.distanceToSquared( point1 );
|
||||
if ( dist < closestDistanceSq ) {
|
||||
|
||||
closestDistanceSq = dist;
|
||||
if ( target1 ) target1.copy( point1 );
|
||||
if ( target2 ) target2.copy( point2 );
|
||||
|
||||
if ( dist < threshold2 ) return Math.sqrt( dist );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0; i < 12; i ++ ) {
|
||||
|
||||
const l1 = segments1[ i ];
|
||||
for ( let i2 = 0; i2 < 12; i2 ++ ) {
|
||||
|
||||
const l2 = segments2[ i2 ];
|
||||
closestPointsSegmentToSegment( l1, l2, point1, point2 );
|
||||
const dist = point1.distanceToSquared( point2 );
|
||||
if ( dist < closestDistanceSq ) {
|
||||
|
||||
closestDistanceSq = dist;
|
||||
if ( target1 ) target1.copy( point1 );
|
||||
if ( target2 ) target2.copy( point2 );
|
||||
|
||||
if ( dist < threshold2 ) return Math.sqrt( dist );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return Math.sqrt( closestDistanceSq );
|
||||
|
||||
};
|
||||
|
||||
} )();
|
||||
95
src/components/DMT/libs/bvh/math/SeparatingAxisBounds.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import { Vector3 } from 'three'
|
||||
|
||||
export class SeparatingAxisBounds {
|
||||
constructor() {
|
||||
this.min = Infinity
|
||||
this.max = -Infinity
|
||||
}
|
||||
|
||||
setFromPointsField(points, field) {
|
||||
let min = Infinity
|
||||
let max = -Infinity
|
||||
for (let i = 0, l = points.length; i < l; i++) {
|
||||
const p = points[ i ]
|
||||
const val = p[ field ]
|
||||
min = val < min ? val : min
|
||||
max = val > max ? val : max
|
||||
}
|
||||
|
||||
this.min = min
|
||||
this.max = max
|
||||
}
|
||||
|
||||
setFromPoints(axis, points) {
|
||||
let min = Infinity
|
||||
let max = -Infinity
|
||||
for (let i = 0, l = points.length; i < l; i++) {
|
||||
const p = points[ i ]
|
||||
const val = axis.dot(p)
|
||||
min = val < min ? val : min
|
||||
max = val > max ? val : max
|
||||
}
|
||||
|
||||
this.min = min
|
||||
this.max = max
|
||||
}
|
||||
|
||||
isSeparated(other) {
|
||||
return this.min > other.max || other.min > this.max
|
||||
}
|
||||
}
|
||||
|
||||
SeparatingAxisBounds.prototype.setFromBox = (function() {
|
||||
const p = new Vector3()
|
||||
return function setFromBox(axis, box) {
|
||||
const boxMin = box.min
|
||||
const boxMax = box.max
|
||||
let min = Infinity
|
||||
let max = -Infinity
|
||||
for (let x = 0; x <= 1; x++) {
|
||||
for (let y = 0; y <= 1; y++) {
|
||||
for (let z = 0; z <= 1; z++) {
|
||||
p.x = boxMin.x * x + boxMax.x * (1 - x)
|
||||
p.y = boxMin.y * y + boxMax.y * (1 - y)
|
||||
p.z = boxMin.z * z + boxMax.z * (1 - z)
|
||||
|
||||
const val = axis.dot(p)
|
||||
min = Math.min(val, min)
|
||||
max = Math.max(val, max)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.min = min
|
||||
this.max = max
|
||||
}
|
||||
})()
|
||||
|
||||
export const areIntersecting = (function() {
|
||||
const cacheSatBounds = new SeparatingAxisBounds()
|
||||
return function areIntersecting(shape1, shape2) {
|
||||
const points1 = shape1.points
|
||||
const satAxes1 = shape1.satAxes
|
||||
const satBounds1 = shape1.satBounds
|
||||
|
||||
const points2 = shape2.points
|
||||
const satAxes2 = shape2.satAxes
|
||||
const satBounds2 = shape2.satBounds
|
||||
|
||||
// check axes of the first shape
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const sb = satBounds1[ i ]
|
||||
const sa = satAxes1[ i ]
|
||||
cacheSatBounds.setFromPoints(sa, points2)
|
||||
if (sb.isSeparated(cacheSatBounds)) return false
|
||||
}
|
||||
|
||||
// check axes of the second shape
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const sb = satBounds2[ i ]
|
||||
const sa = satAxes2[ i ]
|
||||
cacheSatBounds.setFromPoints(sa, points1)
|
||||
if (sb.isSeparated(cacheSatBounds)) return false
|
||||
}
|
||||
}
|
||||
})()
|
||||
306
src/components/DMT/libs/bvh/objects/MeshBVHHelper.js
Normal file
@@ -0,0 +1,306 @@
|
||||
import { LineBasicMaterial, BufferAttribute, Box3, Group, MeshBasicMaterial, Object3D, BufferGeometry } from 'three'
|
||||
import { arrayToBox } from '../utils/ArrayBoxUtilities.js'
|
||||
import { MeshBVH } from '../core/MeshBVH.js'
|
||||
|
||||
const boundingBox = /* @__PURE__ */ new Box3()
|
||||
class MeshBVHRootHelper extends Object3D {
|
||||
get isMesh() {
|
||||
return !this.displayEdges
|
||||
}
|
||||
|
||||
get isLineSegments() {
|
||||
return this.displayEdges
|
||||
}
|
||||
|
||||
get isLine() {
|
||||
return this.displayEdges
|
||||
}
|
||||
|
||||
constructor(bvh, material, depth = 10, group = 0) {
|
||||
super()
|
||||
|
||||
this.material = material
|
||||
this.geometry = new BufferGeometry()
|
||||
this.name = 'MeshBVHRootHelper'
|
||||
this.depth = depth
|
||||
this.displayParents = false
|
||||
this.bvh = bvh
|
||||
this.displayEdges = true
|
||||
this._group = group
|
||||
}
|
||||
|
||||
raycast() {}
|
||||
|
||||
update() {
|
||||
const geometry = this.geometry
|
||||
const boundsTree = this.bvh
|
||||
const group = this._group
|
||||
geometry.dispose()
|
||||
this.visible = false
|
||||
if (boundsTree) {
|
||||
// count the number of bounds required
|
||||
const targetDepth = this.depth - 1
|
||||
const displayParents = this.displayParents
|
||||
let boundsCount = 0
|
||||
boundsTree.traverse((depth, isLeaf) => {
|
||||
if (depth >= targetDepth || isLeaf) {
|
||||
boundsCount++
|
||||
return true
|
||||
} else if (displayParents) {
|
||||
boundsCount++
|
||||
}
|
||||
}, group)
|
||||
|
||||
// fill in the position buffer with the bounds corners
|
||||
let posIndex = 0
|
||||
const positionArray = new Float32Array(8 * 3 * boundsCount)
|
||||
boundsTree.traverse((depth, isLeaf, boundingData) => {
|
||||
const terminate = depth >= targetDepth || isLeaf
|
||||
if (terminate || displayParents) {
|
||||
arrayToBox(0, boundingData, boundingBox)
|
||||
|
||||
const { min, max } = boundingBox
|
||||
for (let x = -1; x <= 1; x += 2) {
|
||||
const xVal = x < 0 ? min.x : max.x
|
||||
for (let y = -1; y <= 1; y += 2) {
|
||||
const yVal = y < 0 ? min.y : max.y
|
||||
for (let z = -1; z <= 1; z += 2) {
|
||||
const zVal = z < 0 ? min.z : max.z
|
||||
positionArray[ posIndex + 0 ] = xVal
|
||||
positionArray[ posIndex + 1 ] = yVal
|
||||
positionArray[ posIndex + 2 ] = zVal
|
||||
|
||||
posIndex += 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return terminate
|
||||
}
|
||||
}, group)
|
||||
|
||||
let indexArray
|
||||
let indices
|
||||
if (this.displayEdges) {
|
||||
// fill in the index buffer to point to the corner points
|
||||
indices = new Uint8Array([
|
||||
// x axis
|
||||
0, 4,
|
||||
1, 5,
|
||||
2, 6,
|
||||
3, 7,
|
||||
|
||||
// y axis
|
||||
0, 2,
|
||||
1, 3,
|
||||
4, 6,
|
||||
5, 7,
|
||||
|
||||
// z axis
|
||||
0, 1,
|
||||
2, 3,
|
||||
4, 5,
|
||||
6, 7
|
||||
])
|
||||
} else {
|
||||
indices = new Uint8Array([
|
||||
|
||||
// X-, X+
|
||||
0, 1, 2,
|
||||
2, 1, 3,
|
||||
|
||||
4, 6, 5,
|
||||
6, 7, 5,
|
||||
|
||||
// Y-, Y+
|
||||
1, 4, 5,
|
||||
0, 4, 1,
|
||||
|
||||
2, 3, 6,
|
||||
3, 7, 6,
|
||||
|
||||
// Z-, Z+
|
||||
0, 2, 4,
|
||||
2, 6, 4,
|
||||
|
||||
1, 5, 3,
|
||||
3, 5, 7
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
if (positionArray.length > 65535) {
|
||||
indexArray = new Uint32Array(indices.length * boundsCount)
|
||||
} else {
|
||||
indexArray = new Uint16Array(indices.length * boundsCount)
|
||||
}
|
||||
|
||||
const indexLength = indices.length
|
||||
for (let i = 0; i < boundsCount; i++) {
|
||||
const posOffset = i * 8
|
||||
const indexOffset = i * indexLength
|
||||
for (let j = 0; j < indexLength; j++) {
|
||||
indexArray[ indexOffset + j ] = posOffset + indices[ j ]
|
||||
}
|
||||
}
|
||||
|
||||
// update the geometry
|
||||
geometry.setIndex(
|
||||
new BufferAttribute(indexArray, 1, false),
|
||||
)
|
||||
geometry.setAttribute(
|
||||
'position',
|
||||
new BufferAttribute(positionArray, 3, false),
|
||||
)
|
||||
this.visible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MeshBVHHelper extends Group {
|
||||
get color() {
|
||||
return this.edgeMaterial.color
|
||||
}
|
||||
|
||||
get opacity() {
|
||||
return this.edgeMaterial.opacity
|
||||
}
|
||||
|
||||
set opacity(v) {
|
||||
this.edgeMaterial.opacity = v
|
||||
this.meshMaterial.opacity = v
|
||||
}
|
||||
|
||||
constructor(mesh = null, bvh = null, depth = 10) {
|
||||
// handle bvh, depth signature
|
||||
if (mesh instanceof MeshBVH) {
|
||||
depth = bvh || 10
|
||||
bvh = mesh
|
||||
mesh = null
|
||||
}
|
||||
|
||||
// handle mesh, depth signature
|
||||
if (typeof bvh === 'number') {
|
||||
depth = bvh
|
||||
bvh = null
|
||||
}
|
||||
|
||||
super()
|
||||
|
||||
this.name = 'MeshBVHHelper'
|
||||
this.depth = depth
|
||||
this.mesh = mesh
|
||||
this.bvh = bvh
|
||||
this.displayParents = false
|
||||
this.displayEdges = true
|
||||
this._roots = []
|
||||
|
||||
const edgeMaterial = new LineBasicMaterial({
|
||||
color: 0x00FF88,
|
||||
transparent: true,
|
||||
opacity: 0.3,
|
||||
depthWrite: false
|
||||
})
|
||||
|
||||
const meshMaterial = new MeshBasicMaterial({
|
||||
color: 0x00FF88,
|
||||
transparent: true,
|
||||
opacity: 0.3,
|
||||
depthWrite: false
|
||||
})
|
||||
|
||||
meshMaterial.color = edgeMaterial.color
|
||||
|
||||
this.edgeMaterial = edgeMaterial
|
||||
this.meshMaterial = meshMaterial
|
||||
|
||||
this.update()
|
||||
}
|
||||
|
||||
update() {
|
||||
const bvh = this.bvh || this.mesh.geometry.boundsTree
|
||||
const totalRoots = bvh ? bvh._roots.length : 0
|
||||
while (this._roots.length > totalRoots) {
|
||||
const root = this._roots.pop()
|
||||
root.geometry.dispose()
|
||||
this.remove(root)
|
||||
}
|
||||
|
||||
for (let i = 0; i < totalRoots; i++) {
|
||||
const { depth, edgeMaterial, meshMaterial, displayParents, displayEdges } = this
|
||||
|
||||
if (i >= this._roots.length) {
|
||||
const root = new MeshBVHRootHelper(bvh, edgeMaterial, depth, i)
|
||||
this.add(root)
|
||||
this._roots.push(root)
|
||||
}
|
||||
|
||||
const root = this._roots[ i ]
|
||||
root.bvh = bvh
|
||||
root.depth = depth
|
||||
root.displayParents = displayParents
|
||||
root.displayEdges = displayEdges
|
||||
root.material = displayEdges ? edgeMaterial : meshMaterial
|
||||
root.update()
|
||||
}
|
||||
}
|
||||
|
||||
updateMatrixWorld(...args) {
|
||||
const mesh = this.mesh
|
||||
const parent = this.parent
|
||||
|
||||
if (mesh !== null) {
|
||||
mesh.updateWorldMatrix(true, false)
|
||||
|
||||
if (parent) {
|
||||
this.matrix
|
||||
.copy(parent.matrixWorld)
|
||||
.invert()
|
||||
.multiply(mesh.matrixWorld)
|
||||
} else {
|
||||
this.matrix
|
||||
.copy(mesh.matrixWorld)
|
||||
}
|
||||
|
||||
this.matrix.decompose(
|
||||
this.position,
|
||||
this.quaternion,
|
||||
this.scale,
|
||||
)
|
||||
}
|
||||
|
||||
super.updateMatrixWorld(...args)
|
||||
}
|
||||
|
||||
copy(source) {
|
||||
this.depth = source.depth
|
||||
this.mesh = source.mesh
|
||||
this.bvh = source.bvh
|
||||
this.opacity = source.opacity
|
||||
this.color.copy(source.color)
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new MeshBVHHelper(this.mesh, this.bvh, this.depth)
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.edgeMaterial.dispose()
|
||||
this.meshMaterial.dispose()
|
||||
|
||||
const children = this.children
|
||||
for (let i = 0, l = children.length; i < l; i++) {
|
||||
children[ i ].geometry.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MeshBVHVisualizer extends MeshBVHHelper {
|
||||
constructor(...args) {
|
||||
super(...args)
|
||||
|
||||
console.warn('MeshBVHVisualizer: MeshBVHVisualizer has been deprecated. Use MeshBVHHelper, instead.')
|
||||
}
|
||||
}
|
||||
|
||||
export { MeshBVHHelper }
|
||||
108
src/components/DMT/libs/bvh/utils/ArrayBoxUtilities.js
Normal file
@@ -0,0 +1,108 @@
|
||||
export function arrayToBox( nodeIndex32, array, target ) {
|
||||
|
||||
target.min.x = array[ nodeIndex32 ];
|
||||
target.min.y = array[ nodeIndex32 + 1 ];
|
||||
target.min.z = array[ nodeIndex32 + 2 ];
|
||||
|
||||
target.max.x = array[ nodeIndex32 + 3 ];
|
||||
target.max.y = array[ nodeIndex32 + 4 ];
|
||||
target.max.z = array[ nodeIndex32 + 5 ];
|
||||
|
||||
return target;
|
||||
|
||||
}
|
||||
|
||||
export function makeEmptyBounds( target ) {
|
||||
|
||||
target[ 0 ] = target[ 1 ] = target[ 2 ] = Infinity;
|
||||
target[ 3 ] = target[ 4 ] = target[ 5 ] = - Infinity;
|
||||
|
||||
}
|
||||
|
||||
export function getLongestEdgeIndex( bounds ) {
|
||||
|
||||
let splitDimIdx = - 1;
|
||||
let splitDist = - Infinity;
|
||||
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
|
||||
const dist = bounds[ i + 3 ] - bounds[ i ];
|
||||
if ( dist > splitDist ) {
|
||||
|
||||
splitDist = dist;
|
||||
splitDimIdx = i;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return splitDimIdx;
|
||||
|
||||
}
|
||||
|
||||
// copies bounds a into bounds b
|
||||
export function copyBounds( source, target ) {
|
||||
|
||||
target.set( source );
|
||||
|
||||
}
|
||||
|
||||
// sets bounds target to the union of bounds a and b
|
||||
export function unionBounds( a, b, target ) {
|
||||
|
||||
let aVal, bVal;
|
||||
for ( let d = 0; d < 3; d ++ ) {
|
||||
|
||||
const d3 = d + 3;
|
||||
|
||||
// set the minimum values
|
||||
aVal = a[ d ];
|
||||
bVal = b[ d ];
|
||||
target[ d ] = aVal < bVal ? aVal : bVal;
|
||||
|
||||
// set the max values
|
||||
aVal = a[ d3 ];
|
||||
bVal = b[ d3 ];
|
||||
target[ d3 ] = aVal > bVal ? aVal : bVal;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// expands the given bounds by the provided triangle bounds
|
||||
export function expandByTriangleBounds( startIndex, triangleBounds, bounds ) {
|
||||
|
||||
for ( let d = 0; d < 3; d ++ ) {
|
||||
|
||||
const tCenter = triangleBounds[ startIndex + 2 * d ];
|
||||
const tHalf = triangleBounds[ startIndex + 2 * d + 1 ];
|
||||
|
||||
const tMin = tCenter - tHalf;
|
||||
const tMax = tCenter + tHalf;
|
||||
|
||||
if ( tMin < bounds[ d ] ) {
|
||||
|
||||
bounds[ d ] = tMin;
|
||||
|
||||
}
|
||||
|
||||
if ( tMax > bounds[ d + 3 ] ) {
|
||||
|
||||
bounds[ d + 3 ] = tMax;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// compute bounds surface area
|
||||
export function computeSurfaceArea( bounds ) {
|
||||
|
||||
const d0 = bounds[ 3 ] - bounds[ 0 ];
|
||||
const d1 = bounds[ 4 ] - bounds[ 1 ];
|
||||
const d2 = bounds[ 5 ] - bounds[ 2 ];
|
||||
|
||||
return 2 * ( d0 * d1 + d1 * d2 + d2 * d0 );
|
||||
|
||||
}
|
||||
41
src/components/DMT/libs/bvh/utils/BufferUtils.js
Normal file
@@ -0,0 +1,41 @@
|
||||
export function isSharedArrayBufferSupported() {
|
||||
|
||||
return typeof SharedArrayBuffer !== 'undefined';
|
||||
|
||||
}
|
||||
|
||||
export function convertToBufferType( array, BufferConstructor ) {
|
||||
|
||||
if ( array === null ) {
|
||||
|
||||
return array;
|
||||
|
||||
} else if ( array.buffer ) {
|
||||
|
||||
const buffer = array.buffer;
|
||||
if ( buffer.constructor === BufferConstructor ) {
|
||||
|
||||
return array;
|
||||
|
||||
}
|
||||
|
||||
const ArrayConstructor = array.constructor;
|
||||
const result = new ArrayConstructor( new BufferConstructor( buffer.byteLength ) );
|
||||
result.set( array );
|
||||
return result;
|
||||
|
||||
} else {
|
||||
|
||||
if ( array.constructor === BufferConstructor ) {
|
||||
|
||||
return array;
|
||||
|
||||
}
|
||||
|
||||
const result = new BufferConstructor( array.byteLength );
|
||||
new Uint8Array( result ).set( new Uint8Array( array ) );
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
14
src/components/DMT/libs/bvh/utils/ExtendedTrianglePool.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ExtendedTriangle } from '../math/ExtendedTriangle.js';
|
||||
import { PrimitivePool } from './PrimitivePool.js';
|
||||
|
||||
class ExtendedTrianglePoolBase extends PrimitivePool {
|
||||
|
||||
constructor() {
|
||||
|
||||
super( () => new ExtendedTriangle() );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const ExtendedTrianglePool = /* @__PURE__ */ new ExtendedTrianglePoolBase();
|
||||
43
src/components/DMT/libs/bvh/utils/ExtensionUtilities.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Ray, Matrix4, Mesh } from 'three'
|
||||
import { convertRaycastIntersect } from './GeometryRayIntersectUtilities.js'
|
||||
import { MeshBVH } from '../core/MeshBVH.js'
|
||||
|
||||
const ray = /* @__PURE__ */ new Ray()
|
||||
const tmpInverseMatrix = /* @__PURE__ */ new Matrix4()
|
||||
const origMeshRaycastFunc = Mesh.prototype.raycast
|
||||
|
||||
export function acceleratedRaycast(raycaster, intersects) {
|
||||
if (this.geometry.boundsTree) {
|
||||
if (this.material === undefined) return
|
||||
|
||||
tmpInverseMatrix.copy(this.matrixWorld).invert()
|
||||
ray.copy(raycaster.ray).applyMatrix4(tmpInverseMatrix)
|
||||
|
||||
const bvh = this.geometry.boundsTree
|
||||
if (raycaster.firstHitOnly === true) {
|
||||
const hit = convertRaycastIntersect(bvh.raycastFirst(ray, this.material), this, raycaster)
|
||||
if (hit) {
|
||||
intersects.push(hit)
|
||||
}
|
||||
} else {
|
||||
const hits = bvh.raycast(ray, this.material)
|
||||
for (let i = 0, l = hits.length; i < l; i++) {
|
||||
const hit = convertRaycastIntersect(hits[ i ], this, raycaster)
|
||||
if (hit) {
|
||||
intersects.push(hit)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
origMeshRaycastFunc.call(this, raycaster, intersects)
|
||||
}
|
||||
}
|
||||
|
||||
export function computeBoundsTree(options) {
|
||||
this.boundsTree = new MeshBVH(this, options)
|
||||
return this.boundsTree
|
||||
}
|
||||
|
||||
export function disposeBoundsTree() {
|
||||
this.boundsTree = null
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// converts the given BVH raycast intersection to align with the three.js raycast
|
||||
// structure (include object, world space distance and point).
|
||||
export function convertRaycastIntersect( hit, object, raycaster ) {
|
||||
|
||||
if ( hit === null ) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
hit.point.applyMatrix4( object.matrixWorld );
|
||||
hit.distance = hit.point.distanceTo( raycaster.ray.origin );
|
||||
hit.object = object;
|
||||
|
||||
if ( hit.distance < raycaster.near || hit.distance > raycaster.far ) {
|
||||
|
||||
return null;
|
||||
|
||||
} else {
|
||||
|
||||
return hit;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
31
src/components/DMT/libs/bvh/utils/PrimitivePool.js
Normal file
@@ -0,0 +1,31 @@
|
||||
export class PrimitivePool {
|
||||
|
||||
constructor( getNewPrimitive ) {
|
||||
|
||||
this._getNewPrimitive = getNewPrimitive;
|
||||
this._primitives = [];
|
||||
|
||||
}
|
||||
|
||||
getPrimitive() {
|
||||
|
||||
const primitives = this._primitives;
|
||||
if ( primitives.length === 0 ) {
|
||||
|
||||
return this._getNewPrimitive();
|
||||
|
||||
} else {
|
||||
|
||||
return primitives.pop();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
releasePrimitive( primitive ) {
|
||||
|
||||
this._primitives.push( primitive );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
555
src/components/DMT/libs/bvh/utils/StaticGeometryGenerator.js
Normal file
@@ -0,0 +1,555 @@
|
||||
import { BufferAttribute, BufferGeometry, Vector3, Vector4, Matrix4, Matrix3 } from 'three'
|
||||
|
||||
const _positionVector = /* @__PURE__*/ new Vector3()
|
||||
const _normalVector = /* @__PURE__*/ new Vector3()
|
||||
const _tangentVector = /* @__PURE__*/ new Vector3()
|
||||
const _tangentVector4 = /* @__PURE__*/ new Vector4()
|
||||
|
||||
const _morphVector = /* @__PURE__*/ new Vector3()
|
||||
const _temp = /* @__PURE__*/ new Vector3()
|
||||
|
||||
const _skinIndex = /* @__PURE__*/ new Vector4()
|
||||
const _skinWeight = /* @__PURE__*/ new Vector4()
|
||||
const _matrix = /* @__PURE__*/ new Matrix4()
|
||||
const _boneMatrix = /* @__PURE__*/ new Matrix4()
|
||||
|
||||
// Confirms that the two provided attributes are compatible
|
||||
function validateAttributes(attr1, attr2) {
|
||||
if (!attr1 && !attr2) {
|
||||
return
|
||||
}
|
||||
|
||||
const sameCount = attr1.count === attr2.count
|
||||
const sameNormalized = attr1.normalized === attr2.normalized
|
||||
const sameType = attr1.array.constructor === attr2.array.constructor
|
||||
const sameItemSize = attr1.itemSize === attr2.itemSize
|
||||
|
||||
if (!sameCount || !sameNormalized || !sameType || !sameItemSize) {
|
||||
throw new Error()
|
||||
}
|
||||
}
|
||||
|
||||
// Clones the given attribute with a new compatible buffer attribute but no data
|
||||
function createAttributeClone(attr, countOverride = null) {
|
||||
const cons = attr.array.constructor
|
||||
const normalized = attr.normalized
|
||||
const itemSize = attr.itemSize
|
||||
const count = countOverride === null ? attr.count : countOverride
|
||||
|
||||
return new BufferAttribute(new cons(itemSize * count), itemSize, normalized)
|
||||
}
|
||||
|
||||
// target offset is the number of elements in the target buffer stride to skip before copying the
|
||||
// attributes contents in to.
|
||||
function copyAttributeContents(attr, target, targetOffset = 0) {
|
||||
if (attr.isInterleavedBufferAttribute) {
|
||||
const itemSize = attr.itemSize
|
||||
for (let i = 0, l = attr.count; i < l; i++) {
|
||||
const io = i + targetOffset
|
||||
target.setX(io, attr.getX(i))
|
||||
if (itemSize >= 2) target.setY(io, attr.getY(i))
|
||||
if (itemSize >= 3) target.setZ(io, attr.getZ(i))
|
||||
if (itemSize >= 4) target.setW(io, attr.getW(i))
|
||||
}
|
||||
} else {
|
||||
const array = target.array
|
||||
const cons = array.constructor
|
||||
const byteOffset = array.BYTES_PER_ELEMENT * attr.itemSize * targetOffset
|
||||
const temp = new cons(array.buffer, byteOffset, attr.array.length)
|
||||
temp.set(attr.array)
|
||||
}
|
||||
}
|
||||
|
||||
// Adds the "matrix" multiplied by "scale" to "target"
|
||||
function addScaledMatrix(target, matrix, scale) {
|
||||
const targetArray = target.elements
|
||||
const matrixArray = matrix.elements
|
||||
for (let i = 0, l = matrixArray.length; i < l; i++) {
|
||||
targetArray[ i ] += matrixArray[ i ] * scale
|
||||
}
|
||||
}
|
||||
|
||||
// A version of "SkinnedMesh.boneTransform" for normals
|
||||
function boneNormalTransform(mesh, index, target) {
|
||||
const skeleton = mesh.skeleton
|
||||
const geometry = mesh.geometry
|
||||
const bones = skeleton.bones
|
||||
const boneInverses = skeleton.boneInverses
|
||||
|
||||
_skinIndex.fromBufferAttribute(geometry.attributes.skinIndex, index)
|
||||
_skinWeight.fromBufferAttribute(geometry.attributes.skinWeight, index)
|
||||
|
||||
_matrix.elements.fill(0)
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const weight = _skinWeight.getComponent(i)
|
||||
|
||||
if (weight !== 0) {
|
||||
const boneIndex = _skinIndex.getComponent(i)
|
||||
_boneMatrix.multiplyMatrices(bones[ boneIndex ].matrixWorld, boneInverses[ boneIndex ])
|
||||
|
||||
addScaledMatrix(_matrix, _boneMatrix, weight)
|
||||
}
|
||||
}
|
||||
|
||||
_matrix.multiply(mesh.bindMatrix).premultiply(mesh.bindMatrixInverse)
|
||||
target.transformDirection(_matrix)
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
// Applies the morph target data to the target vector
|
||||
function applyMorphTarget(morphData, morphInfluences, morphTargetsRelative, i, target) {
|
||||
_morphVector.set(0, 0, 0)
|
||||
for (let j = 0, jl = morphData.length; j < jl; j++) {
|
||||
const influence = morphInfluences[ j ]
|
||||
const morphAttribute = morphData[ j ]
|
||||
|
||||
if (influence === 0) continue
|
||||
|
||||
_temp.fromBufferAttribute(morphAttribute, i)
|
||||
|
||||
if (morphTargetsRelative) {
|
||||
_morphVector.addScaledVector(_temp, influence)
|
||||
} else {
|
||||
_morphVector.addScaledVector(_temp.sub(target), influence)
|
||||
}
|
||||
}
|
||||
|
||||
target.add(_morphVector)
|
||||
}
|
||||
|
||||
// Modified version of BufferGeometryUtils.mergeBufferGeometries that ignores morph targets and updates a attributes in place
|
||||
function mergeBufferGeometries(geometries, options = { useGroups: false, updateIndex: false, skipAttributes: [] }, targetGeometry = new BufferGeometry()) {
|
||||
const isIndexed = geometries[ 0 ].index !== null
|
||||
const { useGroups = false, updateIndex = false, skipAttributes = [] } = options
|
||||
|
||||
const attributesUsed = new Set(Object.keys(geometries[ 0 ].attributes))
|
||||
const attributes = {}
|
||||
|
||||
let offset = 0
|
||||
|
||||
targetGeometry.clearGroups()
|
||||
for (let i = 0; i < geometries.length; ++i) {
|
||||
const geometry = geometries[ i ]
|
||||
let attributesCount = 0
|
||||
|
||||
// ensure that all geometries are indexed, or none
|
||||
if (isIndexed !== (geometry.index !== null)) {
|
||||
throw new Error('StaticGeometryGenerator: All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.')
|
||||
}
|
||||
|
||||
// gather attributes, exit early if they're different
|
||||
for (const name in geometry.attributes) {
|
||||
if (!attributesUsed.has(name)) {
|
||||
throw new Error('StaticGeometryGenerator: All geometries must have compatible attributes; make sure "' + name + '" attribute exists among all geometries, or in none of them.')
|
||||
}
|
||||
|
||||
if (attributes[ name ] === undefined) {
|
||||
attributes[ name ] = []
|
||||
}
|
||||
|
||||
attributes[ name ].push(geometry.attributes[ name ])
|
||||
attributesCount++
|
||||
}
|
||||
|
||||
// ensure geometries have the same number of attributes
|
||||
if (attributesCount !== attributesUsed.size) {
|
||||
throw new Error('StaticGeometryGenerator: Make sure all geometries have the same number of attributes.')
|
||||
}
|
||||
|
||||
if (useGroups) {
|
||||
let count
|
||||
if (isIndexed) {
|
||||
count = geometry.index.count
|
||||
} else if (geometry.attributes.position !== undefined) {
|
||||
count = geometry.attributes.position.count
|
||||
} else {
|
||||
throw new Error('StaticGeometryGenerator: The geometry must have either an index or a position attribute')
|
||||
}
|
||||
|
||||
targetGeometry.addGroup(offset, count, i)
|
||||
offset += count
|
||||
}
|
||||
}
|
||||
|
||||
// merge indices
|
||||
if (isIndexed) {
|
||||
let forceUpdateIndex = false
|
||||
if (!targetGeometry.index) {
|
||||
let indexCount = 0
|
||||
for (let i = 0; i < geometries.length; ++i) {
|
||||
indexCount += geometries[ i ].index.count
|
||||
}
|
||||
|
||||
targetGeometry.setIndex(new BufferAttribute(new Uint32Array(indexCount), 1, false))
|
||||
forceUpdateIndex = true
|
||||
}
|
||||
|
||||
if (updateIndex || forceUpdateIndex) {
|
||||
const targetIndex = targetGeometry.index
|
||||
let targetOffset = 0
|
||||
let indexOffset = 0
|
||||
for (let i = 0; i < geometries.length; ++i) {
|
||||
const geometry = geometries[ i ]
|
||||
const index = geometry.index
|
||||
if (skipAttributes[ i ] !== true) {
|
||||
for (let j = 0; j < index.count; ++j) {
|
||||
targetIndex.setX(targetOffset, index.getX(j) + indexOffset)
|
||||
targetOffset++
|
||||
}
|
||||
}
|
||||
|
||||
indexOffset += geometry.attributes.position.count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// merge attributes
|
||||
for (const name in attributes) {
|
||||
const attrList = attributes[ name ]
|
||||
if (!(name in targetGeometry.attributes)) {
|
||||
let count = 0
|
||||
for (const key in attrList) {
|
||||
count += attrList[ key ].count
|
||||
}
|
||||
|
||||
targetGeometry.setAttribute(name, createAttributeClone(attributes[ name ][ 0 ], count))
|
||||
}
|
||||
|
||||
const targetAttribute = targetGeometry.attributes[ name ]
|
||||
let offset = 0
|
||||
for (let i = 0, l = attrList.length; i < l; i++) {
|
||||
const attr = attrList[ i ]
|
||||
if (skipAttributes[ i ] !== true) {
|
||||
copyAttributeContents(attr, targetAttribute, offset)
|
||||
}
|
||||
|
||||
offset += attr.count
|
||||
}
|
||||
}
|
||||
|
||||
return targetGeometry
|
||||
}
|
||||
|
||||
function checkTypedArrayEquality(a, b) {
|
||||
if (a === null || b === null) {
|
||||
return a === b
|
||||
}
|
||||
|
||||
if (a.length !== b.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (let i = 0, l = a.length; i < l; i++) {
|
||||
if (a[ i ] !== b[ i ]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function invertGeometry(geometry) {
|
||||
const { index, attributes } = geometry
|
||||
if (index) {
|
||||
for (let i = 0, l = index.count; i < l; i += 3) {
|
||||
const v0 = index.getX(i)
|
||||
const v2 = index.getX(i + 2)
|
||||
index.setX(i, v2)
|
||||
index.setX(i + 2, v0)
|
||||
}
|
||||
} else {
|
||||
for (const key in attributes) {
|
||||
const attr = attributes[ key ]
|
||||
const itemSize = attr.itemSize
|
||||
for (let i = 0, l = attr.count; i < l; i += 3) {
|
||||
for (let j = 0; j < itemSize; j++) {
|
||||
const v0 = attr.getComponent(i, j)
|
||||
const v2 = attr.getComponent(i + 2, j)
|
||||
attr.setComponent(i, j, v2)
|
||||
attr.setComponent(i + 2, j, v0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return geometry
|
||||
}
|
||||
|
||||
// Checks whether the geometry changed between this and last evaluation
|
||||
class GeometryDiff {
|
||||
constructor(mesh) {
|
||||
this.matrixWorld = new Matrix4()
|
||||
this.geometryHash = null
|
||||
this.boneMatrices = null
|
||||
this.primitiveCount = -1
|
||||
this.mesh = mesh
|
||||
|
||||
this.update()
|
||||
}
|
||||
|
||||
update() {
|
||||
const mesh = this.mesh
|
||||
const geometry = mesh.geometry
|
||||
const skeleton = mesh.skeleton
|
||||
const primitiveCount = (geometry.index ? geometry.index.count : geometry.attributes.position.count) / 3
|
||||
this.matrixWorld.copy(mesh.matrixWorld)
|
||||
this.geometryHash = geometry.attributes.position.version
|
||||
this.primitiveCount = primitiveCount
|
||||
|
||||
if (skeleton) {
|
||||
// ensure the bone matrix array is updated to the appropriate length
|
||||
if (!skeleton.boneTexture) {
|
||||
skeleton.computeBoneTexture()
|
||||
}
|
||||
|
||||
skeleton.update()
|
||||
|
||||
// copy data if possible otherwise clone it
|
||||
const boneMatrices = skeleton.boneMatrices
|
||||
if (!this.boneMatrices || this.boneMatrices.length !== boneMatrices.length) {
|
||||
this.boneMatrices = boneMatrices.slice()
|
||||
} else {
|
||||
this.boneMatrices.set(boneMatrices)
|
||||
}
|
||||
} else {
|
||||
this.boneMatrices = null
|
||||
}
|
||||
}
|
||||
|
||||
didChange() {
|
||||
const mesh = this.mesh
|
||||
const geometry = mesh.geometry
|
||||
const primitiveCount = (geometry.index ? geometry.index.count : geometry.attributes.position.count) / 3
|
||||
const identical =
|
||||
this.matrixWorld.equals(mesh.matrixWorld) &&
|
||||
this.geometryHash === geometry.attributes.position.version &&
|
||||
checkTypedArrayEquality(mesh.skeleton && mesh.skeleton.boneMatrices || null, this.boneMatrices) &&
|
||||
this.primitiveCount === primitiveCount
|
||||
|
||||
return !identical
|
||||
}
|
||||
}
|
||||
|
||||
export class StaticGeometryGenerator {
|
||||
constructor(meshes) {
|
||||
if (!Array.isArray(meshes)) {
|
||||
meshes = [meshes]
|
||||
}
|
||||
|
||||
const finalMeshes = []
|
||||
meshes.forEach(object => {
|
||||
object.traverseVisible(c => {
|
||||
if (c.isMesh) {
|
||||
finalMeshes.push(c)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.meshes = finalMeshes
|
||||
this.useGroups = true
|
||||
this.applyWorldTransforms = true
|
||||
this.attributes = ['position', 'normal', 'color', 'tangent', 'uv', 'uv2']
|
||||
this._intermediateGeometry = new Array(finalMeshes.length).fill().map(() => new BufferGeometry())
|
||||
this._diffMap = new WeakMap()
|
||||
}
|
||||
|
||||
getMaterials() {
|
||||
const materials = []
|
||||
this.meshes.forEach(mesh => {
|
||||
if (Array.isArray(mesh.material)) {
|
||||
materials.push(...mesh.material)
|
||||
} else {
|
||||
materials.push(mesh.material)
|
||||
}
|
||||
})
|
||||
return materials
|
||||
}
|
||||
|
||||
generate(targetGeometry = new BufferGeometry()) {
|
||||
// track which attributes have been updated and which to skip to avoid unnecessary attribute copies
|
||||
const skipAttributes = []
|
||||
const { meshes, useGroups, _intermediateGeometry, _diffMap } = this
|
||||
for (let i = 0, l = meshes.length; i < l; i++) {
|
||||
const mesh = meshes[ i ]
|
||||
const geom = _intermediateGeometry[ i ]
|
||||
const diff = _diffMap.get(mesh)
|
||||
if (!diff || diff.didChange(mesh)) {
|
||||
this._convertToStaticGeometry(mesh, geom)
|
||||
skipAttributes.push(false)
|
||||
|
||||
if (!diff) {
|
||||
_diffMap.set(mesh, new GeometryDiff(mesh))
|
||||
} else {
|
||||
diff.update()
|
||||
}
|
||||
} else {
|
||||
skipAttributes.push(true)
|
||||
}
|
||||
}
|
||||
|
||||
if (_intermediateGeometry.length === 0) {
|
||||
// if there are no geometries then just create a fake empty geometry to provide
|
||||
targetGeometry.setIndex(null)
|
||||
|
||||
// remove all geometry
|
||||
const attrs = targetGeometry.attributes
|
||||
for (const key in attrs) {
|
||||
targetGeometry.deleteAttribute(key)
|
||||
}
|
||||
|
||||
// create dummy attributes
|
||||
for (const key in this.attributes) {
|
||||
targetGeometry.setAttribute(this.attributes[ key ], new BufferAttribute(new Float32Array(0), 4, false))
|
||||
}
|
||||
} else {
|
||||
mergeBufferGeometries(_intermediateGeometry, { useGroups, skipAttributes }, targetGeometry)
|
||||
}
|
||||
|
||||
for (const key in targetGeometry.attributes) {
|
||||
targetGeometry.attributes[ key ].needsUpdate = true
|
||||
}
|
||||
|
||||
return targetGeometry
|
||||
}
|
||||
|
||||
_convertToStaticGeometry(mesh, targetGeometry = new BufferGeometry()) {
|
||||
const geometry = mesh.geometry
|
||||
const applyWorldTransforms = this.applyWorldTransforms
|
||||
const includeNormal = this.attributes.includes('normal')
|
||||
const includeTangent = this.attributes.includes('tangent')
|
||||
const attributes = geometry.attributes
|
||||
const targetAttributes = targetGeometry.attributes
|
||||
|
||||
// initialize the attributes if they don't exist
|
||||
if (!targetGeometry.index && geometry.index) {
|
||||
targetGeometry.index = geometry.index.clone()
|
||||
}
|
||||
|
||||
if (!targetAttributes.position) {
|
||||
targetGeometry.setAttribute('position', createAttributeClone(attributes.position))
|
||||
}
|
||||
|
||||
if (includeNormal && !targetAttributes.normal && attributes.normal) {
|
||||
targetGeometry.setAttribute('normal', createAttributeClone(attributes.normal))
|
||||
}
|
||||
|
||||
if (includeTangent && !targetAttributes.tangent && attributes.tangent) {
|
||||
targetGeometry.setAttribute('tangent', createAttributeClone(attributes.tangent))
|
||||
}
|
||||
|
||||
// ensure the attributes are consistent
|
||||
validateAttributes(geometry.index, targetGeometry.index)
|
||||
validateAttributes(attributes.position, targetAttributes.position)
|
||||
|
||||
if (includeNormal) {
|
||||
validateAttributes(attributes.normal, targetAttributes.normal)
|
||||
}
|
||||
|
||||
if (includeTangent) {
|
||||
validateAttributes(attributes.tangent, targetAttributes.tangent)
|
||||
}
|
||||
|
||||
// generate transformed vertex attribute data
|
||||
const position = attributes.position
|
||||
const normal = includeNormal ? attributes.normal : null
|
||||
const tangent = includeTangent ? attributes.tangent : null
|
||||
const morphPosition = geometry.morphAttributes.position
|
||||
const morphNormal = geometry.morphAttributes.normal
|
||||
const morphTangent = geometry.morphAttributes.tangent
|
||||
const morphTargetsRelative = geometry.morphTargetsRelative
|
||||
const morphInfluences = mesh.morphTargetInfluences
|
||||
const normalMatrix = new Matrix3()
|
||||
normalMatrix.getNormalMatrix(mesh.matrixWorld)
|
||||
|
||||
// copy the index
|
||||
if (geometry.index) {
|
||||
targetGeometry.index.array.set(geometry.index.array)
|
||||
}
|
||||
|
||||
// copy and apply other attributes
|
||||
for (let i = 0, l = attributes.position.count; i < l; i++) {
|
||||
_positionVector.fromBufferAttribute(position, i)
|
||||
if (normal) {
|
||||
_normalVector.fromBufferAttribute(normal, i)
|
||||
}
|
||||
|
||||
if (tangent) {
|
||||
_tangentVector4.fromBufferAttribute(tangent, i)
|
||||
_tangentVector.fromBufferAttribute(tangent, i)
|
||||
}
|
||||
|
||||
// apply morph target transform
|
||||
if (morphInfluences) {
|
||||
if (morphPosition) {
|
||||
applyMorphTarget(morphPosition, morphInfluences, morphTargetsRelative, i, _positionVector)
|
||||
}
|
||||
|
||||
if (morphNormal) {
|
||||
applyMorphTarget(morphNormal, morphInfluences, morphTargetsRelative, i, _normalVector)
|
||||
}
|
||||
|
||||
if (morphTangent) {
|
||||
applyMorphTarget(morphTangent, morphInfluences, morphTargetsRelative, i, _tangentVector)
|
||||
}
|
||||
}
|
||||
|
||||
// apply bone transform
|
||||
if (mesh.isSkinnedMesh) {
|
||||
mesh.applyBoneTransform(i, _positionVector)
|
||||
if (normal) {
|
||||
boneNormalTransform(mesh, i, _normalVector)
|
||||
}
|
||||
|
||||
if (tangent) {
|
||||
boneNormalTransform(mesh, i, _tangentVector)
|
||||
}
|
||||
}
|
||||
|
||||
// update the vectors of the attributes
|
||||
if (applyWorldTransforms) {
|
||||
_positionVector.applyMatrix4(mesh.matrixWorld)
|
||||
}
|
||||
|
||||
targetAttributes.position.setXYZ(i, _positionVector.x, _positionVector.y, _positionVector.z)
|
||||
|
||||
if (normal) {
|
||||
if (applyWorldTransforms) {
|
||||
_normalVector.applyNormalMatrix(normalMatrix)
|
||||
}
|
||||
|
||||
targetAttributes.normal.setXYZ(i, _normalVector.x, _normalVector.y, _normalVector.z)
|
||||
}
|
||||
|
||||
if (tangent) {
|
||||
if (applyWorldTransforms) {
|
||||
_tangentVector.transformDirection(mesh.matrixWorld)
|
||||
}
|
||||
|
||||
targetAttributes.tangent.setXYZW(i, _tangentVector.x, _tangentVector.y, _tangentVector.z, _tangentVector4.w)
|
||||
}
|
||||
}
|
||||
|
||||
// copy other attributes over
|
||||
for (const i in this.attributes) {
|
||||
const key = this.attributes[ i ]
|
||||
if (key === 'position' || key === 'tangent' || key === 'normal' || !(key in attributes)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!targetAttributes[ key ]) {
|
||||
targetGeometry.setAttribute(key, createAttributeClone(attributes[ key ]))
|
||||
}
|
||||
|
||||
validateAttributes(attributes[ key ], targetAttributes[ key ])
|
||||
copyAttributeContents(attributes[ key ], targetAttributes[ key ])
|
||||
}
|
||||
|
||||
if (mesh.matrixWorld.determinant() < 0) {
|
||||
invertGeometry(targetGeometry)
|
||||
}
|
||||
|
||||
return targetGeometry
|
||||
}
|
||||
}
|
||||
116
src/components/DMT/libs/bvh/utils/ThreeRayIntersectUtilities.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import { Vector3, Vector2, Triangle, DoubleSide, BackSide } from 'three'
|
||||
|
||||
// Ripped and modified From THREE.js Mesh raycast
|
||||
// https://github.com/mrdoob/three.js/blob/0aa87c999fe61e216c1133fba7a95772b503eddf/src/objects/Mesh.js#L115
|
||||
const _vA = /* @__PURE__ */ new Vector3()
|
||||
const _vB = /* @__PURE__ */ new Vector3()
|
||||
const _vC = /* @__PURE__ */ new Vector3()
|
||||
|
||||
const _uvA = /* @__PURE__ */ new Vector2()
|
||||
const _uvB = /* @__PURE__ */ new Vector2()
|
||||
const _uvC = /* @__PURE__ */ new Vector2()
|
||||
|
||||
const _normalA = /* @__PURE__ */ new Vector3()
|
||||
const _normalB = /* @__PURE__ */ new Vector3()
|
||||
const _normalC = /* @__PURE__ */ new Vector3()
|
||||
|
||||
const _intersectionPoint = /* @__PURE__ */ new Vector3()
|
||||
function checkIntersection(ray, pA, pB, pC, point, side) {
|
||||
let intersect
|
||||
if (side === BackSide) {
|
||||
intersect = ray.intersectTriangle(pC, pB, pA, true, point)
|
||||
} else {
|
||||
intersect = ray.intersectTriangle(pA, pB, pC, side !== DoubleSide, point)
|
||||
}
|
||||
|
||||
if (intersect === null) return null
|
||||
|
||||
const distance = ray.origin.distanceTo(point)
|
||||
|
||||
return {
|
||||
|
||||
distance: distance,
|
||||
point: point.clone()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function checkBufferGeometryIntersection(ray, position, normal, uv, uv1, a, b, c, side) {
|
||||
_vA.fromBufferAttribute(position, a)
|
||||
_vB.fromBufferAttribute(position, b)
|
||||
_vC.fromBufferAttribute(position, c)
|
||||
|
||||
const intersection = checkIntersection(ray, _vA, _vB, _vC, _intersectionPoint, side)
|
||||
|
||||
if (intersection) {
|
||||
if (uv) {
|
||||
_uvA.fromBufferAttribute(uv, a)
|
||||
_uvB.fromBufferAttribute(uv, b)
|
||||
_uvC.fromBufferAttribute(uv, c)
|
||||
|
||||
intersection.uv = Triangle.getInterpolation(_intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2())
|
||||
}
|
||||
|
||||
if (uv1) {
|
||||
_uvA.fromBufferAttribute(uv1, a)
|
||||
_uvB.fromBufferAttribute(uv1, b)
|
||||
_uvC.fromBufferAttribute(uv1, c)
|
||||
|
||||
intersection.uv1 = Triangle.getInterpolation(_intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2())
|
||||
}
|
||||
|
||||
if (normal) {
|
||||
_normalA.fromBufferAttribute(normal, a)
|
||||
_normalB.fromBufferAttribute(normal, b)
|
||||
_normalC.fromBufferAttribute(normal, c)
|
||||
|
||||
intersection.normal = Triangle.getInterpolation(_intersectionPoint, _vA, _vB, _vC, _normalA, _normalB, _normalC, new Vector3())
|
||||
if (intersection.normal.dot(ray.direction) > 0) {
|
||||
intersection.normal.multiplyScalar(-1)
|
||||
}
|
||||
}
|
||||
|
||||
const face = {
|
||||
a: a,
|
||||
b: b,
|
||||
c: c,
|
||||
normal: new Vector3(),
|
||||
materialIndex: 0
|
||||
}
|
||||
|
||||
Triangle.getNormal(_vA, _vB, _vC, face.normal)
|
||||
|
||||
intersection.face = face
|
||||
intersection.faceIndex = a
|
||||
}
|
||||
|
||||
return intersection
|
||||
}
|
||||
|
||||
// https://github.com/mrdoob/three.js/blob/0aa87c999fe61e216c1133fba7a95772b503eddf/src/objects/Mesh.js#L258
|
||||
function intersectTri(geo, side, ray, tri, intersections) {
|
||||
const triOffset = tri * 3
|
||||
let a = triOffset + 0
|
||||
let b = triOffset + 1
|
||||
let c = triOffset + 2
|
||||
|
||||
const index = geo.index
|
||||
if (geo.index) {
|
||||
a = index.getX(a)
|
||||
b = index.getX(b)
|
||||
c = index.getX(c)
|
||||
}
|
||||
|
||||
const { position, normal, uv, uv1 } = geo.attributes
|
||||
const intersection = checkBufferGeometryIntersection(ray, position, normal, uv, uv1, a, b, c, side)
|
||||
|
||||
if (intersection) {
|
||||
intersection.faceIndex = tri
|
||||
if (intersections) intersections.push(intersection)
|
||||
return intersection
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export { intersectTri }
|
||||
103
src/components/DMT/libs/bvh/utils/TriangleUtilities.js
Normal file
@@ -0,0 +1,103 @@
|
||||
|
||||
import { Vector2, Vector3, Triangle } from 'three'
|
||||
|
||||
// sets the vertices of triangle `tri` with the 3 vertices after i
|
||||
export function setTriangle(tri, i, index, pos) {
|
||||
const ta = tri.a
|
||||
const tb = tri.b
|
||||
const tc = tri.c
|
||||
|
||||
let i0 = i
|
||||
let i1 = i + 1
|
||||
let i2 = i + 2
|
||||
if (index) {
|
||||
i0 = index.getX(i0)
|
||||
i1 = index.getX(i1)
|
||||
i2 = index.getX(i2)
|
||||
}
|
||||
|
||||
ta.x = pos.getX(i0)
|
||||
ta.y = pos.getY(i0)
|
||||
ta.z = pos.getZ(i0)
|
||||
|
||||
tb.x = pos.getX(i1)
|
||||
tb.y = pos.getY(i1)
|
||||
tb.z = pos.getZ(i1)
|
||||
|
||||
tc.x = pos.getX(i2)
|
||||
tc.y = pos.getY(i2)
|
||||
tc.z = pos.getZ(i2)
|
||||
}
|
||||
|
||||
const tempV1 = /* @__PURE__ */ new Vector3()
|
||||
const tempV2 = /* @__PURE__ */ new Vector3()
|
||||
const tempV3 = /* @__PURE__ */ new Vector3()
|
||||
const tempUV1 = /* @__PURE__ */ new Vector2()
|
||||
const tempUV2 = /* @__PURE__ */ new Vector2()
|
||||
const tempUV3 = /* @__PURE__ */ new Vector2()
|
||||
|
||||
export function getTriangleHitPointInfo(point, geometry, triangleIndex, target) {
|
||||
const indices = geometry.getIndex().array
|
||||
const positions = geometry.getAttribute('position')
|
||||
const uvs = geometry.getAttribute('uv')
|
||||
|
||||
const a = indices[ triangleIndex * 3 ]
|
||||
const b = indices[ triangleIndex * 3 + 1 ]
|
||||
const c = indices[ triangleIndex * 3 + 2 ]
|
||||
|
||||
tempV1.fromBufferAttribute(positions, a)
|
||||
tempV2.fromBufferAttribute(positions, b)
|
||||
tempV3.fromBufferAttribute(positions, c)
|
||||
|
||||
// find the associated material index
|
||||
let materialIndex = 0
|
||||
const groups = geometry.groups
|
||||
const firstVertexIndex = triangleIndex * 3
|
||||
for (let i = 0, l = groups.length; i < l; i++) {
|
||||
const group = groups[ i ]
|
||||
const { start, count } = group
|
||||
if (firstVertexIndex >= start && firstVertexIndex < start + count) {
|
||||
materialIndex = group.materialIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// extract uvs
|
||||
let uv = null
|
||||
if (uvs) {
|
||||
tempUV1.fromBufferAttribute(uvs, a)
|
||||
tempUV2.fromBufferAttribute(uvs, b)
|
||||
tempUV3.fromBufferAttribute(uvs, c)
|
||||
|
||||
if (target && target.uv) uv = target.uv
|
||||
else uv = new Vector2()
|
||||
|
||||
Triangle.getInterpolation(point, tempV1, tempV2, tempV3, tempUV1, tempUV2, tempUV3, uv)
|
||||
}
|
||||
|
||||
// adjust the provided target or create a new one
|
||||
if (target) {
|
||||
if (!target.face) target.face = { }
|
||||
target.face.a = a
|
||||
target.face.b = b
|
||||
target.face.c = c
|
||||
target.face.materialIndex = materialIndex
|
||||
if (!target.face.normal) target.face.normal = new Vector3()
|
||||
Triangle.getNormal(tempV1, tempV2, tempV3, target.face.normal)
|
||||
|
||||
if (uv) target.uv = uv
|
||||
|
||||
return target
|
||||
} else {
|
||||
return {
|
||||
face: {
|
||||
a: a,
|
||||
b: b,
|
||||
c: c,
|
||||
materialIndex: materialIndex,
|
||||
normal: Triangle.getNormal(tempV1, tempV2, tempV3, new Vector3())
|
||||
},
|
||||
uv: uv
|
||||
}
|
||||
}
|
||||
}
|
||||
88
src/components/DMT/libs/bvh/workers/GenerateMeshBVHWorker.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Box3, BufferAttribute } from 'three'
|
||||
import { MeshBVH } from '../core/MeshBVH.js'
|
||||
import { WorkerBase } from './utils/WorkerBase.js'
|
||||
|
||||
export class GenerateMeshBVHWorker extends WorkerBase {
|
||||
constructor() {
|
||||
const worker = new Worker(new URL('./generateMeshBVH.worker.js', import.meta.url), { type: 'module' })
|
||||
super(worker)
|
||||
this.name = 'GenerateMeshBVHWorker'
|
||||
}
|
||||
|
||||
runTask(worker, geometry, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (
|
||||
geometry.getAttribute('position').isInterleavedBufferAttribute ||
|
||||
geometry.index && geometry.index.isInterleavedBufferAttribute
|
||||
) {
|
||||
throw new Error('GenerateMeshBVHWorker: InterleavedBufferAttribute are not supported for the geometry attributes.')
|
||||
}
|
||||
|
||||
worker.onerror = e => {
|
||||
reject(new Error(`GenerateMeshBVHWorker: ${e.message}`))
|
||||
}
|
||||
|
||||
worker.onmessage = e => {
|
||||
const { data } = e
|
||||
|
||||
if (data.error) {
|
||||
reject(new Error(data.error))
|
||||
worker.onmessage = null
|
||||
} else if (data.serialized) {
|
||||
const { serialized, position } = data
|
||||
const bvh = MeshBVH.deserialize(serialized, geometry, { setIndex: false })
|
||||
const boundsOptions = Object.assign({
|
||||
|
||||
setBoundingBox: true
|
||||
|
||||
}, options)
|
||||
|
||||
// we need to replace the arrays because they're neutered entirely by the
|
||||
// webworker transfer.
|
||||
geometry.attributes.position.array = position
|
||||
if (serialized.index) {
|
||||
if (geometry.index) {
|
||||
geometry.index.array = serialized.index
|
||||
} else {
|
||||
const newIndex = new BufferAttribute(serialized.index, 1, false)
|
||||
geometry.setIndex(newIndex)
|
||||
}
|
||||
}
|
||||
|
||||
if (boundsOptions.setBoundingBox) {
|
||||
geometry.boundingBox = bvh.getBoundingBox(new Box3())
|
||||
}
|
||||
|
||||
if (options.onProgress) {
|
||||
options.onProgress(data.progress)
|
||||
}
|
||||
|
||||
resolve(bvh)
|
||||
worker.onmessage = null
|
||||
} else if (options.onProgress) {
|
||||
options.onProgress(data.progress)
|
||||
}
|
||||
}
|
||||
|
||||
const index = geometry.index ? geometry.index.array : null
|
||||
const position = geometry.attributes.position.array
|
||||
const transferable = [position]
|
||||
if (index) {
|
||||
transferable.push(index)
|
||||
}
|
||||
|
||||
worker.postMessage({
|
||||
|
||||
index,
|
||||
position,
|
||||
options: {
|
||||
...options,
|
||||
onProgress: null,
|
||||
includedProgressCallback: Boolean(options.onProgress),
|
||||
groups: [... geometry.groups]
|
||||
}
|
||||
|
||||
}, transferable.map(arr => arr.buffer).filter(v => (typeof SharedArrayBuffer === 'undefined') || !(v instanceof SharedArrayBuffer)))
|
||||
})
|
||||
}
|
||||
}
|
||||
112
src/components/DMT/libs/bvh/workers/ParallelMeshBVHWorker.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import { Box3, BufferAttribute } from 'three'
|
||||
import { MeshBVH } from '../core/MeshBVH.js'
|
||||
import { WorkerBase } from './utils/WorkerBase.js'
|
||||
import { convertToBufferType, isSharedArrayBufferSupported } from '../utils/BufferUtils.js'
|
||||
import { GenerateMeshBVHWorker } from './GenerateMeshBVHWorker.js'
|
||||
import { ensureIndex } from '../core/build/geometryUtils.js'
|
||||
|
||||
const DEFAULT_WORKER_COUNT = typeof navigator !== 'undefined' ? navigator.hardwareConcurrency : 4
|
||||
class _ParallelMeshBVHWorker extends WorkerBase {
|
||||
constructor() {
|
||||
const worker = new Worker(new URL('./parallelMeshBVH.worker.js', import.meta.url), { type: 'module' })
|
||||
super(worker)
|
||||
|
||||
this.name = 'ParallelMeshBVHWorker'
|
||||
this.maxWorkerCount = Math.max(DEFAULT_WORKER_COUNT, 4)
|
||||
|
||||
if (!isSharedArrayBufferSupported()) {
|
||||
throw new Error('ParallelMeshBVHWorker: Shared Array Buffers are not supported.')
|
||||
}
|
||||
}
|
||||
|
||||
runTask(worker, geometry, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!geometry.index && !options.indirect) {
|
||||
ensureIndex(geometry, options)
|
||||
}
|
||||
|
||||
if (
|
||||
geometry.getAttribute('position').isInterleavedBufferAttribute ||
|
||||
geometry.index && geometry.index.isInterleavedBufferAttribute
|
||||
) {
|
||||
throw new Error('ParallelMeshBVHWorker: InterleavedBufferAttribute are not supported for the geometry attributes.')
|
||||
}
|
||||
|
||||
worker.onerror = e => {
|
||||
reject(new Error(`ParallelMeshBVHWorker: ${e.message}`))
|
||||
}
|
||||
|
||||
worker.onmessage = e => {
|
||||
const { data } = e
|
||||
|
||||
if (data.error) {
|
||||
reject(new Error(data.error))
|
||||
worker.onmessage = null
|
||||
} else if (data.serialized) {
|
||||
const { serialized, position } = data
|
||||
const bvh = MeshBVH.deserialize(serialized, geometry, { setIndex: false })
|
||||
const boundsOptions = {
|
||||
setBoundingBox: true,
|
||||
...options
|
||||
}
|
||||
|
||||
// we need to replace the arrays because they're neutered entirely by the
|
||||
// webworker transfer.
|
||||
geometry.attributes.position.array = position
|
||||
if (serialized.index) {
|
||||
if (geometry.index) {
|
||||
geometry.index.array = serialized.index
|
||||
} else {
|
||||
const newIndex = new BufferAttribute(serialized.index, 1, false)
|
||||
geometry.setIndex(newIndex)
|
||||
}
|
||||
}
|
||||
|
||||
if (boundsOptions.setBoundingBox) {
|
||||
geometry.boundingBox = bvh.getBoundingBox(new Box3())
|
||||
}
|
||||
|
||||
if (options.onProgress) {
|
||||
options.onProgress(data.progress)
|
||||
}
|
||||
|
||||
resolve(bvh)
|
||||
worker.onmessage = null
|
||||
} else if (options.onProgress) {
|
||||
options.onProgress(data.progress)
|
||||
}
|
||||
}
|
||||
|
||||
const index = geometry.index ? geometry.index.array : null
|
||||
const position = geometry.attributes.position.array
|
||||
worker.postMessage({
|
||||
|
||||
operation: 'BUILD_BVH',
|
||||
maxWorkerCount: this.maxWorkerCount,
|
||||
index: convertToBufferType(index, SharedArrayBuffer),
|
||||
position: convertToBufferType(position, SharedArrayBuffer),
|
||||
options: {
|
||||
...options,
|
||||
onProgress: null,
|
||||
includedProgressCallback: Boolean(options.onProgress),
|
||||
groups: [... geometry.groups]
|
||||
}
|
||||
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class ParallelMeshBVHWorker {
|
||||
constructor() {
|
||||
if (isSharedArrayBufferSupported()) {
|
||||
return new _ParallelMeshBVHWorker()
|
||||
} else {
|
||||
console.warn('ParallelMeshBVHWorker: SharedArrayBuffers not supported. Falling back to single-threaded GenerateMeshBVHWorker.')
|
||||
|
||||
const object = new GenerateMeshBVHWorker()
|
||||
object.maxWorkerCount = DEFAULT_WORKER_COUNT
|
||||
return object
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
BufferGeometry,
|
||||
BufferAttribute
|
||||
} from 'three'
|
||||
import { MeshBVH } from '../core/MeshBVH.js'
|
||||
|
||||
onmessage = ({ data }) => {
|
||||
let prevTime = performance.now()
|
||||
function onProgressCallback(progress) {
|
||||
// account for error
|
||||
progress = Math.min(progress, 1)
|
||||
|
||||
const currTime = performance.now()
|
||||
if (currTime - prevTime >= 10 && progress !== 1.0) {
|
||||
postMessage({
|
||||
|
||||
error: null,
|
||||
serialized: null,
|
||||
position: null,
|
||||
progress
|
||||
|
||||
})
|
||||
prevTime = currTime
|
||||
}
|
||||
}
|
||||
|
||||
const { index, position, options } = data
|
||||
try {
|
||||
const geometry = new BufferGeometry()
|
||||
geometry.setAttribute('position', new BufferAttribute(position, 3, false))
|
||||
if (index) {
|
||||
geometry.setIndex(new BufferAttribute(index, 1, false))
|
||||
}
|
||||
|
||||
if (options.includedProgressCallback) {
|
||||
options.onProgress = onProgressCallback
|
||||
}
|
||||
|
||||
if (options.groups) {
|
||||
const groups = options.groups
|
||||
for (const i in groups) {
|
||||
const group = groups[ i ]
|
||||
geometry.addGroup(group.start, group.count, group.materialIndex)
|
||||
}
|
||||
}
|
||||
|
||||
const bvh = new MeshBVH(geometry, options)
|
||||
const serialized = MeshBVH.serialize(bvh, { copyIndexBuffer: false })
|
||||
let toTransfer = [position.buffer, ...serialized.roots]
|
||||
if (serialized.index) {
|
||||
toTransfer.push(serialized.index.buffer)
|
||||
}
|
||||
|
||||
toTransfer = toTransfer.filter(v => (typeof SharedArrayBuffer === 'undefined') || !(v instanceof SharedArrayBuffer))
|
||||
|
||||
if (bvh._indirectBuffer) {
|
||||
toTransfer.push(serialized.indirectBuffer.buffer)
|
||||
}
|
||||
|
||||
postMessage({
|
||||
|
||||
error: null,
|
||||
serialized,
|
||||
position,
|
||||
progress: 1
|
||||
|
||||
}, toTransfer)
|
||||
} catch (error) {
|
||||
postMessage({
|
||||
|
||||
error,
|
||||
serialized: null,
|
||||
position: null,
|
||||
progress: 1
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
260
src/components/DMT/libs/bvh/workers/parallelMeshBVH.worker.js
Normal file
@@ -0,0 +1,260 @@
|
||||
import { MathUtils, BufferGeometry, BufferAttribute } from 'three'
|
||||
import { WorkerPool } from './utils/WorkerPool.js'
|
||||
import { BYTES_PER_NODE } from '../core/Constants.js'
|
||||
import { buildTree, generateIndirectBuffer } from '../core/build/buildTree.js'
|
||||
import { countNodes, populateBuffer } from '../core/build/buildUtils.js'
|
||||
import { computeTriangleBounds } from '../core/build/computeBoundsUtils.js'
|
||||
import { getFullGeometryRange, getRootIndexRanges, getTriCount } from '../core/build/geometryUtils.js'
|
||||
import { DEFAULT_OPTIONS } from '../core/MeshBVH.js'
|
||||
|
||||
let isRunning = false
|
||||
let prevTime = 0
|
||||
const workerPool = new WorkerPool(() => new Worker(new URL('./parallelMeshBVH.worker.js', import.meta.url), { type: 'module' }))
|
||||
|
||||
onmessage = async({ data }) => {
|
||||
if (isRunning) {
|
||||
throw new Error('Worker is already running a task.')
|
||||
}
|
||||
|
||||
const { operation } = data
|
||||
if (operation === 'BUILD_BVH') {
|
||||
isRunning = true
|
||||
|
||||
const {
|
||||
maxWorkerCount,
|
||||
index,
|
||||
position,
|
||||
options
|
||||
} = data
|
||||
|
||||
// initialize the number of workers balanced for a binary tree
|
||||
workerPool.setWorkerCount(MathUtils.floorPowerOfTwo(maxWorkerCount))
|
||||
|
||||
// generate necessary buffers and objects
|
||||
const geometry = getGeometry(index, position)
|
||||
const geometryRanges = options.indirect ? getFullGeometryRange(geometry) : getRootIndexRanges(geometry)
|
||||
const indirectBuffer = options.indirect ? generateIndirectBuffer(geometry, true) : null
|
||||
const triCount = getTriCount(geometry)
|
||||
const triangleBounds = new Float32Array(new SharedArrayBuffer(triCount * 6 * 4))
|
||||
|
||||
// generate portions of the triangle bounds buffer over multiple frames
|
||||
const boundsPromises = []
|
||||
for (let i = 0, l = workerPool.workerCount; i < l; i++) {
|
||||
const countPerWorker = Math.ceil(triCount / l)
|
||||
const offset = i * countPerWorker
|
||||
const count = Math.min(countPerWorker, triCount - offset)
|
||||
|
||||
boundsPromises.push(workerPool.runSubTask(
|
||||
i,
|
||||
{
|
||||
operation: 'BUILD_TRIANGLE_BOUNDS',
|
||||
offset,
|
||||
count,
|
||||
index,
|
||||
position,
|
||||
triangleBounds
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
await Promise.all(boundsPromises)
|
||||
|
||||
// create a proxy bvh structure
|
||||
const proxyBvh = {
|
||||
_indirectBuffer: indirectBuffer,
|
||||
geometry: geometry
|
||||
}
|
||||
|
||||
let totalProgress = 0
|
||||
|
||||
const localOptions = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...options,
|
||||
verbose: false,
|
||||
maxDepth: Math.round(Math.log2(workerPool.workerCount)),
|
||||
onProgress: options.includedProgressCallback
|
||||
? getOnProgressDeltaCallback(delta => {
|
||||
totalProgress += 0.1 * delta
|
||||
triggerOnProgress(totalProgress)
|
||||
})
|
||||
: null
|
||||
}
|
||||
|
||||
// generate the ranges for all roots asynchronously
|
||||
const packedRoots = []
|
||||
for (let i = 0, l = geometryRanges.length; i < l; i++) {
|
||||
// build the tree down to the necessary depth
|
||||
const promises = []
|
||||
const range = geometryRanges[ i ]
|
||||
const root = buildTree(proxyBvh, triangleBounds, range.offset, range.count, localOptions)
|
||||
const flatNodes = flattenNodes(root)
|
||||
let bufferLengths = 0
|
||||
let remainingNodes = 0
|
||||
let nextWorker = 0
|
||||
|
||||
// trigger workers for each generated leaf node
|
||||
for (let j = 0, l = flatNodes.length; j < l; j++) {
|
||||
const node = flatNodes[ j ]
|
||||
const isLeaf = Boolean(node.count)
|
||||
if (isLeaf) {
|
||||
// adjust the maxDepth to account for the depth we've already traversed
|
||||
const workerOptions = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...options
|
||||
}
|
||||
|
||||
workerOptions.maxDepth = workerOptions.maxDepth - node.depth
|
||||
|
||||
const pr = workerPool.runSubTask(
|
||||
nextWorker++,
|
||||
{
|
||||
operation: 'BUILD_SUBTREE',
|
||||
offset: node.offset,
|
||||
count: node.count,
|
||||
indirectBuffer,
|
||||
index,
|
||||
position,
|
||||
triangleBounds,
|
||||
options: workerOptions
|
||||
},
|
||||
getOnProgressDeltaCallback(delta => {
|
||||
totalProgress += 0.9 * delta / nextWorker
|
||||
triggerOnProgress(totalProgress)
|
||||
}),
|
||||
).then(data => {
|
||||
const buffer = data.buffer
|
||||
node.buffer = buffer
|
||||
bufferLengths += buffer.byteLength
|
||||
})
|
||||
|
||||
promises.push(pr)
|
||||
} else {
|
||||
remainingNodes++
|
||||
}
|
||||
}
|
||||
|
||||
// wait for the sub trees to complete
|
||||
await Promise.all(promises)
|
||||
|
||||
const BufferConstructor = options.useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer
|
||||
const buffer = new BufferConstructor(bufferLengths + remainingNodes * BYTES_PER_NODE)
|
||||
populateBuffer(0, root, buffer)
|
||||
|
||||
packedRoots.push(buffer)
|
||||
}
|
||||
|
||||
// transfer the data back
|
||||
postMessage({
|
||||
error: null,
|
||||
serialized: {
|
||||
roots: packedRoots,
|
||||
index: index,
|
||||
indirectBuffer: indirectBuffer
|
||||
},
|
||||
position,
|
||||
progress: 1
|
||||
})
|
||||
|
||||
isRunning = false
|
||||
} else if (operation === 'BUILD_SUBTREE') {
|
||||
const {
|
||||
offset,
|
||||
count,
|
||||
indirectBuffer,
|
||||
index,
|
||||
position,
|
||||
triangleBounds,
|
||||
options
|
||||
} = data
|
||||
|
||||
const proxyBvh = {
|
||||
_indirectBuffer: indirectBuffer,
|
||||
geometry: getGeometry(index, position)
|
||||
}
|
||||
|
||||
const localOptions = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...options,
|
||||
onProgress: options.includedProgressCallback ? triggerOnProgress : null
|
||||
}
|
||||
|
||||
const root = buildTree(proxyBvh, triangleBounds, offset, count, localOptions)
|
||||
const nodeCount = countNodes(root)
|
||||
const buffer = new ArrayBuffer(BYTES_PER_NODE * nodeCount)
|
||||
populateBuffer(0, root, buffer)
|
||||
postMessage({ type: 'result', buffer, progress: 1 }, [buffer])
|
||||
} else if (operation === 'BUILD_TRIANGLE_BOUNDS') {
|
||||
const {
|
||||
index,
|
||||
position,
|
||||
triangleBounds,
|
||||
offset,
|
||||
count
|
||||
} = data
|
||||
|
||||
const geometry = getGeometry(index, position)
|
||||
computeTriangleBounds(geometry, triangleBounds, offset, count)
|
||||
postMessage({ type: 'result' })
|
||||
} else if (operation === 'REFIT') {
|
||||
|
||||
// TODO
|
||||
|
||||
} else if (operation === 'REFIT_SUBTREE') {
|
||||
|
||||
// TODO
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions and utils
|
||||
function getOnProgressDeltaCallback(cb) {
|
||||
let lastProgress = 0
|
||||
return function onProgressDeltaCallback(progress) {
|
||||
cb(progress - lastProgress)
|
||||
lastProgress = progress
|
||||
}
|
||||
}
|
||||
|
||||
function triggerOnProgress(progress) {
|
||||
// account for error
|
||||
progress = Math.min(progress, 1)
|
||||
|
||||
const currTime = performance.now()
|
||||
if (currTime - prevTime >= 10 && progress !== 1.0) {
|
||||
postMessage({
|
||||
|
||||
error: null,
|
||||
progress,
|
||||
type: 'progress'
|
||||
|
||||
})
|
||||
prevTime = currTime
|
||||
}
|
||||
}
|
||||
|
||||
function getGeometry(index, position) {
|
||||
const geometry = new BufferGeometry()
|
||||
if (index) {
|
||||
geometry.index = new BufferAttribute(index, 1, false)
|
||||
}
|
||||
|
||||
geometry.setAttribute('position', new BufferAttribute(position, 3))
|
||||
return geometry
|
||||
}
|
||||
|
||||
function flattenNodes(node) {
|
||||
const arr = []
|
||||
traverse(node)
|
||||
return arr
|
||||
|
||||
function traverse(node, depth = 0) {
|
||||
node.depth = depth
|
||||
arr.push(node)
|
||||
|
||||
const isLeaf = Boolean(node.count)
|
||||
if (!isLeaf) {
|
||||
traverse(node.left, depth + 1)
|
||||
traverse(node.right, depth + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/components/DMT/libs/bvh/workers/utils/WorkerBase.js
Normal file
@@ -0,0 +1,60 @@
|
||||
export class WorkerBase {
|
||||
|
||||
constructor( worker ) {
|
||||
|
||||
this.name = 'WorkerBase';
|
||||
this.running = false;
|
||||
this.worker = worker;
|
||||
this.worker.onerror = e => {
|
||||
|
||||
if ( e.message ) {
|
||||
|
||||
throw new Error( `${ this.name }: Could not create Web Worker with error "${ e.message }"` );
|
||||
|
||||
} else {
|
||||
|
||||
throw new Error( `${ this.name }: Could not create Web Worker.` );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
runTask() {}
|
||||
|
||||
generate( ...args ) {
|
||||
|
||||
if ( this.running ) {
|
||||
|
||||
throw new Error( 'GenerateMeshBVHWorker: Already running job.' );
|
||||
|
||||
}
|
||||
|
||||
if ( this.worker === null ) {
|
||||
|
||||
throw new Error( 'GenerateMeshBVHWorker: Worker has been disposed.' );
|
||||
|
||||
}
|
||||
|
||||
this.running = true;
|
||||
|
||||
const promise = this.runTask( this.worker, ...args );
|
||||
promise.finally( () => {
|
||||
|
||||
this.running = false;
|
||||
|
||||
} );
|
||||
|
||||
return promise;
|
||||
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
this.worker.terminate();
|
||||
this.worker = null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
82
src/components/DMT/libs/bvh/workers/utils/WorkerPool.js
Normal file
@@ -0,0 +1,82 @@
|
||||
export class WorkerPool {
|
||||
|
||||
get workerCount() {
|
||||
|
||||
return this.workers.length;
|
||||
|
||||
}
|
||||
|
||||
constructor( getWorkerCallback ) {
|
||||
|
||||
this.workers = [];
|
||||
this._getWorker = getWorkerCallback;
|
||||
|
||||
}
|
||||
|
||||
setWorkerCount( count ) {
|
||||
|
||||
const workers = this.workers;
|
||||
while ( workers.length < count ) {
|
||||
|
||||
workers.push( this._getWorker() );
|
||||
|
||||
}
|
||||
|
||||
while ( workers.length > count ) {
|
||||
|
||||
workers.pop().terminate();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
runSubTask( i, msg, onProgress ) {
|
||||
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
|
||||
const worker = this.workers[ i ];
|
||||
if ( worker.isRunning ) {
|
||||
|
||||
throw new Error( `${ this.name }: Worker ${ i } is already running.` );
|
||||
|
||||
}
|
||||
|
||||
worker.isRunning = true;
|
||||
worker.postMessage( msg );
|
||||
worker.onerror = e => {
|
||||
|
||||
worker.isRunning = false;
|
||||
reject( e );
|
||||
|
||||
};
|
||||
|
||||
worker.onmessage = e => {
|
||||
|
||||
if ( e.data.type === 'progress' ) {
|
||||
|
||||
if ( onProgress ) {
|
||||
|
||||
onProgress( e.data.progress );
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if ( onProgress ) {
|
||||
|
||||
onProgress( 1 );
|
||||
|
||||
}
|
||||
|
||||
worker.isRunning = false;
|
||||
resolve( e.data );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
49
src/directive/clipboard/clipboard.js
Normal file
@@ -0,0 +1,49 @@
|
||||
// Inspired by https://github.com/Inndy/vue-clipboard2
|
||||
const Clipboard = require('clipboard')
|
||||
if (!Clipboard) {
|
||||
throw new Error('you shold npm install `clipboard` --save at first ')
|
||||
}
|
||||
|
||||
export default {
|
||||
bind(el, binding) {
|
||||
if (binding.arg === 'success') {
|
||||
el._v_clipboard_success = binding.value
|
||||
} else if (binding.arg === 'error') {
|
||||
el._v_clipboard_error = binding.value
|
||||
} else {
|
||||
const clipboard = new Clipboard(el, {
|
||||
text() { return binding.value },
|
||||
action() { return binding.arg === 'cut' ? 'cut' : 'copy' }
|
||||
})
|
||||
clipboard.on('success', e => {
|
||||
const callback = el._v_clipboard_success
|
||||
callback && callback(e) // eslint-disable-line
|
||||
})
|
||||
clipboard.on('error', e => {
|
||||
const callback = el._v_clipboard_error
|
||||
callback && callback(e) // eslint-disable-line
|
||||
})
|
||||
el._v_clipboard = clipboard
|
||||
}
|
||||
},
|
||||
update(el, binding) {
|
||||
if (binding.arg === 'success') {
|
||||
el._v_clipboard_success = binding.value
|
||||
} else if (binding.arg === 'error') {
|
||||
el._v_clipboard_error = binding.value
|
||||
} else {
|
||||
el._v_clipboard.text = function() { return binding.value }
|
||||
el._v_clipboard.action = function() { return binding.arg === 'cut' ? 'cut' : 'copy' }
|
||||
}
|
||||
},
|
||||
unbind(el, binding) {
|
||||
if (binding.arg === 'success') {
|
||||
delete el._v_clipboard_success
|
||||
} else if (binding.arg === 'error') {
|
||||
delete el._v_clipboard_error
|
||||
} else {
|
||||
el._v_clipboard.destroy()
|
||||
delete el._v_clipboard
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/directive/clipboard/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import Clipboard from './clipboard'
|
||||
|
||||
const install = function(Vue) {
|
||||
Vue.directive('Clipboard', Clipboard)
|
||||
}
|
||||
|
||||
if (window.Vue) {
|
||||
window.clipboard = Clipboard
|
||||
Vue.use(install); // eslint-disable-line
|
||||
}
|
||||
|
||||
Clipboard.install = install
|
||||
export default Clipboard
|
||||
46
src/directive/el-dragDialog/drag.js
Normal file
@@ -0,0 +1,46 @@
|
||||
export default{
|
||||
bind(el, binding) {
|
||||
const dialogHeaderEl = el.querySelector('.el-dialog__header')
|
||||
const dragDom = el.querySelector('.el-dialog')
|
||||
dialogHeaderEl.style = 'cursor:move;'
|
||||
|
||||
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
|
||||
const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null)
|
||||
|
||||
dialogHeaderEl.onmousedown = (e) => {
|
||||
// 鼠标按下,计算当前元素距离可视区的距离
|
||||
const disX = e.clientX - dialogHeaderEl.offsetLeft
|
||||
const disY = e.clientY - dialogHeaderEl.offsetTop
|
||||
|
||||
// 获取到的值带px 正则匹配替换
|
||||
let styL, styT
|
||||
|
||||
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
|
||||
if (sty.left.includes('%')) {
|
||||
styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100)
|
||||
styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100)
|
||||
} else {
|
||||
styL = +sty.left.replace(/\px/g, '')
|
||||
styT = +sty.top.replace(/\px/g, '')
|
||||
}
|
||||
|
||||
document.onmousemove = function(e) {
|
||||
// 通过事件委托,计算移动的距离
|
||||
const l = e.clientX - disX
|
||||
const t = e.clientY - disY
|
||||
|
||||
// 移动当前元素
|
||||
dragDom.style.left = `${l + styL}px`
|
||||
dragDom.style.top = `${t + styT}px`
|
||||
|
||||
// 将此时的位置传出去
|
||||
// binding.value({x:e.pageX,y:e.pageY})
|
||||
}
|
||||
|
||||
document.onmouseup = function(e) {
|
||||
document.onmousemove = null
|
||||
document.onmouseup = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/directive/el-dragDialog/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import drag from './drag'
|
||||
|
||||
const install = function(Vue) {
|
||||
Vue.directive('el-drag-dialog', drag)
|
||||
}
|
||||
|
||||
if (window.Vue) {
|
||||
window['el-drag-dialog'] = drag
|
||||
Vue.use(install); // eslint-disable-line
|
||||
}
|
||||
|
||||
drag.install = install
|
||||
export default drag
|
||||
91
src/directive/sticky.js
Normal file
@@ -0,0 +1,91 @@
|
||||
const vueSticky = {}
|
||||
let listenAction
|
||||
vueSticky.install = Vue => {
|
||||
Vue.directive('sticky', {
|
||||
inserted(el, binding) {
|
||||
const params = binding.value || {}
|
||||
const stickyTop = params.stickyTop || 0
|
||||
const zIndex = params.zIndex || 1000
|
||||
const elStyle = el.style
|
||||
|
||||
elStyle.position = '-webkit-sticky'
|
||||
elStyle.position = 'sticky'
|
||||
// if the browser support css sticky(Currently Safari, Firefox and Chrome Canary)
|
||||
// if (~elStyle.position.indexOf('sticky')) {
|
||||
// elStyle.top = `${stickyTop}px`;
|
||||
// elStyle.zIndex = zIndex;
|
||||
// return
|
||||
// }
|
||||
const elHeight = el.getBoundingClientRect().height
|
||||
const elWidth = el.getBoundingClientRect().width
|
||||
elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`
|
||||
|
||||
const parentElm = el.parentNode || document.documentElement
|
||||
const placeholder = document.createElement('div')
|
||||
placeholder.style.display = 'none'
|
||||
placeholder.style.width = `${elWidth}px`
|
||||
placeholder.style.height = `${elHeight}px`
|
||||
parentElm.insertBefore(placeholder, el)
|
||||
|
||||
let active = false
|
||||
|
||||
const getScroll = (target, top) => {
|
||||
const prop = top ? 'pageYOffset' : 'pageXOffset'
|
||||
const method = top ? 'scrollTop' : 'scrollLeft'
|
||||
let ret = target[prop]
|
||||
if (typeof ret !== 'number') {
|
||||
ret = window.document.documentElement[method]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
const sticky = () => {
|
||||
if (active) {
|
||||
return
|
||||
}
|
||||
if (!elStyle.height) {
|
||||
elStyle.height = `${el.offsetHeight}px`
|
||||
}
|
||||
|
||||
elStyle.position = 'fixed'
|
||||
elStyle.width = `${elWidth}px`
|
||||
placeholder.style.display = 'inline-block'
|
||||
active = true
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
if (!active) {
|
||||
return
|
||||
}
|
||||
|
||||
elStyle.position = ''
|
||||
placeholder.style.display = 'none'
|
||||
active = false
|
||||
}
|
||||
|
||||
const check = () => {
|
||||
const scrollTop = getScroll(window, true)
|
||||
const offsetTop = el.getBoundingClientRect().top
|
||||
if (offsetTop < stickyTop) {
|
||||
sticky()
|
||||
} else {
|
||||
if (scrollTop < elHeight + stickyTop) {
|
||||
reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
listenAction = () => {
|
||||
check()
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', listenAction)
|
||||
},
|
||||
|
||||
unbind() {
|
||||
window.removeEventListener('scroll', listenAction)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default vueSticky
|
||||
|
||||
13
src/directive/waves/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import waves from './waves'
|
||||
|
||||
const install = function(Vue) {
|
||||
Vue.directive('waves', waves)
|
||||
}
|
||||
|
||||
if (window.Vue) {
|
||||
window.waves = waves
|
||||
Vue.use(install); // eslint-disable-line
|
||||
}
|
||||
|
||||
waves.install = install
|
||||
export default waves
|
||||
26
src/directive/waves/waves.css
Normal file
@@ -0,0 +1,26 @@
|
||||
.waves-ripple {
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
background-clip: padding-box;
|
||||
pointer-events: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-transform: scale(0);
|
||||
-ms-transform: scale(0);
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.waves-ripple.z-active {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(2);
|
||||
-ms-transform: scale(2);
|
||||
transform: scale(2);
|
||||
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
|
||||
}
|
||||
42
src/directive/waves/waves.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import './waves.css'
|
||||
|
||||
export default{
|
||||
bind(el, binding) {
|
||||
el.addEventListener('click', e => {
|
||||
const customOpts = Object.assign({}, binding.value)
|
||||
const opts = Object.assign({
|
||||
ele: el, // 波纹作用元素
|
||||
type: 'hit', // hit点击位置扩散center中心点扩展
|
||||
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
|
||||
}, customOpts)
|
||||
const target = opts.ele
|
||||
if (target) {
|
||||
target.style.position = 'relative'
|
||||
target.style.overflow = 'hidden'
|
||||
const rect = target.getBoundingClientRect()
|
||||
let ripple = target.querySelector('.waves-ripple')
|
||||
if (!ripple) {
|
||||
ripple = document.createElement('span')
|
||||
ripple.className = 'waves-ripple'
|
||||
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
|
||||
target.appendChild(ripple)
|
||||
} else {
|
||||
ripple.className = 'waves-ripple'
|
||||
}
|
||||
switch (opts.type) {
|
||||
case 'center':
|
||||
ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'
|
||||
ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'
|
||||
break
|
||||
default:
|
||||
ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px'
|
||||
ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px'
|
||||
}
|
||||
ripple.style.backgroundColor = opts.color
|
||||
ripple.className = 'waves-ripple z-active'
|
||||
return false
|
||||
}
|
||||
}, false)
|
||||
}
|
||||
}
|
||||
|
||||
370
src/icon/demo.css
Normal file
@@ -0,0 +1,370 @@
|
||||
*{margin: 0;padding: 0;list-style: none;}
|
||||
/*
|
||||
KISSY CSS Reset
|
||||
理念:1. reset 的目的不是清除浏览器的默认样式,这仅是部分工作。清除和重置是紧密不可分的。
|
||||
2. reset 的目的不是让默认样式在所有浏览器下一致,而是减少默认样式有可能带来的问题。
|
||||
3. reset 期望提供一套普适通用的基础样式。但没有银弹,推荐根据具体需求,裁剪和修改后再使用。
|
||||
特色:1. 适应中文;2. 基于最新主流浏览器。
|
||||
维护:玉伯<lifesinger@gmail.com>, 正淳<ragecarrier@gmail.com>
|
||||
*/
|
||||
|
||||
/** 清除内外边距 **/
|
||||
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* structural elements 结构元素 */
|
||||
dl, dt, dd, ul, ol, li, /* list elements 列表元素 */
|
||||
pre, /* text formatting elements 文本格式元素 */
|
||||
form, fieldset, legend, button, input, textarea, /* form elements 表单元素 */
|
||||
th, td /* table elements 表格元素 */ {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/** 设置默认字体 **/
|
||||
body,
|
||||
button, input, select, textarea /* for ie */ {
|
||||
font: 12px/1.5 tahoma, arial, \5b8b\4f53, sans-serif;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 { font-size: 100%; }
|
||||
address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */
|
||||
code, kbd, pre, samp { font-family: courier new, courier, monospace; } /* 统一等宽字体 */
|
||||
small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */
|
||||
|
||||
/** 重置列表元素 **/
|
||||
ul, ol { list-style: none; }
|
||||
|
||||
/** 重置文本格式元素 **/
|
||||
a { text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
|
||||
/** 重置表单元素 **/
|
||||
legend { color: #000; } /* for ie6 */
|
||||
fieldset, img { border: 0; } /* img 搭车:让链接里的 img 无边框 */
|
||||
button, input, select, textarea { font-size: 100%; } /* 使得表单元素在 ie 下能继承字体大小 */
|
||||
/* 注:optgroup 无法扶正 */
|
||||
|
||||
/** 重置表格元素 **/
|
||||
table { border-collapse: collapse; border-spacing: 0; }
|
||||
|
||||
/* 清除浮动 */
|
||||
.ks-clear:after, .clear:after {
|
||||
content: '\20';
|
||||
display: block;
|
||||
height: 0;
|
||||
clear: both;
|
||||
}
|
||||
.ks-clear, .clear {
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 30px 100px;
|
||||
width: 960px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.main h1{font-size:36px; color:#333; text-align:left;margin-bottom:30px; border-bottom: 1px solid #eee;}
|
||||
|
||||
.helps{margin-top:40px;}
|
||||
.helps pre{
|
||||
padding:20px;
|
||||
margin:10px 0;
|
||||
border:solid 1px #e7e1cd;
|
||||
background-color: #fffdef;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.icon_lists{
|
||||
width: 100% !important;
|
||||
|
||||
}
|
||||
|
||||
.icon_lists li{
|
||||
float:left;
|
||||
width: 100px;
|
||||
height:180px;
|
||||
text-align: center;
|
||||
list-style: none !important;
|
||||
}
|
||||
.icon_lists .icon{
|
||||
font-size: 42px;
|
||||
line-height: 100px;
|
||||
margin: 10px 0;
|
||||
color:#333;
|
||||
-webkit-transition: font-size 0.25s ease-out 0s;
|
||||
-moz-transition: font-size 0.25s ease-out 0s;
|
||||
transition: font-size 0.25s ease-out 0s;
|
||||
|
||||
}
|
||||
.icon_lists .icon:hover{
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.markdown {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.markdown img {
|
||||
vertical-align: middle;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
color: #404040;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown h2,
|
||||
.markdown h3,
|
||||
.markdown h4,
|
||||
.markdown h5,
|
||||
.markdown h6 {
|
||||
color: #404040;
|
||||
margin: 1.6em 0 0.6em 0;
|
||||
font-weight: 500;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.markdown h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.markdown h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.markdown h4 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.markdown h5 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown h6 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown hr {
|
||||
height: 1px;
|
||||
border: 0;
|
||||
background: #e9e9e9;
|
||||
margin: 16px 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown p,
|
||||
.markdown pre {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown > p,
|
||||
.markdown > blockquote,
|
||||
.markdown > .highlight,
|
||||
.markdown > ol,
|
||||
.markdown > ul {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.markdown ul > li {
|
||||
list-style: circle;
|
||||
}
|
||||
|
||||
.markdown > ul li,
|
||||
.markdown blockquote ul > li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown > ul li p,
|
||||
.markdown > ol li p {
|
||||
margin: 0.6em 0;
|
||||
}
|
||||
|
||||
.markdown ol > li {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.markdown > ol li,
|
||||
.markdown blockquote ol > li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown code {
|
||||
margin: 0 3px;
|
||||
padding: 0 5px;
|
||||
background: #eee;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.markdown pre {
|
||||
border-radius: 6px;
|
||||
background: #f7f7f7;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.markdown pre code {
|
||||
border: none;
|
||||
background: #f7f7f7;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown strong,
|
||||
.markdown b {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown > table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0px;
|
||||
empty-cells: show;
|
||||
border: 1px solid #e9e9e9;
|
||||
width: 95%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown > table th {
|
||||
white-space: nowrap;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
|
||||
}
|
||||
|
||||
.markdown > table th,
|
||||
.markdown > table td {
|
||||
border: 1px solid #e9e9e9;
|
||||
padding: 8px 16px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown > table th {
|
||||
background: #F7F7F7;
|
||||
}
|
||||
|
||||
.markdown blockquote {
|
||||
font-size: 90%;
|
||||
color: #999;
|
||||
border-left: 4px solid #e9e9e9;
|
||||
padding-left: 0.8em;
|
||||
margin: 1em 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.markdown blockquote p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown .anchor {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.markdown .waiting {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.markdown h1:hover .anchor,
|
||||
.markdown h2:hover .anchor,
|
||||
.markdown h3:hover .anchor,
|
||||
.markdown h4:hover .anchor,
|
||||
.markdown h5:hover .anchor,
|
||||
.markdown h6:hover .anchor {
|
||||
opacity: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.markdown > br,
|
||||
.markdown > p > br {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
background: white;
|
||||
padding: 0.5em;
|
||||
color: #333333;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-meta {
|
||||
color: #969896;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-strong,
|
||||
.hljs-emphasis,
|
||||
.hljs-quote {
|
||||
color: #df5000;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-type {
|
||||
color: #a71d5d;
|
||||
}
|
||||
|
||||
.hljs-literal,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-attribute {
|
||||
color: #0086b3;
|
||||
}
|
||||
|
||||
.hljs-section,
|
||||
.hljs-name {
|
||||
color: #63a35c;
|
||||
}
|
||||
|
||||
.hljs-tag {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-attr,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo {
|
||||
color: #795da3;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #55a532;
|
||||
background-color: #eaffea;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #bd2c00;
|
||||
background-color: #ffecec;
|
||||
}
|
||||
|
||||
.hljs-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
pre{
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
30
src/icon/iconfont.css
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
@font-face {font-family: "fontFamily";
|
||||
src: url('iconfont.eot?t=1527493375212'); /* IE9*/
|
||||
src: url('iconfont.eot?t=1527493375212#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAXoAAsAAAAACHwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kf3Y21hcAAAAYAAAABjAAABnM7GacRnbHlmAAAB5AAAAfsAAAIsdf0weGhlYWQAAAPgAAAALwAAADYRguv+aGhlYQAABBAAAAAcAAAAJAfeA4VobXR4AAAELAAAABAAAAAQD+kAAGxvY2EAAAQ8AAAACgAAAAoBjAC6bWF4cAAABEgAAAAfAAAAIAEaAF1uYW1lAAAEaAAAAVMAAAKFV8s9R3Bvc3QAAAW8AAAAKgAAADsY6RIYeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/sU4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDzjZm7438AQw9zA0AAUZgTJAQAk0wx5eJzFkNENgCAMRK+ChIij+GkcyC9HYOKugdfCDxNw5JX2ckkJAHYAgVwkAvJBYHrpivsBh/sRD+fMs/GumrS0NnUm8UT2LlhSEpZJ1q2edXq9x2T/XQd8oqaO+Vo62H4fLw+3AHicLZC/b9NAFMffu6udpM0PbMdx4jQ/LhfXoDRBtpN2IGkXkArNgNQpKkiNYlSBRNd2YKiQEB0YWFDFCkiIiYEfytAJVbDTv6CFjYXSFhbsckE53dP3fZ/e6X3egQRwfkj3aBY0uAgOXIWbACjXsJIkBWR2s0FqqDNJN9JJanObRXilQTtoVOR0xp1rzhhyRE5hEovoMXfObhAbW80FcgXdTAExlzdXVGtapU9xMmsXH4U3yAvUS3w6tVAPr88upt2yFt2Mq2pOVZ9EZUmKEjKRSuJ9IxOTYpNy+EpKmfpe6RIpYTxnm91eopxX+zvNjYJlxBC3t1HLl5OvFxVTEfeBmdHUXORCIpo1E7yaxs3vU1ktXpj5BuJExK7P6A/agwmIQhxyUAAGltiXKUyxmM5GicaF4Yqns5ZnoCjqns5RBK0GlO4Hy7gTbuFRcESKu6vBvdVdAurL4DF2w/cHB1jvdGjvb5uy9Q/B3VFt1E27x8cPw2vtdpt83QIxGc6f0190A6hgmoKU+HsD8lACDpfBg3kAQ4AYo5ljRcFEecuTRG6NeSzeEpj/QTkVfl5EFofh0trp6dpY8RMOBycnwbLrkpLjBIdC3w1+zvaxduez73/x/XUc3jrDjzi8/RtXwiXx7u2Z7zhOmHbq+8JXB3/CN/1+H+AfnVR/vQB4nGNgZGBgAOLuWSW34/ltvjJwszCAwHXDy/8R9P+pLAzMeUAuBwMTSBQAWOoL7AB4nGNgZGBgbvjfwBDDwgACQJKRARWwAABHCgJtBAAAAAPpAAAEAAAABAAAAAAAAAAAdgC6ARYAAHicY2BkYGBgYQhk4GEAASYg5gJCBob/YD4DABGzAXgAeJxtkk1uwjAQhZ/LT9UgddHSdlmvWBQp/CzZooZlJRbsQ3AgKIkjxyDRXQ/Q8/QQPUE3vUHv0EcwQkIk8uibN2/GthIAd/iFwOF54DqwgMfswFe4xrPjGvWO4zq577iBFkaOm9RfHXvo4s1xC/d45wRRv2H2gk/HAm18Ob7CLb4d16j/OK6T/xw38CiE4yba4smxh5noOm6hIz68sVGhVQs538kk0nmsc+vtQxBmSbqbquUmDc1JONFMmTLRuRz4/ZM4Ubkyx3nldjm0Npax0ZkM6FFpqmVh9FpF1l9ZW4x6vdjpfqQzHm8MA4UQlnEBiTl2jAkiaOSIq2jpO1JAb8Z6St+UPUtsyCGnXHJc0mbsMiiZ7WsSA/j8ZJecEzrzyn1+vhJb7jykatkpuQz7M1Lg5ihOSMkSRVVbU4mo+1hVXQV/jR7f+MzvVzfP/gHCFnRdAHicY2BigAAuBuyAhZGJkZmRhZGVgbGCycCSI7WoPDUzN9GQgQEAJkQEKgAA') format('woff'),
|
||||
url('iconfont.ttf?t=1527493375212') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
|
||||
url('iconfont.svg?t=1527493375212#fontFamily') format('svg'); /* iOS 4.1- */
|
||||
}
|
||||
|
||||
.fontFamily {
|
||||
font-family:"fontFamily" !important;
|
||||
font-size:16px;
|
||||
font-style:normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
[class^="el-icon-cjn"], [class*=" el-icon-cjn"] {
|
||||
font-family:"fontFamily" !important;
|
||||
/* 以下内容参照第三方图标库本身的规则 */
|
||||
font-size: 13px;
|
||||
font-style:normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.el-icon-cjn-09:before { content: "\e60b"; }
|
||||
|
||||
.el-icon-cjn-erweima1:before { content: "\e606"; }
|
||||
|
||||
BIN
src/icon/iconfont.eot
Normal file
1
src/icon/iconfont.js
Normal file
@@ -0,0 +1 @@
|
||||
(function(window) { var svgSprite = '<svg><symbol id="el-icon-cjn-09" viewBox="0 0 1024 1024"><path d="M804.141176 39.152941H39.152941v963.764706h963.764706V201.788235l-198.776471-162.635294z m-493.929411 60.235294h391.529411v180.705883h-391.529411v-180.705883z m481.882353 843.294118h-542.117647v-331.294118h542.117647v331.294118z m150.588235 0h-90.352941v-391.529412h-662.588236v391.529412h-90.352941v-843.294118h150.588236v240.941177h512v-240.941177h18.070588l162.635294 129.505883v713.788235z" fill="#2c2c2c" ></path><path d="M310.211765 671.623529h331.294117v60.235295h-331.294117zM310.211765 792.094118h210.823529v60.235294h-210.823529zM611.388235 129.505882h60.235294v120.470589h-60.235294z" fill="#2c2c2c" ></path></symbol><symbol id="el-icon-cjn-erweima1" viewBox="0 0 1024 1024"><path d="M23.92225303 998.84837371l439.58598408 0L463.50823711 559.26238962 23.92225303 559.26238962 23.92225303 998.84837371zM119.78212494 651.01398129l243.75796004 0 0 245.12738674L119.78212494 896.14136803 119.78212494 651.01398129z" ></path><path d="M23.92225303 457.92481072l439.58598408 0 0-439.58598412L23.92225303 18.33882658 23.92225303 457.92481072zM119.78212494 111.45984508l243.75796004 0 0 245.12738672L119.78212494 356.5872318 119.78212494 111.45984508z" ></path><path d="M570.32352297 18.33882658l0 439.58598414L1009.90950704 457.92481072l0-439.58598412L570.32352297 18.33882658zM911.31078166 355.21780509L667.55282162 355.21780509l0-245.12738681 243.75796004 0L911.31078166 355.21780509z" ></path><path d="M218.38085034 210.05857039l49.29936273 0 0 49.29936276-49.29936273 0 0-49.29936276Z" ></path><path d="M760.67384005 210.05857039l49.29936271 0 0 49.29936276-49.29936273 0 0-49.29936276Z" ></path><path d="M218.38085034 750.98213347l49.29936273 0 0 49.29936266-49.29936273 0 0-49.29936266Z" ></path><path d="M908.57192818 755.09041369L809.97320276 755.09041369 809.97320276 559.26238962 570.32352297 559.26238962 570.32352297 998.84837371 614.14517874 998.84837371 614.14517874 707.16047766 711.37447735 707.16047766 711.37447735 805.75920314 1009.90950704 805.75920314 1009.90950704 559.26238962 908.57192818 559.26238962Z" ></path><path d="M711.37447735 901.61907503l99.96815218 0 0 97.22929868-99.96815218 0 0-97.22929868Z" ></path><path d="M909.94135491 901.61907503l99.96815213 0 0 97.22929868-99.96815213 0 0-97.22929868Z" ></path></symbol></svg>'; var script = (function() { var scripts = document.getElementsByTagName('script'); return scripts[scripts.length - 1] }()); var shouldInjectCss = script.getAttribute('data-injectcss'); var ready = function(fn) { if (document.addEventListener) { if (~['complete', 'loaded', 'interactive'].indexOf(document.readyState)) { setTimeout(fn, 0) } else { var loadFn = function() { document.removeEventListener('DOMContentLoaded', loadFn, false); fn() }; document.addEventListener('DOMContentLoaded', loadFn, false) } } else if (document.attachEvent) { IEContentLoaded(window, fn) } function IEContentLoaded(w, fn) { var d = w.document, done = false, init = function() { if (!done) { done = true; fn() } }; var polling = function() { try { d.documentElement.doScroll('left') } catch (e) { setTimeout(polling, 50); return }init() }; polling(); d.onreadystatechange = function() { if (d.readyState == 'complete') { d.onreadystatechange = null; init() } } } }; var before = function(el, target) { target.parentNode.insertBefore(el, target) }; var prepend = function(el, target) { if (target.firstChild) { before(el, target.firstChild) } else { target.appendChild(el) } }; function appendSvg() { var div, svg; div = document.createElement('div'); div.innerHTML = svgSprite; svgSprite = null; svg = div.getElementsByTagName('svg')[0]; if (svg) { svg.setAttribute('aria-hidden', 'true'); svg.style.position = 'absolute'; svg.style.width = 0; svg.style.height = 0; svg.style.overflow = 'hidden'; prepend(svg, document.body) } } if (shouldInjectCss && !window.__iconfont__svg__cssinject__) { window.__iconfont__svg__cssinject__ = true; try { document.write('<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>') } catch (e) { console && console.log(e) } }ready(appendSvg) })(window)
|
||||
39
src/icon/iconfont.svg
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<!--
|
||||
2013-9-30: Created.
|
||||
-->
|
||||
<svg>
|
||||
<metadata>
|
||||
Created by iconfont
|
||||
</metadata>
|
||||
<defs>
|
||||
|
||||
<font id="fontFamily" horiz-adv-x="1024" >
|
||||
<font-face
|
||||
font-family="fontFamily"
|
||||
font-weight="500"
|
||||
font-stretch="normal"
|
||||
units-per-em="1024"
|
||||
ascent="896"
|
||||
descent="-128"
|
||||
/>
|
||||
<missing-glyph />
|
||||
|
||||
<glyph glyph-name="x" unicode="x" horiz-adv-x="1001"
|
||||
d="M281 543q-27 -1 -53 -1h-83q-18 0 -36.5 -6t-32.5 -18.5t-23 -32t-9 -45.5v-76h912v41q0 16 -0.5 30t-0.5 18q0 13 -5 29t-17 29.5t-31.5 22.5t-49.5 9h-133v-97h-438v97zM955 310v-52q0 -23 0.5 -52t0.5 -58t-10.5 -47.5t-26 -30t-33 -16t-31.5 -4.5q-14 -1 -29.5 -0.5
|
||||
t-29.5 0.5h-32l-45 128h-439l-44 -128h-29h-34q-20 0 -45 1q-25 0 -41 9.5t-25.5 23t-13.5 29.5t-4 30v167h911zM163 247q-12 0 -21 -8.5t-9 -21.5t9 -21.5t21 -8.5q13 0 22 8.5t9 21.5t-9 21.5t-22 8.5zM316 123q-8 -26 -14 -48q-5 -19 -10.5 -37t-7.5 -25t-3 -15t1 -14.5
|
||||
t9.5 -10.5t21.5 -4h37h67h81h80h64h36q23 0 34 12t2 38q-5 13 -9.5 30.5t-9.5 34.5q-5 19 -11 39h-368zM336 498v228q0 11 2.5 23t10 21.5t20.5 15.5t34 6h188q31 0 51.5 -14.5t20.5 -52.5v-227h-327z" />
|
||||
|
||||
|
||||
|
||||
<glyph glyph-name="09" unicode="" d="M804.141 856.847h-764.988v-963.765h963.765v801.129l-198.776 162.635zM310.212 796.612h391.529v-180.706h-391.529v180.706zM792.094-46.682h-542.118v331.294h542.118v-331.294zM942.682-46.682h-90.353v391.529h-662.588v-391.529h-90.353v843.294h150.588v-240.941h512v240.941h18.071l162.635-129.506v-713.788zM310.212 224.376h331.294v-60.235h-331.294zM310.212 103.906h210.824v-60.235h-210.824zM611.388 766.494h60.235v-120.471h-60.235z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="erweima1" unicode="" d="M23.92225303-102.84837371000003l439.58598408 0L463.50823711 336.73761038 23.92225303 336.73761038 23.92225303-102.84837371000003zM119.78212494 244.98601871000005l243.75796004 0 0-245.12738674L119.78212494-0.1413680299999669 119.78212494 244.98601871000005zM23.92225303 438.07518928l439.58598408 0 0 439.58598412L23.92225303 877.66117342 23.92225303 438.07518928zM119.78212494 784.54015492l243.75796004 0 0-245.12738672L119.78212494 539.4127682000001 119.78212494 784.54015492zM570.32352297 877.66117342l0-439.58598414L1009.90950704 438.07518928l0 439.58598412L570.32352297 877.66117342zM911.31078166 540.78219491L667.55282162 540.78219491l0 245.12738681 243.75796004 0L911.31078166 540.78219491zM218.38085034 685.94142961l49.29936273 0 0-49.29936276-49.29936273 0 0 49.29936276ZM760.67384005 685.94142961l49.29936271 0 0-49.29936276-49.29936273 0 0 49.29936276ZM218.38085034 145.01786653l49.29936273 0 0-49.29936266-49.29936273 0 0 49.29936266ZM908.57192818 140.90958631L809.97320276 140.90958631 809.97320276 336.73761038 570.32352297 336.73761038 570.32352297-102.84837371000003 614.14517874-102.84837371000003 614.14517874 188.83952234000003 711.37447735 188.83952234000003 711.37447735 90.24079686000005 1009.90950704 90.24079686000005 1009.90950704 336.73761038 908.57192818 336.73761038ZM711.37447735-5.619075029999976l99.96815218 0 0-97.22929868-99.96815218 0 0 97.22929868ZM909.94135491-5.619075029999976l99.96815213 0 0-97.22929868-99.96815213 0 0 97.22929868Z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
|
||||
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/icon/iconfont.ttf
Normal file
BIN
src/icon/iconfont.woff
Normal file
22
src/icons/svgo.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
# replace default config
|
||||
|
||||
# multipass: true
|
||||
# full: true
|
||||
|
||||
plugins:
|
||||
|
||||
# - name
|
||||
#
|
||||
# or:
|
||||
# - name: false
|
||||
# - name: true
|
||||
#
|
||||
# or:
|
||||
# - name:
|
||||
# param1: 1
|
||||
# param2: 2
|
||||
|
||||
- removeAttrs:
|
||||
attrs:
|
||||
- 'fill'
|
||||
- 'fill-rule'
|
||||
BIN
src/img/ActualOutput.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src/img/FaultsTime.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/img/NumberFaults.png
Normal file
|
After Width: | Height: | Size: 1006 B |
BIN
src/img/OEEAnalysis.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/img/ProductionPlan.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |