[3d] add edge threshold support (#2939)

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Terry Jia
2025-03-09 13:44:30 -04:00
committed by GitHub
parent 96b84761f3
commit 3d6fe41ee9
12 changed files with 169 additions and 4 deletions

View File

@@ -12,6 +12,7 @@
:backgroundImage="backgroundImage"
:upDirection="upDirection"
:materialMode="materialMode"
:edgeThreshold="edgeThreshold"
@materialModeChange="listenMaterialModeChange"
@backgroundColorChange="listenBackgroundColorChange"
@lightIntensityChange="listenLightIntensityChange"
@@ -21,6 +22,7 @@
@showPreviewChange="listenShowPreviewChange"
@backgroundImageChange="listenBackgroundImageChange"
@upDirectionChange="listenUpDirectionChange"
@edgeThresholdChange="listenEdgeThresholdChange"
/>
<Load3DControls
:backgroundColor="backgroundColor"
@@ -36,6 +38,7 @@
:upDirection="upDirection"
:materialMode="materialMode"
:isAnimation="false"
:edgeThreshold="edgeThreshold"
@updateBackgroundImage="handleBackgroundImageUpdate"
@switchCamera="switchCamera"
@toggleGrid="toggleGrid"
@@ -45,6 +48,7 @@
@updateFOV="handleUpdateFOV"
@updateUpDirection="handleUpdateUpDirection"
@updateMaterialMode="handleUpdateMaterialMode"
@updateEdgeThreshold="handleUpdateEdgeThreshold"
/>
</div>
</template>
@@ -79,6 +83,7 @@ const hasBackgroundImage = ref(false)
const backgroundImage = ref('')
const upDirection = ref<UpDirection>('original')
const materialMode = ref<MaterialMode>('original')
const edgeThreshold = ref(85)
const showPreviewButton = computed(() => {
return !props.type.includes('Preview')
@@ -130,6 +135,12 @@ const handleUpdateFOV = (value: number) => {
node.value.properties['FOV'] = fov.value
}
const handleUpdateEdgeThreshold = (value: number) => {
edgeThreshold.value = value
node.value.properties['Edge Threshold'] = edgeThreshold.value
}
const handleBackgroundColorChange = (value: string) => {
backgroundColor.value = value
@@ -158,6 +169,10 @@ const listenUpDirectionChange = (value: UpDirection) => {
upDirection.value = value
}
const listenEdgeThresholdChange = (value: number) => {
edgeThreshold.value = value
}
const listenBackgroundColorChange = (value: string) => {
backgroundColor.value = value
}

View File

@@ -166,7 +166,42 @@
</div>
</div>
</div>
<div
v-if="activeCategory === 'model' && materialMode === 'lineart'"
class="relative show-edge-threshold"
>
<Button
class="p-button-rounded p-button-text"
@click="toggleEdgeThreshold"
>
<i
class="pi pi-sliders-h text-white text-lg"
v-tooltip.right="{
value: t('load3d.edgeThreshold'),
showDelay: 300
}"
></i>
</Button>
<div
v-show="showEdgeThreshold"
class="absolute left-12 top-0 bg-black bg-opacity-50 p-4 rounded-lg shadow-lg"
style="width: 150px"
>
<label class="text-white text-xs mb-1 block"
>{{ t('load3d.edgeThreshold') }}: {{ edgeThreshold }}°</label
>
<Slider
v-model="edgeThreshold"
class="w-full"
@change="updateEdgeThreshold"
:min="0"
:max="120"
:step="1"
/>
</div>
</div>
</div>
<div v-if="activeCategory === 'camera'" class="flex flex-col">
<Button class="p-button-rounded p-button-text" @click="switchCamera">
<i
@@ -279,6 +314,7 @@ const props = defineProps<{
upDirection: UpDirection
materialMode: MaterialMode
isAnimation: boolean
edgeThreshold?: number
}>()
const isMenuOpen = ref(false)
@@ -319,6 +355,7 @@ const emit = defineEmits<{
(e: 'updateBackgroundImage', file: File | null): void
(e: 'updateUpDirection', direction: UpDirection): void
(e: 'updateMaterialMode', mode: MaterialMode): void
(e: 'updateEdgeThreshold', value: number): void
}>()
const backgroundColor = ref(props.backgroundColor)
@@ -347,6 +384,12 @@ const upDirections: UpDirection[] = [
'+z'
]
const showMaterialMode = ref(false)
const edgeThreshold = ref(props.edgeThreshold)
const showEdgeThreshold = ref(false)
const toggleEdgeThreshold = () => {
showEdgeThreshold.value = !showEdgeThreshold.value
}
const materialModes = computed(() => {
const modes: MaterialMode[] = [
@@ -405,6 +448,10 @@ const updateFOV = () => {
emit('updateFOV', fov.value)
}
const updateEdgeThreshold = () => {
emit('updateEdgeThreshold', edgeThreshold.value)
}
const toggleUpDirection = () => {
showUpDirection.value = !showUpDirection.value
}
@@ -454,6 +501,10 @@ const closeSlider = (e: MouseEvent) => {
if (!target.closest('.show-material-mode')) {
showMaterialMode.value = false
}
if (!target.closest('.show-edge-threshold')) {
showEdgeThreshold.value = false
}
}
const openImagePicker = () => {
@@ -564,6 +615,13 @@ watch(
}
)
watch(
() => props.edgeThreshold,
(newValue) => {
edgeThreshold.value = newValue
}
)
onMounted(() => {
document.addEventListener('click', closeSlider)
})

View File

@@ -31,6 +31,7 @@ const props = defineProps<{
backgroundImage: string
upDirection: UpDirection
materialMode: MaterialMode
edgeThreshold?: number
extraListeners?: Record<string, (value: any) => void>
}>()
@@ -51,6 +52,7 @@ const eventConfig = {
backgroundImageChange: (value: string) =>
emit('backgroundImageChange', value),
upDirectionChange: (value: string) => emit('upDirectionChange', value),
edgeThresholdChange: (value: number) => emit('edgeThresholdChange', value),
modelLoadingStart: () =>
loadingOverlayRef.value?.startLoading(t('load3d.loadingModel')),
modelLoadingEnd: () => loadingOverlayRef.value?.endLoading(),
@@ -71,6 +73,7 @@ watchEffect(() => {
rawLoad3d.togglePreview(props.showPreview)
rawLoad3d.setBackgroundImage(props.backgroundImage)
rawLoad3d.setUpDirection(props.upDirection)
rawLoad3d.setEdgeThreshold(props.edgeThreshold)
}
})
@@ -95,6 +98,7 @@ const emit = defineEmits<{
(e: 'showPreviewChange', showPreview: boolean): void
(e: 'backgroundImageChange', backgroundImage: string): void
(e: 'upDirectionChange', upDirection: string): void
(e: 'edgeThresholdChange', threshold: number): void
}>()
const handleEvents = (action: 'add' | 'remove') => {

View File

@@ -68,11 +68,13 @@ class Load3DConfiguration {
this.load3d.setBackgroundColor(bgColor)
const lightIntensity = this.load3d.loadNodeProperty('Light Intensity', 5)
const lightIntensity: number = Number(
this.load3d.loadNodeProperty('Light Intensity', 5)
)
this.load3d.setLightIntensity(lightIntensity)
const fov = this.load3d.loadNodeProperty('FOV', 35)
const fov: number = Number(this.load3d.loadNodeProperty('FOV', 35))
this.load3d.setFOV(fov)
@@ -113,6 +115,12 @@ class Load3DConfiguration {
this.load3d.setMaterialMode(materialMode)
const edgeThreshold: number = Number(
this.load3d.loadNodeProperty('Edge Threshold', 85)
)
this.load3d.setEdgeThreshold(edgeThreshold)
if (isFirstLoad && cameraState && typeof cameraState === 'object') {
try {
this.load3d.setCameraState(cameraState)

View File

@@ -233,6 +233,10 @@ class Load3d {
)
}
setEdgeThreshold(threshold: number): void {
this.modelManager.setEdgeThreshold(threshold)
}
setMaterialMode(mode: MaterialMode): void {
this.modelManager.setMaterialMode(mode)

View File

@@ -161,7 +161,7 @@ export class ModelManager implements ModelManagerInterface {
const mesh = meshes[key]
const parent = mesh.parent
let lineGeom = new THREE.EdgesGeometry(mesh.geometry, 10)
let lineGeom = new THREE.EdgesGeometry(mesh.geometry, 85)
const line = new THREE.LineSegments(
lineGeom,
@@ -200,6 +200,76 @@ export class ModelManager implements ModelManagerInterface {
})
}
setEdgeThreshold(threshold: number): void {
if (!this.edgesModel || !this.currentModel) {
return
}
const linesToRemove: THREE.Object3D[] = []
this.edgesModel.traverse((child) => {
if (
child instanceof THREE.LineSegments ||
child instanceof LineSegments2
) {
linesToRemove.push(child)
}
})
for (const line of linesToRemove) {
if (line.parent) {
line.parent.remove(line)
}
}
const meshes: THREE.Mesh[] = []
this.currentModel.traverse((child) => {
if (child instanceof THREE.Mesh) {
meshes.push(child)
}
})
for (const mesh of meshes) {
const meshClone = mesh.clone()
let lineGeom = new THREE.EdgesGeometry(meshClone.geometry, threshold)
const line = new THREE.LineSegments(
lineGeom,
new THREE.LineBasicMaterial({ color: this.LIGHT_LINES })
)
line.position.copy(mesh.position)
line.scale.copy(mesh.scale)
line.rotation.copy(mesh.rotation)
const thickLineGeom = new LineSegmentsGeometry().fromEdgesGeometry(
lineGeom
)
const thickLines = new LineSegments2(
thickLineGeom,
new LineMaterial({ color: this.LIGHT_LINES, linewidth: 13 })
)
thickLines.position.copy(mesh.position)
thickLines.scale.copy(mesh.scale)
thickLines.rotation.copy(mesh.rotation)
this.edgesModel.add(line)
this.edgesModel.add(thickLines)
}
this.edgesModel.traverse((child) => {
if (
child instanceof THREE.Mesh &&
child.material &&
child.material.resolution
) {
this.renderer.getSize(child.material.resolution)
child.material.resolution.multiplyScalar(window.devicePixelRatio)
child.material.linewidth = 1
}
})
this.eventManager.emitEvent('edgeThresholdChange', threshold)
}
disposeBackgroundModel(): void {
if (this.backgroundModel) {
if (this.backgroundModel.parent) {

View File

@@ -907,6 +907,7 @@
"model": "Model",
"camera": "Camera",
"light": "Light",
"switchingMaterialMode": "Switching Material Mode..."
"switchingMaterialMode": "Switching Material Mode...",
"edgeThreshold": "Edge Threshold"
}
}

View File

@@ -335,6 +335,7 @@
"load3d": {
"backgroundColor": "Couleur de fond",
"camera": "Caméra",
"edgeThreshold": "Seuil de Bordure",
"fov": "FOV",
"light": "Lumière",
"lightIntensity": "Intensité de la lumière",

View File

@@ -335,6 +335,7 @@
"load3d": {
"backgroundColor": "背景色",
"camera": "カメラ",
"edgeThreshold": "エッジ閾値",
"fov": "FOV",
"light": "ライト",
"lightIntensity": "光の強度",

View File

@@ -335,6 +335,7 @@
"load3d": {
"backgroundColor": "배경색",
"camera": "카메라",
"edgeThreshold": "엣지 임계값",
"fov": "FOV",
"light": "빛",
"lightIntensity": "조명 강도",

View File

@@ -335,6 +335,7 @@
"load3d": {
"backgroundColor": "Цвет фона",
"camera": "Камера",
"edgeThreshold": "Пороговое значение края",
"fov": "Угол обзора",
"light": "Свет",
"lightIntensity": "Интенсивность света",

View File

@@ -335,6 +335,7 @@
"load3d": {
"backgroundColor": "背景颜色",
"camera": "相机",
"edgeThreshold": "边缘阈值",
"fov": "视场",
"light": "灯光",
"lightIntensity": "光照强度",