Files
2026-04-29 07:19:21 +03:00

315 lines
12 KiB
QML

// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import QtQuick.Dialogs
import QtQuick3D
import QtQuick3D.Helpers
import QtQuick3D.lightmapviewer
import LightmapFile 1.0
ColumnLayout {
id: root
anchors.fill: parent
property string selectedModelCandidate: ""
onSelectedModelCandidateChanged: {
if (!selectedModelCandidate || selectedModelCandidate === "") {
view.lmTextureCandidates = []
lview.mSelectedTextureCandidate = -1
return
}
view.lmTextureCandidates = LightmapFile.texturesAvailableFor(
selectedModelCandidate)
view.lmSelectedTextureCandidate
= view.lmTextureCandidates.length ? view.lmTextureCandidates[0].value : -1
}
Pane {
Layout.fillWidth: true
padding: 8
ColumnLayout {
width: parent.width
spacing: 6
ColumnLayout {
RowLayout {
Label {
text: "Key:"
}
ComboBox {
id: comboLmModelCandidate
Layout.preferredWidth: 220
model: view.lmModelCandidates
onActivated: root.selectedModelCandidate = currentText
currentIndex: {
const i = view.lmModelCandidates.indexOf(
root.selectedModelCandidate)
return (i >= 0 ? i : -1)
}
enabled: !!selectedEntry
&& selectedEntry.kind === "mesh"
}
Label {
text: "Texture:"
}
ComboBox {
id: comboLmTextureCandidate
Layout.preferredWidth: 220
model: view.lmTextureCandidates
textRole: "name"
valueRole: "value"
function indexForValue(val) {
for (var i = 0; i < view.lmTextureCandidates.length; ++i)
if (view.lmTextureCandidates[i].value === val)
return i
return -1
}
onActivated: view.lmSelectedTextureCandidate = Number(
currentValue)
currentIndex: indexForValue(
view.lmSelectedTextureCandidate)
enabled: !!selectedEntry
&& selectedEntry.kind === "mesh"
}
}
RowLayout {
CheckBox {
id: checkboxBfCull
text: "Backface Culling"
checked: true
}
CheckBox {
id: checkboxDebugUV
text: "Debug UV"
checked: false
}
}
}
}
}
View3D {
id: view
Layout.fillWidth: true
Layout.fillHeight: true
enabled: meshLoader.visible
visible: meshLoader.visible
property var lmModelCandidates: []
property var lmTextureCandidates: []
property int lmSelectedTextureCandidate: -1
property real boundsDiameter: 0
property vector3d boundsCenter
property vector3d boundsSize
function updateBounds(bounds) {
boundsSize = Qt.vector3d(bounds.maximum.x - bounds.minimum.x,
bounds.maximum.y - bounds.minimum.y,
bounds.maximum.z - bounds.minimum.z)
boundsDiameter = Math.max(boundsSize.x, boundsSize.y, boundsSize.z)
boundsCenter = Qt.vector3d(
(bounds.maximum.x + bounds.minimum.x) / 2,
(bounds.maximum.y + bounds.minimum.y) / 2,
(bounds.maximum.z + bounds.minimum.z) / 2)
model.position = Qt.vector3d(-boundsCenter.x, -boundsCenter.y,
-boundsCenter.z)
cameraNode.clipNear = boundsDiameter / 100
cameraNode.clipFar = boundsDiameter * 10
resetCamera()
}
function resetCamera() {
cameraNode.position = boundsCenter
cameraNode.position = Qt.vector3d(0, 0, 2 * boundsDiameter)
cameraNode.eulerRotation = Qt.vector3d(0, 0, 0)
}
function refreshLightmapSelection() {
if (!selectedEntry) {
lmModelCandidates = []
root.selectedModelCandidate = ""
lmTextureCandidates = []
lmSelectedTextureCandidate = -1
return
}
if (selectedEntry.kind === "image") {
lmModelCandidates = [selectedEntry.key]
root.selectedModelCandidate = selectedEntry.key
} else if (selectedEntry.kind === "mesh") {
lmModelCandidates = LightmapFile.keysReferencingMesh(
selectedEntry.key)
root.selectedModelCandidate = lmModelCandidates.length ? lmModelCandidates[0] : ""
} else {
lmModelCandidates = []
root.selectedModelCandidate = ""
lmTextureCandidates = []
lmSelectedTextureCandidate = -1
}
}
Component.onCompleted: refreshLightmapSelection()
Connections {
target: window
function onSelectedEntryChanged() {
view.refreshLightmapSelection()
}
}
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "black"
}
PerspectiveCamera {
id: cameraNode
z: 300
}
Node {
id: modelNode
Model {
id: model
geometry: LightmapMesh {
source: LightmapFile.source
key: selectedEntry.key
onBoundsChanged: view.updateBounds(bounds)
}
materials: CustomMaterial {
shadingMode: CustomMaterial.Unshaded
cullMode: checkboxBfCull.checked ? Material.BackFaceCulling : Material.NoCulling
property TextureInput baseMap: TextureInput {
texture: Texture {
id: lmTexture
minFilter: Texture.Linear
magFilter: Texture.Linear
mipFilter: Texture.None
tilingModeHorizontal: Texture.ClampToEdge
tilingModeVertical: Texture.ClampToEdge
textureData: LightmapFile.textureDataFor(
root.selectedModelCandidate,
view.lmSelectedTextureCandidate)
}
}
property bool debugUV: checkboxDebugUV.checked
vertexShader: "mesh.vert"
fragmentShader: "mesh.frag"
}
}
}
ArcballController {
id: arcballController
controlledObject: modelNode
function jumpToAxis(axis) {
cameraRotation.from = arcballController.controlledObject.rotation
cameraRotation.to = originGizmo.quaternionForAxis(
axis, arcballController.controlledObject.rotation)
cameraRotation.duration = 200
cameraRotation.start()
}
function jumpToRotation(qRotation) {
cameraRotation.from = arcballController.controlledObject.rotation
cameraRotation.to = qRotation
cameraRotation.duration = 100
cameraRotation.start()
}
QuaternionAnimation {
id: cameraRotation
target: arcballController.controlledObject
property: "rotation"
type: QuaternionAnimation.Slerp
running: false
loops: 1
}
}
OriginGizmo {
id: originGizmo
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: 10
width: 120
height: 120
targetNode: modelNode
onAxisClicked: axis => {
arcballController.jumpToAxis(axis)
}
}
DragHandler {
id: dragHandler
target: null
acceptedModifiers: Qt.NoModifier
onCentroidChanged: {
arcballController.mouseMoved(toNDC(centroid.position.x,
centroid.position.y))
}
onActiveChanged: {
if (active) {
view.forceActiveFocus()
arcballController.mousePressed(toNDC(centroid.position.x,
centroid.position.y))
} else
arcballController.mouseReleased(toNDC(centroid.position.x,
centroid.position.y))
}
function toNDC(x, y) {
return Qt.vector2d((2.0 * x / width) - 1.0,
1.0 - (2.0 * y / height))
}
}
WheelHandler {
id: wheelHandler
orientation: Qt.Vertical
target: null
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
onWheel: event => {
let delta = -event.angleDelta.y * 0.01
cameraNode.z += cameraNode.z * 0.1 * delta
}
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Space) {
let rotation = originGizmo.quaternionAlign(
arcballController.controlledObject.rotation)
arcballController.jumpToRotation(rotation)
} else if (event.key === Qt.Key_S) {
settingsPane.toggleHide()
} else if (event.key === Qt.Key_Left
|| event.key === Qt.Key_A) {
let rotation = originGizmo.quaternionRotateLeft(
arcballController.controlledObject.rotation)
arcballController.jumpToRotation(rotation)
} else if (event.key === Qt.Key_Right
|| event.key === Qt.Key_D) {
let rotation = originGizmo.quaternionRotateRight(
arcballController.controlledObject.rotation)
arcballController.jumpToRotation(rotation)
}
}
}
}