315 lines
12 KiB
QML
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)
|
|
}
|
|
}
|
|
}
|
|
}
|