commit
ce9d66cdd1
|
|
@ -0,0 +1,37 @@
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Describe what this PR changes or adds. Include any relevant context or motivation.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Related Issues
|
||||||
|
|
||||||
|
- [ ] **Please link to a relevant GitHub issue for additional context.**
|
||||||
|
- **Bug Fix:** Link to an issue that includes reproduction steps and testing guidance.
|
||||||
|
- **Feature/Enhancement:** Link to an issue with a write-up, rationale, and requirements.
|
||||||
|
|
||||||
|
Issue Link: <!-- e.g., closes #123 or relates to #456 -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
Please ensure the following before requesting review:
|
||||||
|
|
||||||
|
- [ ] I have provided a clear title and detailed description for this pull request.
|
||||||
|
- [ ] If useful, I have included media such as screenshots and video to show off my changes.
|
||||||
|
- [ ] The PR is linked to a relevant issue with sufficient context.
|
||||||
|
- [ ] I have tested the changes locally and verified they work as intended.
|
||||||
|
- [ ] All new and existing tests pass.
|
||||||
|
- [ ] Code follows the project's style guidelines.
|
||||||
|
- [ ] Documentation has been updated if needed.
|
||||||
|
- [ ] Any dependent changes have been merged and published in downstream modules
|
||||||
|
- [ ] I have reviewed the [contributing guidelines](https://github.com/secondlife/viewer/blob/develop/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Notes
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Add any other information, screenshots, or suggestions for reviewers here.
|
||||||
|
-->
|
||||||
|
|
@ -218,8 +218,10 @@ jobs:
|
||||||
prefix=${ba[0]}
|
prefix=${ba[0]}
|
||||||
if [ "$prefix" == "project" ]; then
|
if [ "$prefix" == "project" ]; then
|
||||||
IFS='_' read -ra prj <<< "${ba[1]}"
|
IFS='_' read -ra prj <<< "${ba[1]}"
|
||||||
|
prj_str="${prj[*]}"
|
||||||
# uppercase first letter of each word
|
# uppercase first letter of each word
|
||||||
export viewer_channel="Second Life Project ${prj[*]^}"
|
capitalized=$(echo "$prj_str" | awk '{for (i=1; i<=NF; i++) $i = toupper(substr($i,1,1)) substr($i,2); print}')
|
||||||
|
export viewer_channel="Second Life Project $capitalized"
|
||||||
elif [[ "$prefix" == "release" || "$prefix" == "main" ]];
|
elif [[ "$prefix" == "release" || "$prefix" == "main" ]];
|
||||||
then
|
then
|
||||||
export viewer_channel="Second Life Release"
|
export viewer_channel="Second Life Release"
|
||||||
|
|
@ -304,7 +306,7 @@ jobs:
|
||||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: windows-large
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Sign and package Windows viewer
|
- name: Sign and package Windows viewer
|
||||||
if: env.AZURE_KEY_VAULT_URI && env.AZURE_CERT_NAME && env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET && env.AZURE_TENANT_ID
|
if: env.AZURE_KEY_VAULT_URI && env.AZURE_CERT_NAME && env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET && env.AZURE_TENANT_ID
|
||||||
|
|
@ -455,7 +457,6 @@ jobs:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
target_commitish: ${{ github.sha }}
|
target_commitish: ${{ github.sha }}
|
||||||
previous_tag: release
|
|
||||||
append_body: true
|
append_body: true
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
files: |
|
files: |
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
name: Check PR
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, edited, reopened, synchronize]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-description:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check PR description
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const description = context.payload.pull_request.body || '';
|
||||||
|
if (description.trim().length < 20) {
|
||||||
|
core.setFailed("❌ PR description is too short. Please provide at least 20 characters.");
|
||||||
|
}
|
||||||
|
|
@ -1,70 +1,198 @@
|
||||||
name: Run QA Test # Runs automated tests on a self-hosted QA machine
|
name: Run QA Test # Runs automated tests on self-hosted QA machines
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
#pull-requests: write # maybe need to re-add this later
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
workflows: ["Build"]
|
workflows: ["Build"]
|
||||||
types:
|
types:
|
||||||
- completed
|
- completed
|
||||||
|
workflow_dispatch:
|
||||||
concurrency:
|
inputs:
|
||||||
group: qa-test-run
|
build_id:
|
||||||
cancel-in-progress: true # Cancels any queued job when a new one starts
|
description: 'Build workflow run ID (e.g. For github.com/secondlife/viewer/actions/runs/1234567890 the ID is 1234567890)'
|
||||||
|
required: true
|
||||||
|
default: '14806728332'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
debug-workflow:
|
debug-workflow:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Debug Workflow Variables
|
- name: Debug Workflow Variables
|
||||||
env:
|
|
||||||
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
|
||||||
HEAD_COMMIT_MSG: ${{ github.event.workflow_run.head_commit.message }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "Workflow Conclusion: ${{ github.event.workflow_run.conclusion }}"
|
echo "Workflow Conclusion: ${{ github.event.workflow_run.conclusion }}"
|
||||||
echo "Workflow Head Branch: $HEAD_BRANCH"
|
echo "Workflow Head Branch: ${{ github.event.workflow_run.head_branch }}"
|
||||||
echo "Workflow Run ID: ${{ github.event.workflow_run.id }}"
|
echo "Workflow Run ID: ${{ github.event.workflow_run.id }}"
|
||||||
echo "Head Commit Message: $HEAD_COMMIT_MSG"
|
echo "Head Commit Message: ${{ github.event.workflow_run.head_commit.message }}"
|
||||||
echo "GitHub Ref: ${{ github.ref }}"
|
echo "GitHub Ref: ${{ github.ref }}"
|
||||||
echo "GitHub Ref Name: ${{ github.ref_name }}"
|
echo "GitHub Ref Name: ${{ github.ref_name }}"
|
||||||
echo "GitHub Event Name: ${{ github.event_name }}"
|
echo "GitHub Event Name: ${{ github.event_name }}"
|
||||||
echo "GitHub Workflow Name: ${{ github.workflow }}"
|
echo "GitHub Workflow Name: ${{ github.workflow }}"
|
||||||
|
|
||||||
install-viewer-and-run-tests:
|
install-viewer-and-run-tests:
|
||||||
runs-on: [self-hosted, qa-machine]
|
concurrency:
|
||||||
# Run test only on successful builds of Second_Life_X branches
|
group: ${{ github.workflow }}-${{ matrix.runner }}
|
||||||
|
cancel-in-progress: false # Prevents cancellation of in-progress jobs
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: windows
|
||||||
|
runner: qa-windows-atlas
|
||||||
|
artifact: Windows-installer
|
||||||
|
install-path: 'C:\viewer-automation-main'
|
||||||
|
- os: windows
|
||||||
|
runner: qa-dan-asus
|
||||||
|
artifact: Windows-installer
|
||||||
|
install-path: 'C:\viewer-automation-main'
|
||||||
|
- os: mac
|
||||||
|
runner: qa-mac-atlas
|
||||||
|
artifact: macOS-installer
|
||||||
|
install-path: '$HOME/Documents/viewer-automation'
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
runs-on: [self-hosted, "${{ matrix.runner }}"]
|
||||||
|
# Run test only on successful builds of Second_Life_X branches or on manual dispatch
|
||||||
if: >
|
if: >
|
||||||
|
(github.event_name == 'workflow_run' &&
|
||||||
github.event.workflow_run.conclusion == 'success' &&
|
github.event.workflow_run.conclusion == 'success' &&
|
||||||
(
|
startsWith(github.event.workflow_run.head_branch, 'Second_Life')) ||
|
||||||
startsWith(github.event.workflow_run.head_branch, 'Second_Life')
|
github.event_name == 'workflow_dispatch'
|
||||||
)
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Temporarily Allow PowerShell Scripts (Process Scope)
|
# Windows-specific steps
|
||||||
|
- name: Set Build ID
|
||||||
|
if: matrix.os == 'windows'
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
if ("${{ github.event_name }}" -eq "workflow_dispatch") {
|
||||||
|
echo "BUILD_ID=${{ github.event.inputs.build_id }}" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
|
echo "ARTIFACTS_URL=https://api.github.com/repos/secondlife/viewer/actions/runs/${{ github.event.inputs.build_id }}/artifacts" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
|
} else {
|
||||||
|
echo "BUILD_ID=${{ github.event.workflow_run.id }}" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
|
echo "ARTIFACTS_URL=https://api.github.com/repos/secondlife/viewer/actions/runs/${{ github.event.workflow_run.id }}/artifacts" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Temporarily Allow PowerShell Scripts (Windows)
|
||||||
|
if: matrix.os == 'windows'
|
||||||
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
Set-ExecutionPolicy RemoteSigned -Scope Process -Force
|
Set-ExecutionPolicy RemoteSigned -Scope Process -Force
|
||||||
|
|
||||||
- name: Verify viewer-sikulix-main Exists
|
- name: Verify viewer-automation-main Exists (Windows)
|
||||||
run: |
|
if: matrix.os == 'windows'
|
||||||
if (-Not (Test-Path -Path 'C:\viewer-sikulix-main')) {
|
|
||||||
Write-Host '❌ Error: viewer-sikulix not found on runner!'
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
Write-Host '✅ viewer-sikulix is already available.'
|
|
||||||
|
|
||||||
- name: Fetch & Download Windows Installer Artifact
|
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
$BUILD_ID = "${{ github.event.workflow_run.id }}"
|
if (-Not (Test-Path -Path '${{ matrix.install-path }}')) {
|
||||||
$ARTIFACTS_URL = "https://api.github.com/repos/secondlife/viewer/actions/runs/$BUILD_ID/artifacts"
|
Write-Host '❌ Error: viewer-automation folder not found on runner!'
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Host '✅ viewer-automation folder is provided.'
|
||||||
|
|
||||||
|
- name: Verify viewer-automation-main is Up-To-Date (Windows)
|
||||||
|
if: matrix.os == 'windows'
|
||||||
|
shell: pwsh
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
cd ${{ matrix.install-path }}
|
||||||
|
Write-Host "Checking for repository updates..."
|
||||||
|
|
||||||
|
# Check if .git directory exists
|
||||||
|
if (Test-Path -Path ".git") {
|
||||||
|
try {
|
||||||
|
# Save local changes instead of discarding them
|
||||||
|
git stash push -m "Automated stash before update $(Get-Date)"
|
||||||
|
Write-Host "Local changes saved (if any)"
|
||||||
|
|
||||||
|
# Update the repository
|
||||||
|
git pull
|
||||||
|
Write-Host "✅ Repository updated successfully"
|
||||||
|
|
||||||
|
# Try to restore local changes if any were stashed
|
||||||
|
$stashList = git stash list
|
||||||
|
if ($stashList -match "Automated stash before update") {
|
||||||
|
try {
|
||||||
|
git stash pop
|
||||||
|
Write-Host "✅ Local changes restored successfully"
|
||||||
|
} catch {
|
||||||
|
Write-Host "⚠️ Conflict when restoring local changes"
|
||||||
|
# Save the conflicted state in a new branch for later review
|
||||||
|
$branchName = "conflict-recovery-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
|
||||||
|
git checkout -b $branchName
|
||||||
|
Write-Host "✅ Created branch '$branchName' with conflicted state"
|
||||||
|
|
||||||
|
# For test execution, revert to a clean state
|
||||||
|
git reset --hard HEAD
|
||||||
|
Write-Host "✅ Reset to clean state for test execution"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Host "⚠️ Could not update repository: $_"
|
||||||
|
Write-Host "Continuing with existing files..."
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "⚠️ Not a Git repository, using existing files"
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Verify Python Installation (Windows)
|
||||||
|
if: matrix.os == 'windows'
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
try {
|
||||||
|
$pythonVersion = (python --version)
|
||||||
|
Write-Host "✅ Python found: $pythonVersion"
|
||||||
|
} catch {
|
||||||
|
Write-Host "❌ Error: Python not found in PATH. Please install Python on this runner."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Setup Python Virtual Environment (Windows)
|
||||||
|
if: matrix.os == 'windows'
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
|
||||||
|
cd ${{ matrix.install-path }}
|
||||||
|
|
||||||
|
if (-Not (Test-Path -Path ".venv")) {
|
||||||
|
Write-Host "Creating virtual environment..."
|
||||||
|
python -m venv .venv
|
||||||
|
} else {
|
||||||
|
Write-Host "Using existing virtual environment"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Direct environment activation to avoid script execution issues
|
||||||
|
$env:VIRTUAL_ENV = "$PWD\.venv"
|
||||||
|
$env:PATH = "$env:VIRTUAL_ENV\Scripts;$env:PATH"
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
if (Test-Path -Path "requirements.txt") {
|
||||||
|
Write-Host "Installing dependencies from requirements.txt..."
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Install Playwright browsers - add this line
|
||||||
|
Write-Host "Installing Playwright browsers..."
|
||||||
|
python -m playwright install
|
||||||
|
} else {
|
||||||
|
pip install outleap requests behave playwright
|
||||||
|
# Install Playwright browsers - add this line
|
||||||
|
Write-Host "Installing Playwright browsers..."
|
||||||
|
python -m playwright install
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Fetch & Download Installer Artifact (Windows)
|
||||||
|
if: matrix.os == 'windows'
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$BUILD_ID = "${{ env.BUILD_ID }}"
|
||||||
|
$ARTIFACTS_URL = "${{ env.ARTIFACTS_URL }}"
|
||||||
|
|
||||||
# Fetch the correct artifact URL
|
# Fetch the correct artifact URL
|
||||||
$response = Invoke-RestMethod -Headers @{Authorization="token ${{ secrets.GITHUB_TOKEN }}" } -Uri $ARTIFACTS_URL
|
$response = Invoke-RestMethod -Headers @{Authorization="token ${{ secrets.GITHUB_TOKEN }}" } -Uri $ARTIFACTS_URL
|
||||||
$ARTIFACT_NAME = ($response.artifacts | Where-Object { $_.name -eq "Windows-installer" }).archive_download_url
|
$ARTIFACT_NAME = ($response.artifacts | Where-Object { $_.name -eq "${{ matrix.artifact }}" }).archive_download_url
|
||||||
|
|
||||||
if (-Not $ARTIFACT_NAME) {
|
if (-Not $ARTIFACT_NAME) {
|
||||||
Write-Host "❌ Error: Windows-installer artifact not found!"
|
Write-Host "❌ Error: ${{ matrix.artifact }} artifact not found!"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,16 +208,19 @@ jobs:
|
||||||
|
|
||||||
# Ensure download succeeded
|
# Ensure download succeeded
|
||||||
if (-Not (Test-Path $InstallerPath)) {
|
if (-Not (Test-Path $InstallerPath)) {
|
||||||
Write-Host "❌ Error: Failed to download Windows-installer.zip"
|
Write-Host "❌ Error: Failed to download ${{ matrix.artifact }}.zip"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
- name: Extract Installer & Locate Executable
|
# Set the path for other steps
|
||||||
|
echo "DOWNLOAD_PATH=$DownloadPath" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
|
|
||||||
|
- name: Extract Installer & Locate Executable (Windows)
|
||||||
|
if: matrix.os == 'windows'
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
# Explicitly set BUILD_ID again (since it does not appear to persist across steps)
|
$BUILD_ID = "${{ env.BUILD_ID }}"
|
||||||
$BUILD_ID = "${{ github.event.workflow_run.id }}"
|
$ExtractPath = "${{ env.DOWNLOAD_PATH }}"
|
||||||
$ExtractPath = "$env:TEMP\secondlife-build-$BUILD_ID"
|
|
||||||
$InstallerZip = "$ExtractPath\installer.zip"
|
$InstallerZip = "$ExtractPath\installer.zip"
|
||||||
|
|
||||||
# Print paths for debugging
|
# Print paths for debugging
|
||||||
|
|
@ -119,16 +250,19 @@ jobs:
|
||||||
Write-Host "✅ Installer found: $INSTALLER_PATH"
|
Write-Host "✅ Installer found: $INSTALLER_PATH"
|
||||||
echo "INSTALLER_PATH=$INSTALLER_PATH" | Out-File -FilePath $env:GITHUB_ENV -Append
|
echo "INSTALLER_PATH=$INSTALLER_PATH" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
|
|
||||||
- name: Install Second Life Using Task Scheduler (Bypass UAC)
|
- name: Install Second Life (Windows)
|
||||||
|
if: matrix.os == 'windows'
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
|
# Windows - Use Task Scheduler to bypass UAC
|
||||||
$action = New-ScheduledTaskAction -Execute "${{ env.INSTALLER_PATH }}" -Argument "/S"
|
$action = New-ScheduledTaskAction -Execute "${{ env.INSTALLER_PATH }}" -Argument "/S"
|
||||||
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
|
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
|
||||||
$task = New-ScheduledTask -Action $action -Principal $principal
|
$task = New-ScheduledTask -Action $action -Principal $principal
|
||||||
Register-ScheduledTask -TaskName "SilentSLInstaller" -InputObject $task -Force
|
Register-ScheduledTask -TaskName "SilentSLInstaller" -InputObject $task -Force
|
||||||
Start-ScheduledTask -TaskName "SilentSLInstaller"
|
Start-ScheduledTask -TaskName "SilentSLInstaller"
|
||||||
|
|
||||||
- name: Wait for Installation to Complete
|
- name: Wait for Installation to Complete (Windows)
|
||||||
|
if: matrix.os == 'windows'
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
Write-Host "Waiting for the Second Life installer to finish..."
|
Write-Host "Waiting for the Second Life installer to finish..."
|
||||||
|
|
@ -139,18 +273,16 @@ jobs:
|
||||||
|
|
||||||
Write-Host "✅ Installation completed!"
|
Write-Host "✅ Installation completed!"
|
||||||
|
|
||||||
- name: Cleanup Task Scheduler Entry
|
- name: Cleanup After Installation (Windows)
|
||||||
|
if: matrix.os == 'windows'
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
|
# Cleanup Task Scheduler Entry
|
||||||
Unregister-ScheduledTask -TaskName "SilentSLInstaller" -Confirm:$false
|
Unregister-ScheduledTask -TaskName "SilentSLInstaller" -Confirm:$false
|
||||||
Write-Host "✅ Task Scheduler entry removed."
|
Write-Host "✅ Task Scheduler entry removed."
|
||||||
|
|
||||||
- name: Delete Installer ZIP
|
# Delete Installer ZIP
|
||||||
shell: pwsh
|
$DeletePath = "${{ env.DOWNLOAD_PATH }}\installer.zip"
|
||||||
run: |
|
|
||||||
# Explicitly set BUILD_ID again
|
|
||||||
$BUILD_ID = "${{ github.event.workflow_run.id }}"
|
|
||||||
$DeletePath = "$env:TEMP\secondlife-build-$BUILD_ID\installer.zip"
|
|
||||||
|
|
||||||
Write-Host "Checking if installer ZIP exists: $DeletePath"
|
Write-Host "Checking if installer ZIP exists: $DeletePath"
|
||||||
|
|
||||||
|
|
@ -162,13 +294,305 @@ jobs:
|
||||||
Write-Host "⚠️ Warning: ZIP file does not exist, skipping deletion."
|
Write-Host "⚠️ Warning: ZIP file does not exist, skipping deletion."
|
||||||
}
|
}
|
||||||
|
|
||||||
- name: Run QA Test Script
|
- name: Run QA Test Script (Windows)
|
||||||
|
if: matrix.os == 'windows'
|
||||||
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
Write-Host "Running QA Test script..."
|
Write-Host "Running QA Test script on Windows runner: ${{ matrix.runner }}..."
|
||||||
python C:\viewer-sikulix-main\runTests.py
|
cd ${{ matrix.install-path }}
|
||||||
|
|
||||||
|
# Activate virtual environment
|
||||||
|
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
|
||||||
|
$env:VIRTUAL_ENV = "$PWD\.venv"
|
||||||
|
$env:PATH = "$env:VIRTUAL_ENV\Scripts;$env:PATH"
|
||||||
|
|
||||||
|
# Set runner name as environment variable
|
||||||
|
$env:RUNNER_NAME = "${{ matrix.runner }}"
|
||||||
|
|
||||||
|
# Run the test script
|
||||||
|
python runTests.py
|
||||||
|
|
||||||
|
# Mac-specific steps
|
||||||
|
- name: Set Build ID (Mac)
|
||||||
|
if: matrix.os == 'mac'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||||
|
echo "BUILD_ID=${{ github.event.inputs.build_id }}" >> $GITHUB_ENV
|
||||||
|
echo "ARTIFACTS_URL=https://api.github.com/repos/secondlife/viewer/actions/runs/${{ github.event.inputs.build_id }}/artifacts" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "BUILD_ID=${{ github.event.workflow_run.id }}" >> $GITHUB_ENV
|
||||||
|
echo "ARTIFACTS_URL=https://api.github.com/repos/secondlife/viewer/actions/runs/${{ github.event.workflow_run.id }}/artifacts" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Verify viewer-automation-main Exists (Mac)
|
||||||
|
if: matrix.os == 'mac'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ ! -d "${{ matrix.install-path }}" ]; then
|
||||||
|
echo "❌ Error: viewer-automation folder not found on runner!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✅ viewer-automation is provided."
|
||||||
|
|
||||||
|
- name: Verify viewer-automation-main is Up-To-Date (Mac)
|
||||||
|
if: matrix.os == 'mac'
|
||||||
|
shell: bash
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
cd ${{ matrix.install-path }}
|
||||||
|
echo "Checking for repository updates..."
|
||||||
|
|
||||||
|
# Check if .git directory exists
|
||||||
|
if [ -d ".git" ]; then
|
||||||
|
# Save local changes instead of discarding them
|
||||||
|
git stash push -m "Automated stash before update $(date)"
|
||||||
|
echo "Local changes saved (if any)"
|
||||||
|
|
||||||
|
# Update the repository
|
||||||
|
git pull || echo "⚠️ Could not update repository"
|
||||||
|
echo "✅ Repository updated (or attempted update)"
|
||||||
|
|
||||||
|
# Try to restore local changes if any were stashed
|
||||||
|
if git stash list | grep -q "Automated stash before update"; then
|
||||||
|
# Try to pop the stash, but be prepared for conflicts
|
||||||
|
if ! git stash pop; then
|
||||||
|
echo "⚠️ Conflict when restoring local changes"
|
||||||
|
# Save the conflicted state in a new branch for later review
|
||||||
|
branch_name="conflict-recovery-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
git checkout -b "$branch_name"
|
||||||
|
echo "✅ Created branch '$branch_name' with conflicted state"
|
||||||
|
|
||||||
|
# For test execution, revert to a clean state
|
||||||
|
git reset --hard HEAD
|
||||||
|
echo "✅ Reset to clean state for test execution"
|
||||||
|
else
|
||||||
|
echo "✅ Local changes restored successfully"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ Not a Git repository, using existing files"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Verify Python Installation (Mac)
|
||||||
|
if: matrix.os == 'mac'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if command -v python3 &> /dev/null; then
|
||||||
|
PYTHON_VERSION=$(python3 --version)
|
||||||
|
echo "✅ Python found: $PYTHON_VERSION"
|
||||||
|
else
|
||||||
|
echo "❌ Error: Python3 not found in PATH. Please install Python on this runner."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Setup Python Virtual Environment (Mac)
|
||||||
|
if: matrix.os == 'mac'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cd ${{ matrix.install-path }}
|
||||||
|
|
||||||
|
# Create virtual environment if it doesn't exist
|
||||||
|
if [ ! -d ".venv" ]; then
|
||||||
|
echo "Creating virtual environment..."
|
||||||
|
python3 -m venv .venv
|
||||||
|
else
|
||||||
|
echo "Using existing virtual environment"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Activate virtual environment
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
if [ -f "requirements.txt" ]; then
|
||||||
|
pip install -r requirements.txt
|
||||||
|
echo "✅ Installed dependencies from requirements.txt"
|
||||||
|
|
||||||
|
# Install Playwright browsers - add this line
|
||||||
|
echo "Installing Playwright browsers..."
|
||||||
|
python -m playwright install
|
||||||
|
else
|
||||||
|
pip install outleap requests behave playwright
|
||||||
|
echo "⚠️ requirements.txt not found, installed basic dependencies"
|
||||||
|
|
||||||
|
# Install Playwright browsers - add this line
|
||||||
|
echo "Installing Playwright browsers..."
|
||||||
|
python -m playwright install
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Fetch & Download Installer Artifact (Mac)
|
||||||
|
if: matrix.os == 'mac'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Mac-specific Bash commands
|
||||||
|
response=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s ${{ env.ARTIFACTS_URL }})
|
||||||
|
ARTIFACT_NAME=$(echo $response | jq -r '.artifacts[] | select(.name=="${{ matrix.artifact }}") | .archive_download_url')
|
||||||
|
|
||||||
|
if [ -z "$ARTIFACT_NAME" ]; then
|
||||||
|
echo "❌ Error: ${{ matrix.artifact }} artifact not found!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Artifact found: $ARTIFACT_NAME"
|
||||||
|
|
||||||
|
# Secure download path
|
||||||
|
DOWNLOAD_PATH="/tmp/secondlife-build-${{ env.BUILD_ID }}"
|
||||||
|
mkdir -p $DOWNLOAD_PATH
|
||||||
|
INSTALLER_PATH="$DOWNLOAD_PATH/installer.zip"
|
||||||
|
|
||||||
|
# Download the ZIP
|
||||||
|
curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -L $ARTIFACT_NAME -o $INSTALLER_PATH
|
||||||
|
|
||||||
|
# Ensure download succeeded
|
||||||
|
if [ ! -f "$INSTALLER_PATH" ]; then
|
||||||
|
echo "❌ Error: Failed to download ${{ matrix.artifact }}.zip"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set the path for other steps
|
||||||
|
echo "DOWNLOAD_PATH=$DOWNLOAD_PATH" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Extract Installer & Locate Executable (Mac)
|
||||||
|
if: matrix.os == 'mac'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
EXTRACT_PATH="${{ env.DOWNLOAD_PATH }}"
|
||||||
|
INSTALLER_ZIP="$EXTRACT_PATH/installer.zip"
|
||||||
|
|
||||||
|
# Debug output
|
||||||
|
echo "Extract Path: $EXTRACT_PATH"
|
||||||
|
echo "Installer ZIP Path: $INSTALLER_ZIP"
|
||||||
|
|
||||||
|
# Verify ZIP exists
|
||||||
|
if [ ! -f "$INSTALLER_ZIP" ]; then
|
||||||
|
echo "❌ Error: ZIP file not found at $INSTALLER_ZIP!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ ZIP file exists and is valid. Extracting..."
|
||||||
|
|
||||||
|
# Extract the ZIP
|
||||||
|
unzip -o "$INSTALLER_ZIP" -d "$EXTRACT_PATH"
|
||||||
|
|
||||||
|
# Find DMG file
|
||||||
|
INSTALLER_PATH=$(find "$EXTRACT_PATH" -name "*.dmg" -type f | head -1)
|
||||||
|
|
||||||
|
if [ -z "$INSTALLER_PATH" ]; then
|
||||||
|
echo "❌ Error: No installer DMG found in the extracted files!"
|
||||||
|
echo "📂 Extracted Files:"
|
||||||
|
ls -la "$EXTRACT_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Installer found: $INSTALLER_PATH"
|
||||||
|
echo "INSTALLER_PATH=$INSTALLER_PATH" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install Second Life (Mac)
|
||||||
|
if: matrix.os == 'mac'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Mac installation
|
||||||
|
echo "Mounting DMG installer..."
|
||||||
|
MOUNT_POINT="/tmp/secondlife-dmg"
|
||||||
|
mkdir -p "$MOUNT_POINT"
|
||||||
|
|
||||||
|
# Mount the DMG
|
||||||
|
hdiutil attach "$INSTALLER_PATH" -mountpoint "$MOUNT_POINT" -nobrowse
|
||||||
|
|
||||||
|
echo "✅ DMG mounted at $MOUNT_POINT"
|
||||||
|
|
||||||
|
echo "Installing application to default location from DMG..."
|
||||||
|
|
||||||
|
# Find the .app bundle in the DMG
|
||||||
|
APP_PATH=$(find "$MOUNT_POINT" -name "*.app" -type d | head -1)
|
||||||
|
|
||||||
|
if [ -z "$APP_PATH" ]; then
|
||||||
|
echo "❌ Error: No .app bundle found in the mounted DMG!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
APP_NAME=$(basename "$APP_PATH")
|
||||||
|
DEST_PATH="/Applications/$APP_NAME"
|
||||||
|
|
||||||
|
# Handle existing installation
|
||||||
|
if [ -d "$DEST_PATH" ]; then
|
||||||
|
echo "Found existing installation at: $DEST_PATH"
|
||||||
|
echo "Moving existing installation to trash..."
|
||||||
|
|
||||||
|
# Move to trash instead of force removing
|
||||||
|
TRASH_PATH="$HOME/.Trash/$(date +%Y%m%d_%H%M%S)_$APP_NAME"
|
||||||
|
mv "$DEST_PATH" "$TRASH_PATH" || {
|
||||||
|
echo "⚠️ Could not move to trash, trying direct removal..."
|
||||||
|
rm -rf "$DEST_PATH" || {
|
||||||
|
echo "❌ Could not remove existing installation"
|
||||||
|
echo "Please manually remove: $DEST_PATH"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "✅ Existing installation handled successfully"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy the .app to /Applications
|
||||||
|
echo "Copying app from: $APP_PATH"
|
||||||
|
echo "To destination: /Applications/"
|
||||||
|
cp -R "$APP_PATH" /Applications/
|
||||||
|
|
||||||
|
# Verify the app was copied successfully
|
||||||
|
if [ ! -d "$DEST_PATH" ]; then
|
||||||
|
echo "❌ Error: Failed to install application to /Applications!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Application installed successfully to /Applications"
|
||||||
|
|
||||||
|
# Save mount point for cleanup
|
||||||
|
echo "MOUNT_POINT=$MOUNT_POINT" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Wait for Installation to Complete (Mac)
|
||||||
|
if: matrix.os == 'mac'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "Waiting for installation to complete..."
|
||||||
|
# Sleep to allow installation to finish (adjust as needed)
|
||||||
|
sleep 30
|
||||||
|
echo "✅ Installation completed"
|
||||||
|
|
||||||
|
- name: Cleanup After Installation (Mac)
|
||||||
|
if: matrix.os == 'mac'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Mac cleanup
|
||||||
|
# Unmount the DMG
|
||||||
|
echo "Unmounting DMG..."
|
||||||
|
hdiutil detach "${{ env.MOUNT_POINT }}" -force
|
||||||
|
|
||||||
|
# Clean up temporary files
|
||||||
|
echo "Cleaning up temporary files..."
|
||||||
|
rm -rf "${{ env.DOWNLOAD_PATH }}"
|
||||||
|
rm -rf "${{ env.MOUNT_POINT }}"
|
||||||
|
|
||||||
|
echo "✅ Cleanup completed"
|
||||||
|
|
||||||
|
- name: Run QA Test Script (Mac)
|
||||||
|
if: matrix.os == 'mac'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "Running QA Test script on Mac runner: ${{ matrix.runner }}..."
|
||||||
|
cd ${{ matrix.install-path }}
|
||||||
|
|
||||||
|
# Activate virtual environment
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
# Set runner name as environment variable
|
||||||
|
export RUNNER_NAME="${{ matrix.runner }}"
|
||||||
|
|
||||||
|
# Run the test script
|
||||||
|
python runTests.py
|
||||||
|
|
||||||
# - name: Upload Test Results
|
# - name: Upload Test Results
|
||||||
# uses: actions/upload-artifact@v3
|
# if: always()
|
||||||
|
# uses: actions/upload-artifact@v4
|
||||||
# with:
|
# with:
|
||||||
# name: test-results
|
# name: test-results-${{ matrix.runner }}
|
||||||
# path: C:\viewer-sikulix-main\regressionTest\test_results.html
|
# path: ${{ matrix.install-path }}/regressionTest/test_results.html
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ repos:
|
||||||
- id: indent-with-spaces
|
- id: indent-with-spaces
|
||||||
files: \.(cpp|c|h|inl|py|glsl|cmake)$
|
files: \.(cpp|c|h|inl|py|glsl|cmake)$
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.4.0
|
rev: v5.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-xml
|
- id: check-xml
|
||||||
- id: mixed-line-ending
|
- id: mixed-line-ending
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ changes.
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
- [Communication](#communication)
|
- [Communication](#communication)
|
||||||
|
- [What to work on](#what-to-work-on)
|
||||||
- [Reporting bugs and requesting features](#reporting-bugs-and-requesting-features)
|
- [Reporting bugs and requesting features](#reporting-bugs-and-requesting-features)
|
||||||
- [Contributing pull requests](#contributing-pull-requests)
|
- [Contributing pull requests](#contributing-pull-requests)
|
||||||
|
|
||||||
|
|
@ -35,6 +36,16 @@ developer-to-developer or support.
|
||||||
discussion between viewer maintainers.
|
discussion between viewer maintainers.
|
||||||
- Our [discord channel](https://discord.com/channels/677442248157167619/1357059883400167585) is available for real-time discussion.
|
- Our [discord channel](https://discord.com/channels/677442248157167619/1357059883400167585) is available for real-time discussion.
|
||||||
|
|
||||||
|
## What to work on
|
||||||
|
|
||||||
|
If you're looking for ways to contribute code, here are some ways to get involved:
|
||||||
|
|
||||||
|
- Explore existing issues on the [GitHub issue tracker](https://github.com/secondlife/viewer/issues) to find known problems, bugs, or enhancement discussions.
|
||||||
|
- File new issues if you’ve discovered a bug or have a specific idea to propose. If your idea is user-facing, consider submitting it through feedback.secondlife.com so it can reach a broader audience and be prioritized by Linden Lab staff.
|
||||||
|
- Look for the [help wanted](https://github.com/secondlife/viewer/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22) label. These are tasks the core maintainers have specifically identified as good candidates for community help.
|
||||||
|
- Talk to maintainers before starting significant work. Even if an issue exists, discussing your approach first ensures alignment with ongoing efforts and increases the likelihood your pull request will be accepted.
|
||||||
|
|
||||||
|
Collaboration is essential. We encourage contributors to work closely with the Second Life engineering team and other developers to keep work consistent and maintainable.
|
||||||
|
|
||||||
## Reporting bugs and requesting features
|
## Reporting bugs and requesting features
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2870,6 +2870,64 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
||||||
<key>version</key>
|
<key>version</key>
|
||||||
<string>1.0.9-5e8947c</string>
|
<string>1.0.9-5e8947c</string>
|
||||||
</map>
|
</map>
|
||||||
|
<key>discord_sdk</key>
|
||||||
|
<map>
|
||||||
|
<key>platforms</key>
|
||||||
|
<map>
|
||||||
|
<key>windows64</key>
|
||||||
|
<map>
|
||||||
|
<key>archive</key>
|
||||||
|
<map>
|
||||||
|
<key>creds</key>
|
||||||
|
<string>github</string>
|
||||||
|
<key>hash</key>
|
||||||
|
<string>e11571bf76b27d15c244069988ae372eaa5afae9</string>
|
||||||
|
<key>hash_algorithm</key>
|
||||||
|
<string>sha1</string>
|
||||||
|
<key>url</key>
|
||||||
|
<string>https://api.github.com/repos/secondlife/3p-discord-sdk/releases/assets/279333720</string>
|
||||||
|
</map>
|
||||||
|
<key>name</key>
|
||||||
|
<string>windows64</string>
|
||||||
|
</map>
|
||||||
|
<key>darwin64</key>
|
||||||
|
<map>
|
||||||
|
<key>archive</key>
|
||||||
|
<map>
|
||||||
|
<key>creds</key>
|
||||||
|
<string>github</string>
|
||||||
|
<key>hash</key>
|
||||||
|
<string>dc21df8b051c425163acf3eff8f06e32f407c9e0</string>
|
||||||
|
<key>hash_algorithm</key>
|
||||||
|
<string>sha1</string>
|
||||||
|
<key>url</key>
|
||||||
|
<string>https://api.github.com/repos/secondlife/3p-discord-sdk/releases/assets/279333706</string>
|
||||||
|
</map>
|
||||||
|
<key>name</key>
|
||||||
|
<string>darwin64</string>
|
||||||
|
</map>
|
||||||
|
</map>
|
||||||
|
<key>license</key>
|
||||||
|
<string>discord_sdk</string>
|
||||||
|
<key>license_file</key>
|
||||||
|
<string>LICENSES/discord_sdk.txt</string>
|
||||||
|
<key>copyright</key>
|
||||||
|
<string>Discord Inc.</string>
|
||||||
|
<key>version</key>
|
||||||
|
<string>1.4.9649.16733550144</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>discord_sdk</string>
|
||||||
|
<key>vcs_branch</key>
|
||||||
|
<string>main</string>
|
||||||
|
<key>vcs_revision</key>
|
||||||
|
<string>ef5c7c4a490ceac2df2b2f046788b1daf1bbb392</string>
|
||||||
|
<key>vcs_url</key>
|
||||||
|
<string>https://github.com/secondlife/3p-discord-sdk</string>
|
||||||
|
<key>canonical_repo</key>
|
||||||
|
<string>https://github.com/secondlife/3p-discord-sdk</string>
|
||||||
|
<key>description</key>
|
||||||
|
<string>Discord Social SDK</string>
|
||||||
|
</map>
|
||||||
</map>
|
</map>
|
||||||
<key>package_description</key>
|
<key>package_description</key>
|
||||||
<map>
|
<map>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
# Test plan for PRIM_MEDIA_FIRST_CLICK_INTERACT
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- At least two accounts
|
||||||
|
- At least one group
|
||||||
|
- Land under your control
|
||||||
|
|
||||||
|
## Feature Brief
|
||||||
|
|
||||||
|
Historically media-on-a-prim (MOAP) in Second Life has been bound to a focus system which blocks mouse click/hover events, this feature creates exceptions to this focus system for a configurable set of objects to meet user preference.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
The following scripts and test cases cover each individual operational mode of the feature; in practice these modes can be combined by advanced users in any configuration they desire from debug settings. Even though the intended use case combines multiple modes, individual modes can be tested for functionality when tested as described below.
|
||||||
|
|
||||||
|
If testing an arbitrary combination of operational modes beyond what the GUI offers is desired, the parameters of the bitfield for calculation are located in lltoolpie.h under the MediaFirstClickTypes enum. As of writing there exists a total of ~127 possible unique/valid combinations, which is why testing each mode individually is considered the most efficient for a full functionality test.
|
||||||
|
|
||||||
|
### Scripts
|
||||||
|
|
||||||
|
#### Script A
|
||||||
|
|
||||||
|
This script creates a media surface that is eligible for media first-click interact. Depending on test conditions, this will exhibit new behavior.
|
||||||
|
|
||||||
|
```lsl
|
||||||
|
default {
|
||||||
|
state_entry() {
|
||||||
|
llSetLinkMedia( LINK_THIS, 0, [
|
||||||
|
PRIM_MEDIA_CURRENT_URL, "http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/agni/avatars.html",
|
||||||
|
PRIM_MEDIA_HOME_URL, "http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/agni/avatars.html",
|
||||||
|
PRIM_MEDIA_FIRST_CLICK_INTERACT, TRUE,
|
||||||
|
PRIM_MEDIA_AUTO_PLAY, TRUE,
|
||||||
|
PRIM_MEDIA_CONTROLS, PRIM_MEDIA_CONTROLS_MINI
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Script B
|
||||||
|
|
||||||
|
This script creates a media surface that is NOT eligible for media first-click interact. In all but one test case, this will behave the same way.
|
||||||
|
|
||||||
|
```lsl
|
||||||
|
default {
|
||||||
|
state_entry() {
|
||||||
|
llSetLinkMedia( LINK_THIS, 0, [
|
||||||
|
PRIM_MEDIA_CURRENT_URL, "http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/agni/avatars.html",
|
||||||
|
PRIM_MEDIA_HOME_URL, "http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/agni/avatars.html",
|
||||||
|
PRIM_MEDIA_FIRST_CLICK_INTERACT, FALSE,
|
||||||
|
PRIM_MEDIA_AUTO_PLAY, TRUE,
|
||||||
|
PRIM_MEDIA_CONTROLS, PRIM_MEDIA_CONTROLS_MINI
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Standard testing procedure
|
||||||
|
|
||||||
|
You will be asked to enable media faces on multiple cubes, make sure that the webpage loads on each, and interact with them in the following ways.
|
||||||
|
|
||||||
|
1. Enable media for the cube, and verify that it displays a webpage.
|
||||||
|
2. Click on the terrain to clear any focus.
|
||||||
|
3. Hover your mouse over UI elements of the webpage, and **observe** if they highlight/react to the mouse cursor.
|
||||||
|
4. If hover events are not registered, clicking on the webpage and then **observe** if they begin reacting to hover events.
|
||||||
|
5. Clicking on the terrain to clear any focus once again.
|
||||||
|
6. Clicking on a UI element of the webpage and **observe** if it reacts to the first click, or requires a second click. *(Maximum of 2 clicks per attempt)*
|
||||||
|
|
||||||
|
These steps will be repeated for one or more pairs of cubes per test case to ensure that media first click interact is functioning within expectations. Unless otherwise mentioned for a specific test case, you simply need only be in the same region as the cubes to test with them.
|
||||||
|
|
||||||
|
## Test cases
|
||||||
|
|
||||||
|
All test cases begin with at least two cubes rezzed, one containing Script A henceforth referred to as Cube A and one with Script B referred to as Cube B. The steps of some test cases may impact the condition of the cubes, so keeping a spare set rezzed or in inventory to rapidly duplicate should improve efficiency if testing cases in series.
|
||||||
|
|
||||||
|
### Case 1 (MEDIA_FIRST_CLICK_NONE)
|
||||||
|
|
||||||
|
Ensure that debug setting `MediaFirstClickInteract` is set to `0`
|
||||||
|
|
||||||
|
Starting with Cube A and Cube B, perform the testing procedure on each.
|
||||||
|
|
||||||
|
**Expected observations:** Both webpages do not react to hover events until clicked, both webpages do not react to clicks until clicked once to establish focus
|
||||||
|
|
||||||
|
### Case 2 (MEDIA_FIRST_CLICK_HUD)
|
||||||
|
|
||||||
|
Ensure that debug setting `MediaFirstClickInteract` is set to `1`
|
||||||
|
|
||||||
|
Starting with Cube A and Cube B, attach them both to your HUD and perform the testing procedure on each. You may need to rotate or scale the cubes to fit on your screen before beginning testing. You may attach both at the same time, or only one at a time.
|
||||||
|
|
||||||
|
**Expected observations:** The webpage on Cube A will react to mouse cursor hover events and clicks without needing a focus click, but the webpage on Cube B will not.
|
||||||
|
|
||||||
|
### Case 3 (MEDIA_FIRST_CLICK_OWN)
|
||||||
|
|
||||||
|
Ensure that debug setting `MediaFirstClickInteract` is set to `2`
|
||||||
|
|
||||||
|
This test case requires two pairs of cubes, and the second pair must not be owned by your testing account. What owns them is not important, it can be a group or your second testing account.
|
||||||
|
|
||||||
|
Perform the testing procedure on both sets of cubes.
|
||||||
|
|
||||||
|
**Expected observations:** The webpage on Cube A will react to mouse cursor hover events and clicks without needing a focus click, but the webpage on Cube B will not. The other pair of cubes will react the same as your Cube B.
|
||||||
|
|
||||||
|
### Case 4 (MEDIA_FIRST_CLICK_GROUP)
|
||||||
|
|
||||||
|
Ensure that debug setting `MediaFirstClickInteract` is set to `4`
|
||||||
|
|
||||||
|
This test case requires two cubes, and the second cube must be deeded or set to a group that your testing account is a member of. As long as the second set of cubes is set to a group that your test account is a member of, the avatar that owns them does not matter.
|
||||||
|
|
||||||
|
Perform the testing procedure on both sets of cubes.
|
||||||
|
|
||||||
|
**Expected observations:** The cube owned by your primary account will not react to mouse cursor hover events and clicks without needing a focus click. The cube set to group will react to mouse cursor hover events and clicks without needing a focus click.
|
||||||
|
|
||||||
|
### Case 5 (MEDIA_FIRST_CLICK_FRIEND)
|
||||||
|
|
||||||
|
Ensure that debug setting `MediaFirstClickInteract` is set to `8`
|
||||||
|
|
||||||
|
This test case requires three sets of cubes, one owned by you, one owned by another avatar on your friend list, and a third set owned by an avatar that is not on your friend list, or deeded to group. You can optionally use two sets of cubes, and dissolve friendship with your second account to test non-friend cubes.
|
||||||
|
|
||||||
|
Perform the testing procedure on all cubes
|
||||||
|
|
||||||
|
**Expected observations:** Cube A owned by a friended avatar will react to mouse cursor hover events and clicks without needing a focus click. All other cubes will not.
|
||||||
|
|
||||||
|
### Case 6 (MEDIA_FIRST_CLICK_LAND)
|
||||||
|
|
||||||
|
Ensure that debug setting `MediaFirstClickInteract` is set to `16`
|
||||||
|
|
||||||
|
This is the most tricky test case due to the multiple combinations that this operational mode considers valid. You will need multiple cubes, and can omit Cube B for brevity unless running a full test pass. This is probably most efficiently tested from your second account, using your first account to adjust the test parameters to fit other sub-cases.
|
||||||
|
|
||||||
|
Note: This requires the avatar that is performing the tests to physically be in the same parcel as the test cube(s). If you are standing outside of the parcel the media cubes are in, they will never react to mouse cursor hover events and clicks without needing a focus click under this operational mode.
|
||||||
|
|
||||||
|
1. Place down a set of cubes owned by the same avatar as the land
|
||||||
|
- The second account should see Cube A react to mouse cursor hover events and clicks without needing a focus click
|
||||||
|
- Cube B if tested, will not react in all further sub-cases and will not be mentioned further.
|
||||||
|
|
||||||
|
2. Adjust the conditions of the cubes and parcel such that they are owned by another avatar, but have the same group as the land set
|
||||||
|
- The second account should see Cube A react to mouse cursor hover events and clicks without needing a focus click
|
||||||
|
|
||||||
|
3. Adjust the conditions of the cubes and parcel such that they are deeded to the same group that the parcel is deeded to
|
||||||
|
- The second account should see Cube A react to mouse cursor hover events and clicks without needing a focus click
|
||||||
|
|
||||||
|
4. Adjust the conditions of the cubes and parcel such that the parcel and cubes do not share an owner, or a group
|
||||||
|
- The second account should see Cube A NOT react to mouse cursor hover events until clicked, and clicks WILL need a focus click before registering.
|
||||||
|
|
||||||
|
### Case 7 (MEDIA_FIRST_CLICK_ANY) (optional)
|
||||||
|
|
||||||
|
Ensure that debug setting `MediaFirstClickInteract` is set to `32767`
|
||||||
|
|
||||||
|
Repeat test cases 1-6.
|
||||||
|
|
||||||
|
1. Test case 1 should fail
|
||||||
|
2. Test cases 2-6 should pass
|
||||||
|
|
||||||
|
### Case 8 (MEDIA_FIRST_CLICK_BYPASS_MOAP_FLAG) (optional)
|
||||||
|
|
||||||
|
Ensure that debug setting `MediaFirstClickInteract` is set to `65535`
|
||||||
|
|
||||||
|
Repeat test cases 1-6, there is no pass/fail for this run.
|
||||||
|
|
||||||
|
All cubes including B types should exhibit the same first-click interact behavior.
|
||||||
|
|
@ -20,6 +20,7 @@ set(cmake_SOURCE_FILES
|
||||||
Copy3rdPartyLibs.cmake
|
Copy3rdPartyLibs.cmake
|
||||||
DBusGlib.cmake
|
DBusGlib.cmake
|
||||||
DeploySharedLibs.cmake
|
DeploySharedLibs.cmake
|
||||||
|
Discord.cmake
|
||||||
DragDrop.cmake
|
DragDrop.cmake
|
||||||
EXPAT.cmake
|
EXPAT.cmake
|
||||||
FindAutobuild.cmake
|
FindAutobuild.cmake
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@
|
||||||
|
|
||||||
include(CMakeCopyIfDifferent)
|
include(CMakeCopyIfDifferent)
|
||||||
include(Linking)
|
include(Linking)
|
||||||
|
if (USE_DISCORD)
|
||||||
|
include(Discord)
|
||||||
|
endif ()
|
||||||
include(OPENAL)
|
include(OPENAL)
|
||||||
|
|
||||||
# When we copy our dependent libraries, we almost always want to copy them to
|
# When we copy our dependent libraries, we almost always want to copy them to
|
||||||
|
|
@ -75,6 +78,10 @@ if(WINDOWS)
|
||||||
endif(ADDRESS_SIZE EQUAL 32)
|
endif(ADDRESS_SIZE EQUAL 32)
|
||||||
endif (USE_BUGSPLAT)
|
endif (USE_BUGSPLAT)
|
||||||
|
|
||||||
|
if (TARGET ll::discord_sdk)
|
||||||
|
list(APPEND release_files discord_partner_sdk.dll)
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (TARGET ll::openal)
|
if (TARGET ll::openal)
|
||||||
list(APPEND release_files openal32.dll alut.dll)
|
list(APPEND release_files openal32.dll alut.dll)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
@ -180,6 +187,10 @@ elseif(DARWIN)
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (TARGET ll::discord_sdk)
|
||||||
|
list(APPEND release_files libdiscord_partner_sdk.dylib)
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (TARGET ll::openal)
|
if (TARGET ll::openal)
|
||||||
list(APPEND release_files libalut.dylib libopenal.dylib)
|
list(APPEND release_files libalut.dylib libopenal.dylib)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
include(Prebuilt)
|
||||||
|
|
||||||
|
include_guard()
|
||||||
|
|
||||||
|
add_library(ll::discord_sdk INTERFACE IMPORTED)
|
||||||
|
target_compile_definitions(ll::discord_sdk INTERFACE LL_DISCORD=1)
|
||||||
|
|
||||||
|
use_prebuilt_binary(discord_sdk)
|
||||||
|
|
||||||
|
target_include_directories(ll::discord_sdk SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include/discord_sdk)
|
||||||
|
target_link_libraries(ll::discord_sdk INTERFACE discord_partner_sdk)
|
||||||
|
|
@ -72,7 +72,6 @@ else()
|
||||||
find_library(COCOA_LIBRARY Cocoa)
|
find_library(COCOA_LIBRARY Cocoa)
|
||||||
find_library(IOKIT_LIBRARY IOKit)
|
find_library(IOKIT_LIBRARY IOKit)
|
||||||
|
|
||||||
find_library(AGL_LIBRARY AGL)
|
|
||||||
find_library(APPKIT_LIBRARY AppKit)
|
find_library(APPKIT_LIBRARY AppKit)
|
||||||
find_library(COREAUDIO_LIBRARY CoreAudio)
|
find_library(COREAUDIO_LIBRARY CoreAudio)
|
||||||
|
|
||||||
|
|
@ -81,7 +80,6 @@ else()
|
||||||
${IOKIT_LIBRARY}
|
${IOKIT_LIBRARY}
|
||||||
${COREFOUNDATION_LIBRARY}
|
${COREFOUNDATION_LIBRARY}
|
||||||
${CARBON_LIBRARY}
|
${CARBON_LIBRARY}
|
||||||
${AGL_LIBRARY}
|
|
||||||
${APPKIT_LIBRARY}
|
${APPKIT_LIBRARY}
|
||||||
${COREAUDIO_LIBRARY}
|
${COREAUDIO_LIBRARY}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ set(llappearance_SOURCE_FILES
|
||||||
llavatarjoint.cpp
|
llavatarjoint.cpp
|
||||||
llavatarjointmesh.cpp
|
llavatarjointmesh.cpp
|
||||||
lldriverparam.cpp
|
lldriverparam.cpp
|
||||||
|
lljointdata.h
|
||||||
lllocaltextureobject.cpp
|
lllocaltextureobject.cpp
|
||||||
llpolyskeletaldistortion.cpp
|
llpolyskeletaldistortion.cpp
|
||||||
llpolymesh.cpp
|
llpolymesh.cpp
|
||||||
|
|
|
||||||
|
|
@ -29,16 +29,17 @@
|
||||||
#include "llavatarappearance.h"
|
#include "llavatarappearance.h"
|
||||||
#include "llavatarappearancedefines.h"
|
#include "llavatarappearancedefines.h"
|
||||||
#include "llavatarjointmesh.h"
|
#include "llavatarjointmesh.h"
|
||||||
|
#include "lljointdata.h"
|
||||||
#include "llstl.h"
|
#include "llstl.h"
|
||||||
#include "lldir.h"
|
#include "lldir.h"
|
||||||
#include "llpolymorph.h"
|
#include "llpolymorph.h"
|
||||||
#include "llpolymesh.h"
|
#include "llpolymesh.h"
|
||||||
#include "llpolyskeletaldistortion.h"
|
#include "llpolyskeletaldistortion.h"
|
||||||
#include "llstl.h"
|
|
||||||
#include "lltexglobalcolor.h"
|
#include "lltexglobalcolor.h"
|
||||||
#include "llwearabledata.h"
|
#include "llwearabledata.h"
|
||||||
#include "boost/bind.hpp"
|
#include "boost/bind.hpp"
|
||||||
#include "boost/tokenizer.hpp"
|
#include "boost/tokenizer.hpp"
|
||||||
|
#include "v4math.h"
|
||||||
|
|
||||||
using namespace LLAvatarAppearanceDefines;
|
using namespace LLAvatarAppearanceDefines;
|
||||||
|
|
||||||
|
|
@ -71,11 +72,13 @@ public:
|
||||||
mChildren.clear();
|
mChildren.clear();
|
||||||
}
|
}
|
||||||
bool parseXml(LLXmlTreeNode* node);
|
bool parseXml(LLXmlTreeNode* node);
|
||||||
|
glm::mat4 getJointMatrix();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string mName;
|
std::string mName;
|
||||||
std::string mSupport;
|
std::string mSupport;
|
||||||
std::string mAliases;
|
std::string mAliases;
|
||||||
|
std::string mGroup;
|
||||||
bool mIsJoint;
|
bool mIsJoint;
|
||||||
LLVector3 mPos;
|
LLVector3 mPos;
|
||||||
LLVector3 mEnd;
|
LLVector3 mEnd;
|
||||||
|
|
@ -105,11 +108,17 @@ public:
|
||||||
S32 getNumBones() const { return mNumBones; }
|
S32 getNumBones() const { return mNumBones; }
|
||||||
S32 getNumCollisionVolumes() const { return mNumCollisionVolumes; }
|
S32 getNumCollisionVolumes() const { return mNumCollisionVolumes; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
typedef std::vector<LLAvatarBoneInfo*> bone_info_list_t;
|
||||||
|
static void getJointMatricesAndHierarhy(
|
||||||
|
LLAvatarBoneInfo* bone_info,
|
||||||
|
LLJointData& data,
|
||||||
|
const glm::mat4& parent_mat);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
S32 mNumBones;
|
S32 mNumBones;
|
||||||
S32 mNumCollisionVolumes;
|
S32 mNumCollisionVolumes;
|
||||||
LLAvatarAppearance::joint_alias_map_t mJointAliasMap;
|
LLAvatarAppearance::joint_alias_map_t mJointAliasMap;
|
||||||
typedef std::vector<LLAvatarBoneInfo*> bone_info_list_t;
|
|
||||||
bone_info_list_t mBoneInfoList;
|
bone_info_list_t mBoneInfoList;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1598,6 +1607,15 @@ bool LLAvatarBoneInfo::parseXml(LLXmlTreeNode* node)
|
||||||
mSupport = "base";
|
mSupport = "base";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skeleton has 133 bones, but shader only allows 110 (LL_MAX_JOINTS_PER_MESH_OBJECT)
|
||||||
|
// Groups can be used by importer to cut out unused groups of joints
|
||||||
|
static LLStdStringHandle group_string = LLXmlTree::addAttributeString("group");
|
||||||
|
if (!node->getFastAttributeString(group_string, mGroup))
|
||||||
|
{
|
||||||
|
LL_WARNS() << "Bone without group " << mName << LL_ENDL;
|
||||||
|
mGroup = "global";
|
||||||
|
}
|
||||||
|
|
||||||
if (mIsJoint)
|
if (mIsJoint)
|
||||||
{
|
{
|
||||||
static LLStdStringHandle pivot_string = LLXmlTree::addAttributeString("pivot");
|
static LLStdStringHandle pivot_string = LLXmlTree::addAttributeString("pivot");
|
||||||
|
|
@ -1623,6 +1641,21 @@ bool LLAvatarBoneInfo::parseXml(LLXmlTreeNode* node)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
glm::mat4 LLAvatarBoneInfo::getJointMatrix()
|
||||||
|
{
|
||||||
|
glm::mat4 mat(1.0f);
|
||||||
|
// 1. Scaling
|
||||||
|
mat = glm::scale(mat, glm::vec3(mScale[0], mScale[1], mScale[2]));
|
||||||
|
// 2. Rotation (Euler angles rad)
|
||||||
|
mat = glm::rotate(mat, mRot[0], glm::vec3(1, 0, 0));
|
||||||
|
mat = glm::rotate(mat, mRot[1], glm::vec3(0, 1, 0));
|
||||||
|
mat = glm::rotate(mat, mRot[2], glm::vec3(0, 0, 1));
|
||||||
|
// 3. Position
|
||||||
|
mat = glm::translate(mat, glm::vec3(mPos[0], mPos[1], mPos[2]));
|
||||||
|
return mat;
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// LLAvatarSkeletonInfo::parseXml()
|
// LLAvatarSkeletonInfo::parseXml()
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
@ -1653,6 +1686,25 @@ bool LLAvatarSkeletonInfo::parseXml(LLXmlTreeNode* node)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LLAvatarSkeletonInfo::getJointMatricesAndHierarhy(
|
||||||
|
LLAvatarBoneInfo* bone_info,
|
||||||
|
LLJointData& data,
|
||||||
|
const glm::mat4& parent_mat)
|
||||||
|
{
|
||||||
|
data.mName = bone_info->mName;
|
||||||
|
data.mJointMatrix = bone_info->getJointMatrix();
|
||||||
|
data.mScale = glm::vec3(bone_info->mScale[0], bone_info->mScale[1], bone_info->mScale[2]);
|
||||||
|
data.mRotation = bone_info->mRot;
|
||||||
|
data.mRestMatrix = parent_mat * data.mJointMatrix;
|
||||||
|
data.mIsJoint = bone_info->mIsJoint;
|
||||||
|
data.mGroup = bone_info->mGroup;
|
||||||
|
for (LLAvatarBoneInfo* child_info : bone_info->mChildren)
|
||||||
|
{
|
||||||
|
LLJointData& child_data = data.mChildren.emplace_back();
|
||||||
|
getJointMatricesAndHierarhy(child_info, child_data, data.mRestMatrix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Make aliases for joint and push to map.
|
//Make aliases for joint and push to map.
|
||||||
void LLAvatarAppearance::makeJointAliases(LLAvatarBoneInfo *bone_info)
|
void LLAvatarAppearance::makeJointAliases(LLAvatarBoneInfo *bone_info)
|
||||||
{
|
{
|
||||||
|
|
@ -1714,6 +1766,16 @@ const LLAvatarAppearance::joint_alias_map_t& LLAvatarAppearance::getJointAliases
|
||||||
return mJointAliasMap;
|
return mJointAliasMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LLAvatarAppearance::getJointMatricesAndHierarhy(std::vector<LLJointData> &data) const
|
||||||
|
{
|
||||||
|
glm::mat4 identity(1.f);
|
||||||
|
for (LLAvatarBoneInfo* bone_info : sAvatarSkeletonInfo->mBoneInfoList)
|
||||||
|
{
|
||||||
|
LLJointData& child_data = data.emplace_back();
|
||||||
|
LLAvatarSkeletonInfo::getJointMatricesAndHierarhy(bone_info, child_data, identity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// parseXmlSkeletonNode(): parses <skeleton> nodes from XML tree
|
// parseXmlSkeletonNode(): parses <skeleton> nodes from XML tree
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
#include "lltexlayer.h"
|
#include "lltexlayer.h"
|
||||||
#include "llviewervisualparam.h"
|
#include "llviewervisualparam.h"
|
||||||
#include "llxmltree.h"
|
#include "llxmltree.h"
|
||||||
|
#include "v4math.h"
|
||||||
|
|
||||||
class LLTexLayerSet;
|
class LLTexLayerSet;
|
||||||
class LLTexGlobalColor;
|
class LLTexGlobalColor;
|
||||||
|
|
@ -41,6 +42,7 @@ class LLTexGlobalColorInfo;
|
||||||
class LLWearableData;
|
class LLWearableData;
|
||||||
class LLAvatarBoneInfo;
|
class LLAvatarBoneInfo;
|
||||||
class LLAvatarSkeletonInfo;
|
class LLAvatarSkeletonInfo;
|
||||||
|
class LLJointData;
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// LLAvatarAppearance
|
// LLAvatarAppearance
|
||||||
|
|
@ -138,7 +140,7 @@ public:
|
||||||
LLVector3 mHeadOffset{}; // current head position
|
LLVector3 mHeadOffset{}; // current head position
|
||||||
LLAvatarJoint* mRoot{ nullptr };
|
LLAvatarJoint* mRoot{ nullptr };
|
||||||
|
|
||||||
typedef std::map<std::string, LLJoint*> joint_map_t;
|
typedef std::map<std::string, LLJoint*, std::less<>> joint_map_t;
|
||||||
joint_map_t mJointMap;
|
joint_map_t mJointMap;
|
||||||
|
|
||||||
typedef std::map<std::string, LLVector3> joint_state_map_t;
|
typedef std::map<std::string, LLVector3> joint_state_map_t;
|
||||||
|
|
@ -151,9 +153,11 @@ public:
|
||||||
public:
|
public:
|
||||||
typedef std::vector<LLAvatarJoint*> avatar_joint_list_t;
|
typedef std::vector<LLAvatarJoint*> avatar_joint_list_t;
|
||||||
const avatar_joint_list_t& getSkeleton() { return mSkeleton; }
|
const avatar_joint_list_t& getSkeleton() { return mSkeleton; }
|
||||||
typedef std::map<std::string, std::string> joint_alias_map_t;
|
typedef std::map<std::string, std::string, std::less<>> joint_alias_map_t;
|
||||||
const joint_alias_map_t& getJointAliases();
|
const joint_alias_map_t& getJointAliases();
|
||||||
|
typedef std::map<std::string, std::string> joint_parent_map_t; // matrix plus parent
|
||||||
|
typedef std::map<std::string, glm::mat4> joint_rest_map_t;
|
||||||
|
void getJointMatricesAndHierarhy(std::vector<LLJointData> &data) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static bool parseSkeletonFile(const std::string& filename, LLXmlTree& skeleton_xml_tree);
|
static bool parseSkeletonFile(const std::string& filename, LLXmlTree& skeleton_xml_tree);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
/**
|
||||||
|
* @file lljointdata.h
|
||||||
|
* @brief LLJointData class for holding individual joint data and skeleton
|
||||||
|
*
|
||||||
|
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
|
||||||
|
* Second Life Viewer Source Code
|
||||||
|
* Copyright (C) 2025, Linden Research, Inc.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation;
|
||||||
|
* version 2.1 of the License only.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||||
|
* $/LicenseInfo$
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LL_LLJOINTDATA_H
|
||||||
|
#define LL_LLJOINTDATA_H
|
||||||
|
|
||||||
|
#include "v4math.h"
|
||||||
|
|
||||||
|
// may be just move LLAvatarBoneInfo
|
||||||
|
class LLJointData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string mName;
|
||||||
|
std::string mGroup;
|
||||||
|
glm::mat4 mJointMatrix;
|
||||||
|
glm::mat4 mRestMatrix;
|
||||||
|
glm::vec3 mScale;
|
||||||
|
LLVector3 mRotation;
|
||||||
|
|
||||||
|
typedef std::vector<LLJointData> bones_t;
|
||||||
|
bones_t mChildren;
|
||||||
|
|
||||||
|
bool mIsJoint; // if not, collision_volume
|
||||||
|
enum SupportCategory
|
||||||
|
{
|
||||||
|
SUPPORT_BASE,
|
||||||
|
SUPPORT_EXTENDED
|
||||||
|
};
|
||||||
|
SupportCategory mSupport;
|
||||||
|
void setSupport(const std::string& support)
|
||||||
|
{
|
||||||
|
if (support == "extended")
|
||||||
|
{
|
||||||
|
mSupport = SUPPORT_EXTENDED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSupport = SUPPORT_BASE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //LL_LLJOINTDATA_H
|
||||||
|
|
@ -1293,7 +1293,7 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
|
||||||
{
|
{
|
||||||
if (!force_render && !hasMorph())
|
if (!force_render && !hasMorph())
|
||||||
{
|
{
|
||||||
LL_DEBUGS() << "skipping renderMorphMasks for " << getUUID() << LL_ENDL;
|
LL_DEBUGS("Morph") << "skipping renderMorphMasks for " << getUUID() << LL_ENDL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LL_PROFILE_ZONE_SCOPED;
|
LL_PROFILE_ZONE_SCOPED;
|
||||||
|
|
@ -1325,7 +1325,7 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
|
||||||
success &= param->render( x, y, width, height );
|
success &= param->render( x, y, width, height );
|
||||||
if (!success && !force_render)
|
if (!success && !force_render)
|
||||||
{
|
{
|
||||||
LL_DEBUGS() << "Failed to render param " << param->getID() << " ; skipping morph mask." << LL_ENDL;
|
LL_DEBUGS("Morph") << "Failed to render param " << param->getID() << " ; skipping morph mask." << LL_ENDL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1365,7 +1365,7 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LL_WARNS() << "Skipping rendering of " << getInfo()->mStaticImageFileName
|
LL_WARNS("Morph") << "Skipping rendering of " << getInfo()->mStaticImageFileName
|
||||||
<< "; expected 1 or 4 components." << LL_ENDL;
|
<< "; expected 1 or 4 components." << LL_ENDL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1404,7 +1404,7 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
|
||||||
// We can get bad morph masks during login, on minimize, and occasional gl errors.
|
// We can get bad morph masks during login, on minimize, and occasional gl errors.
|
||||||
// We should only be doing this when we believe something has changed with respect to the user's appearance.
|
// We should only be doing this when we believe something has changed with respect to the user's appearance.
|
||||||
{
|
{
|
||||||
LL_DEBUGS("Avatar") << "gl alpha cache of morph mask not found, doing readback: " << getName() << LL_ENDL;
|
LL_DEBUGS("Morph") << "gl alpha cache of morph mask not found, doing readback: " << getName() << LL_ENDL;
|
||||||
// clear out a slot if we have filled our cache
|
// clear out a slot if we have filled our cache
|
||||||
S32 max_cache_entries = getTexLayerSet()->getAvatarAppearance()->isSelf() ? 4 : 1;
|
S32 max_cache_entries = getTexLayerSet()->getAvatarAppearance()->isSelf() ? 4 : 1;
|
||||||
while ((S32)mAlphaCache.size() >= max_cache_entries)
|
while ((S32)mAlphaCache.size() >= max_cache_entries)
|
||||||
|
|
@ -1444,7 +1444,13 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
|
||||||
}
|
}
|
||||||
|
|
||||||
glGetTexImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGBA, GL_UNSIGNED_BYTE, temp);
|
glGetTexImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGBA, GL_UNSIGNED_BYTE, temp);
|
||||||
|
GLenum error = glGetError();
|
||||||
|
if (error != GL_NO_ERROR)
|
||||||
|
{
|
||||||
|
LL_INFOS("Morph") << "GL Error while reading back morph texture. Error code: " << error << LL_ENDL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
U8* alpha_cursor = alpha_data;
|
U8* alpha_cursor = alpha_data;
|
||||||
U8* pixel = temp;
|
U8* pixel = temp;
|
||||||
for (int i = 0; i < pixels; i++)
|
for (int i = 0; i < pixels; i++)
|
||||||
|
|
@ -1452,6 +1458,7 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
|
||||||
*alpha_cursor++ = pixel[3];
|
*alpha_cursor++ = pixel[3];
|
||||||
pixel += 4;
|
pixel += 4;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
gGL.getTexUnit(0)->disable();
|
gGL.getTexUnit(0)->disable();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ LLQuaternion::Order bvhStringToOrder( char *str )
|
||||||
// LLBVHLoader()
|
// LLBVHLoader()
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map<std::string, std::string>& joint_alias_map )
|
LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map<std::string, std::string, std::less<>>& joint_alias_map )
|
||||||
{
|
{
|
||||||
reset();
|
reset();
|
||||||
errorLine = 0;
|
errorLine = 0;
|
||||||
|
|
@ -156,9 +156,9 @@ LLBVHLoader::LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recognize all names we've been told are legal.
|
// Recognize all names we've been told are legal.
|
||||||
for (std::map<std::string, std::string>::value_type& alias_pair : joint_alias_map)
|
for (const auto& [alias, joint] : joint_alias_map)
|
||||||
{
|
{
|
||||||
makeTranslation( alias_pair.first , alias_pair.second );
|
makeTranslation(alias, joint);
|
||||||
}
|
}
|
||||||
|
|
||||||
char error_text[128]; /* Flawfinder: ignore */
|
char error_text[128]; /* Flawfinder: ignore */
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,7 @@ class LLBVHLoader
|
||||||
friend class LLKeyframeMotion;
|
friend class LLKeyframeMotion;
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map<std::string, std::string>& joint_alias_map );
|
LLBVHLoader(const char* buffer, ELoadStatus &loadStatus, S32 &errorLine, std::map<std::string, std::string, std::less<>>& joint_alias_map );
|
||||||
~LLBVHLoader();
|
~LLBVHLoader();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -77,12 +77,11 @@ LLCharacter::~LLCharacter()
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// getJoint()
|
// getJoint()
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
LLJoint *LLCharacter::getJoint( const std::string &name )
|
LLJoint* LLCharacter::getJoint(std::string_view name)
|
||||||
{
|
{
|
||||||
LLJoint* joint = NULL;
|
LLJoint* joint = nullptr;
|
||||||
|
|
||||||
LLJoint *root = getRootJoint();
|
if (LLJoint* root = getRootJoint())
|
||||||
if (root)
|
|
||||||
{
|
{
|
||||||
joint = root->findJoint(name);
|
joint = root->findJoint(name);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ public:
|
||||||
// get the specified joint
|
// get the specified joint
|
||||||
// default implementation does recursive search,
|
// default implementation does recursive search,
|
||||||
// subclasses may optimize/cache results.
|
// subclasses may optimize/cache results.
|
||||||
virtual LLJoint *getJoint( const std::string &name );
|
virtual LLJoint* getJoint(std::string_view name);
|
||||||
|
|
||||||
// get the position of the character
|
// get the position of the character
|
||||||
virtual LLVector3 getCharacterPosition() = 0;
|
virtual LLVector3 getCharacterPosition() = 0;
|
||||||
|
|
|
||||||
|
|
@ -242,21 +242,20 @@ LLJoint *LLJoint::getRoot()
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// findJoint()
|
// findJoint()
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
LLJoint *LLJoint::findJoint( const std::string &name )
|
LLJoint* LLJoint::findJoint(std::string_view name)
|
||||||
{
|
{
|
||||||
if (name == getName())
|
if (name == getName())
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
for (LLJoint* joint : mChildren)
|
for (LLJoint* joint : mChildren)
|
||||||
{
|
{
|
||||||
LLJoint *found = joint->findJoint(name);
|
if (LLJoint* found = joint->findJoint(name))
|
||||||
if (found)
|
|
||||||
{
|
{
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,7 @@ public:
|
||||||
LLJoint *getRoot();
|
LLJoint *getRoot();
|
||||||
|
|
||||||
// search for child joints by name
|
// search for child joints by name
|
||||||
LLJoint *findJoint( const std::string &name );
|
LLJoint* findJoint(std::string_view name);
|
||||||
|
|
||||||
// add/remove children
|
// add/remove children
|
||||||
void addChild( LLJoint *joint );
|
void addChild( LLJoint *joint );
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
|
|
||||||
#include "apr_portable.h"
|
#include "apr_portable.h"
|
||||||
|
|
||||||
|
#include "llapp.h"
|
||||||
#include "llthread.h"
|
#include "llthread.h"
|
||||||
#include "llmutex.h"
|
#include "llmutex.h"
|
||||||
|
|
||||||
|
|
@ -35,6 +36,7 @@
|
||||||
#include "lltrace.h"
|
#include "lltrace.h"
|
||||||
#include "lltracethreadrecorder.h"
|
#include "lltracethreadrecorder.h"
|
||||||
#include "llexception.h"
|
#include "llexception.h"
|
||||||
|
#include "workqueue.h"
|
||||||
|
|
||||||
#if LL_LINUX
|
#if LL_LINUX
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
|
|
@ -106,6 +108,27 @@ namespace
|
||||||
return s_thread_id;
|
return s_thread_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if LL_WINDOWS
|
||||||
|
|
||||||
|
static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific
|
||||||
|
|
||||||
|
U32 exception_filter(U32 code, struct _EXCEPTION_POINTERS* exception_infop)
|
||||||
|
{
|
||||||
|
if (LLApp::instance()->reportCrashToBugsplat((void*)exception_infop))
|
||||||
|
{
|
||||||
|
// Handled
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
else if (code == STATUS_MSC_EXCEPTION)
|
||||||
|
{
|
||||||
|
// C++ exception, go on
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle it, convert to std::exception
|
||||||
|
return EXCEPTION_EXECUTE_HANDLER;
|
||||||
|
}
|
||||||
|
#endif // LL_WINDOWS
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
LL_COMMON_API bool on_main_thread()
|
LL_COMMON_API bool on_main_thread()
|
||||||
|
|
@ -157,20 +180,11 @@ void LLThread::threadRun()
|
||||||
// Run the user supplied function
|
// Run the user supplied function
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
try
|
#ifdef LL_WINDOWS
|
||||||
{
|
sehHandle(); // Structured Exception Handling
|
||||||
run();
|
#else
|
||||||
}
|
tryRun();
|
||||||
catch (const LLContinueError &e)
|
#endif
|
||||||
{
|
|
||||||
LL_WARNS("THREAD") << "ContinueException on thread '" << mName <<
|
|
||||||
"' reentering run(). Error what is: '" << e.what() << "'" << LL_ENDL;
|
|
||||||
//output possible call stacks to log file.
|
|
||||||
LLError::LLCallStacks::print();
|
|
||||||
|
|
||||||
LOG_UNHANDLED_EXCEPTION("LLThread");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} while (true);
|
} while (true);
|
||||||
|
|
@ -188,6 +202,69 @@ void LLThread::threadRun()
|
||||||
mStatus = STOPPED;
|
mStatus = STOPPED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LLThread::tryRun()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
catch (const LLContinueError& e)
|
||||||
|
{
|
||||||
|
LL_WARNS("THREAD") << "ContinueException on thread '" << mName <<
|
||||||
|
"'. Error what is: '" << e.what() << "'" << LL_ENDL;
|
||||||
|
LLError::LLCallStacks::print();
|
||||||
|
|
||||||
|
LOG_UNHANDLED_EXCEPTION("LLThread");
|
||||||
|
}
|
||||||
|
catch (std::bad_alloc&)
|
||||||
|
{
|
||||||
|
// Todo: improve this, this is going to have a different callstack
|
||||||
|
// instead of showing where it crashed
|
||||||
|
LL_WARNS("THREAD") << "Out of memory in a thread: " << mName << LL_ENDL;
|
||||||
|
|
||||||
|
LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
|
||||||
|
main_queue->post(
|
||||||
|
// Bind the current exception, rethrow it in main loop.
|
||||||
|
[]() {
|
||||||
|
LLError::LLUserWarningMsg::showOutOfMemory();
|
||||||
|
LL_ERRS("THREAD") << "Out of memory in a thread" << LL_ENDL;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#ifndef LL_WINDOWS
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
// Stash any other kind of uncaught exception to be rethrown by main thread.
|
||||||
|
LL_WARNS("THREAD") << "Capturing and rethrowing uncaught exception in LLThread "
|
||||||
|
<< mName << LL_ENDL;
|
||||||
|
|
||||||
|
LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
|
||||||
|
main_queue->post(
|
||||||
|
// Bind the current exception, rethrow it in main loop.
|
||||||
|
[exc = std::current_exception()]() { std::rethrow_exception(exc); });
|
||||||
|
}
|
||||||
|
#endif // else LL_WINDOWS
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef LL_WINDOWS
|
||||||
|
void LLThread::sehHandle()
|
||||||
|
{
|
||||||
|
__try
|
||||||
|
{
|
||||||
|
// handle stop and continue exceptions first
|
||||||
|
tryRun();
|
||||||
|
}
|
||||||
|
__except (exception_filter(GetExceptionCode(), GetExceptionInformation()))
|
||||||
|
{
|
||||||
|
// convert to C++ styled exception
|
||||||
|
// Note: it might be better to use _se_set_translator
|
||||||
|
// if you want exception to inherit full callstack
|
||||||
|
char integer_string[512];
|
||||||
|
sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode());
|
||||||
|
throw std::exception(integer_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
LLThread::LLThread(const std::string& name, apr_pool_t *poolp) :
|
LLThread::LLThread(const std::string& name, apr_pool_t *poolp) :
|
||||||
mPaused(false),
|
mPaused(false),
|
||||||
mName(name),
|
mName(name),
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ public:
|
||||||
// Called from MAIN THREAD.
|
// Called from MAIN THREAD.
|
||||||
void pause();
|
void pause();
|
||||||
void unpause();
|
void unpause();
|
||||||
bool isPaused() { return isStopped() || mPaused; }
|
bool isPaused() const { return isStopped() || mPaused; }
|
||||||
|
|
||||||
// Cause the thread to wake up and check its condition
|
// Cause the thread to wake up and check its condition
|
||||||
void wake();
|
void wake();
|
||||||
|
|
@ -97,6 +97,11 @@ private:
|
||||||
|
|
||||||
// static function passed to APR thread creation routine
|
// static function passed to APR thread creation routine
|
||||||
void threadRun();
|
void threadRun();
|
||||||
|
void tryRun();
|
||||||
|
|
||||||
|
#ifdef LL_WINDOWS
|
||||||
|
void sehHandle();
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string mName;
|
std::string mName;
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,8 @@ void LL::WorkQueueBase::callWork(const Work& work)
|
||||||
LOG_UNHANDLED_EXCEPTION(getKey());
|
LOG_UNHANDLED_EXCEPTION(getKey());
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
|
{
|
||||||
|
if (getKey() != "mainloop")
|
||||||
{
|
{
|
||||||
// Stash any other kind of uncaught exception to be rethrown by main thread.
|
// Stash any other kind of uncaught exception to be rethrown by main thread.
|
||||||
LL_WARNS("LLCoros") << "Capturing and rethrowing uncaught exception in WorkQueueBase "
|
LL_WARNS("LLCoros") << "Capturing and rethrowing uncaught exception in WorkQueueBase "
|
||||||
|
|
@ -219,6 +221,12 @@ void LL::WorkQueueBase::callWork(const Work& work)
|
||||||
// Bind the current exception, rethrow it in main loop.
|
// Bind the current exception, rethrow it in main loop.
|
||||||
[exc = std::current_exception()]() { std::rethrow_exception(exc); });
|
[exc = std::current_exception()]() { std::rethrow_exception(exc); });
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// let main loop crash
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif // else LL_WINDOWS
|
#endif // else LL_WINDOWS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,9 +110,10 @@ std::vector<std::string> LLDir::getFilesInDir(const std::string &dirname)
|
||||||
|
|
||||||
std::vector<std::string> v;
|
std::vector<std::string> v;
|
||||||
|
|
||||||
if (exists(p))
|
boost::system::error_code ec;
|
||||||
|
if (exists(p, ec) && !ec.failed())
|
||||||
{
|
{
|
||||||
if (is_directory(p))
|
if (is_directory(p, ec) && !ec.failed())
|
||||||
{
|
{
|
||||||
boost::filesystem::directory_iterator end_iter;
|
boost::filesystem::directory_iterator end_iter;
|
||||||
for (boost::filesystem::directory_iterator dir_itr(p);
|
for (boost::filesystem::directory_iterator dir_itr(p);
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ bool LLImageDimensionsInfo::load(const std::string& src_filename,U32 codec)
|
||||||
bool LLImageDimensionsInfo::getImageDimensionsBmp()
|
bool LLImageDimensionsInfo::getImageDimensionsBmp()
|
||||||
{
|
{
|
||||||
// Make sure the file is long enough.
|
// Make sure the file is long enough.
|
||||||
const S32 DATA_LEN = 26; // BMP header (14) + DIB header size (4) + width (4) + height (4)
|
constexpr S32 DATA_LEN = 26; // BMP header (14) + DIB header size (4) + width (4) + height (4)
|
||||||
if (!checkFileLength(DATA_LEN))
|
if (!checkFileLength(DATA_LEN))
|
||||||
{
|
{
|
||||||
LL_WARNS() << "Premature end of file" << LL_ENDL;
|
LL_WARNS() << "Premature end of file" << LL_ENDL;
|
||||||
|
|
@ -105,7 +105,7 @@ bool LLImageDimensionsInfo::getImageDimensionsBmp()
|
||||||
|
|
||||||
bool LLImageDimensionsInfo::getImageDimensionsTga()
|
bool LLImageDimensionsInfo::getImageDimensionsTga()
|
||||||
{
|
{
|
||||||
const S32 TGA_FILE_HEADER_SIZE = 12;
|
constexpr S32 TGA_FILE_HEADER_SIZE = 12;
|
||||||
|
|
||||||
// Make sure the file is long enough.
|
// Make sure the file is long enough.
|
||||||
if (!checkFileLength(TGA_FILE_HEADER_SIZE + 1 /* width */ + 1 /* height */))
|
if (!checkFileLength(TGA_FILE_HEADER_SIZE + 1 /* width */ + 1 /* height */))
|
||||||
|
|
@ -124,7 +124,7 @@ bool LLImageDimensionsInfo::getImageDimensionsTga()
|
||||||
|
|
||||||
bool LLImageDimensionsInfo::getImageDimensionsPng()
|
bool LLImageDimensionsInfo::getImageDimensionsPng()
|
||||||
{
|
{
|
||||||
const S32 PNG_MAGIC_SIZE = 8;
|
constexpr S32 PNG_MAGIC_SIZE = 8;
|
||||||
|
|
||||||
// Make sure the file is long enough.
|
// Make sure the file is long enough.
|
||||||
if (!checkFileLength(PNG_MAGIC_SIZE + 8 + sizeof(S32) * 2 /* width, height */))
|
if (!checkFileLength(PNG_MAGIC_SIZE + 8 + sizeof(S32) * 2 /* width, height */))
|
||||||
|
|
@ -134,7 +134,7 @@ bool LLImageDimensionsInfo::getImageDimensionsPng()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read PNG signature.
|
// Read PNG signature.
|
||||||
const U8 png_magic[PNG_MAGIC_SIZE] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
|
constexpr U8 png_magic[PNG_MAGIC_SIZE] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
|
||||||
U8 signature[PNG_MAGIC_SIZE];
|
U8 signature[PNG_MAGIC_SIZE];
|
||||||
mInfile.read((void*)signature, PNG_MAGIC_SIZE);
|
mInfile.read((void*)signature, PNG_MAGIC_SIZE);
|
||||||
|
|
||||||
|
|
@ -166,34 +166,36 @@ bool LLImageDimensionsInfo::getImageDimensionsJpeg()
|
||||||
{
|
{
|
||||||
sJpegErrorEncountered = false;
|
sJpegErrorEncountered = false;
|
||||||
clean();
|
clean();
|
||||||
FILE *fp = LLFile::fopen(mSrcFilename, "rb");
|
FILE* fp = LLFile::fopen(mSrcFilename, "rb");
|
||||||
if (fp == NULL)
|
if (!fp)
|
||||||
{
|
{
|
||||||
setLastError("Unable to open file for reading", mSrcFilename);
|
setLastError("Unable to open file for reading", mSrcFilename);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make sure this is a JPEG file. */
|
/* Make sure this is a JPEG file. */
|
||||||
const size_t JPEG_MAGIC_SIZE = 2;
|
constexpr size_t JPEG_MAGIC_SIZE = 2;
|
||||||
const U8 jpeg_magic[JPEG_MAGIC_SIZE] = {0xFF, 0xD8};
|
constexpr U8 jpeg_magic[JPEG_MAGIC_SIZE] = {0xFF, 0xD8};
|
||||||
U8 signature[JPEG_MAGIC_SIZE];
|
U8 signature[JPEG_MAGIC_SIZE];
|
||||||
|
|
||||||
if (fread(signature, sizeof(signature), 1, fp) != 1)
|
if (fread(signature, sizeof(signature), 1, fp) != 1)
|
||||||
{
|
{
|
||||||
LL_WARNS() << "Premature end of file" << LL_ENDL;
|
LL_WARNS() << "Premature end of file" << LL_ENDL;
|
||||||
|
fclose(fp);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (memcmp(signature, jpeg_magic, JPEG_MAGIC_SIZE) != 0)
|
if (memcmp(signature, jpeg_magic, JPEG_MAGIC_SIZE) != 0)
|
||||||
{
|
{
|
||||||
LL_WARNS() << "Not a JPEG" << LL_ENDL;
|
LL_WARNS() << "Not a JPEG" << LL_ENDL;
|
||||||
mWarning = "texture_load_format_error";
|
mWarning = "texture_load_format_error";
|
||||||
|
fclose(fp);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
fseek(fp, 0, SEEK_SET); // go back to start of the file
|
fseek(fp, 0, SEEK_SET); // go back to start of the file
|
||||||
|
|
||||||
/* Init jpeg */
|
/* Init jpeg */
|
||||||
jpeg_error_mgr jerr;
|
jpeg_error_mgr jerr;
|
||||||
jpeg_decompress_struct cinfo;
|
jpeg_decompress_struct cinfo{};
|
||||||
cinfo.err = jpeg_std_error(&jerr);
|
cinfo.err = jpeg_std_error(&jerr);
|
||||||
// Call our function instead of exit() if Libjpeg encounters an error.
|
// Call our function instead of exit() if Libjpeg encounters an error.
|
||||||
// This is done to avoid crash in this case (STORM-472).
|
// This is done to avoid crash in this case (STORM-472).
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class LLImageDimensionsInfo
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LLImageDimensionsInfo():
|
LLImageDimensionsInfo():
|
||||||
mData(NULL)
|
mData(nullptr)
|
||||||
,mHeight(0)
|
,mHeight(0)
|
||||||
,mWidth(0)
|
,mWidth(0)
|
||||||
{}
|
{}
|
||||||
|
|
@ -67,7 +67,7 @@ protected:
|
||||||
{
|
{
|
||||||
mInfile.close();
|
mInfile.close();
|
||||||
delete[] mData;
|
delete[] mData;
|
||||||
mData = NULL;
|
mData = nullptr;
|
||||||
mWidth = 0;
|
mWidth = 0;
|
||||||
mHeight = 0;
|
mHeight = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -281,10 +281,11 @@ S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 r
|
||||||
S32 height = (h > 0) ? h : 2048;
|
S32 height = (h > 0) ? h : 2048;
|
||||||
S32 max_dimension = llmax(width, height); // Find largest dimension
|
S32 max_dimension = llmax(width, height); // Find largest dimension
|
||||||
S32 block_area = MAX_BLOCK_SIZE * MAX_BLOCK_SIZE; // Calculated initial block area from established max block size (currently 64)
|
S32 block_area = MAX_BLOCK_SIZE * MAX_BLOCK_SIZE; // Calculated initial block area from established max block size (currently 64)
|
||||||
block_area *= llmax((max_dimension / MAX_BLOCK_SIZE / max_components), 1); // Adjust initial block area by ratio of largest dimension to block size per component
|
S32 max_layers = (S32)llmax(llround(log2f((float)max_dimension) - log2f((float)MAX_BLOCK_SIZE)), 4); // Find number of powers of two between extents and block size to a minimum of 4
|
||||||
S32 totalbytes = (S32) (block_area * max_components * precision); // First block layer computed before loop without compression rate
|
block_area *= llmax(max_layers, 1); // Adjust initial block area by max number of layers
|
||||||
S32 block_layers = 1; // Start at layer 1 since first block layer is computed outside loop
|
S32 totalbytes = (S32) (MIN_LAYER_SIZE * max_components * precision); // Start estimation with a minimum reasonable size
|
||||||
while (block_layers < 6) // Walk five layers for the five discards in JPEG2000
|
S32 block_layers = 0;
|
||||||
|
while (block_layers <= max_layers) // Walk the layers
|
||||||
{
|
{
|
||||||
if (block_layers <= (5 - discard_level)) // Walk backwards from discard 5 to required discard layer.
|
if (block_layers <= (5 - discard_level)) // Walk backwards from discard 5 to required discard layer.
|
||||||
totalbytes += (S32) (block_area * max_components * precision * rate); // Add each block layer reduced by assumed compression rate
|
totalbytes += (S32) (block_area * max_components * precision * rate); // Add each block layer reduced by assumed compression rate
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,6 @@
|
||||||
#include "event.h"
|
#include "event.h"
|
||||||
#include "cio.h"
|
#include "cio.h"
|
||||||
|
|
||||||
#define MAX_ENCODED_DISCARD_LEVELS 5
|
|
||||||
|
|
||||||
// Factory function: see declaration in llimagej2c.cpp
|
// Factory function: see declaration in llimagej2c.cpp
|
||||||
LLImageJ2CImpl* fallbackCreateLLImageJ2CImpl()
|
LLImageJ2CImpl* fallbackCreateLLImageJ2CImpl()
|
||||||
{
|
{
|
||||||
|
|
@ -132,73 +130,96 @@ static void opj_error(const char* msg, void* user_data)
|
||||||
|
|
||||||
static OPJ_SIZE_T opj_read(void * buffer, OPJ_SIZE_T bytes, void* user_data)
|
static OPJ_SIZE_T opj_read(void * buffer, OPJ_SIZE_T bytes, void* user_data)
|
||||||
{
|
{
|
||||||
llassert(user_data);
|
llassert(user_data && buffer);
|
||||||
|
|
||||||
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
|
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
|
||||||
OPJ_SIZE_T remainder = (jpeg_codec->size - jpeg_codec->offset);
|
|
||||||
if (remainder <= 0)
|
if (jpeg_codec->offset < 0 || static_cast<OPJ_SIZE_T>(jpeg_codec->offset) >= jpeg_codec->size)
|
||||||
{
|
{
|
||||||
jpeg_codec->offset = jpeg_codec->size;
|
jpeg_codec->offset = jpeg_codec->size;
|
||||||
// Indicate end of stream (hacky?)
|
return static_cast<OPJ_SIZE_T>(-1); // Indicate EOF
|
||||||
return (OPJ_OFF_T)-1;
|
|
||||||
}
|
}
|
||||||
OPJ_SIZE_T to_read = llclamp(U32(bytes), U32(0), U32(remainder));
|
|
||||||
|
OPJ_SIZE_T remainder = jpeg_codec->size - static_cast<OPJ_SIZE_T>(jpeg_codec->offset);
|
||||||
|
OPJ_SIZE_T to_read = (bytes < remainder) ? bytes : remainder;
|
||||||
|
|
||||||
memcpy(buffer, jpeg_codec->buffer + jpeg_codec->offset, to_read);
|
memcpy(buffer, jpeg_codec->buffer + jpeg_codec->offset, to_read);
|
||||||
jpeg_codec->offset += to_read;
|
jpeg_codec->offset += to_read;
|
||||||
|
|
||||||
return to_read;
|
return to_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
static OPJ_SIZE_T opj_write(void * buffer, OPJ_SIZE_T bytes, void* user_data)
|
static OPJ_SIZE_T opj_write(void * buffer, OPJ_SIZE_T bytes, void* user_data)
|
||||||
{
|
{
|
||||||
llassert(user_data);
|
llassert(user_data && buffer);
|
||||||
|
|
||||||
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
|
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
|
||||||
OPJ_SIZE_T remainder = jpeg_codec->size - jpeg_codec->offset;
|
OPJ_OFF_T required_offset = jpeg_codec->offset + static_cast<OPJ_OFF_T>(bytes);
|
||||||
if (remainder < bytes)
|
|
||||||
|
// Overflow check
|
||||||
|
if (required_offset < jpeg_codec->offset)
|
||||||
|
return 0; // Overflow detected
|
||||||
|
|
||||||
|
// Resize if needed (exponential growth)
|
||||||
|
if (required_offset > static_cast<OPJ_OFF_T>(jpeg_codec->size))
|
||||||
{
|
{
|
||||||
OPJ_SIZE_T new_size = jpeg_codec->size + (bytes - remainder);
|
OPJ_SIZE_T new_size = jpeg_codec->size ? jpeg_codec->size : 1024;
|
||||||
|
while (required_offset > static_cast<OPJ_OFF_T>(new_size))
|
||||||
|
new_size *= 2;
|
||||||
|
|
||||||
|
const OPJ_SIZE_T MAX_BUFFER_SIZE = 512 * 1024 * 1024; // 512 MB, increase if needed
|
||||||
|
if (new_size > MAX_BUFFER_SIZE) return 0;
|
||||||
|
|
||||||
U8* new_buffer = (U8*)ll_aligned_malloc_16(new_size);
|
U8* new_buffer = (U8*)ll_aligned_malloc_16(new_size);
|
||||||
memcpy(new_buffer, jpeg_codec->buffer, jpeg_codec->offset);
|
if (!new_buffer) return 0; // Allocation failed
|
||||||
U8* old_buffer = jpeg_codec->buffer;
|
|
||||||
|
if (jpeg_codec->offset > 0)
|
||||||
|
memcpy(new_buffer, jpeg_codec->buffer, static_cast<size_t>(jpeg_codec->offset));
|
||||||
|
|
||||||
|
ll_aligned_free_16(jpeg_codec->buffer);
|
||||||
jpeg_codec->buffer = new_buffer;
|
jpeg_codec->buffer = new_buffer;
|
||||||
ll_aligned_free_16(old_buffer);
|
|
||||||
jpeg_codec->size = new_size;
|
jpeg_codec->size = new_size;
|
||||||
}
|
}
|
||||||
memcpy(jpeg_codec->buffer + jpeg_codec->offset, buffer, bytes);
|
|
||||||
jpeg_codec->offset += bytes;
|
memcpy(jpeg_codec->buffer + jpeg_codec->offset, buffer, static_cast<size_t>(bytes));
|
||||||
|
jpeg_codec->offset = required_offset;
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
static OPJ_OFF_T opj_skip(OPJ_OFF_T bytes, void* user_data)
|
static OPJ_OFF_T opj_skip(OPJ_OFF_T bytes, void* user_data)
|
||||||
{
|
{
|
||||||
|
llassert(user_data);
|
||||||
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
|
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
|
||||||
jpeg_codec->offset += bytes;
|
|
||||||
|
|
||||||
if (jpeg_codec->offset > (OPJ_OFF_T)jpeg_codec->size)
|
OPJ_OFF_T new_offset = jpeg_codec->offset + bytes;
|
||||||
|
|
||||||
|
if (new_offset < 0 || new_offset > static_cast<OPJ_OFF_T>(jpeg_codec->size))
|
||||||
{
|
{
|
||||||
jpeg_codec->offset = jpeg_codec->size;
|
// Clamp and indicate EOF or error
|
||||||
// Indicate end of stream
|
jpeg_codec->offset = llclamp<OPJ_OFF_T>(new_offset, 0, static_cast<OPJ_OFF_T>(jpeg_codec->size));
|
||||||
return (OPJ_OFF_T)-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jpeg_codec->offset < 0)
|
|
||||||
{
|
|
||||||
// Shouldn't be possible?
|
|
||||||
jpeg_codec->offset = 0;
|
|
||||||
return (OPJ_OFF_T)-1;
|
return (OPJ_OFF_T)-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jpeg_codec->offset = new_offset;
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
static OPJ_BOOL opj_seek(OPJ_OFF_T bytes, void * user_data)
|
static OPJ_BOOL opj_seek(OPJ_OFF_T offset, void * user_data)
|
||||||
{
|
{
|
||||||
|
llassert(user_data);
|
||||||
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
|
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
|
||||||
jpeg_codec->offset = bytes;
|
|
||||||
jpeg_codec->offset = llclamp(U32(jpeg_codec->offset), U32(0), U32(jpeg_codec->size));
|
if (offset < 0 || offset > static_cast<OPJ_OFF_T>(jpeg_codec->size))
|
||||||
|
return OPJ_FALSE;
|
||||||
|
|
||||||
|
jpeg_codec->offset = offset;
|
||||||
return OPJ_TRUE;
|
return OPJ_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void opj_free_user_data(void * user_data)
|
static void opj_free_user_data(void * user_data)
|
||||||
{
|
{
|
||||||
|
llassert(user_data);
|
||||||
|
|
||||||
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
|
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
|
||||||
// Don't free, data is managed externally
|
// Don't free, data is managed externally
|
||||||
jpeg_codec->buffer = nullptr;
|
jpeg_codec->buffer = nullptr;
|
||||||
|
|
@ -208,14 +229,54 @@ static void opj_free_user_data(void * user_data)
|
||||||
|
|
||||||
static void opj_free_user_data_write(void * user_data)
|
static void opj_free_user_data_write(void * user_data)
|
||||||
{
|
{
|
||||||
|
llassert(user_data);
|
||||||
|
|
||||||
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
|
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
|
||||||
// Free, data was allocated here
|
// Free, data was allocated here
|
||||||
|
if (jpeg_codec->buffer)
|
||||||
|
{
|
||||||
ll_aligned_free_16(jpeg_codec->buffer);
|
ll_aligned_free_16(jpeg_codec->buffer);
|
||||||
jpeg_codec->buffer = nullptr;
|
jpeg_codec->buffer = nullptr;
|
||||||
|
}
|
||||||
jpeg_codec->size = 0;
|
jpeg_codec->size = 0;
|
||||||
jpeg_codec->offset = 0;
|
jpeg_codec->offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimates the number of layers necessary depending on the image surface (w x h)
|
||||||
|
*/
|
||||||
|
static U32 estimate_num_layers(U32 surface)
|
||||||
|
{
|
||||||
|
if (surface <= 1024) return 2; // Tiny (≤32×32)
|
||||||
|
else if (surface <= 16384) return 3; // Small (≤128×128)
|
||||||
|
else if (surface <= 262144) return 4; // Medium (≤512×512)
|
||||||
|
else if (surface <= 1048576) return 5; // Up to ~1MP
|
||||||
|
else return 6; // Up to ~1.5–2MP
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the parameters.tcp_rates according to the number of layers and a last tcp_rate value (which equals to the final compression ratio).
|
||||||
|
*
|
||||||
|
* Example for 6 layers:
|
||||||
|
*
|
||||||
|
* i = 5, parameters.tcp_rates[6 - 1 - 5] = 8.0f * (1 << (5 << 1)) = 8192 // Layer 5 (lowest quality)
|
||||||
|
* i = 4, parameters.tcp_rates[6 - 1 - 4] = 8.0f * (1 << (4 << 1)) = 2048 // Layer 4
|
||||||
|
* i = 3, parameters.tcp_rates[6 - 1 - 3] = 8.0f * (1 << (3 << 1)) = 512 // Layer 3
|
||||||
|
* i = 2, parameters.tcp_rates[6 - 1 - 2] = 8.0f * (1 << (2 << 1)) = 128 // Layer 2
|
||||||
|
* i = 1, parameters.tcp_rates[6 - 1 - 1] = 8.0f * (1 << (1 << 1)) = 32 // Layer 1
|
||||||
|
* i = 0, parameters.tcp_rates[6 - 1 - 0] = 8.0f * (1 << (0 << 1)) = 8 // Layer 0 (highest quality)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static void set_tcp_rates(opj_cparameters_t* parameters, U32 num_layers = 1, F32 last_tcp_rate = LAST_TCP_RATE)
|
||||||
|
{
|
||||||
|
parameters->tcp_numlayers = num_layers;
|
||||||
|
|
||||||
|
for (int i = num_layers - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
parameters->tcp_rates[num_layers - 1 - i] = last_tcp_rate * static_cast<F32>(1 << (i << 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class JPEG2KDecode : public JPEG2KBase
|
class JPEG2KDecode : public JPEG2KBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -430,15 +491,16 @@ public:
|
||||||
|
|
||||||
opj_set_default_encoder_parameters(¶meters);
|
opj_set_default_encoder_parameters(¶meters);
|
||||||
parameters.cod_format = OPJ_CODEC_J2K;
|
parameters.cod_format = OPJ_CODEC_J2K;
|
||||||
parameters.cp_disto_alloc = 1;
|
parameters.prog_order = OPJ_RLCP; // should be the default, but, just in case
|
||||||
|
parameters.cp_disto_alloc = 1; // enable rate allocation by distortion
|
||||||
|
parameters.max_cs_size = 0; // do not cap max size because we're using tcp_rates and also irrelevant with lossless.
|
||||||
|
|
||||||
if (reversible)
|
if (reversible)
|
||||||
{
|
{
|
||||||
parameters.max_cs_size = 0; // do not limit size for reversible compression
|
|
||||||
parameters.irreversible = 0; // should be the default, but, just in case
|
parameters.irreversible = 0; // should be the default, but, just in case
|
||||||
parameters.tcp_numlayers = 1;
|
parameters.tcp_numlayers = 1;
|
||||||
/* documentation seems to be wrong, should be 0.0f for lossless, not 1.0f
|
/* documentation seems to be wrong, should be 0.0f for lossless, not 1.0f
|
||||||
see https://github.com/uclouvain/openjpeg/blob/39e8c50a2f9bdcf36810ee3d41bcbf1cc78968ae/src/lib/openjp2/j2k.c#L7755
|
see https://github.com/uclouvain/openjpeg/blob/e7453e398b110891778d8da19209792c69ca7169/src/lib/openjp2/j2k.c#L7817
|
||||||
*/
|
*/
|
||||||
parameters.tcp_rates[0] = 0.0f;
|
parameters.tcp_rates[0] = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
@ -493,53 +555,22 @@ public:
|
||||||
|
|
||||||
encoder = opj_create_compress(OPJ_CODEC_J2K);
|
encoder = opj_create_compress(OPJ_CODEC_J2K);
|
||||||
|
|
||||||
parameters.tcp_mct = (image->numcomps >= 3) ? 1 : 0;
|
parameters.tcp_mct = (image->numcomps >= 3) ? 1 : 0; // no color transform for RGBA images
|
||||||
parameters.cod_format = OPJ_CODEC_J2K;
|
|
||||||
parameters.prog_order = OPJ_RLCP;
|
|
||||||
parameters.cp_disto_alloc = 1;
|
|
||||||
|
|
||||||
// if not lossless compression, computes tcp_numlayers and max_cs_size depending on the image dimensions
|
// if not lossless compression, computes tcp_numlayers and max_cs_size depending on the image dimensions
|
||||||
if( parameters.irreversible ) {
|
if( parameters.irreversible )
|
||||||
|
{
|
||||||
|
|
||||||
// computes a number of layers
|
// computes a number of layers
|
||||||
U32 surface = rawImageIn.getWidth() * rawImageIn.getHeight();
|
U32 surface = rawImageIn.getWidth() * rawImageIn.getHeight();
|
||||||
U32 nb_layers = 1;
|
|
||||||
U32 s = 64*64;
|
|
||||||
while (surface > s)
|
|
||||||
{
|
|
||||||
nb_layers++;
|
|
||||||
s *= 4;
|
|
||||||
}
|
|
||||||
nb_layers = llclamp(nb_layers, 1, 6);
|
|
||||||
|
|
||||||
parameters.tcp_numlayers = nb_layers;
|
// gets the necessary number of layers
|
||||||
parameters.tcp_rates[nb_layers - 1] = (U32)(1.f / DEFAULT_COMPRESSION_RATE); // 1:8 by default
|
U32 nb_layers = estimate_num_layers(surface);
|
||||||
|
|
||||||
// for each subsequent layer, computes its rate and adds surface * numcomps * 1/rate to the max_cs_size
|
// fills parameters.tcp_rates and updates parameters.tcp_numlayers
|
||||||
U32 max_cs_size = (U32)(surface * image->numcomps * DEFAULT_COMPRESSION_RATE);
|
set_tcp_rates(¶meters, nb_layers, LAST_TCP_RATE);
|
||||||
U32 multiplier;
|
|
||||||
for (int i = nb_layers - 2; i >= 0; i--)
|
|
||||||
{
|
|
||||||
if( i == nb_layers - 2 )
|
|
||||||
{
|
|
||||||
multiplier = 15;
|
|
||||||
}
|
|
||||||
else if( i == nb_layers - 3 )
|
|
||||||
{
|
|
||||||
multiplier = 4;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
multiplier = 2;
|
|
||||||
}
|
|
||||||
parameters.tcp_rates[i] = parameters.tcp_rates[i + 1] * multiplier;
|
|
||||||
max_cs_size += (U32)(surface * image->numcomps * (1 / parameters.tcp_rates[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
//ensure that we have at least a minimal size
|
|
||||||
max_cs_size = llmax(max_cs_size, (U32)FIRST_PACKET_SIZE);
|
|
||||||
|
|
||||||
parameters.max_cs_size = max_cs_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opj_setup_encoder(encoder, ¶meters, image))
|
if (!opj_setup_encoder(encoder, ¶meters, image))
|
||||||
|
|
@ -551,7 +582,20 @@ public:
|
||||||
opj_set_warning_handler(encoder, opj_warn, this);
|
opj_set_warning_handler(encoder, opj_warn, this);
|
||||||
opj_set_error_handler(encoder, opj_error, this);
|
opj_set_error_handler(encoder, opj_error, this);
|
||||||
|
|
||||||
U32 tile_count = (rawImageIn.getWidth() >> 6) * (rawImageIn.getHeight() >> 6);
|
U32 width_tiles = (rawImageIn.getWidth() >> 6);
|
||||||
|
U32 height_tiles = (rawImageIn.getHeight() >> 6);
|
||||||
|
|
||||||
|
// Allow images with a width or height that are MIN_IMAGE_SIZE <= x < 64
|
||||||
|
if (width_tiles == 0 && (rawImageIn.getWidth() >= MIN_IMAGE_SIZE))
|
||||||
|
{
|
||||||
|
width_tiles = 1;
|
||||||
|
}
|
||||||
|
if (height_tiles == 0 && (rawImageIn.getHeight() >= MIN_IMAGE_SIZE))
|
||||||
|
{
|
||||||
|
height_tiles = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
U32 tile_count = width_tiles * height_tiles;
|
||||||
U32 data_size_guess = tile_count * TILE_SIZE;
|
U32 data_size_guess = tile_count * TILE_SIZE;
|
||||||
|
|
||||||
// will be freed in opj_free_user_data_write
|
// will be freed in opj_free_user_data_write
|
||||||
|
|
@ -566,7 +610,7 @@ public:
|
||||||
opj_stream_destroy(stream);
|
opj_stream_destroy(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
stream = opj_stream_create(data_size_guess, false);
|
stream = opj_stream_create(data_size_guess, OPJ_FALSE);
|
||||||
if (!stream)
|
if (!stream)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -607,17 +651,15 @@ public:
|
||||||
|
|
||||||
void setImage(const LLImageRaw& raw)
|
void setImage(const LLImageRaw& raw)
|
||||||
{
|
{
|
||||||
opj_image_cmptparm_t cmptparm[MAX_ENCODED_DISCARD_LEVELS];
|
|
||||||
memset(&cmptparm[0], 0, MAX_ENCODED_DISCARD_LEVELS * sizeof(opj_image_cmptparm_t));
|
|
||||||
|
|
||||||
S32 numcomps = raw.getComponents();
|
S32 numcomps = raw.getComponents();
|
||||||
S32 width = raw.getWidth();
|
S32 width = raw.getWidth();
|
||||||
S32 height = raw.getHeight();
|
S32 height = raw.getHeight();
|
||||||
|
|
||||||
|
std::vector<opj_image_cmptparm_t> cmptparm(numcomps);
|
||||||
|
|
||||||
for (S32 c = 0; c < numcomps; c++)
|
for (S32 c = 0; c < numcomps; c++)
|
||||||
{
|
{
|
||||||
cmptparm[c].prec = 8;
|
cmptparm[c].prec = 8; // replaces .bpp
|
||||||
cmptparm[c].bpp = 8;
|
|
||||||
cmptparm[c].sgnd = 0;
|
cmptparm[c].sgnd = 0;
|
||||||
cmptparm[c].dx = parameters.subsampling_dx;
|
cmptparm[c].dx = parameters.subsampling_dx;
|
||||||
cmptparm[c].dy = parameters.subsampling_dy;
|
cmptparm[c].dy = parameters.subsampling_dy;
|
||||||
|
|
@ -625,7 +667,7 @@ public:
|
||||||
cmptparm[c].h = height;
|
cmptparm[c].h = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
image = opj_image_create(numcomps, &cmptparm[0], OPJ_CLRSPC_SRGB);
|
image = opj_image_create(numcomps, cmptparm.data(), OPJ_CLRSPC_SRGB);
|
||||||
|
|
||||||
image->x1 = width;
|
image->x1 = width;
|
||||||
image->y1 = height;
|
image->y1 = height;
|
||||||
|
|
@ -637,7 +679,7 @@ public:
|
||||||
{
|
{
|
||||||
for (S32 x = 0; x < width; x++)
|
for (S32 x = 0; x < width; x++)
|
||||||
{
|
{
|
||||||
const U8 *pixel = src_datap + (y*width + x) * numcomps;
|
const U8 *pixel = src_datap + (y * width + x) * numcomps;
|
||||||
for (S32 c = 0; c < numcomps; c++)
|
for (S32 c = 0; c < numcomps; c++)
|
||||||
{
|
{
|
||||||
image->comps[c].data[i] = *pixel;
|
image->comps[c].data[i] = *pixel;
|
||||||
|
|
@ -857,12 +899,11 @@ bool LLImageJ2COJ::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, con
|
||||||
{
|
{
|
||||||
JPEG2KEncode encode(comment_text, reversible);
|
JPEG2KEncode encode(comment_text, reversible);
|
||||||
bool encoded = encode.encode(raw_image, base);
|
bool encoded = encode.encode(raw_image, base);
|
||||||
if (encoded)
|
if (!encoded)
|
||||||
{
|
{
|
||||||
LL_WARNS() << "Openjpeg encoding implementation isn't complete, returning false" << LL_ENDL;
|
LL_WARNS() << "Openjpeg encoding was unsuccessful, returning false" << LL_ENDL;
|
||||||
}
|
}
|
||||||
return encoded;
|
return encoded;
|
||||||
//return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LLImageJ2COJ::getMetadata(LLImageJ2C &base)
|
bool LLImageJ2COJ::getMetadata(LLImageJ2C &base)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@
|
||||||
|
|
||||||
#include "llimagej2c.h"
|
#include "llimagej2c.h"
|
||||||
|
|
||||||
|
const F32 LAST_TCP_RATE = 1.f/DEFAULT_COMPRESSION_RATE; // should be 8, giving a 1:8 ratio
|
||||||
|
|
||||||
class LLImageJ2COJ : public LLImageJ2CImpl
|
class LLImageJ2COJ : public LLImageJ2CImpl
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -37,98 +37,98 @@
|
||||||
#include "llsettingsdaycycle.h"
|
#include "llsettingsdaycycle.h"
|
||||||
|
|
||||||
// Grid out of which parcels taken is stepped every 4 meters.
|
// Grid out of which parcels taken is stepped every 4 meters.
|
||||||
const F32 PARCEL_GRID_STEP_METERS = 4.f;
|
constexpr F32 PARCEL_GRID_STEP_METERS = 4.f;
|
||||||
|
|
||||||
// Area of one "square" of parcel
|
// Area of one "square" of parcel
|
||||||
const S32 PARCEL_UNIT_AREA = 16;
|
constexpr S32 PARCEL_UNIT_AREA = 16;
|
||||||
|
|
||||||
// Height _above_ground_ that parcel boundary ends
|
// Height _above_ground_ that parcel boundary ends
|
||||||
const F32 PARCEL_HEIGHT = 50.f;
|
constexpr F32 PARCEL_HEIGHT = 50.f;
|
||||||
|
|
||||||
//Height above ground which parcel boundries exist for explicitly banned avatars
|
//Height above ground which parcel boundries exist for explicitly banned avatars
|
||||||
const F32 BAN_HEIGHT = 5000.f;
|
constexpr F32 BAN_HEIGHT = 5000.f;
|
||||||
|
|
||||||
// Maximum number of entries in an access list
|
// Maximum number of entries in an access list
|
||||||
const S32 PARCEL_MAX_ACCESS_LIST = 300;
|
constexpr S32 PARCEL_MAX_ACCESS_LIST = 300;
|
||||||
//Maximum number of entires in an update packet
|
//Maximum number of entires in an update packet
|
||||||
//for access/ban lists.
|
//for access/ban lists.
|
||||||
const F32 PARCEL_MAX_ENTRIES_PER_PACKET = 48.f;
|
constexpr F32 PARCEL_MAX_ENTRIES_PER_PACKET = 48.f;
|
||||||
|
|
||||||
// Maximum number of experiences
|
// Maximum number of experiences
|
||||||
const S32 PARCEL_MAX_EXPERIENCE_LIST = 24;
|
constexpr S32 PARCEL_MAX_EXPERIENCE_LIST = 24;
|
||||||
|
|
||||||
// Weekly charge for listing a parcel in the directory
|
// Weekly charge for listing a parcel in the directory
|
||||||
const S32 PARCEL_DIRECTORY_FEE = 30;
|
constexpr S32 PARCEL_DIRECTORY_FEE = 30;
|
||||||
|
|
||||||
const S32 PARCEL_PASS_PRICE_DEFAULT = 10;
|
constexpr S32 PARCEL_PASS_PRICE_DEFAULT = 10;
|
||||||
const F32 PARCEL_PASS_HOURS_DEFAULT = 1.f;
|
constexpr F32 PARCEL_PASS_HOURS_DEFAULT = 1.f;
|
||||||
|
|
||||||
// Number of "chunks" in which parcel overlay data is sent
|
// Number of "chunks" in which parcel overlay data is sent
|
||||||
// Chunk 0 = southern rows, entire width
|
// Chunk 0 = southern rows, entire width
|
||||||
const S32 PARCEL_OVERLAY_CHUNKS = 4;
|
constexpr S32 PARCEL_OVERLAY_CHUNKS = 4;
|
||||||
|
|
||||||
// Bottom three bits are a color index for the land overlay
|
// Bottom three bits are a color index for the land overlay
|
||||||
const U8 PARCEL_COLOR_MASK = 0x07;
|
constexpr U8 PARCEL_COLOR_MASK = 0x07;
|
||||||
const U8 PARCEL_PUBLIC = 0x00;
|
constexpr U8 PARCEL_PUBLIC = 0x00;
|
||||||
const U8 PARCEL_OWNED = 0x01;
|
constexpr U8 PARCEL_OWNED = 0x01;
|
||||||
const U8 PARCEL_GROUP = 0x02;
|
constexpr U8 PARCEL_GROUP = 0x02;
|
||||||
const U8 PARCEL_SELF = 0x03;
|
constexpr U8 PARCEL_SELF = 0x03;
|
||||||
const U8 PARCEL_FOR_SALE = 0x04;
|
constexpr U8 PARCEL_FOR_SALE = 0x04;
|
||||||
const U8 PARCEL_AUCTION = 0x05;
|
constexpr U8 PARCEL_AUCTION = 0x05;
|
||||||
// unused 0x06
|
// unused 0x06
|
||||||
// unused 0x07
|
// unused 0x07
|
||||||
// flag, unused 0x08
|
// flag, unused 0x08
|
||||||
const U8 PARCEL_HIDDENAVS = 0x10; // avatars not visible outside of parcel. Used for 'see avs' feature, but must be off for compatibility
|
constexpr U8 PARCEL_HIDDENAVS = 0x10; // avatars not visible outside of parcel. Used for 'see avs' feature, but must be off for compatibility
|
||||||
const U8 PARCEL_SOUND_LOCAL = 0x20;
|
constexpr U8 PARCEL_SOUND_LOCAL = 0x20;
|
||||||
const U8 PARCEL_WEST_LINE = 0x40; // flag, property line on west edge
|
constexpr U8 PARCEL_WEST_LINE = 0x40; // flag, property line on west edge
|
||||||
const U8 PARCEL_SOUTH_LINE = 0x80; // flag, property line on south edge
|
constexpr U8 PARCEL_SOUTH_LINE = 0x80; // flag, property line on south edge
|
||||||
|
|
||||||
// Transmission results for parcel properties
|
// Transmission results for parcel properties
|
||||||
const S32 PARCEL_RESULT_NO_DATA = -1;
|
constexpr S32 PARCEL_RESULT_NO_DATA = -1;
|
||||||
const S32 PARCEL_RESULT_SUCCESS = 0; // got exactly one parcel
|
constexpr S32 PARCEL_RESULT_SUCCESS = 0; // got exactly one parcel
|
||||||
const S32 PARCEL_RESULT_MULTIPLE = 1; // got multiple parcels
|
constexpr S32 PARCEL_RESULT_MULTIPLE = 1; // got multiple parcels
|
||||||
|
|
||||||
const S32 SELECTED_PARCEL_SEQ_ID = -10000;
|
constexpr S32 SELECTED_PARCEL_SEQ_ID = -10000;
|
||||||
const S32 COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID = -20000;
|
constexpr S32 COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID = -20000;
|
||||||
const S32 COLLISION_BANNED_PARCEL_SEQ_ID = -30000;
|
constexpr S32 COLLISION_BANNED_PARCEL_SEQ_ID = -30000;
|
||||||
const S32 COLLISION_NOT_ON_LIST_PARCEL_SEQ_ID = -40000;
|
constexpr S32 COLLISION_NOT_ON_LIST_PARCEL_SEQ_ID = -40000;
|
||||||
const S32 HOVERED_PARCEL_SEQ_ID = -50000;
|
constexpr S32 HOVERED_PARCEL_SEQ_ID = -50000;
|
||||||
|
|
||||||
const U32 RT_NONE = 0x1 << 0;
|
constexpr U32 RT_NONE = 0x1 << 0;
|
||||||
const U32 RT_OWNER = 0x1 << 1;
|
constexpr U32 RT_OWNER = 0x1 << 1;
|
||||||
const U32 RT_GROUP = 0x1 << 2;
|
constexpr U32 RT_GROUP = 0x1 << 2;
|
||||||
const U32 RT_OTHER = 0x1 << 3;
|
constexpr U32 RT_OTHER = 0x1 << 3;
|
||||||
const U32 RT_LIST = 0x1 << 4;
|
constexpr U32 RT_LIST = 0x1 << 4;
|
||||||
const U32 RT_SELL = 0x1 << 5;
|
constexpr U32 RT_SELL = 0x1 << 5;
|
||||||
|
|
||||||
const S32 INVALID_PARCEL_ID = -1;
|
constexpr S32 INVALID_PARCEL_ID = -1;
|
||||||
|
|
||||||
const S32 INVALID_PARCEL_ENVIRONMENT_VERSION = -2;
|
constexpr S32 INVALID_PARCEL_ENVIRONMENT_VERSION = -2;
|
||||||
// if Region settings are used, parcel env. version is -1
|
// if Region settings are used, parcel env. version is -1
|
||||||
const S32 UNSET_PARCEL_ENVIRONMENT_VERSION = -1;
|
constexpr S32 UNSET_PARCEL_ENVIRONMENT_VERSION = -1;
|
||||||
|
|
||||||
// Timeouts for parcels
|
// Timeouts for parcels
|
||||||
// default is 21 days * 24h/d * 60m/h * 60s/m *1000000 usec/s = 1814400000000
|
// default is 21 days * 24h/d * 60m/h * 60s/m *1000000 usec/s = 1814400000000
|
||||||
const U64 DEFAULT_USEC_CONVERSION_TIMEOUT = U64L(1814400000000);
|
constexpr U64 DEFAULT_USEC_CONVERSION_TIMEOUT = U64L(1814400000000);
|
||||||
// ***** TESTING is 10 minutes
|
// ***** TESTING is 10 minutes
|
||||||
//const U64 DEFAULT_USEC_CONVERSION_TIMEOUT = U64L(600000000);
|
//const U64 DEFAULT_USEC_CONVERSION_TIMEOUT = U64L(600000000);
|
||||||
|
|
||||||
// group is 60 days * 24h/d * 60m/h * 60s/m *1000000 usec/s = 5184000000000
|
// group is 60 days * 24h/d * 60m/h * 60s/m *1000000 usec/s = 5184000000000
|
||||||
const U64 GROUP_USEC_CONVERSION_TIMEOUT = U64L(5184000000000);
|
constexpr U64 GROUP_USEC_CONVERSION_TIMEOUT = U64L(5184000000000);
|
||||||
// ***** TESTING is 10 minutes
|
// ***** TESTING is 10 minutes
|
||||||
//const U64 GROUP_USEC_CONVERSION_TIMEOUT = U64L(600000000);
|
//const U64 GROUP_USEC_CONVERSION_TIMEOUT = U64L(600000000);
|
||||||
|
|
||||||
// default sale timeout is 2 days -> 172800000000
|
// default sale timeout is 2 days -> 172800000000
|
||||||
const U64 DEFAULT_USEC_SALE_TIMEOUT = U64L(172800000000);
|
constexpr U64 DEFAULT_USEC_SALE_TIMEOUT = U64L(172800000000);
|
||||||
// ***** TESTING is 10 minutes
|
// ***** TESTING is 10 minutes
|
||||||
//const U64 DEFAULT_USEC_SALE_TIMEOUT = U64L(600000000);
|
//const U64 DEFAULT_USEC_SALE_TIMEOUT = U64L(600000000);
|
||||||
|
|
||||||
// more grace period extensions.
|
// more grace period extensions.
|
||||||
const U64 SEVEN_DAYS_IN_USEC = U64L(604800000000);
|
constexpr U64 SEVEN_DAYS_IN_USEC = U64L(604800000000);
|
||||||
|
|
||||||
// if more than 100,000s before sale revert, and no extra extension
|
// if more than 100,000s before sale revert, and no extra extension
|
||||||
// has been given, go ahead and extend it more. That's about 1.2 days.
|
// has been given, go ahead and extend it more. That's about 1.2 days.
|
||||||
const S32 EXTEND_GRACE_IF_MORE_THAN_SEC = 100000;
|
constexpr S32 EXTEND_GRACE_IF_MORE_THAN_SEC = 100000;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -252,7 +252,7 @@ public:
|
||||||
void setMediaDesc(const std::string& desc);
|
void setMediaDesc(const std::string& desc);
|
||||||
void setMediaID(const LLUUID& id) { mMediaID = id; }
|
void setMediaID(const LLUUID& id) { mMediaID = id; }
|
||||||
void setMediaAutoScale ( U8 flagIn ) { mMediaAutoScale = flagIn; }
|
void setMediaAutoScale ( U8 flagIn ) { mMediaAutoScale = flagIn; }
|
||||||
void setMediaLoop (U8 loop) { mMediaLoop = loop; }
|
void setMediaLoop(U8 loop) { mMediaLoop = loop; }
|
||||||
void setMediaWidth(S32 width);
|
void setMediaWidth(S32 width);
|
||||||
void setMediaHeight(S32 height);
|
void setMediaHeight(S32 height);
|
||||||
void setMediaCurrentURL(const std::string& url);
|
void setMediaCurrentURL(const std::string& url);
|
||||||
|
|
|
||||||
|
|
@ -361,14 +361,12 @@ LLSD LLSettingsBase::interpolateSDValue(const std::string& key_name, const LLSD
|
||||||
new_array = q.getValue();
|
new_array = q.getValue();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // TODO: We could expand this to inspect the type and do a deep lerp based on type.
|
{
|
||||||
// for now assume a heterogeneous array of reals.
|
|
||||||
size_t len = std::max(value.size(), other_value.size());
|
size_t len = std::max(value.size(), other_value.size());
|
||||||
|
|
||||||
for (size_t i = 0; i < len; ++i)
|
for (size_t i = 0; i < len; ++i)
|
||||||
{
|
{
|
||||||
|
new_array[i] = interpolateSDValue(key_name, value[i], other_value[i], defaults, mix, skip, slerps);
|
||||||
new_array[i] = lerp((F32)value[i].asReal(), (F32)other_value[i].asReal(), (F32)mix);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -657,15 +657,16 @@ void LLSettingsSky::blend(LLSettingsBase::ptr_t &end, F64 blendf)
|
||||||
mHasLegacyHaze |= lerp_legacy_float(mHazeDensity, mLegacyHazeDensity, other->mHazeDensity, other->mLegacyHazeDensity, 0.7f, (F32)blendf);
|
mHasLegacyHaze |= lerp_legacy_float(mHazeDensity, mLegacyHazeDensity, other->mHazeDensity, other->mLegacyHazeDensity, 0.7f, (F32)blendf);
|
||||||
mHasLegacyHaze |= lerp_legacy_float(mDistanceMultiplier, mLegacyDistanceMultiplier, other->mDistanceMultiplier, other->mLegacyDistanceMultiplier, 0.8f, (F32)blendf);
|
mHasLegacyHaze |= lerp_legacy_float(mDistanceMultiplier, mLegacyDistanceMultiplier, other->mDistanceMultiplier, other->mLegacyDistanceMultiplier, 0.8f, (F32)blendf);
|
||||||
mHasLegacyHaze |= lerp_legacy_float(mDensityMultiplier, mLegacyDensityMultiplier, other->mDensityMultiplier, other->mLegacyDensityMultiplier, 0.0001f, (F32)blendf);
|
mHasLegacyHaze |= lerp_legacy_float(mDensityMultiplier, mLegacyDensityMultiplier, other->mDensityMultiplier, other->mLegacyDensityMultiplier, 0.0001f, (F32)blendf);
|
||||||
|
mHasLegacyHaze |= lerp_legacy_color(mAmbientColor, mLegacyAmbientColor, other->mAmbientColor, other->mLegacyAmbientColor, LLColor3(0.25f, 0.25f, 0.25f), (F32)blendf);
|
||||||
mHasLegacyHaze |= lerp_legacy_color(mBlueHorizon, mLegacyBlueHorizon, other->mBlueHorizon, other->mLegacyBlueHorizon, LLColor3(0.4954f, 0.4954f, 0.6399f), (F32)blendf);
|
mHasLegacyHaze |= lerp_legacy_color(mBlueHorizon, mLegacyBlueHorizon, other->mBlueHorizon, other->mLegacyBlueHorizon, LLColor3(0.4954f, 0.4954f, 0.6399f), (F32)blendf);
|
||||||
mHasLegacyHaze |= lerp_legacy_color(mBlueDensity, mLegacyBlueDensity, other->mBlueDensity, other->mLegacyBlueDensity, LLColor3(0.2447f, 0.4487f, 0.7599f), (F32)blendf);
|
mHasLegacyHaze |= lerp_legacy_color(mBlueDensity, mLegacyBlueDensity, other->mBlueDensity, other->mLegacyBlueDensity, LLColor3(0.2447f, 0.4487f, 0.7599f), (F32)blendf);
|
||||||
|
|
||||||
parammapping_t defaults = other->getParameterMap();
|
parammapping_t defaults = other->getParameterMap();
|
||||||
stringset_t skip = getSkipInterpolateKeys();
|
stringset_t skip = getSkipInterpolateKeys();
|
||||||
stringset_t slerps = getSlerpKeys();
|
stringset_t slerps = getSlerpKeys();
|
||||||
mAbsorptionConfigs = interpolateSDMap(mAbsorptionConfigs, other->mAbsorptionConfigs, defaults, blendf, skip, slerps);
|
mAbsorptionConfigs = interpolateSDValue("absorption_config", mAbsorptionConfigs, other->mAbsorptionConfigs, defaults, blendf, skip, slerps);
|
||||||
mMieConfigs = interpolateSDMap(mMieConfigs, other->mMieConfigs, defaults, blendf, skip, slerps);
|
mMieConfigs = interpolateSDValue("mie_config", mMieConfigs, other->mMieConfigs, defaults, blendf, skip, slerps);
|
||||||
mRayleighConfigs = interpolateSDMap(mRayleighConfigs, other->mRayleighConfigs, defaults, blendf, skip, slerps);
|
mRayleighConfigs = interpolateSDValue("rayleigh_config", mRayleighConfigs, other->mRayleighConfigs, defaults, blendf, skip, slerps);
|
||||||
|
|
||||||
setDirtyFlag(true);
|
setDirtyFlag(true);
|
||||||
setReplaced();
|
setReplaced();
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
#include "linden_common.h"
|
#include "linden_common.h"
|
||||||
|
|
||||||
#include "math.h"
|
#include "math.h"
|
||||||
//#include "vmath.h"
|
|
||||||
#include "v3math.h"
|
#include "v3math.h"
|
||||||
#include "llquaternion.h"
|
#include "llquaternion.h"
|
||||||
#include "m3math.h"
|
#include "m3math.h"
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
#include "linden_common.h"
|
#include "linden_common.h"
|
||||||
|
|
||||||
#include "llmath.h"
|
#include "llmath.h"
|
||||||
//#include "vmath.h"
|
|
||||||
#include "v3math.h"
|
#include "v3math.h"
|
||||||
#include "patch_dct.h"
|
#include "patch_dct.h"
|
||||||
#include "patch_code.h"
|
#include "patch_code.h"
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
#include "linden_common.h"
|
#include "linden_common.h"
|
||||||
|
|
||||||
#include "llmath.h"
|
#include "llmath.h"
|
||||||
//#include "vmath.h"
|
|
||||||
#include "v3math.h"
|
#include "v3math.h"
|
||||||
#include "patch_dct.h"
|
#include "patch_dct.h"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
#include "linden_common.h"
|
#include "linden_common.h"
|
||||||
|
|
||||||
#include "llmath.h"
|
#include "llmath.h"
|
||||||
//#include "vmath.h"
|
|
||||||
#include "v3math.h"
|
#include "v3math.h"
|
||||||
#include "patch_dct.h"
|
#include "patch_dct.h"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -575,7 +575,7 @@ void LLPluginProcessParent::idle(void)
|
||||||
params.args.add("-e");
|
params.args.add("-e");
|
||||||
params.args.add("tell application \"Terminal\"");
|
params.args.add("tell application \"Terminal\"");
|
||||||
params.args.add("-e");
|
params.args.add("-e");
|
||||||
params.args.add(STRINGIZE("set win to do script \"lldb -pid "
|
params.args.add(STRINGIZE("set win to do script \"lldb -p "
|
||||||
<< mProcess->getProcessID() << "\""));
|
<< mProcess->getProcessID() << "\""));
|
||||||
params.args.add("-e");
|
params.args.add("-e");
|
||||||
params.args.add("do script \"continue\" in win");
|
params.args.add("do script \"continue\" in win");
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ include(TinyGLTF)
|
||||||
|
|
||||||
set(llprimitive_SOURCE_FILES
|
set(llprimitive_SOURCE_FILES
|
||||||
lldaeloader.cpp
|
lldaeloader.cpp
|
||||||
llgltfloader.cpp
|
|
||||||
llgltfmaterial.cpp
|
llgltfmaterial.cpp
|
||||||
llmaterialid.cpp
|
llmaterialid.cpp
|
||||||
llmaterial.cpp
|
llmaterial.cpp
|
||||||
|
|
@ -32,7 +31,6 @@ set(llprimitive_SOURCE_FILES
|
||||||
set(llprimitive_HEADER_FILES
|
set(llprimitive_HEADER_FILES
|
||||||
CMakeLists.txt
|
CMakeLists.txt
|
||||||
lldaeloader.h
|
lldaeloader.h
|
||||||
llgltfloader.h
|
|
||||||
llgltfmaterial.h
|
llgltfmaterial.h
|
||||||
llgltfmaterial_templates.h
|
llgltfmaterial_templates.h
|
||||||
legacy_object_types.h
|
legacy_object_types.h
|
||||||
|
|
|
||||||
|
|
@ -204,12 +204,15 @@ LLModel::EModelStatus load_face_from_dom_triangles(
|
||||||
|
|
||||||
if (idx_stride <= 0
|
if (idx_stride <= 0
|
||||||
|| (pos_source && pos_offset >= idx_stride)
|
|| (pos_source && pos_offset >= idx_stride)
|
||||||
|
|| (pos_source && pos_offset < 0)
|
||||||
|| (tc_source && tc_offset >= idx_stride)
|
|| (tc_source && tc_offset >= idx_stride)
|
||||||
|| (norm_source && norm_offset >= idx_stride))
|
|| (tc_source && tc_offset < 0)
|
||||||
|
|| (norm_source && norm_offset >= idx_stride)
|
||||||
|
|| (norm_source && norm_offset < 0))
|
||||||
{
|
{
|
||||||
// Looks like these offsets should fit inside idx_stride
|
// Looks like these offsets should fit inside idx_stride
|
||||||
// Might be good idea to also check idx.getCount()%idx_stride != 0
|
// Might be good idea to also check idx.getCount()%idx_stride != 0
|
||||||
LL_WARNS() << "Invalid pos_offset " << pos_offset << ", tc_offset " << tc_offset << " or norm_offset " << norm_offset << LL_ENDL;
|
LL_WARNS() << "Invalid idx_stride " << idx_stride << ", pos_offset " << pos_offset << ", tc_offset " << tc_offset << " or norm_offset " << norm_offset << LL_ENDL;
|
||||||
return LLModel::BAD_ELEMENT;
|
return LLModel::BAD_ELEMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -880,9 +883,10 @@ LLDAELoader::LLDAELoader(
|
||||||
void* opaque_userdata,
|
void* opaque_userdata,
|
||||||
JointTransformMap& jointTransformMap,
|
JointTransformMap& jointTransformMap,
|
||||||
JointNameSet& jointsFromNodes,
|
JointNameSet& jointsFromNodes,
|
||||||
std::map<std::string, std::string>& jointAliasMap,
|
std::map<std::string, std::string, std::less<>>& jointAliasMap,
|
||||||
U32 maxJointsPerMesh,
|
U32 maxJointsPerMesh,
|
||||||
U32 modelLimit,
|
U32 modelLimit,
|
||||||
|
U32 debugMode,
|
||||||
bool preprocess)
|
bool preprocess)
|
||||||
: LLModelLoader(
|
: LLModelLoader(
|
||||||
filename,
|
filename,
|
||||||
|
|
@ -895,8 +899,9 @@ LLDAELoader::LLDAELoader(
|
||||||
jointTransformMap,
|
jointTransformMap,
|
||||||
jointsFromNodes,
|
jointsFromNodes,
|
||||||
jointAliasMap,
|
jointAliasMap,
|
||||||
maxJointsPerMesh),
|
maxJointsPerMesh,
|
||||||
mGeneratedModelLimit(modelLimit),
|
modelLimit,
|
||||||
|
debugMode),
|
||||||
mPreprocessDAE(preprocess)
|
mPreprocessDAE(preprocess)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -1680,6 +1685,7 @@ void LLDAELoader::processDomModel(LLModel* model, DAE* dae, daeElement* root, do
|
||||||
{
|
{
|
||||||
materials[model->mMaterialList[i]] = LLImportMaterial();
|
materials[model->mMaterialList[i]] = LLImportMaterial();
|
||||||
}
|
}
|
||||||
|
// todo: likely a bug here, shouldn't be using suffixed label, see how it gets used in other places.
|
||||||
mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials));
|
mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials));
|
||||||
stretch_extents(model, transformation);
|
stretch_extents(model, transformation);
|
||||||
}
|
}
|
||||||
|
|
@ -2412,7 +2418,7 @@ std::string LLDAELoader::getElementLabel(daeElement *element)
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
size_t LLDAELoader::getSuffixPosition(std::string label)
|
size_t LLDAELoader::getSuffixPosition(const std::string &label)
|
||||||
{
|
{
|
||||||
if ((label.find("_LOD") != -1) || (label.find("_PHYS") != -1))
|
if ((label.find("_LOD") != -1) || (label.find("_PHYS") != -1))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,10 @@ public:
|
||||||
void* opaque_userdata,
|
void* opaque_userdata,
|
||||||
JointTransformMap& jointTransformMap,
|
JointTransformMap& jointTransformMap,
|
||||||
JointNameSet& jointsFromNodes,
|
JointNameSet& jointsFromNodes,
|
||||||
std::map<std::string, std::string>& jointAliasMap,
|
std::map<std::string, std::string, std::less<>>& jointAliasMap,
|
||||||
U32 maxJointsPerMesh,
|
U32 maxJointsPerMesh,
|
||||||
U32 modelLimit,
|
U32 modelLimit,
|
||||||
|
U32 debugMode,
|
||||||
bool preprocess);
|
bool preprocess);
|
||||||
virtual ~LLDAELoader() ;
|
virtual ~LLDAELoader() ;
|
||||||
|
|
||||||
|
|
@ -97,13 +98,12 @@ protected:
|
||||||
bool loadModelsFromDomMesh(domMesh* mesh, std::vector<LLModel*>& models_out, U32 submodel_limit);
|
bool loadModelsFromDomMesh(domMesh* mesh, std::vector<LLModel*>& models_out, U32 submodel_limit);
|
||||||
|
|
||||||
static std::string getElementLabel(daeElement *element);
|
static std::string getElementLabel(daeElement *element);
|
||||||
static size_t getSuffixPosition(std::string label);
|
static size_t getSuffixPosition(const std::string& label);
|
||||||
static std::string getLodlessLabel(daeElement *element);
|
static std::string getLodlessLabel(daeElement *element);
|
||||||
|
|
||||||
static std::string preprocessDAE(std::string filename);
|
static std::string preprocessDAE(std::string filename);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
U32 mGeneratedModelLimit; // Attempt to limit amount of generated submodels
|
|
||||||
bool mPreprocessDAE;
|
bool mPreprocessDAE;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,404 +0,0 @@
|
||||||
/**
|
|
||||||
* @file LLGLTFLoader.cpp
|
|
||||||
* @brief LLGLTFLoader class implementation
|
|
||||||
*
|
|
||||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
|
||||||
* Second Life Viewer Source Code
|
|
||||||
* Copyright (C) 2022, Linden Research, Inc.
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation;
|
|
||||||
* version 2.1 of the License only.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*
|
|
||||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
||||||
* $/LicenseInfo$
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "llgltfloader.h"
|
|
||||||
|
|
||||||
// Import & define single-header gltf import/export lib
|
|
||||||
#define TINYGLTF_IMPLEMENTATION
|
|
||||||
#define TINYGLTF_USE_CPP14 // default is C++ 11
|
|
||||||
|
|
||||||
// tinygltf by default loads image files using STB
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
|
||||||
// to use our own image loading:
|
|
||||||
// 1. replace this definition with TINYGLTF_NO_STB_IMAGE
|
|
||||||
// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)
|
|
||||||
|
|
||||||
// tinygltf saves image files using STB
|
|
||||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
||||||
// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data)
|
|
||||||
|
|
||||||
// Additionally, disable inclusion of STB header files entirely with
|
|
||||||
// TINYGLTF_NO_INCLUDE_STB_IMAGE
|
|
||||||
// TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE
|
|
||||||
#include "tinygltf/tiny_gltf.h"
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: includes inherited from dae loader. Validate / prune
|
|
||||||
|
|
||||||
#include "llsdserialize.h"
|
|
||||||
#include "lljoint.h"
|
|
||||||
|
|
||||||
#include "llmatrix4a.h"
|
|
||||||
|
|
||||||
#include <boost/regex.hpp>
|
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
|
||||||
|
|
||||||
static const std::string lod_suffix[LLModel::NUM_LODS] =
|
|
||||||
{
|
|
||||||
"_LOD0",
|
|
||||||
"_LOD1",
|
|
||||||
"_LOD2",
|
|
||||||
"",
|
|
||||||
"_PHYS",
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
LLGLTFLoader::LLGLTFLoader(std::string filename,
|
|
||||||
S32 lod,
|
|
||||||
LLModelLoader::load_callback_t load_cb,
|
|
||||||
LLModelLoader::joint_lookup_func_t joint_lookup_func,
|
|
||||||
LLModelLoader::texture_load_func_t texture_load_func,
|
|
||||||
LLModelLoader::state_callback_t state_cb,
|
|
||||||
void * opaque_userdata,
|
|
||||||
JointTransformMap & jointTransformMap,
|
|
||||||
JointNameSet & jointsFromNodes,
|
|
||||||
std::map<std::string, std::string> &jointAliasMap,
|
|
||||||
U32 maxJointsPerMesh,
|
|
||||||
U32 modelLimit) //,
|
|
||||||
//bool preprocess)
|
|
||||||
: LLModelLoader( filename,
|
|
||||||
lod,
|
|
||||||
load_cb,
|
|
||||||
joint_lookup_func,
|
|
||||||
texture_load_func,
|
|
||||||
state_cb,
|
|
||||||
opaque_userdata,
|
|
||||||
jointTransformMap,
|
|
||||||
jointsFromNodes,
|
|
||||||
jointAliasMap,
|
|
||||||
maxJointsPerMesh ),
|
|
||||||
//mPreprocessGLTF(preprocess),
|
|
||||||
mMeshesLoaded(false),
|
|
||||||
mMaterialsLoaded(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
LLGLTFLoader::~LLGLTFLoader() {}
|
|
||||||
|
|
||||||
bool LLGLTFLoader::OpenFile(const std::string &filename)
|
|
||||||
{
|
|
||||||
tinygltf::TinyGLTF loader;
|
|
||||||
std::string error_msg;
|
|
||||||
std::string warn_msg;
|
|
||||||
std::string filename_lc(filename);
|
|
||||||
LLStringUtil::toLower(filename_lc);
|
|
||||||
|
|
||||||
// Load a tinygltf model fom a file. Assumes that the input filename has already been
|
|
||||||
// been sanitized to one of (.gltf , .glb) extensions, so does a simple find to distinguish.
|
|
||||||
if (std::string::npos == filename_lc.rfind(".gltf"))
|
|
||||||
{ // file is binary
|
|
||||||
mGltfLoaded = loader.LoadBinaryFromFile(&mGltfModel, &error_msg, &warn_msg, filename);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ // file is ascii
|
|
||||||
mGltfLoaded = loader.LoadASCIIFromFile(&mGltfModel, &error_msg, &warn_msg, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mGltfLoaded)
|
|
||||||
{
|
|
||||||
if (!warn_msg.empty())
|
|
||||||
LL_WARNS("GLTF_IMPORT") << "gltf load warning: " << warn_msg.c_str() << LL_ENDL;
|
|
||||||
if (!error_msg.empty())
|
|
||||||
LL_WARNS("GLTF_IMPORT") << "gltf load error: " << error_msg.c_str() << LL_ENDL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mMeshesLoaded = parseMeshes();
|
|
||||||
if (mMeshesLoaded) uploadMeshes();
|
|
||||||
|
|
||||||
mMaterialsLoaded = parseMaterials();
|
|
||||||
if (mMaterialsLoaded) uploadMaterials();
|
|
||||||
|
|
||||||
return (mMeshesLoaded || mMaterialsLoaded);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LLGLTFLoader::parseMeshes()
|
|
||||||
{
|
|
||||||
if (!mGltfLoaded) return false;
|
|
||||||
|
|
||||||
// 2022-04 DJH Volume params from dae example. TODO understand PCODE
|
|
||||||
LLVolumeParams volume_params;
|
|
||||||
volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
|
|
||||||
|
|
||||||
for (tinygltf::Mesh mesh : mGltfModel.meshes)
|
|
||||||
{
|
|
||||||
LLModel *pModel = new LLModel(volume_params, 0.f);
|
|
||||||
|
|
||||||
if (populateModelFromMesh(pModel, mesh) &&
|
|
||||||
(LLModel::NO_ERRORS == pModel->getStatus()) &&
|
|
||||||
validate_model(pModel))
|
|
||||||
{
|
|
||||||
mModelList.push_back(pModel);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
setLoadState(ERROR_MODEL + pModel->getStatus());
|
|
||||||
delete(pModel);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh)
|
|
||||||
{
|
|
||||||
pModel->mLabel = mesh.name;
|
|
||||||
int pos_idx;
|
|
||||||
tinygltf::Accessor indices_a, positions_a, normals_a, uv0_a, color0_a;
|
|
||||||
|
|
||||||
auto prims = mesh.primitives;
|
|
||||||
for (auto prim : prims)
|
|
||||||
{
|
|
||||||
if (prim.indices >= 0) indices_a = mGltfModel.accessors[prim.indices];
|
|
||||||
|
|
||||||
pos_idx = (prim.attributes.count("POSITION") > 0) ? prim.attributes.at("POSITION") : -1;
|
|
||||||
if (pos_idx >= 0)
|
|
||||||
{
|
|
||||||
positions_a = mGltfModel.accessors[pos_idx];
|
|
||||||
if (TINYGLTF_COMPONENT_TYPE_FLOAT != positions_a.componentType)
|
|
||||||
continue;
|
|
||||||
auto positions_bv = mGltfModel.bufferViews[positions_a.bufferView];
|
|
||||||
auto positions_buf = mGltfModel.buffers[positions_bv.buffer];
|
|
||||||
//auto type = positions_vb.
|
|
||||||
//if (positions_buf.name
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
int norm_idx, tan_idx, uv0_idx, uv1_idx, color0_idx, color1_idx;
|
|
||||||
norm_idx = (prim.attributes.count("NORMAL") > 0) ? prim.attributes.at("NORMAL") : -1;
|
|
||||||
tan_idx = (prim.attributes.count("TANGENT") > 0) ? prim.attributes.at("TANGENT") : -1;
|
|
||||||
uv0_idx = (prim.attributes.count("TEXCOORDS_0") > 0) ? prim.attributes.at("TEXCOORDS_0") : -1;
|
|
||||||
uv1_idx = (prim.attributes.count("TEXCOORDS_1") > 0) ? prim.attributes.at("TEXCOORDS_1") : -1;
|
|
||||||
color0_idx = (prim.attributes.count("COLOR_0") > 0) ? prim.attributes.at("COLOR_0") : -1;
|
|
||||||
color1_idx = (prim.attributes.count("COLOR_1") > 0) ? prim.attributes.at("COLOR_1") : -1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (prim.mode == TINYGLTF_MODE_TRIANGLES)
|
|
||||||
{
|
|
||||||
//auto pos = mesh. TODO resume here DJH 2022-04
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//pModel->addFace()
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LLGLTFLoader::parseMaterials()
|
|
||||||
{
|
|
||||||
if (!mGltfLoaded) return false;
|
|
||||||
|
|
||||||
// fill local texture data structures
|
|
||||||
mSamplers.clear();
|
|
||||||
for (auto in_sampler : mGltfModel.samplers)
|
|
||||||
{
|
|
||||||
gltf_sampler sampler;
|
|
||||||
sampler.magFilter = in_sampler.magFilter > 0 ? in_sampler.magFilter : GL_LINEAR;
|
|
||||||
sampler.minFilter = in_sampler.minFilter > 0 ? in_sampler.minFilter : GL_LINEAR;;
|
|
||||||
sampler.wrapS = in_sampler.wrapS;
|
|
||||||
sampler.wrapT = in_sampler.wrapT;
|
|
||||||
sampler.name = in_sampler.name; // unused
|
|
||||||
mSamplers.push_back(sampler);
|
|
||||||
}
|
|
||||||
|
|
||||||
mImages.clear();
|
|
||||||
for (auto in_image : mGltfModel.images)
|
|
||||||
{
|
|
||||||
gltf_image image;
|
|
||||||
image.numChannels = in_image.component;
|
|
||||||
image.bytesPerChannel = in_image.bits >> 3; // Convert bits to bytes
|
|
||||||
image.pixelType = in_image.pixel_type; // Maps exactly, i.e. TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE == GL_UNSIGNED_BYTE, etc
|
|
||||||
image.size = static_cast<U32>(in_image.image.size());
|
|
||||||
image.height = in_image.height;
|
|
||||||
image.width = in_image.width;
|
|
||||||
image.data = in_image.image.data();
|
|
||||||
|
|
||||||
if (in_image.as_is)
|
|
||||||
{
|
|
||||||
LL_WARNS("GLTF_IMPORT") << "Unsupported image encoding" << LL_ENDL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image.size != image.height * image.width * image.numChannels * image.bytesPerChannel)
|
|
||||||
{
|
|
||||||
LL_WARNS("GLTF_IMPORT") << "Image size error" << LL_ENDL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mImages.push_back(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
mTextures.clear();
|
|
||||||
for (auto in_tex : mGltfModel.textures)
|
|
||||||
{
|
|
||||||
gltf_texture tex;
|
|
||||||
tex.imageIdx = in_tex.source;
|
|
||||||
tex.samplerIdx = in_tex.sampler;
|
|
||||||
tex.imageUuid.setNull();
|
|
||||||
|
|
||||||
if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size())
|
|
||||||
{
|
|
||||||
LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mTextures.push_back(tex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse each material
|
|
||||||
for (tinygltf::Material gltf_material : mGltfModel.materials)
|
|
||||||
{
|
|
||||||
gltf_render_material mat;
|
|
||||||
mat.name = gltf_material.name;
|
|
||||||
|
|
||||||
tinygltf::PbrMetallicRoughness& pbr = gltf_material.pbrMetallicRoughness;
|
|
||||||
mat.hasPBR = true; // Always true, for now
|
|
||||||
|
|
||||||
mat.baseColor.set(pbr.baseColorFactor.data());
|
|
||||||
mat.hasBaseTex = pbr.baseColorTexture.index >= 0;
|
|
||||||
mat.baseColorTexIdx = pbr.baseColorTexture.index;
|
|
||||||
mat.baseColorTexCoords = pbr.baseColorTexture.texCoord;
|
|
||||||
|
|
||||||
mat.metalness = pbr.metallicFactor;
|
|
||||||
mat.roughness = pbr.roughnessFactor;
|
|
||||||
mat.hasMRTex = pbr.metallicRoughnessTexture.index >= 0;
|
|
||||||
mat.metalRoughTexIdx = pbr.metallicRoughnessTexture.index;
|
|
||||||
mat.metalRoughTexCoords = pbr.metallicRoughnessTexture.texCoord;
|
|
||||||
|
|
||||||
mat.normalScale = gltf_material.normalTexture.scale;
|
|
||||||
mat.hasNormalTex = gltf_material.normalTexture.index >= 0;
|
|
||||||
mat.normalTexIdx = gltf_material.normalTexture.index;
|
|
||||||
mat.normalTexCoords = gltf_material.normalTexture.texCoord;
|
|
||||||
|
|
||||||
mat.occlusionScale = gltf_material.occlusionTexture.strength;
|
|
||||||
mat.hasOcclusionTex = gltf_material.occlusionTexture.index >= 0;
|
|
||||||
mat.occlusionTexIdx = gltf_material.occlusionTexture.index;
|
|
||||||
mat.occlusionTexCoords = gltf_material.occlusionTexture.texCoord;
|
|
||||||
|
|
||||||
mat.emissiveColor.set(gltf_material.emissiveFactor.data());
|
|
||||||
mat.hasEmissiveTex = gltf_material.emissiveTexture.index >= 0;
|
|
||||||
mat.emissiveTexIdx = gltf_material.emissiveTexture.index;
|
|
||||||
mat.emissiveTexCoords = gltf_material.emissiveTexture.texCoord;
|
|
||||||
|
|
||||||
mat.alphaMode = gltf_material.alphaMode;
|
|
||||||
mat.alphaMask = gltf_material.alphaCutoff;
|
|
||||||
|
|
||||||
if ((mat.hasNormalTex && (mat.normalTexIdx >= mTextures.size())) ||
|
|
||||||
(mat.hasOcclusionTex && (mat.occlusionTexIdx >= mTextures.size())) ||
|
|
||||||
(mat.hasEmissiveTex && (mat.emissiveTexIdx >= mTextures.size())) ||
|
|
||||||
(mat.hasBaseTex && (mat.baseColorTexIdx >= mTextures.size())) ||
|
|
||||||
(mat.hasMRTex && (mat.metalRoughTexIdx >= mTextures.size())))
|
|
||||||
{
|
|
||||||
LL_WARNS("GLTF_IMPORT") << "Texture resource index error" << LL_ENDL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || // mesh can have up to 3 sets of UV
|
|
||||||
(mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) ||
|
|
||||||
(mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) ||
|
|
||||||
(mat.hasBaseTex && (mat.baseColorTexCoords > 2)) ||
|
|
||||||
(mat.hasMRTex && (mat.metalRoughTexCoords > 2)))
|
|
||||||
{
|
|
||||||
LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mMaterials.push_back(mat);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: convert raw vertex buffers to UUIDs
|
|
||||||
void LLGLTFLoader::uploadMeshes()
|
|
||||||
{
|
|
||||||
llassert(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert raw image buffers to texture UUIDs & assemble into a render material
|
|
||||||
void LLGLTFLoader::uploadMaterials()
|
|
||||||
{
|
|
||||||
for (gltf_render_material mat : mMaterials) // Initially 1 material per gltf file, but design for multiple
|
|
||||||
{
|
|
||||||
if (mat.hasBaseTex)
|
|
||||||
{
|
|
||||||
gltf_texture& gtex = mTextures[mat.baseColorTexIdx];
|
|
||||||
if (gtex.imageUuid.isNull())
|
|
||||||
{
|
|
||||||
gtex.imageUuid = imageBufferToTextureUUID(gtex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mat.hasMRTex)
|
|
||||||
{
|
|
||||||
gltf_texture& gtex = mTextures[mat.metalRoughTexIdx];
|
|
||||||
if (gtex.imageUuid.isNull())
|
|
||||||
{
|
|
||||||
gtex.imageUuid = imageBufferToTextureUUID(gtex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mat.hasNormalTex)
|
|
||||||
{
|
|
||||||
gltf_texture& gtex = mTextures[mat.normalTexIdx];
|
|
||||||
if (gtex.imageUuid.isNull())
|
|
||||||
{
|
|
||||||
gtex.imageUuid = imageBufferToTextureUUID(gtex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mat.hasOcclusionTex)
|
|
||||||
{
|
|
||||||
gltf_texture& gtex = mTextures[mat.occlusionTexIdx];
|
|
||||||
if (gtex.imageUuid.isNull())
|
|
||||||
{
|
|
||||||
gtex.imageUuid = imageBufferToTextureUUID(gtex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mat.hasEmissiveTex)
|
|
||||||
{
|
|
||||||
gltf_texture& gtex = mTextures[mat.emissiveTexIdx];
|
|
||||||
if (gtex.imageUuid.isNull())
|
|
||||||
{
|
|
||||||
gtex.imageUuid = imageBufferToTextureUUID(gtex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex)
|
|
||||||
{
|
|
||||||
//gltf_image& image = mImages[tex.imageIdx];
|
|
||||||
//gltf_sampler& sampler = mSamplers[tex.samplerIdx];
|
|
||||||
|
|
||||||
// fill an LLSD container with image+sampler data
|
|
||||||
|
|
||||||
// upload texture
|
|
||||||
|
|
||||||
// retrieve UUID
|
|
||||||
|
|
||||||
return LLUUID::null;
|
|
||||||
}
|
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
/**
|
|
||||||
* @file LLGLTFLoader.h
|
|
||||||
* @brief LLGLTFLoader class definition
|
|
||||||
*
|
|
||||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
|
||||||
* Second Life Viewer Source Code
|
|
||||||
* Copyright (C) 2022, Linden Research, Inc.
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation;
|
|
||||||
* version 2.1 of the License only.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*
|
|
||||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
||||||
* $/LicenseInfo$
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef LL_LLGLTFLoader_H
|
|
||||||
#define LL_LLGLTFLoader_H
|
|
||||||
|
|
||||||
#include "tinygltf/tiny_gltf.h"
|
|
||||||
|
|
||||||
#include "llglheaders.h"
|
|
||||||
#include "llmodelloader.h"
|
|
||||||
|
|
||||||
// gltf_* structs are temporary, used to organize the subset of data that eventually goes into the material LLSD
|
|
||||||
|
|
||||||
class gltf_sampler
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
// Uses GL enums
|
|
||||||
S32 minFilter; // GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR
|
|
||||||
S32 magFilter; // GL_NEAREST or GL_LINEAR
|
|
||||||
S32 wrapS; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT
|
|
||||||
S32 wrapT; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT
|
|
||||||
//S32 wrapR; // Found in some sample files, but not part of glTF 2.0 spec. Ignored.
|
|
||||||
std::string name; // optional, currently unused
|
|
||||||
// extensions and extras are sampler optional fields that we don't support - at least initially
|
|
||||||
};
|
|
||||||
|
|
||||||
class gltf_image
|
|
||||||
{
|
|
||||||
public:// Note that glTF images are defined with row 0 at the top (opposite of OpenGL)
|
|
||||||
U8* data; // ptr to decoded image data
|
|
||||||
U32 size; // in bytes, regardless of channel width
|
|
||||||
U32 width;
|
|
||||||
U32 height;
|
|
||||||
U32 numChannels; // range 1..4
|
|
||||||
U32 bytesPerChannel; // converted from gltf "bits", expects only 8, 16 or 32 as input
|
|
||||||
U32 pixelType; // one of (TINYGLTF_COMPONENT_TYPE)_UNSIGNED_BYTE, _UNSIGNED_SHORT, _UNSIGNED_INT, or _FLOAT
|
|
||||||
};
|
|
||||||
|
|
||||||
class gltf_texture
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
U32 imageIdx;
|
|
||||||
U32 samplerIdx;
|
|
||||||
LLUUID imageUuid = LLUUID::null;
|
|
||||||
};
|
|
||||||
|
|
||||||
class gltf_render_material
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
std::string name;
|
|
||||||
|
|
||||||
// scalar values
|
|
||||||
LLColor4 baseColor; // linear encoding. Multiplied with vertex color, if present.
|
|
||||||
double metalness;
|
|
||||||
double roughness;
|
|
||||||
double normalScale; // scale applies only to X,Y components of normal
|
|
||||||
double occlusionScale; // strength multiplier for occlusion
|
|
||||||
LLColor4 emissiveColor; // emissive mulitiplier, assumed linear encoding (spec 2.0 is silent)
|
|
||||||
std::string alphaMode; // "OPAQUE", "MASK" or "BLEND"
|
|
||||||
double alphaMask; // alpha cut-off
|
|
||||||
|
|
||||||
// textures
|
|
||||||
U32 baseColorTexIdx; // always sRGB encoded
|
|
||||||
U32 metalRoughTexIdx; // always linear, roughness in G channel, metalness in B channel
|
|
||||||
U32 normalTexIdx; // linear, valid range R[0-1], G[0-1], B[0.5-1]. Normal = texel * 2 - vec3(1.0)
|
|
||||||
U32 occlusionTexIdx; // linear, occlusion in R channel, 0 meaning fully occluded, 1 meaning not occluded
|
|
||||||
U32 emissiveTexIdx; // always stored as sRGB, in nits (candela / meter^2)
|
|
||||||
|
|
||||||
// texture coordinates
|
|
||||||
U32 baseColorTexCoords;
|
|
||||||
U32 metalRoughTexCoords;
|
|
||||||
U32 normalTexCoords;
|
|
||||||
U32 occlusionTexCoords;
|
|
||||||
U32 emissiveTexCoords;
|
|
||||||
|
|
||||||
// TODO: Add traditional (diffuse, normal, specular) UUIDs here, or add this struct to LL_TextureEntry??
|
|
||||||
|
|
||||||
bool hasPBR;
|
|
||||||
bool hasBaseTex, hasMRTex, hasNormalTex, hasOcclusionTex, hasEmissiveTex;
|
|
||||||
|
|
||||||
// This field is populated after upload
|
|
||||||
LLUUID material_uuid = LLUUID::null;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class gltf_mesh
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
std::string name;
|
|
||||||
|
|
||||||
// TODO add mesh import DJH 2022-04
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class LLGLTFLoader : public LLModelLoader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
typedef std::map<std::string, LLImportMaterial> material_map;
|
|
||||||
|
|
||||||
LLGLTFLoader(std::string filename,
|
|
||||||
S32 lod,
|
|
||||||
LLModelLoader::load_callback_t load_cb,
|
|
||||||
LLModelLoader::joint_lookup_func_t joint_lookup_func,
|
|
||||||
LLModelLoader::texture_load_func_t texture_load_func,
|
|
||||||
LLModelLoader::state_callback_t state_cb,
|
|
||||||
void * opaque_userdata,
|
|
||||||
JointTransformMap & jointTransformMap,
|
|
||||||
JointNameSet & jointsFromNodes,
|
|
||||||
std::map<std::string, std::string> &jointAliasMap,
|
|
||||||
U32 maxJointsPerMesh,
|
|
||||||
U32 modelLimit); //,
|
|
||||||
//bool preprocess );
|
|
||||||
virtual ~LLGLTFLoader();
|
|
||||||
|
|
||||||
virtual bool OpenFile(const std::string &filename);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
tinygltf::Model mGltfModel;
|
|
||||||
bool mGltfLoaded;
|
|
||||||
bool mMeshesLoaded;
|
|
||||||
bool mMaterialsLoaded;
|
|
||||||
|
|
||||||
std::vector<gltf_mesh> mMeshes;
|
|
||||||
std::vector<gltf_render_material> mMaterials;
|
|
||||||
|
|
||||||
std::vector<gltf_texture> mTextures;
|
|
||||||
std::vector<gltf_image> mImages;
|
|
||||||
std::vector<gltf_sampler> mSamplers;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool parseMeshes();
|
|
||||||
void uploadMeshes();
|
|
||||||
bool parseMaterials();
|
|
||||||
void uploadMaterials();
|
|
||||||
bool populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh);
|
|
||||||
LLUUID imageBufferToTextureUUID(const gltf_texture& tex);
|
|
||||||
|
|
||||||
// bool mPreprocessGLTF;
|
|
||||||
|
|
||||||
/* Below inherited from dae loader - unknown if/how useful here
|
|
||||||
|
|
||||||
void processElement(gltfElement *element, bool &badElement, GLTF *gltf);
|
|
||||||
void processGltfModel(LLModel *model, GLTF *gltf, gltfElement *pRoot, gltfMesh *mesh, gltfSkin *skin);
|
|
||||||
|
|
||||||
material_map getMaterials(LLModel *model, gltfInstance_geometry *instance_geo, GLTF *gltf);
|
|
||||||
LLImportMaterial profileToMaterial(gltfProfile_COMMON *material, GLTF *gltf);
|
|
||||||
LLColor4 getGltfColor(gltfElement *element);
|
|
||||||
|
|
||||||
gltfElement *getChildFromElement(gltfElement *pElement, std::string const &name);
|
|
||||||
|
|
||||||
bool isNodeAJoint(gltfNode *pNode);
|
|
||||||
void processJointNode(gltfNode *pNode, std::map<std::string, LLMatrix4> &jointTransforms);
|
|
||||||
void extractTranslation(gltfTranslate *pTranslate, LLMatrix4 &transform);
|
|
||||||
void extractTranslationViaElement(gltfElement *pTranslateElement, LLMatrix4 &transform);
|
|
||||||
void extractTranslationViaSID(gltfElement *pElement, LLMatrix4 &transform);
|
|
||||||
void buildJointToNodeMappingFromScene(gltfElement *pRoot);
|
|
||||||
void processJointToNodeMapping(gltfNode *pNode);
|
|
||||||
void processChildJoints(gltfNode *pParentNode);
|
|
||||||
|
|
||||||
bool verifyCount(int expected, int result);
|
|
||||||
|
|
||||||
// Verify that a controller matches vertex counts
|
|
||||||
bool verifyController(gltfController *pController);
|
|
||||||
|
|
||||||
static bool addVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh, LLSD &log_msg);
|
|
||||||
static bool createVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh);
|
|
||||||
|
|
||||||
static LLModel *loadModelFromGltfMesh(gltfMesh *mesh);
|
|
||||||
|
|
||||||
// Loads a mesh breaking it into one or more models as necessary
|
|
||||||
// to get around volume face limitations while retaining >8 materials
|
|
||||||
//
|
|
||||||
bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector<LLModel *> &models_out, U32 submodel_limit);
|
|
||||||
|
|
||||||
static std::string getElementLabel(gltfElement *element);
|
|
||||||
static size_t getSuffixPosition(std::string label);
|
|
||||||
static std::string getLodlessLabel(gltfElement *element);
|
|
||||||
|
|
||||||
static std::string preprocessGLTF(std::string filename);
|
|
||||||
*/
|
|
||||||
|
|
||||||
};
|
|
||||||
#endif // LL_LLGLTFLLOADER_H
|
|
||||||
|
|
@ -339,6 +339,162 @@ void LLModel::normalizeVolumeFaces()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LLModel::normalizeVolumeFacesAndWeights()
|
||||||
|
{
|
||||||
|
if (!mVolumeFaces.empty())
|
||||||
|
{
|
||||||
|
LLVector4a min, max;
|
||||||
|
|
||||||
|
// For all of the volume faces
|
||||||
|
// in the model, loop over
|
||||||
|
// them and see what the extents
|
||||||
|
// of the volume along each axis.
|
||||||
|
min = mVolumeFaces[0].mExtents[0];
|
||||||
|
max = mVolumeFaces[0].mExtents[1];
|
||||||
|
|
||||||
|
for (U32 i = 1; i < mVolumeFaces.size(); ++i)
|
||||||
|
{
|
||||||
|
LLVolumeFace& face = mVolumeFaces[i];
|
||||||
|
|
||||||
|
update_min_max(min, max, face.mExtents[0]);
|
||||||
|
update_min_max(min, max, face.mExtents[1]);
|
||||||
|
|
||||||
|
if (face.mTexCoords)
|
||||||
|
{
|
||||||
|
LLVector2& min_tc = face.mTexCoordExtents[0];
|
||||||
|
LLVector2& max_tc = face.mTexCoordExtents[1];
|
||||||
|
|
||||||
|
min_tc = face.mTexCoords[0];
|
||||||
|
max_tc = face.mTexCoords[0];
|
||||||
|
|
||||||
|
for (S32 j = 1; j < face.mNumVertices; ++j)
|
||||||
|
{
|
||||||
|
update_min_max(min_tc, max_tc, face.mTexCoords[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
face.mTexCoordExtents[0].set(0, 0);
|
||||||
|
face.mTexCoordExtents[1].set(1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we have the extents of the model
|
||||||
|
// we can compute the offset needed to center
|
||||||
|
// the model at the origin.
|
||||||
|
|
||||||
|
// Compute center of the model
|
||||||
|
// and make it negative to get translation
|
||||||
|
// needed to center at origin.
|
||||||
|
LLVector4a trans;
|
||||||
|
trans.setAdd(min, max);
|
||||||
|
trans.mul(-0.5f);
|
||||||
|
|
||||||
|
// Compute the total size along all
|
||||||
|
// axes of the model.
|
||||||
|
LLVector4a size;
|
||||||
|
size.setSub(max, min);
|
||||||
|
|
||||||
|
// Prevent division by zero.
|
||||||
|
F32 x = size[0];
|
||||||
|
F32 y = size[1];
|
||||||
|
F32 z = size[2];
|
||||||
|
F32 w = size[3];
|
||||||
|
if (fabs(x) < F_APPROXIMATELY_ZERO)
|
||||||
|
{
|
||||||
|
x = 1.0;
|
||||||
|
}
|
||||||
|
if (fabs(y) < F_APPROXIMATELY_ZERO)
|
||||||
|
{
|
||||||
|
y = 1.0;
|
||||||
|
}
|
||||||
|
if (fabs(z) < F_APPROXIMATELY_ZERO)
|
||||||
|
{
|
||||||
|
z = 1.0;
|
||||||
|
}
|
||||||
|
size.set(x, y, z, w);
|
||||||
|
|
||||||
|
// Compute scale as reciprocal of size
|
||||||
|
LLVector4a scale;
|
||||||
|
scale.splat(1.f);
|
||||||
|
scale.div(size);
|
||||||
|
|
||||||
|
LLVector4a inv_scale(1.f);
|
||||||
|
inv_scale.div(scale);
|
||||||
|
|
||||||
|
for (U32 i = 0; i < mVolumeFaces.size(); ++i)
|
||||||
|
{
|
||||||
|
LLVolumeFace& face = mVolumeFaces[i];
|
||||||
|
|
||||||
|
// We shrink the extents so
|
||||||
|
// that they fall within
|
||||||
|
// the unit cube.
|
||||||
|
// VFExtents change
|
||||||
|
face.mExtents[0].add(trans);
|
||||||
|
face.mExtents[0].mul(scale);
|
||||||
|
|
||||||
|
face.mExtents[1].add(trans);
|
||||||
|
face.mExtents[1].mul(scale);
|
||||||
|
|
||||||
|
// For all the positions, we scale
|
||||||
|
// the positions to fit within the unit cube.
|
||||||
|
LLVector4a* pos = (LLVector4a*)face.mPositions;
|
||||||
|
LLVector4a* norm = (LLVector4a*)face.mNormals;
|
||||||
|
LLVector4a* t = (LLVector4a*)face.mTangents;
|
||||||
|
|
||||||
|
for (S32 j = 0; j < face.mNumVertices; ++j)
|
||||||
|
{
|
||||||
|
pos[j].add(trans);
|
||||||
|
pos[j].mul(scale);
|
||||||
|
if (norm && !norm[j].equals3(LLVector4a::getZero()))
|
||||||
|
{
|
||||||
|
norm[j].mul(inv_scale);
|
||||||
|
norm[j].normalize3();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t)
|
||||||
|
{
|
||||||
|
F32 w = t[j].getF32ptr()[3];
|
||||||
|
t[j].mul(inv_scale);
|
||||||
|
t[j].normalize3();
|
||||||
|
t[j].getF32ptr()[3] = w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
weight_map old_weights = mSkinWeights;
|
||||||
|
mSkinWeights.clear();
|
||||||
|
mPosition.clear();
|
||||||
|
|
||||||
|
for (auto& weights : old_weights)
|
||||||
|
{
|
||||||
|
LLVector4a pos(weights.first.mV[VX], weights.first.mV[VY], weights.first.mV[VZ]);
|
||||||
|
pos.add(trans);
|
||||||
|
pos.mul(scale);
|
||||||
|
LLVector3 scaled_pos(pos.getF32ptr());
|
||||||
|
mPosition.push_back(scaled_pos);
|
||||||
|
mSkinWeights[scaled_pos] = weights.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mNormalizedScale is the scale at which
|
||||||
|
// we would need to multiply the model
|
||||||
|
// by to get the original size of the
|
||||||
|
// model instead of the normalized size.
|
||||||
|
LLVector4a normalized_scale;
|
||||||
|
normalized_scale.splat(1.f);
|
||||||
|
normalized_scale.div(scale);
|
||||||
|
mNormalizedScale.set(normalized_scale.getF32ptr());
|
||||||
|
mNormalizedTranslation.set(trans.getF32ptr());
|
||||||
|
mNormalizedTranslation *= -1.f;
|
||||||
|
|
||||||
|
// remember normalized scale so original dimensions can be recovered for mesh processing (i.e. tangent generation)
|
||||||
|
for (auto& face : mVolumeFaces)
|
||||||
|
{
|
||||||
|
face.mNormalizedScale = mNormalizedScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void LLModel::getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const
|
void LLModel::getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const
|
||||||
{
|
{
|
||||||
scale_out = mNormalizedScale;
|
scale_out = mNormalizedScale;
|
||||||
|
|
@ -667,7 +823,7 @@ LLSD LLModel::writeModel(
|
||||||
bool upload_skin,
|
bool upload_skin,
|
||||||
bool upload_joints,
|
bool upload_joints,
|
||||||
bool lock_scale_if_joint_position,
|
bool lock_scale_if_joint_position,
|
||||||
bool nowrite,
|
EWriteModelMode write_mode,
|
||||||
bool as_slm,
|
bool as_slm,
|
||||||
int submodel_id)
|
int submodel_id)
|
||||||
{
|
{
|
||||||
|
|
@ -946,10 +1102,10 @@ LLSD LLModel::writeModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeModelToStream(ostr, mdl, nowrite, as_slm);
|
return writeModelToStream(ostr, mdl, write_mode, as_slm);
|
||||||
}
|
}
|
||||||
|
|
||||||
LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, bool nowrite, bool as_slm)
|
LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, EWriteModelMode write_mode, bool as_slm)
|
||||||
{
|
{
|
||||||
std::string::size_type cur_offset = 0;
|
std::string::size_type cur_offset = 0;
|
||||||
|
|
||||||
|
|
@ -1011,7 +1167,11 @@ LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, bool nowrite, bo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nowrite)
|
if (write_mode == WRITE_HUMAN)
|
||||||
|
{
|
||||||
|
ostr << mdl;
|
||||||
|
}
|
||||||
|
else if (write_mode == WRITE_BINARY)
|
||||||
{
|
{
|
||||||
LLSDSerialize::toBinary(header, ostr);
|
LLSDSerialize::toBinary(header, ostr);
|
||||||
|
|
||||||
|
|
@ -1566,11 +1726,21 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi
|
||||||
{
|
{
|
||||||
ret["joint_names"][i] = mJointNames[i];
|
ret["joint_names"][i] = mJointNames[i];
|
||||||
|
|
||||||
|
// For model to work at all there must be a matching bind matrix,
|
||||||
|
// so supply an indentity one if it isn't true
|
||||||
|
// Note: can build an actual bind matrix from joints
|
||||||
|
const LLMatrix4a& inv_bind = mInvBindMatrix.size() > i ? mInvBindMatrix[i] : LLMatrix4a::identity();
|
||||||
|
if (i >= mInvBindMatrix.size())
|
||||||
|
{
|
||||||
|
LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds inverse bind matrix size "
|
||||||
|
<< mInvBindMatrix.size() << LL_ENDL;
|
||||||
|
}
|
||||||
|
|
||||||
for (U32 j = 0; j < 4; j++)
|
for (U32 j = 0; j < 4; j++)
|
||||||
{
|
{
|
||||||
for (U32 k = 0; k < 4; k++)
|
for (U32 k = 0; k < 4; k++)
|
||||||
{
|
{
|
||||||
ret["inverse_bind_matrix"][i][j*4+k] = mInvBindMatrix[i].mMatrix[j][k];
|
ret["inverse_bind_matrix"][i][j * 4 + k] = inv_bind.mMatrix[j][k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1583,15 +1753,25 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( include_joints && mAlternateBindMatrix.size() > 0 )
|
// optional 'joint overrides'
|
||||||
|
if (include_joints && mAlternateBindMatrix.size() > 0)
|
||||||
{
|
{
|
||||||
for (U32 i = 0; i < mJointNames.size(); ++i)
|
for (U32 i = 0; i < mJointNames.size(); ++i)
|
||||||
{
|
{
|
||||||
|
// If there is not enough to match mJointNames,
|
||||||
|
// either supply no alternate matrixes at all or supply
|
||||||
|
// replacements
|
||||||
|
const LLMatrix4a& alt_bind = mAlternateBindMatrix.size() > i ? mAlternateBindMatrix[i] : LLMatrix4a::identity();
|
||||||
|
if (i >= mAlternateBindMatrix.size())
|
||||||
|
{
|
||||||
|
LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds alternate bind matrix size "
|
||||||
|
<< mAlternateBindMatrix.size() << LL_ENDL;
|
||||||
|
}
|
||||||
for (U32 j = 0; j < 4; j++)
|
for (U32 j = 0; j < 4; j++)
|
||||||
{
|
{
|
||||||
for (U32 k = 0; k < 4; k++)
|
for (U32 k = 0; k < 4; k++)
|
||||||
{
|
{
|
||||||
ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k];
|
ret["alt_inverse_bind_matrix"][i][j * 4 + k] = alt_bind.mMatrix[j][k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,12 @@ public:
|
||||||
bool loadSkinInfo(LLSD& header, std::istream& is);
|
bool loadSkinInfo(LLSD& header, std::istream& is);
|
||||||
bool loadDecomposition(LLSD& header, std::istream& is);
|
bool loadDecomposition(LLSD& header, std::istream& is);
|
||||||
|
|
||||||
|
enum EWriteModelMode
|
||||||
|
{
|
||||||
|
WRITE_NO = 0,
|
||||||
|
WRITE_BINARY,
|
||||||
|
WRITE_HUMAN,
|
||||||
|
};
|
||||||
static LLSD writeModel(
|
static LLSD writeModel(
|
||||||
std::ostream& ostr,
|
std::ostream& ostr,
|
||||||
LLModel* physics,
|
LLModel* physics,
|
||||||
|
|
@ -171,14 +177,14 @@ public:
|
||||||
bool upload_skin,
|
bool upload_skin,
|
||||||
bool upload_joints,
|
bool upload_joints,
|
||||||
bool lock_scale_if_joint_position,
|
bool lock_scale_if_joint_position,
|
||||||
bool nowrite = false,
|
EWriteModelMode write_mode = WRITE_BINARY,
|
||||||
bool as_slm = false,
|
bool as_slm = false,
|
||||||
int submodel_id = 0);
|
int submodel_id = 0);
|
||||||
|
|
||||||
static LLSD writeModelToStream(
|
static LLSD writeModelToStream(
|
||||||
std::ostream& ostr,
|
std::ostream& ostr,
|
||||||
LLSD& mdl,
|
LLSD& mdl,
|
||||||
bool nowrite = false, bool as_slm = false);
|
EWriteModelMode write_mode = WRITE_BINARY, bool as_slm = false);
|
||||||
|
|
||||||
void ClearFacesAndMaterials() { mVolumeFaces.clear(); mMaterialList.clear(); }
|
void ClearFacesAndMaterials() { mVolumeFaces.clear(); mMaterialList.clear(); }
|
||||||
|
|
||||||
|
|
@ -202,6 +208,7 @@ public:
|
||||||
|
|
||||||
void sortVolumeFacesByMaterialName();
|
void sortVolumeFacesByMaterialName();
|
||||||
void normalizeVolumeFaces();
|
void normalizeVolumeFaces();
|
||||||
|
void normalizeVolumeFacesAndWeights();
|
||||||
void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL);
|
void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL);
|
||||||
void remapVolumeFaces();
|
void remapVolumeFaces();
|
||||||
void optimizeVolumeFaces();
|
void optimizeVolumeFaces();
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
|
|
||||||
#include "llmatrix4a.h"
|
#include "llmatrix4a.h"
|
||||||
#include <boost/bind.hpp>
|
#include <boost/bind.hpp>
|
||||||
|
#include <boost/exception/diagnostic_information.hpp>
|
||||||
|
|
||||||
std::list<LLModelLoader*> LLModelLoader::sActiveLoaderList;
|
std::list<LLModelLoader*> LLModelLoader::sActiveLoaderList;
|
||||||
|
|
||||||
|
|
@ -113,7 +114,9 @@ LLModelLoader::LLModelLoader(
|
||||||
JointTransformMap& jointTransformMap,
|
JointTransformMap& jointTransformMap,
|
||||||
JointNameSet& jointsFromNodes,
|
JointNameSet& jointsFromNodes,
|
||||||
JointMap& legalJointNamesMap,
|
JointMap& legalJointNamesMap,
|
||||||
U32 maxJointsPerMesh)
|
U32 maxJointsPerMesh,
|
||||||
|
U32 modelLimit,
|
||||||
|
U32 debugMode)
|
||||||
: mJointList( jointTransformMap )
|
: mJointList( jointTransformMap )
|
||||||
, mJointsFromNode( jointsFromNodes )
|
, mJointsFromNode( jointsFromNodes )
|
||||||
, LLThread("Model Loader")
|
, LLThread("Model Loader")
|
||||||
|
|
@ -121,7 +124,6 @@ LLModelLoader::LLModelLoader(
|
||||||
, mLod(lod)
|
, mLod(lod)
|
||||||
, mTrySLM(false)
|
, mTrySLM(false)
|
||||||
, mFirstTransform(true)
|
, mFirstTransform(true)
|
||||||
, mNumOfFetchingTextures(0)
|
|
||||||
, mLoadCallback(load_cb)
|
, mLoadCallback(load_cb)
|
||||||
, mJointLookupFunc(joint_lookup_func)
|
, mJointLookupFunc(joint_lookup_func)
|
||||||
, mTextureLoadFunc(texture_load_func)
|
, mTextureLoadFunc(texture_load_func)
|
||||||
|
|
@ -132,7 +134,10 @@ LLModelLoader::LLModelLoader(
|
||||||
, mNoNormalize(false)
|
, mNoNormalize(false)
|
||||||
, mNoOptimize(false)
|
, mNoOptimize(false)
|
||||||
, mCacheOnlyHitIfRigged(false)
|
, mCacheOnlyHitIfRigged(false)
|
||||||
|
, mTexturesNeedScaling(false)
|
||||||
, mMaxJointsPerMesh(maxJointsPerMesh)
|
, mMaxJointsPerMesh(maxJointsPerMesh)
|
||||||
|
, mGeneratedModelLimit(modelLimit)
|
||||||
|
, mDebugMode(debugMode)
|
||||||
, mJointMap(legalJointNamesMap)
|
, mJointMap(legalJointNamesMap)
|
||||||
{
|
{
|
||||||
assert_main_thread();
|
assert_main_thread();
|
||||||
|
|
@ -149,7 +154,44 @@ LLModelLoader::~LLModelLoader()
|
||||||
void LLModelLoader::run()
|
void LLModelLoader::run()
|
||||||
{
|
{
|
||||||
mWarningsArray.clear();
|
mWarningsArray.clear();
|
||||||
|
try
|
||||||
|
{
|
||||||
doLoadModel();
|
doLoadModel();
|
||||||
|
}
|
||||||
|
// Model loader isn't mission critical, so we just log all exceptions
|
||||||
|
catch (const LLException& e)
|
||||||
|
{
|
||||||
|
LL_WARNS("THREAD") << "LLException in model loader: " << e.what() << "" << LL_ENDL;
|
||||||
|
LLSD args;
|
||||||
|
args["Message"] = "UnknownException";
|
||||||
|
args["FILENAME"] = mFilename;
|
||||||
|
args["EXCEPTION"] = e.what();
|
||||||
|
mWarningsArray.append(args);
|
||||||
|
setLoadState(ERROR_PARSING);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
LL_WARNS() << "Exception in LLModelLoader::run: " << e.what() << LL_ENDL;
|
||||||
|
LLSD args;
|
||||||
|
args["Message"] = "UnknownException";
|
||||||
|
args["FILENAME"] = mFilename;
|
||||||
|
args["EXCEPTION"] = e.what();
|
||||||
|
mWarningsArray.append(args);
|
||||||
|
setLoadState(ERROR_PARSING);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
LOG_UNHANDLED_EXCEPTION("LLModelLoader");
|
||||||
|
LLSD args;
|
||||||
|
args["Message"] = "UnknownException";
|
||||||
|
args["FILENAME"] = mFilename;
|
||||||
|
args["EXCEPTION"] = boost::current_exception_diagnostic_information();
|
||||||
|
mWarningsArray.append(args);
|
||||||
|
setLoadState(ERROR_PARSING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: we are inside of a thread, push this into main thread worker,
|
||||||
|
// not into doOnIdleOneTime that laks tread safety
|
||||||
doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback,this));
|
doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback,this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,7 +243,9 @@ bool LLModelLoader::doLoadModel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return OpenFile(mFilename);
|
bool res = OpenFile(mFilename);
|
||||||
|
dumpDebugData(); // conditional on mDebugMode
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLModelLoader::setLoadState(U32 state)
|
void LLModelLoader::setLoadState(U32 state)
|
||||||
|
|
@ -466,6 +510,148 @@ bool LLModelLoader::isRigSuitableForJointPositionUpload( const std::vector<std::
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LLModelLoader::dumpDebugData()
|
||||||
|
{
|
||||||
|
if (mDebugMode == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string log_file = mFilename + "_importer.txt";
|
||||||
|
LLStringUtil::toLower(log_file);
|
||||||
|
llofstream file;
|
||||||
|
file.open(log_file.c_str());
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
LL_WARNS() << "dumpDebugData failed to open file " << log_file << LL_ENDL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
file << "Importing: " << mFilename << "\n";
|
||||||
|
|
||||||
|
std::map<std::string, LLMatrix4a> inv_bind;
|
||||||
|
std::map<std::string, LLMatrix4a> alt_bind;
|
||||||
|
for (LLPointer<LLModel>& mdl : mModelList)
|
||||||
|
{
|
||||||
|
|
||||||
|
file << "Model name: " << mdl->mLabel << "\n";
|
||||||
|
const LLMeshSkinInfo& skin_info = mdl->mSkinInfo;
|
||||||
|
file << "Shape Bind matrix: " << skin_info.mBindShapeMatrix << "\n";
|
||||||
|
file << "Skin Weights count: " << (S32)mdl->mSkinWeights.size() << "\n";
|
||||||
|
|
||||||
|
// some objects might have individual bind matrices,
|
||||||
|
// but for now it isn't accounted for
|
||||||
|
size_t joint_count = skin_info.mJointNames.size();
|
||||||
|
for (size_t i = 0; i< joint_count;i++)
|
||||||
|
{
|
||||||
|
const std::string& joint = skin_info.mJointNames[i];
|
||||||
|
if (skin_info.mInvBindMatrix.size() > i)
|
||||||
|
{
|
||||||
|
inv_bind[joint] = skin_info.mInvBindMatrix[i];
|
||||||
|
}
|
||||||
|
if (skin_info.mAlternateBindMatrix.size() > i)
|
||||||
|
{
|
||||||
|
alt_bind[joint] = skin_info.mAlternateBindMatrix[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file << "\nInv Bind matrices.\n";
|
||||||
|
for (auto& bind : inv_bind)
|
||||||
|
{
|
||||||
|
file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
file << "\nAlt Bind matrices.\n";
|
||||||
|
for (auto& bind : alt_bind)
|
||||||
|
{
|
||||||
|
file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mDebugMode == 2)
|
||||||
|
{
|
||||||
|
S32 model_count = 0;
|
||||||
|
for (LLPointer<LLModel>& mdl : mModelList)
|
||||||
|
{
|
||||||
|
const LLVolume::face_list_t &face_list = mdl->getVolumeFaces();
|
||||||
|
for (S32 face = 0; face < face_list.size(); face++)
|
||||||
|
{
|
||||||
|
const LLVolumeFace& vf = face_list[face];
|
||||||
|
file << "\nModel: " << mdl->mLabel
|
||||||
|
<< " face " << face
|
||||||
|
<< " has " << vf.mNumVertices
|
||||||
|
<< " vertices and " << vf.mNumIndices
|
||||||
|
<< " indices " << "\n";
|
||||||
|
|
||||||
|
file << "\nPositions for model: " << mdl->mLabel << " face " << face << "\n";
|
||||||
|
|
||||||
|
for (S32 pos = 0; pos < vf.mNumVertices; ++pos)
|
||||||
|
{
|
||||||
|
file << vf.mPositions[pos] << " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
file << "\n\nIndices for model: " << mdl->mLabel << " face " << face << "\n";
|
||||||
|
|
||||||
|
for (S32 ind = 0; ind < vf.mNumIndices; ++ind)
|
||||||
|
{
|
||||||
|
file << vf.mIndices[ind] << " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file << "\n\nWeights for model: " << mdl->mLabel;
|
||||||
|
for (auto& weights : mdl->mSkinWeights)
|
||||||
|
{
|
||||||
|
file << "\nVertex: " << weights.first << " Weights: ";
|
||||||
|
for (auto& weight : weights.second)
|
||||||
|
{
|
||||||
|
file << weight.mJointIdx << ":" << weight.mWeight << " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file << "\n";
|
||||||
|
model_count++;
|
||||||
|
if (model_count == 5)
|
||||||
|
{
|
||||||
|
file << "Too many models, stopping at 5.\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mDebugMode > 2)
|
||||||
|
{
|
||||||
|
file << "\nModel LLSDs\n";
|
||||||
|
S32 model_count = 0;
|
||||||
|
// some files contain too many models, so stop at 5.
|
||||||
|
for (LLPointer<LLModel>& mdl : mModelList)
|
||||||
|
{
|
||||||
|
const LLMeshSkinInfo& skin_info = mdl->mSkinInfo;
|
||||||
|
size_t joint_count = skin_info.mJointNames.size();
|
||||||
|
size_t alt_count = skin_info.mAlternateBindMatrix.size();
|
||||||
|
|
||||||
|
LLModel::writeModel(
|
||||||
|
file,
|
||||||
|
nullptr,
|
||||||
|
mdl,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
mdl->mPhysics,
|
||||||
|
joint_count > 0,
|
||||||
|
alt_count > 0,
|
||||||
|
false,
|
||||||
|
LLModel::WRITE_HUMAN,
|
||||||
|
false,
|
||||||
|
mdl->mSubmodelID);
|
||||||
|
|
||||||
|
file << "\n";
|
||||||
|
model_count++;
|
||||||
|
if (model_count == 5)
|
||||||
|
{
|
||||||
|
file << "Too many models, stopping at 5.\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//called in the main thread
|
//called in the main thread
|
||||||
void LLModelLoader::loadTextures()
|
void LLModelLoader::loadTextures()
|
||||||
|
|
@ -484,7 +670,7 @@ void LLModelLoader::loadTextures()
|
||||||
|
|
||||||
if(!material.mDiffuseMapFilename.empty())
|
if(!material.mDiffuseMapFilename.empty())
|
||||||
{
|
{
|
||||||
mNumOfFetchingTextures += mTextureLoadFunc(material, mOpaqueData);
|
mTextureLoadFunc(material, mOpaqueData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class LLJoint;
|
||||||
|
|
||||||
typedef std::map<std::string, LLMatrix4> JointTransformMap;
|
typedef std::map<std::string, LLMatrix4> JointTransformMap;
|
||||||
typedef std::map<std::string, LLMatrix4>::iterator JointTransformMapIt;
|
typedef std::map<std::string, LLMatrix4>::iterator JointTransformMapIt;
|
||||||
typedef std::map<std::string, std::string> JointMap;
|
typedef std::map<std::string, std::string, std::less<>> JointMap;
|
||||||
typedef std::deque<std::string> JointNameSet;
|
typedef std::deque<std::string> JointNameSet;
|
||||||
|
|
||||||
const S32 SLM_SUPPORTED_VERSION = 3;
|
const S32 SLM_SUPPORTED_VERSION = 3;
|
||||||
|
|
@ -109,8 +109,10 @@ public:
|
||||||
|
|
||||||
bool mTrySLM;
|
bool mTrySLM;
|
||||||
bool mCacheOnlyHitIfRigged; // ignore cached SLM if it does not contain rig info (and we want rig info)
|
bool mCacheOnlyHitIfRigged; // ignore cached SLM if it does not contain rig info (and we want rig info)
|
||||||
|
bool mTexturesNeedScaling;
|
||||||
|
|
||||||
model_list mModelList;
|
model_list mModelList;
|
||||||
|
// The scene is pretty much what ends up getting loaded for upload. Basically assign things to this guy if you want something uploaded.
|
||||||
scene mScene;
|
scene mScene;
|
||||||
|
|
||||||
typedef std::queue<LLPointer<LLModel> > model_queue;
|
typedef std::queue<LLPointer<LLModel> > model_queue;
|
||||||
|
|
@ -119,10 +121,16 @@ public:
|
||||||
model_queue mPhysicsQ;
|
model_queue mPhysicsQ;
|
||||||
|
|
||||||
//map of avatar joints as named in COLLADA assets to internal joint names
|
//map of avatar joints as named in COLLADA assets to internal joint names
|
||||||
|
// Do not use this for anything other than looking up the name of a joint. This is populated elsewhere.
|
||||||
JointMap mJointMap;
|
JointMap mJointMap;
|
||||||
|
|
||||||
|
// The joint list is what you want to use to actually setup the specific joint transformations.
|
||||||
JointTransformMap& mJointList;
|
JointTransformMap& mJointList;
|
||||||
JointNameSet& mJointsFromNode;
|
JointNameSet& mJointsFromNode;
|
||||||
|
|
||||||
|
|
||||||
U32 mMaxJointsPerMesh;
|
U32 mMaxJointsPerMesh;
|
||||||
|
U32 mDebugMode; // see dumDebugData() for details
|
||||||
|
|
||||||
LLModelLoader(
|
LLModelLoader(
|
||||||
std::string filename,
|
std::string filename,
|
||||||
|
|
@ -135,7 +143,9 @@ public:
|
||||||
JointTransformMap& jointTransformMap,
|
JointTransformMap& jointTransformMap,
|
||||||
JointNameSet& jointsFromNodes,
|
JointNameSet& jointsFromNodes,
|
||||||
JointMap& legalJointNamesMap,
|
JointMap& legalJointNamesMap,
|
||||||
U32 maxJointsPerMesh);
|
U32 maxJointsPerMesh,
|
||||||
|
U32 modelLimit,
|
||||||
|
U32 debugMode);
|
||||||
virtual ~LLModelLoader();
|
virtual ~LLModelLoader();
|
||||||
|
|
||||||
virtual void setNoNormalize() { mNoNormalize = true; }
|
virtual void setNoNormalize() { mNoNormalize = true; }
|
||||||
|
|
@ -161,9 +171,6 @@ public:
|
||||||
|
|
||||||
void stretch_extents(const LLModel* model, const LLMatrix4& mat);
|
void stretch_extents(const LLModel* model, const LLMatrix4& mat);
|
||||||
|
|
||||||
S32 mNumOfFetchingTextures ; // updated in the main thread
|
|
||||||
bool areTexturesReady() { return !mNumOfFetchingTextures; } // called in the main thread.
|
|
||||||
|
|
||||||
bool verifyCount( int expected, int result );
|
bool verifyCount( int expected, int result );
|
||||||
|
|
||||||
//Determines the viability of an asset to be used as an avatar rig (w or w/o joint upload caps)
|
//Determines the viability of an asset to be used as an avatar rig (w or w/o joint upload caps)
|
||||||
|
|
@ -192,6 +199,7 @@ public:
|
||||||
|
|
||||||
const LLSD logOut() const { return mWarningsArray; }
|
const LLSD logOut() const { return mWarningsArray; }
|
||||||
void clearLog() { mWarningsArray.clear(); }
|
void clearLog() { mWarningsArray.clear(); }
|
||||||
|
void dumpDebugData();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
|
@ -203,6 +211,7 @@ protected:
|
||||||
|
|
||||||
bool mRigValidJointUpload;
|
bool mRigValidJointUpload;
|
||||||
U32 mLegacyRigFlags;
|
U32 mLegacyRigFlags;
|
||||||
|
U32 mGeneratedModelLimit; // Attempt to limit amount of generated submodels
|
||||||
|
|
||||||
bool mNoNormalize;
|
bool mNoNormalize;
|
||||||
bool mNoOptimize;
|
bool mNoOptimize;
|
||||||
|
|
|
||||||
|
|
@ -1870,8 +1870,17 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre
|
||||||
glGetTexLevelParameteriv(mTarget, gl_discard, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, (GLint*)&glbytes);
|
glGetTexLevelParameteriv(mTarget, gl_discard, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, (GLint*)&glbytes);
|
||||||
if(!imageraw->allocateDataSize(width, height, ncomponents, glbytes))
|
if(!imageraw->allocateDataSize(width, height, ncomponents, glbytes))
|
||||||
{
|
{
|
||||||
LL_WARNS() << "Memory allocation failed for reading back texture. Size is: " << glbytes << LL_ENDL ;
|
constexpr S64 MAX_GL_BYTES = 2048 * 2048;
|
||||||
LL_WARNS() << "width: " << width << "height: " << height << "components: " << ncomponents << LL_ENDL ;
|
if (glbytes > 0 && glbytes <= MAX_GL_BYTES)
|
||||||
|
{
|
||||||
|
LLError::LLUserWarningMsg::showOutOfMemory();
|
||||||
|
LL_ERRS() << "Memory allocation failed for reading back texture. Data size: " << glbytes << LL_ENDL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LL_WARNS() << "Memory allocation failed for reading back texture. Data size is: " << glbytes << LL_ENDL;
|
||||||
|
LL_WARNS() << "width: " << width << "height: " << height << "components: " << ncomponents << LL_ENDL;
|
||||||
|
}
|
||||||
return false ;
|
return false ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1882,8 +1891,18 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre
|
||||||
{
|
{
|
||||||
if(!imageraw->allocateDataSize(width, height, ncomponents))
|
if(!imageraw->allocateDataSize(width, height, ncomponents))
|
||||||
{
|
{
|
||||||
LL_WARNS() << "Memory allocation failed for reading back texture." << LL_ENDL ;
|
constexpr F32 MAX_IMAGE_SIZE = 2048 * 2048;
|
||||||
LL_WARNS() << "width: " << width << "height: " << height << "components: " << ncomponents << LL_ENDL ;
|
F32 size = (F32)width * (F32)height * (F32)ncomponents;
|
||||||
|
if (size > 0 && size <= MAX_IMAGE_SIZE)
|
||||||
|
{
|
||||||
|
LLError::LLUserWarningMsg::showOutOfMemory();
|
||||||
|
LL_ERRS() << "Memory allocation failed for reading back texture. Data size: " << size << LL_ENDL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LL_WARNS() << "Memory allocation failed for reading back texture." << LL_ENDL;
|
||||||
|
LL_WARNS() << "width: " << width << "height: " << height << "components: " << ncomponents << LL_ENDL;
|
||||||
|
}
|
||||||
return false ;
|
return false ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2230,6 +2230,9 @@ void LLLineEditor::clear()
|
||||||
{
|
{
|
||||||
mText.clear();
|
mText.clear();
|
||||||
setCursor(0);
|
setCursor(0);
|
||||||
|
mFontBufferPreSelection.reset();
|
||||||
|
mFontBufferSelection.reset();
|
||||||
|
mFontBufferPostSelection.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
//virtual
|
//virtual
|
||||||
|
|
|
||||||
|
|
@ -1007,7 +1007,7 @@ void LLScrollListCtrl::deleteItems(const LLSD& sd)
|
||||||
void LLScrollListCtrl::deleteSelectedItems()
|
void LLScrollListCtrl::deleteSelectedItems()
|
||||||
{
|
{
|
||||||
item_list::iterator iter;
|
item_list::iterator iter;
|
||||||
for (iter = mItemList.begin(); iter < mItemList.end(); )
|
for (iter = mItemList.begin(); iter != mItemList.end(); )
|
||||||
{
|
{
|
||||||
LLScrollListItem* itemp = *iter;
|
LLScrollListItem* itemp = *iter;
|
||||||
if (itemp->getSelected())
|
if (itemp->getSelected())
|
||||||
|
|
|
||||||
|
|
@ -2228,6 +2228,7 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url)
|
||||||
registrar.add("Url.ReportAbuse", boost::bind(&LLUrlAction::reportAbuse, url));
|
registrar.add("Url.ReportAbuse", boost::bind(&LLUrlAction::reportAbuse, url));
|
||||||
registrar.add("Url.SendIM", boost::bind(&LLUrlAction::sendIM, url));
|
registrar.add("Url.SendIM", boost::bind(&LLUrlAction::sendIM, url));
|
||||||
registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url));
|
registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url));
|
||||||
|
registrar.add("Url.ShowParcelOnMap", boost::bind(&LLUrlAction::showParcelOnMap, url));
|
||||||
registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url));
|
registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url));
|
||||||
registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url));
|
registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
#include "llview.h"
|
#include "llview.h"
|
||||||
#include "llwindow.h"
|
#include "llwindow.h"
|
||||||
#include "llurlregistry.h"
|
#include "llurlregistry.h"
|
||||||
|
#include "v3dmath.h"
|
||||||
|
|
||||||
|
|
||||||
// global state for the callback functions
|
// global state for the callback functions
|
||||||
|
|
@ -128,6 +129,23 @@ void LLUrlAction::showLocationOnMap(std::string url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LLUrlAction::showParcelOnMap(std::string url)
|
||||||
|
{
|
||||||
|
LLSD path_array = LLURI(url).pathArray();
|
||||||
|
auto path_parts = path_array.size();
|
||||||
|
|
||||||
|
if (path_parts < 3) // no parcel id
|
||||||
|
{
|
||||||
|
LL_WARNS() << "Global coordinates are missing in url: [" << url << "]" << LL_ENDL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LLVector3d parcel_pos = LLUrlEntryParcel::getParcelPos(LLUUID(LLURI::unescape(path_array[2])));
|
||||||
|
std::ostringstream pos;
|
||||||
|
pos << parcel_pos.mdV[VX] << '/' << parcel_pos.mdV[VY] << '/' << parcel_pos.mdV[VZ];
|
||||||
|
executeSLURL("secondlife:///app/worldmap_global/" + pos.str());
|
||||||
|
}
|
||||||
|
|
||||||
void LLUrlAction::copyURLToClipboard(std::string url)
|
void LLUrlAction::copyURLToClipboard(std::string url)
|
||||||
{
|
{
|
||||||
LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(url));
|
LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(url));
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@ public:
|
||||||
/// if the Url specifies an SL location, show it on a map
|
/// if the Url specifies an SL location, show it on a map
|
||||||
static void showLocationOnMap(std::string url);
|
static void showLocationOnMap(std::string url);
|
||||||
|
|
||||||
|
static void showParcelOnMap(std::string url);
|
||||||
|
|
||||||
/// perform the appropriate action for left-clicking on a Url
|
/// perform the appropriate action for left-clicking on a Url
|
||||||
static void clickAction(std::string url, bool trusted_content);
|
static void clickAction(std::string url, bool trusted_content);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
#include "lluicolortable.h"
|
#include "lluicolortable.h"
|
||||||
#include "message.h"
|
#include "message.h"
|
||||||
#include "llexperiencecache.h"
|
#include "llexperiencecache.h"
|
||||||
|
#include "v3dmath.h"
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
std::string localize_slapp_label(const std::string& url, const std::string& full_name);
|
std::string localize_slapp_label(const std::string& url, const std::string& full_name);
|
||||||
|
|
@ -1082,6 +1083,7 @@ LLUUID LLUrlEntryParcel::sSessionID(LLUUID::null);
|
||||||
LLHost LLUrlEntryParcel::sRegionHost;
|
LLHost LLUrlEntryParcel::sRegionHost;
|
||||||
bool LLUrlEntryParcel::sDisconnected(false);
|
bool LLUrlEntryParcel::sDisconnected(false);
|
||||||
std::set<LLUrlEntryParcel*> LLUrlEntryParcel::sParcelInfoObservers;
|
std::set<LLUrlEntryParcel*> LLUrlEntryParcel::sParcelInfoObservers;
|
||||||
|
std::map<LLUUID, LLVector3d> LLUrlEntryParcel::sParcelPos;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g.,
|
/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g.,
|
||||||
|
|
@ -1174,6 +1176,20 @@ void LLUrlEntryParcel::processParcelInfo(const LLParcelData& parcel_data)
|
||||||
url_entry->onParcelInfoReceived(parcel_data.parcel_id.asString(), label);
|
url_entry->onParcelInfoReceived(parcel_data.parcel_id.asString(), label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (sParcelPos.find(parcel_data.parcel_id) == sParcelPos.end())
|
||||||
|
{
|
||||||
|
sParcelPos[parcel_data.parcel_id] = LLVector3d(parcel_data.global_x, parcel_data.global_y, parcel_data.global_z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
LLVector3d LLUrlEntryParcel::getParcelPos(const LLUUID& parcel_id)
|
||||||
|
{
|
||||||
|
if (sParcelPos.find(parcel_id) != sParcelPos.end())
|
||||||
|
{
|
||||||
|
return sParcelPos[parcel_id];
|
||||||
|
}
|
||||||
|
return LLVector3d();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
class LLAvatarName;
|
class LLAvatarName;
|
||||||
|
class LLVector3d;
|
||||||
|
|
||||||
#define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))"
|
#define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))"
|
||||||
|
|
||||||
|
|
@ -436,6 +437,8 @@ public:
|
||||||
// Processes parcel label and triggers notifying observers.
|
// Processes parcel label and triggers notifying observers.
|
||||||
static void processParcelInfo(const LLParcelData& parcel_data);
|
static void processParcelInfo(const LLParcelData& parcel_data);
|
||||||
|
|
||||||
|
static LLVector3d getParcelPos(const LLUUID& parcel_id);
|
||||||
|
|
||||||
// Next setters are used to update agent and viewer connection information
|
// Next setters are used to update agent and viewer connection information
|
||||||
// upon events like user login, viewer disconnect and user changing region host.
|
// upon events like user login, viewer disconnect and user changing region host.
|
||||||
// These setters are made public to be accessible from newview and should not be
|
// These setters are made public to be accessible from newview and should not be
|
||||||
|
|
@ -449,6 +452,7 @@ private:
|
||||||
static LLHost sRegionHost;
|
static LLHost sRegionHost;
|
||||||
static bool sDisconnected;
|
static bool sDisconnected;
|
||||||
static std::set<LLUrlEntryParcel*> sParcelInfoObservers;
|
static std::set<LLUrlEntryParcel*> sParcelInfoObservers;
|
||||||
|
static std::map<LLUUID, LLVector3d> sParcelPos;
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -670,7 +670,10 @@ LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection()
|
||||||
peerConnection->init(this);
|
peerConnection->init(this);
|
||||||
|
|
||||||
mPeerConnections.emplace_back(peerConnection);
|
mPeerConnections.emplace_back(peerConnection);
|
||||||
peerConnection->enableSenderTracks(!mMute);
|
// Should it really start disabled?
|
||||||
|
// Seems like something doesn't get the memo and senders need to be reset later
|
||||||
|
// to remove the voice indicator from taskbar
|
||||||
|
peerConnection->enableSenderTracks(false);
|
||||||
if (mPeerConnections.empty())
|
if (mPeerConnections.empty())
|
||||||
{
|
{
|
||||||
setRecording(true);
|
setRecording(true);
|
||||||
|
|
@ -704,7 +707,7 @@ void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_conn
|
||||||
LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl() :
|
LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl() :
|
||||||
mWebRTCImpl(nullptr),
|
mWebRTCImpl(nullptr),
|
||||||
mPeerConnection(nullptr),
|
mPeerConnection(nullptr),
|
||||||
mMute(true),
|
mMute(MUTE_INITIAL),
|
||||||
mAnswerReceived(false)
|
mAnswerReceived(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -739,6 +742,19 @@ void LLWebRTCPeerConnectionImpl::terminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// to remove 'Secondlife is recording' icon from taskbar
|
||||||
|
// if user was speaking
|
||||||
|
auto senders = mPeerConnection->GetSenders();
|
||||||
|
for (auto& sender : senders)
|
||||||
|
{
|
||||||
|
auto track = sender->track();
|
||||||
|
if (track)
|
||||||
|
{
|
||||||
|
track->set_enabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mPeerConnection->SetAudioRecording(false);
|
||||||
|
|
||||||
mPeerConnection->Close();
|
mPeerConnection->Close();
|
||||||
if (mLocalStream)
|
if (mLocalStream)
|
||||||
{
|
{
|
||||||
|
|
@ -828,6 +844,7 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti
|
||||||
audioOptions.auto_gain_control = true;
|
audioOptions.auto_gain_control = true;
|
||||||
audioOptions.echo_cancellation = true;
|
audioOptions.echo_cancellation = true;
|
||||||
audioOptions.noise_suppression = true;
|
audioOptions.noise_suppression = true;
|
||||||
|
audioOptions.init_recording_on_send = false;
|
||||||
|
|
||||||
mLocalStream = mPeerConnectionFactory->CreateLocalMediaStream("SLStream");
|
mLocalStream = mPeerConnectionFactory->CreateLocalMediaStream("SLStream");
|
||||||
|
|
||||||
|
|
@ -892,6 +909,7 @@ void LLWebRTCPeerConnectionImpl::enableSenderTracks(bool enable)
|
||||||
{
|
{
|
||||||
sender->track()->set_enabled(enable);
|
sender->track()->set_enabled(enable);
|
||||||
}
|
}
|
||||||
|
mPeerConnection->SetAudioRecording(enable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -932,9 +950,17 @@ void LLWebRTCPeerConnectionImpl::AnswerAvailable(const std::string &sdp)
|
||||||
|
|
||||||
void LLWebRTCPeerConnectionImpl::setMute(bool mute)
|
void LLWebRTCPeerConnectionImpl::setMute(bool mute)
|
||||||
{
|
{
|
||||||
mMute = mute;
|
EMicMuteState new_state = mute ? MUTE_MUTED : MUTE_UNMUTED;
|
||||||
|
if (new_state == mMute)
|
||||||
|
{
|
||||||
|
return; // no change
|
||||||
|
}
|
||||||
|
bool force_reset = mMute == MUTE_INITIAL && mute;
|
||||||
|
bool enable = !mute;
|
||||||
|
mMute = new_state;
|
||||||
|
|
||||||
mWebRTCImpl->PostSignalingTask(
|
mWebRTCImpl->PostSignalingTask(
|
||||||
[this]()
|
[this, force_reset, enable]()
|
||||||
{
|
{
|
||||||
if (mPeerConnection)
|
if (mPeerConnection)
|
||||||
{
|
{
|
||||||
|
|
@ -946,16 +972,34 @@ void LLWebRTCPeerConnectionImpl::setMute(bool mute)
|
||||||
auto track = sender->track();
|
auto track = sender->track();
|
||||||
if (track)
|
if (track)
|
||||||
{
|
{
|
||||||
track->set_enabled(!mMute);
|
if (force_reset)
|
||||||
|
{
|
||||||
|
// Force notify observers
|
||||||
|
// Was it disabled too early?
|
||||||
|
// Without this microphone icon in Win's taskbar will stay
|
||||||
|
track->set_enabled(true);
|
||||||
|
}
|
||||||
|
track->set_enabled(enable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mPeerConnection->SetAudioRecording(enable);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLWebRTCPeerConnectionImpl::resetMute()
|
void LLWebRTCPeerConnectionImpl::resetMute()
|
||||||
{
|
{
|
||||||
setMute(mMute);
|
switch(mMute)
|
||||||
|
{
|
||||||
|
case MUTE_MUTED:
|
||||||
|
setMute(true);
|
||||||
|
break;
|
||||||
|
case MUTE_UNMUTED:
|
||||||
|
setMute(false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLWebRTCPeerConnectionImpl::setReceiveVolume(float volume)
|
void LLWebRTCPeerConnectionImpl::setReceiveVolume(float volume)
|
||||||
|
|
|
||||||
|
|
@ -417,7 +417,12 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface,
|
||||||
|
|
||||||
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> mPeerConnectionFactory;
|
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> mPeerConnectionFactory;
|
||||||
|
|
||||||
bool mMute;
|
typedef enum {
|
||||||
|
MUTE_INITIAL,
|
||||||
|
MUTE_MUTED,
|
||||||
|
MUTE_UNMUTED,
|
||||||
|
} EMicMuteState;
|
||||||
|
EMicMuteState mMute;
|
||||||
|
|
||||||
// signaling
|
// signaling
|
||||||
std::vector<LLWebRTCSignalingObserver *> mSignalingObserverList;
|
std::vector<LLWebRTCSignalingObserver *> mSignalingObserverList;
|
||||||
|
|
|
||||||
|
|
@ -657,7 +657,7 @@ attributedStringInfo getSegments(NSAttributedString *str)
|
||||||
};
|
};
|
||||||
|
|
||||||
int string_length = [aString length];
|
int string_length = [aString length];
|
||||||
unichar text[string_length];
|
unichar *text = new unichar[string_length];
|
||||||
attributedStringInfo segments;
|
attributedStringInfo segments;
|
||||||
// I used 'respondsToSelector:@selector(string)'
|
// I used 'respondsToSelector:@selector(string)'
|
||||||
// to judge aString is an attributed string or not.
|
// to judge aString is an attributed string or not.
|
||||||
|
|
@ -685,6 +685,8 @@ attributedStringInfo getSegments(NSAttributedString *str)
|
||||||
// we must clear the marked text when aString is null.
|
// we must clear the marked text when aString is null.
|
||||||
[self unmarkText];
|
[self unmarkText];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete [] text;
|
||||||
} else {
|
} else {
|
||||||
if (mHasMarkedText)
|
if (mHasMarkedText)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,6 @@ LLWindow::LLWindow(LLWindowCallbacks* callbacks, bool fullscreen, U32 flags)
|
||||||
mFullscreen(fullscreen),
|
mFullscreen(fullscreen),
|
||||||
mFullscreenWidth(0),
|
mFullscreenWidth(0),
|
||||||
mFullscreenHeight(0),
|
mFullscreenHeight(0),
|
||||||
mFullscreenBits(0),
|
|
||||||
mFullscreenRefresh(0),
|
mFullscreenRefresh(0),
|
||||||
mSupportedResolutions(NULL),
|
mSupportedResolutions(NULL),
|
||||||
mNumSupportedResolutions(0),
|
mNumSupportedResolutions(0),
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,6 @@ protected:
|
||||||
bool mFullscreen;
|
bool mFullscreen;
|
||||||
S32 mFullscreenWidth;
|
S32 mFullscreenWidth;
|
||||||
S32 mFullscreenHeight;
|
S32 mFullscreenHeight;
|
||||||
S32 mFullscreenBits;
|
|
||||||
S32 mFullscreenRefresh;
|
S32 mFullscreenRefresh;
|
||||||
LLWindowResolution* mSupportedResolutions;
|
LLWindowResolution* mSupportedResolutions;
|
||||||
S32 mNumSupportedResolutions;
|
S32 mNumSupportedResolutions;
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,11 @@
|
||||||
#pragma comment(lib, "dxguid.lib") // needed for llurlentry test to build on some systems
|
#pragma comment(lib, "dxguid.lib") // needed for llurlentry test to build on some systems
|
||||||
#pragma comment(lib, "dinput8")
|
#pragma comment(lib, "dinput8")
|
||||||
|
|
||||||
|
#pragma comment(lib, "UxTheme.lib")
|
||||||
|
#pragma comment(lib, "Dwmapi.lib")
|
||||||
|
#include <Uxtheme.h>
|
||||||
|
#include <dwmapi.h> // needed for DwmSetWindowAttribute to set window theme
|
||||||
|
|
||||||
const S32 MAX_MESSAGE_PER_UPDATE = 20;
|
const S32 MAX_MESSAGE_PER_UPDATE = 20;
|
||||||
const S32 BITS_PER_PIXEL = 32;
|
const S32 BITS_PER_PIXEL = 32;
|
||||||
const S32 MAX_NUM_RESOLUTIONS = 32;
|
const S32 MAX_NUM_RESOLUTIONS = 32;
|
||||||
|
|
@ -85,6 +90,10 @@ const F32 ICON_FLASH_TIME = 0.5f;
|
||||||
#define USER_DEFAULT_SCREEN_DPI 96 // Win7
|
#define USER_DEFAULT_SCREEN_DPI 96 // Win7
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef WM_DWMCOLORIZATIONCOLORCHANGED
|
||||||
|
#define WM_DWMCOLORIZATIONCOLORCHANGED 0x0320
|
||||||
|
#endif
|
||||||
|
|
||||||
// Claim a couple unused GetMessage() message IDs
|
// Claim a couple unused GetMessage() message IDs
|
||||||
const UINT WM_DUMMY_(WM_USER + 0x0017);
|
const UINT WM_DUMMY_(WM_USER + 0x0017);
|
||||||
const UINT WM_POST_FUNCTION_(WM_USER + 0x0018);
|
const UINT WM_POST_FUNCTION_(WM_USER + 0x0018);
|
||||||
|
|
@ -104,6 +113,7 @@ static std::thread::id sMainThreadId;
|
||||||
|
|
||||||
|
|
||||||
LPWSTR gIconResource = IDI_APPLICATION;
|
LPWSTR gIconResource = IDI_APPLICATION;
|
||||||
|
LPWSTR gIconSmallResource = IDI_APPLICATION;
|
||||||
LPDIRECTINPUT8 gDirectInput8;
|
LPDIRECTINPUT8 gDirectInput8;
|
||||||
|
|
||||||
LLW32MsgCallback gAsyncMsgCallback = NULL;
|
LLW32MsgCallback gAsyncMsgCallback = NULL;
|
||||||
|
|
@ -137,6 +147,17 @@ typedef HRESULT(STDAPICALLTYPE *GetDpiForMonitorType)(
|
||||||
_Out_ UINT *dpiX,
|
_Out_ UINT *dpiX,
|
||||||
_Out_ UINT *dpiY);
|
_Out_ UINT *dpiY);
|
||||||
|
|
||||||
|
typedef enum PREFERRED_APP_MODE
|
||||||
|
{
|
||||||
|
DEFAULT,
|
||||||
|
ALLOW_DARK,
|
||||||
|
FORCE_DARK,
|
||||||
|
FORCE_LIGHT,
|
||||||
|
MAX
|
||||||
|
} PREFERRED_APP_MODE;
|
||||||
|
|
||||||
|
typedef PREFERRED_APP_MODE(WINAPI* fnSetPreferredAppMode)(PREFERRED_APP_MODE mode);
|
||||||
|
|
||||||
//
|
//
|
||||||
// LLWindowWin32
|
// LLWindowWin32
|
||||||
//
|
//
|
||||||
|
|
@ -512,6 +533,7 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
|
||||||
|
|
||||||
mFSAASamples = fsaa_samples;
|
mFSAASamples = fsaa_samples;
|
||||||
mIconResource = gIconResource;
|
mIconResource = gIconResource;
|
||||||
|
mIconSmallResource = gIconSmallResource;
|
||||||
mOverrideAspectRatio = 0.f;
|
mOverrideAspectRatio = 0.f;
|
||||||
mNativeAspectRatio = 0.f;
|
mNativeAspectRatio = 0.f;
|
||||||
mInputProcessingPaused = false;
|
mInputProcessingPaused = false;
|
||||||
|
|
@ -700,8 +722,7 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev_mode.dmPelsWidth == width &&
|
if (dev_mode.dmPelsWidth == width &&
|
||||||
dev_mode.dmPelsHeight == height &&
|
dev_mode.dmPelsHeight == height)
|
||||||
dev_mode.dmBitsPerPel == BITS_PER_PIXEL)
|
|
||||||
{
|
{
|
||||||
success = true;
|
success = true;
|
||||||
if ((dev_mode.dmDisplayFrequency - current_refresh)
|
if ((dev_mode.dmDisplayFrequency - current_refresh)
|
||||||
|
|
@ -741,7 +762,7 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
|
||||||
// If we found a good resolution, use it.
|
// If we found a good resolution, use it.
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
success = setDisplayResolution(width, height, BITS_PER_PIXEL, closest_refresh);
|
success = setDisplayResolution(width, height, closest_refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep a copy of the actual current device mode in case we minimize
|
// Keep a copy of the actual current device mode in case we minimize
|
||||||
|
|
@ -754,7 +775,6 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
|
||||||
mFullscreen = true;
|
mFullscreen = true;
|
||||||
mFullscreenWidth = dev_mode.dmPelsWidth;
|
mFullscreenWidth = dev_mode.dmPelsWidth;
|
||||||
mFullscreenHeight = dev_mode.dmPelsHeight;
|
mFullscreenHeight = dev_mode.dmPelsHeight;
|
||||||
mFullscreenBits = dev_mode.dmBitsPerPel;
|
|
||||||
mFullscreenRefresh = dev_mode.dmDisplayFrequency;
|
mFullscreenRefresh = dev_mode.dmDisplayFrequency;
|
||||||
|
|
||||||
LL_INFOS("Window") << "Running at " << dev_mode.dmPelsWidth
|
LL_INFOS("Window") << "Running at " << dev_mode.dmPelsWidth
|
||||||
|
|
@ -768,7 +788,6 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
|
||||||
mFullscreen = false;
|
mFullscreen = false;
|
||||||
mFullscreenWidth = -1;
|
mFullscreenWidth = -1;
|
||||||
mFullscreenHeight = -1;
|
mFullscreenHeight = -1;
|
||||||
mFullscreenBits = -1;
|
|
||||||
mFullscreenRefresh = -1;
|
mFullscreenRefresh = -1;
|
||||||
|
|
||||||
std::map<std::string,std::string> args;
|
std::map<std::string,std::string> args;
|
||||||
|
|
@ -847,6 +866,8 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
|
||||||
// Initialize (boot strap) the Language text input management,
|
// Initialize (boot strap) the Language text input management,
|
||||||
// based on the system's (or user's) default settings.
|
// based on the system's (or user's) default settings.
|
||||||
allowLanguageTextInput(NULL, false);
|
allowLanguageTextInput(NULL, false);
|
||||||
|
updateWindowTheme();
|
||||||
|
setCustomIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1196,7 +1217,7 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo
|
||||||
// If we found a good resolution, use it.
|
// If we found a good resolution, use it.
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
success = setDisplayResolution(width, height, BITS_PER_PIXEL, closest_refresh);
|
success = setDisplayResolution(width, height, closest_refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep a copy of the actual current device mode in case we minimize
|
// Keep a copy of the actual current device mode in case we minimize
|
||||||
|
|
@ -1208,7 +1229,6 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo
|
||||||
mFullscreen = true;
|
mFullscreen = true;
|
||||||
mFullscreenWidth = dev_mode.dmPelsWidth;
|
mFullscreenWidth = dev_mode.dmPelsWidth;
|
||||||
mFullscreenHeight = dev_mode.dmPelsHeight;
|
mFullscreenHeight = dev_mode.dmPelsHeight;
|
||||||
mFullscreenBits = dev_mode.dmBitsPerPel;
|
|
||||||
mFullscreenRefresh = dev_mode.dmDisplayFrequency;
|
mFullscreenRefresh = dev_mode.dmDisplayFrequency;
|
||||||
|
|
||||||
LL_INFOS("Window") << "Running at " << dev_mode.dmPelsWidth
|
LL_INFOS("Window") << "Running at " << dev_mode.dmPelsWidth
|
||||||
|
|
@ -1234,7 +1254,6 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo
|
||||||
mFullscreen = false;
|
mFullscreen = false;
|
||||||
mFullscreenWidth = -1;
|
mFullscreenWidth = -1;
|
||||||
mFullscreenHeight = -1;
|
mFullscreenHeight = -1;
|
||||||
mFullscreenBits = -1;
|
|
||||||
mFullscreenRefresh = -1;
|
mFullscreenRefresh = -1;
|
||||||
|
|
||||||
LL_INFOS("Window") << "Unable to run fullscreen at " << width << "x" << height << LL_ENDL;
|
LL_INFOS("Window") << "Unable to run fullscreen at " << width << "x" << height << LL_ENDL;
|
||||||
|
|
@ -3023,6 +3042,17 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
|
||||||
WINDOW_IMP_POST(window_imp->mMouseVanish = true);
|
WINDOW_IMP_POST(window_imp->mMouseVanish = true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Check if theme-related settings changed
|
||||||
|
else if (l_param && (wcscmp((LPCWSTR)l_param, L"ImmersiveColorSet") == 0))
|
||||||
|
{
|
||||||
|
WINDOW_IMP_POST(window_imp->updateWindowTheme());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_DWMCOLORIZATIONCOLORCHANGED:
|
||||||
|
{
|
||||||
|
WINDOW_IMP_POST(window_imp->updateWindowTheme());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -3532,7 +3562,7 @@ F32 LLWindowWin32::getPixelAspectRatio()
|
||||||
|
|
||||||
// Change display resolution. Returns true if successful.
|
// Change display resolution. Returns true if successful.
|
||||||
// protected
|
// protected
|
||||||
bool LLWindowWin32::setDisplayResolution(S32 width, S32 height, S32 bits, S32 refresh)
|
bool LLWindowWin32::setDisplayResolution(S32 width, S32 height, S32 refresh)
|
||||||
{
|
{
|
||||||
DEVMODE dev_mode;
|
DEVMODE dev_mode;
|
||||||
::ZeroMemory(&dev_mode, sizeof(DEVMODE));
|
::ZeroMemory(&dev_mode, sizeof(DEVMODE));
|
||||||
|
|
@ -3544,7 +3574,6 @@ bool LLWindowWin32::setDisplayResolution(S32 width, S32 height, S32 bits, S32 re
|
||||||
{
|
{
|
||||||
if (dev_mode.dmPelsWidth == width &&
|
if (dev_mode.dmPelsWidth == width &&
|
||||||
dev_mode.dmPelsHeight == height &&
|
dev_mode.dmPelsHeight == height &&
|
||||||
dev_mode.dmBitsPerPel == bits &&
|
|
||||||
dev_mode.dmDisplayFrequency == refresh )
|
dev_mode.dmDisplayFrequency == refresh )
|
||||||
{
|
{
|
||||||
// ...display mode identical, do nothing
|
// ...display mode identical, do nothing
|
||||||
|
|
@ -3556,9 +3585,8 @@ bool LLWindowWin32::setDisplayResolution(S32 width, S32 height, S32 bits, S32 re
|
||||||
dev_mode.dmSize = sizeof(dev_mode);
|
dev_mode.dmSize = sizeof(dev_mode);
|
||||||
dev_mode.dmPelsWidth = width;
|
dev_mode.dmPelsWidth = width;
|
||||||
dev_mode.dmPelsHeight = height;
|
dev_mode.dmPelsHeight = height;
|
||||||
dev_mode.dmBitsPerPel = bits;
|
|
||||||
dev_mode.dmDisplayFrequency = refresh;
|
dev_mode.dmDisplayFrequency = refresh;
|
||||||
dev_mode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
|
dev_mode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
|
||||||
|
|
||||||
// CDS_FULLSCREEN indicates that this is a temporary change to the device mode.
|
// CDS_FULLSCREEN indicates that this is a temporary change to the device mode.
|
||||||
LONG cds_result = ChangeDisplaySettings(&dev_mode, CDS_FULLSCREEN);
|
LONG cds_result = ChangeDisplaySettings(&dev_mode, CDS_FULLSCREEN);
|
||||||
|
|
@ -3568,7 +3596,7 @@ bool LLWindowWin32::setDisplayResolution(S32 width, S32 height, S32 bits, S32 re
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
LL_WARNS("Window") << "setDisplayResolution failed, "
|
LL_WARNS("Window") << "setDisplayResolution failed, "
|
||||||
<< width << "x" << height << "x" << bits << " @ " << refresh << LL_ENDL;
|
<< width << "x" << height << " @ " << refresh << LL_ENDL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
|
|
@ -3579,7 +3607,7 @@ bool LLWindowWin32::setFullscreenResolution()
|
||||||
{
|
{
|
||||||
if (mFullscreen)
|
if (mFullscreen)
|
||||||
{
|
{
|
||||||
return setDisplayResolution( mFullscreenWidth, mFullscreenHeight, mFullscreenBits, mFullscreenRefresh);
|
return setDisplayResolution( mFullscreenWidth, mFullscreenHeight, mFullscreenRefresh);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -4967,3 +4995,69 @@ void LLWindowWin32::updateWindowRect()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LLWindowWin32::isSystemAppDarkMode()
|
||||||
|
{
|
||||||
|
HKEY hKey;
|
||||||
|
DWORD dwValue = 1; // Default to light theme
|
||||||
|
DWORD dwSize = sizeof(DWORD);
|
||||||
|
|
||||||
|
// Check registry for system theme preference
|
||||||
|
LSTATUS ret_code =
|
||||||
|
RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_READ, &hKey);
|
||||||
|
if (ERROR_SUCCESS == ret_code)
|
||||||
|
{
|
||||||
|
if (RegQueryValueExW(hKey, L"AppsUseLightTheme", NULL, NULL, (LPBYTE)&dwValue, &dwSize) != ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
// If AppsUseLightTheme is not found, check SystemUsesLightTheme
|
||||||
|
dwSize = sizeof(DWORD);
|
||||||
|
RegQueryValueExW(hKey, L"SystemUsesLightTheme", NULL, NULL, (LPBYTE)&dwValue, &dwSize);
|
||||||
|
}
|
||||||
|
RegCloseKey(hKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if dark mode
|
||||||
|
return dwValue == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LLWindowWin32::updateWindowTheme()
|
||||||
|
{
|
||||||
|
bool use_dark_mode = isSystemAppDarkMode();
|
||||||
|
if (use_dark_mode == mCurrentDarkMode)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mCurrentDarkMode = use_dark_mode;
|
||||||
|
|
||||||
|
HMODULE hUxTheme = LoadLibraryExW(L"uxtheme.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||||
|
if (hUxTheme)
|
||||||
|
{
|
||||||
|
auto SetPreferredAppMode = (fnSetPreferredAppMode)GetProcAddress(hUxTheme, "SetPreferredAppMode");
|
||||||
|
if (SetPreferredAppMode)
|
||||||
|
{
|
||||||
|
SetPreferredAppMode(use_dark_mode ? ALLOW_DARK : FORCE_LIGHT);
|
||||||
|
}
|
||||||
|
FreeLibrary(hUxTheme);
|
||||||
|
}
|
||||||
|
BOOL dark_mode(use_dark_mode);
|
||||||
|
DwmSetWindowAttribute(mWindowHandle, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark_mode, sizeof(dark_mode));
|
||||||
|
|
||||||
|
LL_INFOS("Window") << "Viewer window theme is set to " << (use_dark_mode ? "dark" : "light") << " mode" << LL_ENDL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LLWindowWin32::setCustomIcon()
|
||||||
|
{
|
||||||
|
if (mWindowHandle)
|
||||||
|
{
|
||||||
|
HICON hDefaultIcon = LoadIcon(mhInstance, mIconResource);
|
||||||
|
HICON hSmallIcon = LoadIcon(mhInstance, mIconSmallResource);
|
||||||
|
mWindowThread->post([=]()
|
||||||
|
{
|
||||||
|
SendMessage(mWindowHandle, WM_SETICON, ICON_BIG, (LPARAM)hDefaultIcon);
|
||||||
|
SendMessage(mWindowHandle, WM_SETICON, ICON_SMALL, (LPARAM)hSmallIcon);
|
||||||
|
|
||||||
|
SetClassLongPtr(mWindowHandle, GCLP_HICON, (LONG_PTR)hDefaultIcon);
|
||||||
|
SetClassLongPtr(mWindowHandle, GCLP_HICONSM, (LONG_PTR)hSmallIcon);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ protected:
|
||||||
virtual LLSD getNativeKeyData();
|
virtual LLSD getNativeKeyData();
|
||||||
|
|
||||||
// Changes display resolution. Returns true if successful
|
// Changes display resolution. Returns true if successful
|
||||||
bool setDisplayResolution(S32 width, S32 height, S32 bits, S32 refresh);
|
bool setDisplayResolution(S32 width, S32 height, S32 refresh);
|
||||||
|
|
||||||
// Go back to last fullscreen display resolution.
|
// Go back to last fullscreen display resolution.
|
||||||
bool setFullscreenResolution();
|
bool setFullscreenResolution();
|
||||||
|
|
@ -214,6 +214,7 @@ protected:
|
||||||
bool mCustomGammaSet;
|
bool mCustomGammaSet;
|
||||||
|
|
||||||
LPWSTR mIconResource;
|
LPWSTR mIconResource;
|
||||||
|
LPWSTR mIconSmallResource;
|
||||||
bool mInputProcessingPaused;
|
bool mInputProcessingPaused;
|
||||||
|
|
||||||
// The following variables are for Language Text Input control.
|
// The following variables are for Language Text Input control.
|
||||||
|
|
@ -246,6 +247,11 @@ protected:
|
||||||
RECT mRect;
|
RECT mRect;
|
||||||
RECT mClientRect;
|
RECT mClientRect;
|
||||||
|
|
||||||
|
void updateWindowTheme();
|
||||||
|
bool isSystemAppDarkMode();
|
||||||
|
void setCustomIcon();
|
||||||
|
bool mCurrentDarkMode { false };
|
||||||
|
|
||||||
struct LLWindowWin32Thread;
|
struct LLWindowWin32Thread;
|
||||||
LLWindowWin32Thread* mWindowThread = nullptr;
|
LLWindowWin32Thread* mWindowThread = nullptr;
|
||||||
LLThreadSafeQueue<std::function<void()>> mFunctionQueue;
|
LLThreadSafeQueue<std::function<void()>> mFunctionQueue;
|
||||||
|
|
@ -281,6 +287,7 @@ private:
|
||||||
|
|
||||||
extern LLW32MsgCallback gAsyncMsgCallback;
|
extern LLW32MsgCallback gAsyncMsgCallback;
|
||||||
extern LPWSTR gIconResource;
|
extern LPWSTR gIconResource;
|
||||||
|
extern LPWSTR gIconSmallResource;
|
||||||
|
|
||||||
S32 OSMessageBoxWin32(const std::string& text, const std::string& caption, U32 type);
|
S32 OSMessageBoxWin32(const std::string& text, const std::string& caption, U32 type);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ include(CMakeCopyIfDifferent)
|
||||||
include(CubemapToEquirectangularJS)
|
include(CubemapToEquirectangularJS)
|
||||||
include(DBusGlib)
|
include(DBusGlib)
|
||||||
include(DragDrop)
|
include(DragDrop)
|
||||||
|
if (USE_DISCORD)
|
||||||
|
include(Discord)
|
||||||
|
endif ()
|
||||||
include(EXPAT)
|
include(EXPAT)
|
||||||
include(Hunspell)
|
include(Hunspell)
|
||||||
include(JPEGEncoderBasic)
|
include(JPEGEncoderBasic)
|
||||||
|
|
@ -76,6 +79,7 @@ set(viewer_SOURCE_FILES
|
||||||
gltf/accessor.cpp
|
gltf/accessor.cpp
|
||||||
gltf/primitive.cpp
|
gltf/primitive.cpp
|
||||||
gltf/animation.cpp
|
gltf/animation.cpp
|
||||||
|
gltf/llgltfloader.cpp
|
||||||
groupchatlistener.cpp
|
groupchatlistener.cpp
|
||||||
llaccountingcostmanager.cpp
|
llaccountingcostmanager.cpp
|
||||||
llaisapi.cpp
|
llaisapi.cpp
|
||||||
|
|
@ -178,11 +182,11 @@ set(viewer_SOURCE_FILES
|
||||||
llflexibleobject.cpp
|
llflexibleobject.cpp
|
||||||
llfloater360capture.cpp
|
llfloater360capture.cpp
|
||||||
llfloaterabout.cpp
|
llfloaterabout.cpp
|
||||||
|
llfloateravatarwelcomepack.cpp
|
||||||
llfloaterbvhpreview.cpp
|
llfloaterbvhpreview.cpp
|
||||||
llfloateraddpaymentmethod.cpp
|
llfloateraddpaymentmethod.cpp
|
||||||
llfloaterauction.cpp
|
llfloaterauction.cpp
|
||||||
llfloaterautoreplacesettings.cpp
|
llfloaterautoreplacesettings.cpp
|
||||||
llfloateravatar.cpp
|
|
||||||
llfloateravatarpicker.cpp
|
llfloateravatarpicker.cpp
|
||||||
llfloateravatarrendersettings.cpp
|
llfloateravatarrendersettings.cpp
|
||||||
llfloateravatartextures.cpp
|
llfloateravatartextures.cpp
|
||||||
|
|
@ -748,6 +752,7 @@ set(viewer_HEADER_FILES
|
||||||
gltf/buffer_util.h
|
gltf/buffer_util.h
|
||||||
gltf/primitive.h
|
gltf/primitive.h
|
||||||
gltf/animation.h
|
gltf/animation.h
|
||||||
|
gltf/llgltfloader.h
|
||||||
llaccountingcost.h
|
llaccountingcost.h
|
||||||
llaccountingcostmanager.h
|
llaccountingcostmanager.h
|
||||||
llaisapi.h
|
llaisapi.h
|
||||||
|
|
@ -851,11 +856,11 @@ set(viewer_HEADER_FILES
|
||||||
llflexibleobject.h
|
llflexibleobject.h
|
||||||
llfloater360capture.h
|
llfloater360capture.h
|
||||||
llfloaterabout.h
|
llfloaterabout.h
|
||||||
|
llfloateravatarwelcomepack.h
|
||||||
llfloaterbvhpreview.h
|
llfloaterbvhpreview.h
|
||||||
llfloateraddpaymentmethod.h
|
llfloateraddpaymentmethod.h
|
||||||
llfloaterauction.h
|
llfloaterauction.h
|
||||||
llfloaterautoreplacesettings.h
|
llfloaterautoreplacesettings.h
|
||||||
llfloateravatar.h
|
|
||||||
llfloateravatarpicker.h
|
llfloateravatarpicker.h
|
||||||
llfloateravatarrendersettings.h
|
llfloateravatarrendersettings.h
|
||||||
llfloateravatartextures.h
|
llfloateravatartextures.h
|
||||||
|
|
@ -1570,6 +1575,7 @@ if (WINDOWS)
|
||||||
res-sdl/ll_icon.BMP
|
res-sdl/ll_icon.BMP
|
||||||
res/ll_icon.BMP
|
res/ll_icon.BMP
|
||||||
res/ll_icon.ico
|
res/ll_icon.ico
|
||||||
|
res/ll_icon_small.ico
|
||||||
res/resource.h
|
res/resource.h
|
||||||
res/toolpickobject.cur
|
res/toolpickobject.cur
|
||||||
res/toolpickobject2.cur
|
res/toolpickobject2.cur
|
||||||
|
|
@ -1785,6 +1791,12 @@ if (WINDOWS)
|
||||||
)
|
)
|
||||||
endif (ADDRESS_SIZE EQUAL 64)
|
endif (ADDRESS_SIZE EQUAL 64)
|
||||||
|
|
||||||
|
if (TARGET ll::discord_sdk)
|
||||||
|
list(APPEND COPY_INPUT_DEPENDENCIES
|
||||||
|
${SHARED_LIB_STAGING_DIR}/discord_partner_sdk.dll
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (TARGET ll::openal)
|
if (TARGET ll::openal)
|
||||||
list(APPEND COPY_INPUT_DEPENDENCIES
|
list(APPEND COPY_INPUT_DEPENDENCIES
|
||||||
${SHARED_LIB_STAGING_DIR}/OpenAL32.dll
|
${SHARED_LIB_STAGING_DIR}/OpenAL32.dll
|
||||||
|
|
@ -1801,6 +1813,7 @@ if (WINDOWS)
|
||||||
--arch=${ARCH}
|
--arch=${ARCH}
|
||||||
--artwork=${ARTWORK_DIR}
|
--artwork=${ARTWORK_DIR}
|
||||||
"--bugsplat=${BUGSPLAT_DB}"
|
"--bugsplat=${BUGSPLAT_DB}"
|
||||||
|
"--discord=${USE_DISCORD}"
|
||||||
"--openal=${USE_OPENAL}"
|
"--openal=${USE_OPENAL}"
|
||||||
"--tracy=${USE_TRACY}"
|
"--tracy=${USE_TRACY}"
|
||||||
--build=${CMAKE_CURRENT_BINARY_DIR}
|
--build=${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
|
@ -1839,6 +1852,7 @@ if (WINDOWS)
|
||||||
--arch=${ARCH}
|
--arch=${ARCH}
|
||||||
--artwork=${ARTWORK_DIR}
|
--artwork=${ARTWORK_DIR}
|
||||||
"--bugsplat=${BUGSPLAT_DB}"
|
"--bugsplat=${BUGSPLAT_DB}"
|
||||||
|
"--discord=${USE_DISCORD}"
|
||||||
"--openal=${USE_OPENAL}"
|
"--openal=${USE_OPENAL}"
|
||||||
"--tracy=${USE_TRACY}"
|
"--tracy=${USE_TRACY}"
|
||||||
--build=${CMAKE_CURRENT_BINARY_DIR}
|
--build=${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
|
@ -1903,6 +1917,7 @@ if (WINDOWS)
|
||||||
--arch=${ARCH}
|
--arch=${ARCH}
|
||||||
--artwork=${ARTWORK_DIR}
|
--artwork=${ARTWORK_DIR}
|
||||||
"--bugsplat=${BUGSPLAT_DB}"
|
"--bugsplat=${BUGSPLAT_DB}"
|
||||||
|
"--discord=${USE_DISCORD}"
|
||||||
"--openal=${USE_OPENAL}"
|
"--openal=${USE_OPENAL}"
|
||||||
"--tracy=${USE_TRACY}"
|
"--tracy=${USE_TRACY}"
|
||||||
--build=${CMAKE_CURRENT_BINARY_DIR}
|
--build=${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
|
@ -1998,6 +2013,10 @@ target_link_libraries(${VIEWER_BINARY_NAME}
|
||||||
ll::openxr
|
ll::openxr
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (USE_DISCORD)
|
||||||
|
target_link_libraries(${VIEWER_BINARY_NAME} ll::discord_sdk )
|
||||||
|
endif ()
|
||||||
|
|
||||||
if( TARGET ll::intel_memops )
|
if( TARGET ll::intel_memops )
|
||||||
target_link_libraries(${VIEWER_BINARY_NAME} ll::intel_memops )
|
target_link_libraries(${VIEWER_BINARY_NAME} ll::intel_memops )
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -2054,6 +2073,7 @@ if (LINUX)
|
||||||
--arch=${ARCH}
|
--arch=${ARCH}
|
||||||
--artwork=${ARTWORK_DIR}
|
--artwork=${ARTWORK_DIR}
|
||||||
"--bugsplat=${BUGSPLAT_DB}"
|
"--bugsplat=${BUGSPLAT_DB}"
|
||||||
|
"--discord=${USE_DISCORD}"
|
||||||
"--openal=${USE_OPENAL}"
|
"--openal=${USE_OPENAL}"
|
||||||
"--tracy=${USE_TRACY}"
|
"--tracy=${USE_TRACY}"
|
||||||
--build=${CMAKE_CURRENT_BINARY_DIR}
|
--build=${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
|
@ -2082,6 +2102,7 @@ if (LINUX)
|
||||||
--arch=${ARCH}
|
--arch=${ARCH}
|
||||||
--artwork=${ARTWORK_DIR}
|
--artwork=${ARTWORK_DIR}
|
||||||
"--bugsplat=${BUGSPLAT_DB}"
|
"--bugsplat=${BUGSPLAT_DB}"
|
||||||
|
"--discord=${USE_DISCORD}"
|
||||||
"--openal=${USE_OPENAL}"
|
"--openal=${USE_OPENAL}"
|
||||||
"--tracy=${USE_TRACY}"
|
"--tracy=${USE_TRACY}"
|
||||||
--build=${CMAKE_CURRENT_BINARY_DIR}
|
--build=${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
|
@ -2158,6 +2179,7 @@ if (DARWIN)
|
||||||
--arch=${ARCH}
|
--arch=${ARCH}
|
||||||
--artwork=${ARTWORK_DIR}
|
--artwork=${ARTWORK_DIR}
|
||||||
"--bugsplat=${BUGSPLAT_DB}"
|
"--bugsplat=${BUGSPLAT_DB}"
|
||||||
|
"--discord=${USE_DISCORD}"
|
||||||
"--openal=${USE_OPENAL}"
|
"--openal=${USE_OPENAL}"
|
||||||
"--tracy=${USE_TRACY}"
|
"--tracy=${USE_TRACY}"
|
||||||
--build=${CMAKE_CURRENT_BINARY_DIR}
|
--build=${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
|
@ -2193,6 +2215,7 @@ if (DARWIN)
|
||||||
--arch=${ARCH}
|
--arch=${ARCH}
|
||||||
--artwork=${ARTWORK_DIR}
|
--artwork=${ARTWORK_DIR}
|
||||||
"--bugsplat=${BUGSPLAT_DB}"
|
"--bugsplat=${BUGSPLAT_DB}"
|
||||||
|
"--discord=${USE_DISCORD}"
|
||||||
"--openal=${USE_OPENAL}"
|
"--openal=${USE_OPENAL}"
|
||||||
"--tracy=${USE_TRACY}"
|
"--tracy=${USE_TRACY}"
|
||||||
--build=${CMAKE_CURRENT_BINARY_DIR}
|
--build=${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
7.1.15
|
7.2.0
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,9 @@
|
||||||
label_ref="Command_Avatar_Label"
|
label_ref="Command_Avatar_Label"
|
||||||
tooltip_ref="Command_Avatar_Tooltip"
|
tooltip_ref="Command_Avatar_Tooltip"
|
||||||
execute_function="Floater.ToggleOrBringToFront"
|
execute_function="Floater.ToggleOrBringToFront"
|
||||||
execute_parameters="avatar"
|
execute_parameters="avatar_welcome_pack"
|
||||||
is_running_function="Floater.IsOpen"
|
is_running_function="Floater.IsOpen"
|
||||||
is_running_parameters="avatar"
|
is_running_parameters="avatar_welcome_pack"
|
||||||
/>
|
/>
|
||||||
<command name="build"
|
<command name="build"
|
||||||
available_in_toybox="true"
|
available_in_toybox="true"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:noNamespaceSchemaLocation="llsd.xsd">
|
xsi:noNamespaceSchemaLocation="llsd.xsd">
|
||||||
<map>
|
<map>
|
||||||
<key>ImporterDebug</key>
|
<key>ImporterDebugVerboseLogging</key>
|
||||||
<map>
|
<map>
|
||||||
<key>Comment</key>
|
<key>Comment</key>
|
||||||
<string>Enable debug output to more precisely identify sources of import errors. Warning: the output can slow down import on many machines.</string>
|
<string>Enable debug output to more precisely identify sources of import errors. Warning: the output can slow down import on many machines.</string>
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
<key>ImporterModelLimit</key>
|
<key>ImporterModelLimit</key>
|
||||||
<map>
|
<map>
|
||||||
<key>Comment</key>
|
<key>Comment</key>
|
||||||
<string>Limits amount of importer generated models for dae files</string>
|
<string>Limits amount of importer generated (when over 8 faces) models for dae and gltf files</string>
|
||||||
<key>Persist</key>
|
<key>Persist</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<key>Type</key>
|
<key>Type</key>
|
||||||
|
|
@ -35,6 +35,17 @@
|
||||||
<key>Value</key>
|
<key>Value</key>
|
||||||
<integer>768</integer>
|
<integer>768</integer>
|
||||||
</map>
|
</map>
|
||||||
|
<key>ImporterDebugMode</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>At 0 does nothing, at 1 dumps skinning data near orifinal file, at 2 dumps skining data and positions/weights of first 5 models, at 3 dumps skinning data and models as llsd</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>U32</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</map>
|
||||||
<key>ImporterPreprocessDAE</key>
|
<key>ImporterPreprocessDAE</key>
|
||||||
<map>
|
<map>
|
||||||
<key>Comment</key>
|
<key>Comment</key>
|
||||||
|
|
@ -621,16 +632,16 @@
|
||||||
<key>Value</key>
|
<key>Value</key>
|
||||||
<real>16.0</real>
|
<real>16.0</real>
|
||||||
</map>
|
</map>
|
||||||
<key>AvatarPickerURL</key>
|
<key>AvatarWelcomePack</key>
|
||||||
<map>
|
<map>
|
||||||
<key>Comment</key>
|
<key>Comment</key>
|
||||||
<string>Avatar picker contents</string>
|
<string>Avatar Welcome Pack contents</string>
|
||||||
<key>Persist</key>
|
<key>Persist</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<key>Type</key>
|
<key>Type</key>
|
||||||
<string>String</string>
|
<string>String</string>
|
||||||
<key>Value</key>
|
<key>Value</key>
|
||||||
<string>http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/avatars.html</string>
|
<string>http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/vawp/index.html</string>
|
||||||
</map>
|
</map>
|
||||||
<!--AvatarBakedTextureUploadTimeout is in use by QA-->
|
<!--AvatarBakedTextureUploadTimeout is in use by QA-->
|
||||||
<key>AvatarBakedTextureUploadTimeout</key>
|
<key>AvatarBakedTextureUploadTimeout</key>
|
||||||
|
|
@ -1139,6 +1150,39 @@
|
||||||
<key>Value</key>
|
<key>Value</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
</map>
|
</map>
|
||||||
|
<key>EnableDiscord</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>When set, connect to Discord to enable Rich Presence</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>Boolean</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</map>
|
||||||
|
<key>ShowDiscordActivityDetails</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>When set, show avatar name on Discord Rich Presence</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>Boolean</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</map>
|
||||||
|
<key>ShowDiscordActivityState</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>When set, show location on Discord Rich Presence</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>Boolean</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</map>
|
||||||
<key>EnableDiskCacheDebugInfo</key>
|
<key>EnableDiskCacheDebugInfo</key>
|
||||||
<map>
|
<map>
|
||||||
<key>Comment</key>
|
<key>Comment</key>
|
||||||
|
|
@ -1153,13 +1197,13 @@
|
||||||
<key>DiskCachePercentOfTotal</key>
|
<key>DiskCachePercentOfTotal</key>
|
||||||
<map>
|
<map>
|
||||||
<key>Comment</key>
|
<key>Comment</key>
|
||||||
<string>The percent of total cache size (defined by CacheSize) to use for the disk cache</string>
|
<string>The percent of total cache size (defined by CacheSize) to use for the disk cache (ex: asset storage, excludes textures)</string>
|
||||||
<key>Persist</key>
|
<key>Persist</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<key>Type</key>
|
<key>Type</key>
|
||||||
<string>F32</string>
|
<string>F32</string>
|
||||||
<key>Value</key>
|
<key>Value</key>
|
||||||
<real>40.0</real>
|
<real>35.0</real>
|
||||||
</map>
|
</map>
|
||||||
<key>DiskCacheDirName</key>
|
<key>DiskCacheDirName</key>
|
||||||
<map>
|
<map>
|
||||||
|
|
@ -1203,7 +1247,7 @@
|
||||||
<key>Type</key>
|
<key>Type</key>
|
||||||
<string>U32</string>
|
<string>U32</string>
|
||||||
<key>Value</key>
|
<key>Value</key>
|
||||||
<integer>4096</integer>
|
<integer>6144</integer>
|
||||||
</map>
|
</map>
|
||||||
<key>CacheValidateCounter</key>
|
<key>CacheValidateCounter</key>
|
||||||
<map>
|
<map>
|
||||||
|
|
@ -9606,6 +9650,17 @@
|
||||||
<key>Value</key>
|
<key>Value</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
</map>
|
</map>
|
||||||
|
<key>ObscureBalanceInStatusBar</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>If true, balance will be shows as '*'</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>Boolean</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</map>
|
||||||
<key>RenderUIBuffer</key>
|
<key>RenderUIBuffer</key>
|
||||||
<map>
|
<map>
|
||||||
<key>Comment</key>
|
<key>Comment</key>
|
||||||
|
|
@ -11499,6 +11554,28 @@
|
||||||
<key>Value</key>
|
<key>Value</key>
|
||||||
<string>fss.txt</string>
|
<string>fss.txt</string>
|
||||||
</map>
|
</map>
|
||||||
|
<key>StatsFrametimeSampleSeconds</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>The number of seconds to sample extended frametime data (percentiles, stddev).</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>S32</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<integer>5</integer>
|
||||||
|
</map>
|
||||||
|
<key>StatsFrametimeEventThreshold</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>The percentage that the frametime difference must exceed in order to register a frametime event. 0.1 = 10%, 0.25 = 25%, etc.</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>F32</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<real>0.1</real>
|
||||||
|
</map>
|
||||||
<key>SystemLanguage</key>
|
<key>SystemLanguage</key>
|
||||||
<map>
|
<map>
|
||||||
<key>Comment</key>
|
<key>Comment</key>
|
||||||
|
|
@ -13731,7 +13808,7 @@
|
||||||
<key>FullScreen</key>
|
<key>FullScreen</key>
|
||||||
<map>
|
<map>
|
||||||
<key>Comment</key>
|
<key>Comment</key>
|
||||||
<string>run a fullscreen session</string>
|
<string>Run a fullscreen session. MacOS not supported</string>
|
||||||
<key>Persist</key>
|
<key>Persist</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<key>Type</key>
|
<key>Type</key>
|
||||||
|
|
@ -16267,5 +16344,71 @@
|
||||||
<key>Value</key>
|
<key>Value</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
</map>
|
</map>
|
||||||
|
<key>MediaAutoPlayHuds</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>Automatically play HUD media</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>Boolean</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</map>
|
||||||
|
<key>MediaFirstClickInteract</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>This setting controls which media (once loaded) does not require a first click to focus before interaction can begin. This allows clicks to be passed directly to media bypassing the focus click requirement. This setting is a bitfield, precomputed values are as follows: Disabled=0; Worn HUDs only=1; Owned objects=2; Friend objects=4; Group objects=8; Landowner objects=16; Any object=32767; All MOAP=32768. For complete details see lltoolpie.h enum MediaFirstClickTypes.</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>S32</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<integer>31</integer>
|
||||||
|
</map>
|
||||||
|
<key>EnableSelectionHints</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>Whether or not to send editing hints to animate the arm when editing an object.</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>Boolean</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</map>
|
||||||
|
<key>EnableLookAtTarget</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>Whether or not to animate the avatar head and send look at targets when moving the cursor or focusing on objects</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>Boolean</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</map>
|
||||||
|
<key>LimitLookAtTarget</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>Whether or not to clamp the look at targets around the avatar head before sending</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>Boolean</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</map>
|
||||||
|
<key>LimitLookAtTargetDistance</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>Distance to limit look at target to</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>F32</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</map>
|
||||||
</map>
|
</map>
|
||||||
</llsd>
|
</llsd>
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ bool Buffer::prep(Asset& asset)
|
||||||
std::string dir = gDirUtilp->getDirName(asset.mFilename);
|
std::string dir = gDirUtilp->getDirName(asset.mFilename);
|
||||||
std::string bin_file = dir + gDirUtilp->getDirDelimiter() + mUri;
|
std::string bin_file = dir + gDirUtilp->getDirDelimiter() + mUri;
|
||||||
|
|
||||||
std::ifstream file(bin_file, std::ios::binary);
|
llifstream file(bin_file.c_str(), std::ios::binary);
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
{
|
{
|
||||||
LL_WARNS("GLTF") << "Failed to open file: " << bin_file << LL_ENDL;
|
LL_WARNS("GLTF") << "Failed to open file: " << bin_file << LL_ENDL;
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,10 @@ namespace LL
|
||||||
"KHR_texture_transform"
|
"KHR_texture_transform"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::unordered_set<std::string> ExtensionsIgnored = {
|
||||||
|
"KHR_materials_pbrSpecularGlossiness"
|
||||||
|
};
|
||||||
|
|
||||||
Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode)
|
Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode)
|
||||||
{
|
{
|
||||||
if (alpha_mode == "OPAQUE")
|
if (alpha_mode == "OPAQUE")
|
||||||
|
|
@ -471,6 +475,8 @@ void Asset::update()
|
||||||
LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltf - addTextureStats");
|
LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltf - addTextureStats");
|
||||||
|
|
||||||
for (auto& image : mImages)
|
for (auto& image : mImages)
|
||||||
|
{
|
||||||
|
if (image.mLoadIntoTexturePipe)
|
||||||
{
|
{
|
||||||
if (image.mTexture.notNull())
|
if (image.mTexture.notNull())
|
||||||
{ // HACK - force texture to be loaded full rez
|
{ // HACK - force texture to be loaded full rez
|
||||||
|
|
@ -481,23 +487,29 @@ void Asset::update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Asset::prep()
|
bool Asset::prep()
|
||||||
{
|
{
|
||||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
|
||||||
// check required extensions and fail if not supported
|
// check required extensions
|
||||||
bool unsupported = false;
|
|
||||||
for (auto& extension : mExtensionsRequired)
|
for (auto& extension : mExtensionsRequired)
|
||||||
{
|
{
|
||||||
if (ExtensionsSupported.find(extension) == ExtensionsSupported.end())
|
if (ExtensionsSupported.find(extension) == ExtensionsSupported.end())
|
||||||
|
{
|
||||||
|
if (ExtensionsIgnored.find(extension) == ExtensionsIgnored.end())
|
||||||
{
|
{
|
||||||
LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL;
|
LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL;
|
||||||
unsupported = true;
|
mUnsupportedExtensions.push_back(extension);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mIgnoredExtensions.push_back(extension);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (unsupported)
|
if (mUnsupportedExtensions.size() > 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -513,7 +525,7 @@ bool Asset::prep()
|
||||||
|
|
||||||
for (auto& image : mImages)
|
for (auto& image : mImages)
|
||||||
{
|
{
|
||||||
if (!image.prep(*this))
|
if (!image.prep(*this, mLoadIntoVRAM))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -542,7 +554,8 @@ bool Asset::prep()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (mLoadIntoVRAM)
|
||||||
|
{
|
||||||
// prepare vertex buffers
|
// prepare vertex buffers
|
||||||
|
|
||||||
// material count is number of materials + 1 for default material
|
// material count is number of materials + 1 for default material
|
||||||
|
|
@ -603,6 +616,7 @@ bool Asset::prep()
|
||||||
if (vertex_count[variant] > 0)
|
if (vertex_count[variant] > 0)
|
||||||
{
|
{
|
||||||
U32 mat_idx = mat_id + 1;
|
U32 mat_idx = mat_id + 1;
|
||||||
|
#if 0
|
||||||
LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask);
|
LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask);
|
||||||
|
|
||||||
rd.mBatches[variant][mat_idx].mVertexBuffer = vb;
|
rd.mBatches[variant][mat_idx].mVertexBuffer = vb;
|
||||||
|
|
@ -624,6 +638,7 @@ bool Asset::prep()
|
||||||
vb->unmapBuffer();
|
vb->unmapBuffer();
|
||||||
|
|
||||||
vb->unbind();
|
vb->unbind();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -634,10 +649,11 @@ bool Asset::prep()
|
||||||
{
|
{
|
||||||
for (auto& primitive : mesh.mPrimitives)
|
for (auto& primitive : mesh.mPrimitives)
|
||||||
{
|
{
|
||||||
llassert(primitive.mVertexBuffer.notNull());
|
//llassert(primitive.mVertexBuffer.notNull());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
// build render batches
|
// build render batches
|
||||||
for (S32 node_id = 0; node_id < mNodes.size(); ++node_id)
|
for (S32 node_id = 0; node_id < mNodes.size(); ++node_id)
|
||||||
{
|
{
|
||||||
|
|
@ -664,6 +680,7 @@ bool Asset::prep()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -672,13 +689,14 @@ Asset::Asset(const Value& src)
|
||||||
*this = src;
|
*this = src;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Asset::load(std::string_view filename)
|
bool Asset::load(std::string_view filename, bool loadIntoVRAM)
|
||||||
{
|
{
|
||||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
|
||||||
|
mLoadIntoVRAM = loadIntoVRAM;
|
||||||
mFilename = filename;
|
mFilename = filename;
|
||||||
std::string ext = gDirUtilp->getExtension(mFilename);
|
std::string ext = gDirUtilp->getExtension(mFilename);
|
||||||
|
|
||||||
std::ifstream file(filename.data(), std::ios::binary);
|
llifstream file(filename.data(), std::ios::binary);
|
||||||
if (file.is_open())
|
if (file.is_open())
|
||||||
{
|
{
|
||||||
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||||
|
|
@ -692,7 +710,7 @@ bool Asset::load(std::string_view filename)
|
||||||
}
|
}
|
||||||
else if (ext == "glb")
|
else if (ext == "glb")
|
||||||
{
|
{
|
||||||
return loadBinary(str);
|
return loadBinary(str, mLoadIntoVRAM);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -709,8 +727,9 @@ bool Asset::load(std::string_view filename)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Asset::loadBinary(const std::string& data)
|
bool Asset::loadBinary(const std::string& data, bool loadIntoVRAM)
|
||||||
{
|
{
|
||||||
|
mLoadIntoVRAM = loadIntoVRAM;
|
||||||
// load from binary gltf
|
// load from binary gltf
|
||||||
const U8* ptr = (const U8*)data.data();
|
const U8* ptr = (const U8*)data.data();
|
||||||
const U8* end = ptr + data.size();
|
const U8* end = ptr + data.size();
|
||||||
|
|
@ -935,8 +954,9 @@ void Asset::eraseBufferView(S32 bufferView)
|
||||||
|
|
||||||
LLViewerFetchedTexture* fetch_texture(const LLUUID& id);
|
LLViewerFetchedTexture* fetch_texture(const LLUUID& id);
|
||||||
|
|
||||||
bool Image::prep(Asset& asset)
|
bool Image::prep(Asset& asset, bool loadIntoVRAM)
|
||||||
{
|
{
|
||||||
|
mLoadIntoTexturePipe = loadIntoVRAM;
|
||||||
LLUUID id;
|
LLUUID id;
|
||||||
if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull())
|
if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull())
|
||||||
{ // loaded from an asset, fetch the texture from the asset system
|
{ // loaded from an asset, fetch the texture from the asset system
|
||||||
|
|
@ -951,12 +971,12 @@ bool Image::prep(Asset& asset)
|
||||||
{ // embedded in a buffer, load the texture from the buffer
|
{ // embedded in a buffer, load the texture from the buffer
|
||||||
BufferView& bufferView = asset.mBufferViews[mBufferView];
|
BufferView& bufferView = asset.mBufferViews[mBufferView];
|
||||||
Buffer& buffer = asset.mBuffers[bufferView.mBuffer];
|
Buffer& buffer = asset.mBuffers[bufferView.mBuffer];
|
||||||
|
if (mLoadIntoTexturePipe)
|
||||||
|
{
|
||||||
U8* data = buffer.mData.data() + bufferView.mByteOffset;
|
U8* data = buffer.mData.data() + bufferView.mByteOffset;
|
||||||
|
|
||||||
mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType);
|
mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType);
|
||||||
|
}
|
||||||
if (mTexture.isNull())
|
else if (mTexture.isNull() && mLoadIntoTexturePipe)
|
||||||
{
|
{
|
||||||
LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL;
|
LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL;
|
||||||
LL_WARNS("GLTF") << " image: " << mName << LL_ENDL;
|
LL_WARNS("GLTF") << " image: " << mName << LL_ENDL;
|
||||||
|
|
@ -971,12 +991,12 @@ bool Image::prep(Asset& asset)
|
||||||
std::string img_file = dir + gDirUtilp->getDirDelimiter() + mUri;
|
std::string img_file = dir + gDirUtilp->getDirDelimiter() + mUri;
|
||||||
|
|
||||||
LLUUID tracking_id = LLLocalBitmapMgr::getInstance()->addUnit(img_file);
|
LLUUID tracking_id = LLLocalBitmapMgr::getInstance()->addUnit(img_file);
|
||||||
if (tracking_id.notNull())
|
if (tracking_id.notNull() && mLoadIntoTexturePipe)
|
||||||
{
|
{
|
||||||
LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id);
|
LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id);
|
||||||
mTexture = LLViewerTextureManager::getFetchedTexture(world_id);
|
mTexture = LLViewerTextureManager::getFetchedTexture(world_id);
|
||||||
}
|
}
|
||||||
else
|
else if (mLoadIntoTexturePipe)
|
||||||
{
|
{
|
||||||
LL_WARNS("GLTF") << "Failed to load image from file:" << LL_ENDL;
|
LL_WARNS("GLTF") << "Failed to load image from file:" << LL_ENDL;
|
||||||
LL_WARNS("GLTF") << " image: " << mName << LL_ENDL;
|
LL_WARNS("GLTF") << " image: " << mName << LL_ENDL;
|
||||||
|
|
@ -991,7 +1011,7 @@ bool Image::prep(Asset& asset)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!asset.mFilename.empty())
|
if (!asset.mFilename.empty() && mLoadIntoTexturePipe)
|
||||||
{ // local preview, boost image so it doesn't discard and force to save raw image in case we save out or upload
|
{ // local preview, boost image so it doesn't discard and force to save raw image in case we save out or upload
|
||||||
mTexture->setBoostLevel(LLViewerTexture::BOOST_PREVIEW);
|
mTexture->setBoostLevel(LLViewerTexture::BOOST_PREVIEW);
|
||||||
mTexture->forceToSaveRawImage(0, F32_MAX);
|
mTexture->forceToSaveRawImage(0, F32_MAX);
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,7 @@ namespace LL
|
||||||
void serialize(boost::json::object& dst) const;
|
void serialize(boost::json::object& dst) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Image is for images that we want to load for the given asset. This acts as an interface into the viewer's texture pipe.
|
||||||
class Image
|
class Image
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -301,6 +302,8 @@ namespace LL
|
||||||
S32 mBits = -1;
|
S32 mBits = -1;
|
||||||
S32 mPixelType = -1;
|
S32 mPixelType = -1;
|
||||||
|
|
||||||
|
bool mLoadIntoTexturePipe = false;
|
||||||
|
|
||||||
LLPointer<LLViewerFetchedTexture> mTexture;
|
LLPointer<LLViewerFetchedTexture> mTexture;
|
||||||
|
|
||||||
const Image& operator=(const Value& src);
|
const Image& operator=(const Value& src);
|
||||||
|
|
@ -316,7 +319,7 @@ namespace LL
|
||||||
// preserve only uri and name
|
// preserve only uri and name
|
||||||
void clearData(Asset& asset);
|
void clearData(Asset& asset);
|
||||||
|
|
||||||
bool prep(Asset& asset);
|
bool prep(Asset& asset, bool loadIntoVRAM);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Render Batch -- vertex buffer and list of primitives to render using
|
// Render Batch -- vertex buffer and list of primitives to render using
|
||||||
|
|
@ -391,6 +394,10 @@ namespace LL
|
||||||
|
|
||||||
// UBO for storing material data
|
// UBO for storing material data
|
||||||
U32 mMaterialsUBO = 0;
|
U32 mMaterialsUBO = 0;
|
||||||
|
bool mLoadIntoVRAM = false;
|
||||||
|
|
||||||
|
std::vector<std::string> mUnsupportedExtensions;
|
||||||
|
std::vector<std::string> mIgnoredExtensions;
|
||||||
|
|
||||||
// prepare for first time use
|
// prepare for first time use
|
||||||
bool prep();
|
bool prep();
|
||||||
|
|
@ -428,12 +435,12 @@ namespace LL
|
||||||
// accepts .gltf and .glb files
|
// accepts .gltf and .glb files
|
||||||
// Any existing data will be lost
|
// Any existing data will be lost
|
||||||
// returns result of prep() on success
|
// returns result of prep() on success
|
||||||
bool load(std::string_view filename);
|
bool load(std::string_view filename, bool loadIntoVRAM);
|
||||||
|
|
||||||
// load .glb contents from memory
|
// load .glb contents from memory
|
||||||
// data - binary contents of .glb file
|
// data - binary contents of .glb file
|
||||||
// returns result of prep() on success
|
// returns result of prep() on success
|
||||||
bool loadBinary(const std::string& data);
|
bool loadBinary(const std::string& data, bool loadIntoVRAM);
|
||||||
|
|
||||||
const Asset& operator=(const Value& src);
|
const Asset& operator=(const Value& src);
|
||||||
void serialize(boost::json::object& dst) const;
|
void serialize(boost::json::object& dst) const;
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,12 @@ namespace LL
|
||||||
dst.load3(src);
|
dst.load3(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline void copyVec3<F32, LLColor4U>(F32* src, LLColor4U& dst)
|
||||||
|
{
|
||||||
|
dst.set((U8)(src[0] * 255.f), (U8)(src[1] * 255.f), (U8)(src[2] * 255.f), 255);
|
||||||
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
inline void copyVec3<U16, LLColor4U>(U16* src, LLColor4U& dst)
|
inline void copyVec3<U16, LLColor4U>(U16* src, LLColor4U& dst)
|
||||||
{
|
{
|
||||||
|
|
@ -369,6 +375,11 @@ namespace LL
|
||||||
template<class T>
|
template<class T>
|
||||||
inline void copy(Asset& asset, Accessor& accessor, LLStrider<T>& dst)
|
inline void copy(Asset& asset, Accessor& accessor, LLStrider<T>& dst)
|
||||||
{
|
{
|
||||||
|
if (accessor.mBufferView == INVALID_INDEX)
|
||||||
|
{
|
||||||
|
LL_WARNS("GLTF") << "Invalid buffer" << LL_ENDL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
const BufferView& bufferView = asset.mBufferViews[accessor.mBufferView];
|
const BufferView& bufferView = asset.mBufferViews[accessor.mBufferView];
|
||||||
const Buffer& buffer = asset.mBuffers[bufferView.mBuffer];
|
const Buffer& buffer = asset.mBuffers[bufferView.mBuffer];
|
||||||
const U8* src = buffer.mData.data() + bufferView.mByteOffset + accessor.mByteOffset;
|
const U8* src = buffer.mData.data() + bufferView.mByteOffset + accessor.mByteOffset;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,216 @@
|
||||||
|
/**
|
||||||
|
* @file LLGLTFLoader.h
|
||||||
|
* @brief LLGLTFLoader class definition
|
||||||
|
*
|
||||||
|
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||||
|
* Second Life Viewer Source Code
|
||||||
|
* Copyright (C) 2022, Linden Research, Inc.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation;
|
||||||
|
* version 2.1 of the License only.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||||
|
* $/LicenseInfo$
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LL_LLGLTFLoader_H
|
||||||
|
#define LL_LLGLTFLoader_H
|
||||||
|
|
||||||
|
#include "tinygltf/tiny_gltf.h"
|
||||||
|
|
||||||
|
#include "asset.h"
|
||||||
|
|
||||||
|
#include "llglheaders.h"
|
||||||
|
#include "lljointdata.h"
|
||||||
|
#include "llmodelloader.h"
|
||||||
|
|
||||||
|
class LLGLTFLoader : public LLModelLoader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef std::map<std::string, LLImportMaterial> material_map;
|
||||||
|
typedef std::map<std::string, std::string> joint_viewer_parent_map_t;
|
||||||
|
typedef std::map<std::string, glm::mat4> joint_viewer_rest_map_t;
|
||||||
|
typedef std::map<S32, glm::mat4> joint_node_mat4_map_t;
|
||||||
|
|
||||||
|
struct JointNodeData
|
||||||
|
{
|
||||||
|
JointNodeData()
|
||||||
|
: mJointListIdx(-1)
|
||||||
|
, mNodeIdx(-1)
|
||||||
|
, mParentNodeIdx(-1)
|
||||||
|
, mIsValidViewerJoint(false)
|
||||||
|
, mIsParentValidViewerJoint(false)
|
||||||
|
, mIsOverrideValid(false)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
S32 mJointListIdx;
|
||||||
|
S32 mNodeIdx;
|
||||||
|
S32 mParentNodeIdx;
|
||||||
|
glm::mat4 mGltfRestMatrix;
|
||||||
|
glm::mat4 mViewerRestMatrix;
|
||||||
|
glm::mat4 mOverrideRestMatrix;
|
||||||
|
glm::mat4 mGltfMatrix;
|
||||||
|
glm::mat4 mOverrideMatrix;
|
||||||
|
std::string mName;
|
||||||
|
bool mIsValidViewerJoint;
|
||||||
|
bool mIsParentValidViewerJoint;
|
||||||
|
bool mIsOverrideValid;
|
||||||
|
};
|
||||||
|
typedef std::map <S32, JointNodeData> joints_data_map_t;
|
||||||
|
typedef std::map <std::string, S32> joints_name_to_node_map_t;
|
||||||
|
|
||||||
|
class LLGLTFImportMaterial : public LLImportMaterial
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string name;
|
||||||
|
LLGLTFImportMaterial() = default;
|
||||||
|
LLGLTFImportMaterial(const LLImportMaterial& mat, const std::string& n) : LLImportMaterial(mat), name(n) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
LLGLTFLoader(std::string filename,
|
||||||
|
S32 lod,
|
||||||
|
LLModelLoader::load_callback_t load_cb,
|
||||||
|
LLModelLoader::joint_lookup_func_t joint_lookup_func,
|
||||||
|
LLModelLoader::texture_load_func_t texture_load_func,
|
||||||
|
LLModelLoader::state_callback_t state_cb,
|
||||||
|
void * opaque_userdata,
|
||||||
|
JointTransformMap & jointTransformMap,
|
||||||
|
JointNameSet & jointsFromNodes,
|
||||||
|
std::map<std::string, std::string, std::less<>> & jointAliasMap,
|
||||||
|
U32 maxJointsPerMesh,
|
||||||
|
U32 modelLimit,
|
||||||
|
U32 debugMode,
|
||||||
|
std::vector<LLJointData> viewer_skeleton); //,
|
||||||
|
//bool preprocess );
|
||||||
|
virtual ~LLGLTFLoader();
|
||||||
|
|
||||||
|
virtual bool OpenFile(const std::string &filename);
|
||||||
|
|
||||||
|
struct GLTFVertex
|
||||||
|
{
|
||||||
|
glm::vec3 position;
|
||||||
|
glm::vec3 normal;
|
||||||
|
glm::vec2 uv0;
|
||||||
|
glm::u16vec4 joints;
|
||||||
|
glm::vec4 weights;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
LL::GLTF::Asset mGLTFAsset;
|
||||||
|
tinygltf::Model mGltfModel;
|
||||||
|
bool mGltfLoaded = false;
|
||||||
|
bool mApplyXYRotation = false;
|
||||||
|
|
||||||
|
// GLTF isn't aware of viewer's skeleton and uses it's own,
|
||||||
|
// so need to take viewer's joints and use them to
|
||||||
|
// recalculate iverse bind matrices
|
||||||
|
std::vector<LLJointData> mViewerJointData;
|
||||||
|
|
||||||
|
// vector of vectors because of a posibility of having more than one skin
|
||||||
|
typedef std::vector<LLMeshSkinInfo::matrix_list_t> bind_matrices_t;
|
||||||
|
typedef std::vector<std::vector<std::string> > joint_names_t;
|
||||||
|
bind_matrices_t mInverseBindMatrices;
|
||||||
|
bind_matrices_t mAlternateBindMatrices;
|
||||||
|
joint_names_t mJointNames; // empty string when no legal name for a given idx
|
||||||
|
std::vector<std::vector<S32>> mJointUsage; // detect and warn about unsed joints
|
||||||
|
|
||||||
|
// what group a joint belongs to.
|
||||||
|
// For purpose of stripping unused groups when joints are over limit.
|
||||||
|
struct JointGroups
|
||||||
|
{
|
||||||
|
std::string mGroup;
|
||||||
|
std::string mParentGroup;
|
||||||
|
};
|
||||||
|
typedef std::map<std::string, JointGroups, std::less<> > joint_to_group_map_t;
|
||||||
|
joint_to_group_map_t mJointGroups;
|
||||||
|
|
||||||
|
// per skin joint count, needs to be tracked for the sake of limits check.
|
||||||
|
std::vector<S32> mValidJointsCount;
|
||||||
|
|
||||||
|
// Cached material information
|
||||||
|
typedef std::map<S32, LLGLTFImportMaterial> MaterialCache;
|
||||||
|
MaterialCache mMaterialCache;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool parseMeshes();
|
||||||
|
void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const;
|
||||||
|
void processNodeHierarchy(S32 node_idx, std::map<std::string, S32>& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params);
|
||||||
|
bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx);
|
||||||
|
LLGLTFImportMaterial processMaterial(S32 material_index, S32 fallback_index);
|
||||||
|
std::string processTexture(S32 texture_index, const std::string& texture_type, const std::string& material_name);
|
||||||
|
bool validateTextureIndex(S32 texture_index, S32& source_index);
|
||||||
|
std::string generateMaterialName(S32 material_index, S32 fallback_index = -1);
|
||||||
|
bool populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats);
|
||||||
|
void populateJointsFromSkin(S32 skin_idx);
|
||||||
|
void populateJointGroups();
|
||||||
|
void addModelToScene(LLModel* pModel, const std::string& model_name, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, const material_map& mats);
|
||||||
|
void buildJointGroup(LLJointData& viewer_data, const std::string& parent_group);
|
||||||
|
void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& support_rest) const;
|
||||||
|
glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const;
|
||||||
|
glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const;
|
||||||
|
glm::mat4 computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const;
|
||||||
|
bool checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx);
|
||||||
|
void checkForXYrotation(const LL::GLTF::Skin& gltf_skin);
|
||||||
|
void checkGlobalJointUsage();
|
||||||
|
|
||||||
|
std::string extractTextureToTempFile(S32 textureIndex, const std::string& texture_type);
|
||||||
|
|
||||||
|
void notifyUnsupportedExtension(bool unsupported);
|
||||||
|
|
||||||
|
static size_t getSuffixPosition(const std::string& label);
|
||||||
|
static std::string getLodlessLabel(const LL::GLTF::Mesh& mesh);
|
||||||
|
|
||||||
|
// bool mPreprocessGLTF;
|
||||||
|
|
||||||
|
/* Below inherited from dae loader - unknown if/how useful here
|
||||||
|
|
||||||
|
void processElement(gltfElement *element, bool &badElement, GLTF *gltf);
|
||||||
|
void processGltfModel(LLModel *model, GLTF *gltf, gltfElement *pRoot, gltfMesh *mesh, gltfSkin *skin);
|
||||||
|
|
||||||
|
material_map getMaterials(LLModel *model, gltfInstance_geometry *instance_geo, GLTF *gltf);
|
||||||
|
LLImportMaterial profileToMaterial(gltfProfile_COMMON *material, GLTF *gltf);
|
||||||
|
LLColor4 getGltfColor(gltfElement *element);
|
||||||
|
|
||||||
|
gltfElement *getChildFromElement(gltfElement *pElement, std::string const &name);
|
||||||
|
|
||||||
|
bool isNodeAJoint(gltfNode *pNode);
|
||||||
|
void processJointNode(gltfNode *pNode, std::map<std::string, LLMatrix4> &jointTransforms);
|
||||||
|
void extractTranslation(gltfTranslate *pTranslate, LLMatrix4 &transform);
|
||||||
|
void extractTranslationViaElement(gltfElement *pTranslateElement, LLMatrix4 &transform);
|
||||||
|
void extractTranslationViaSID(gltfElement *pElement, LLMatrix4 &transform);
|
||||||
|
void buildJointToNodeMappingFromScene(gltfElement *pRoot);
|
||||||
|
void processJointToNodeMapping(gltfNode *pNode);
|
||||||
|
void processChildJoints(gltfNode *pParentNode);
|
||||||
|
|
||||||
|
bool verifyCount(int expected, int result);
|
||||||
|
|
||||||
|
// Verify that a controller matches vertex counts
|
||||||
|
bool verifyController(gltfController *pController);
|
||||||
|
|
||||||
|
static bool addVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh, LLSD &log_msg);
|
||||||
|
static bool createVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh);
|
||||||
|
|
||||||
|
static LLModel *loadModelFromGltfMesh(gltfMesh *mesh);
|
||||||
|
|
||||||
|
// Loads a mesh breaking it into one or more models as necessary
|
||||||
|
// to get around volume face limitations while retaining >8 materials
|
||||||
|
//
|
||||||
|
bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector<LLModel *> &models_out, U32 submodel_limit);
|
||||||
|
|
||||||
|
static std::string preprocessGLTF(std::string filename);
|
||||||
|
*/
|
||||||
|
|
||||||
|
};
|
||||||
|
#endif // LL_LLGLTFLLOADER_H
|
||||||
|
|
@ -319,7 +319,7 @@ void GLTFSceneManager::load(const std::string& filename)
|
||||||
{
|
{
|
||||||
std::shared_ptr<Asset> asset = std::make_shared<Asset>();
|
std::shared_ptr<Asset> asset = std::make_shared<Asset>();
|
||||||
|
|
||||||
if (asset->load(filename))
|
if (asset->load(filename, true))
|
||||||
{
|
{
|
||||||
gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions
|
gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions
|
||||||
asset->updateTransforms();
|
asset->updateTransforms();
|
||||||
|
|
|
||||||
|
|
@ -3430,11 +3430,14 @@ void LLAgent::initOriginGlobal(const LLVector3d &origin_global)
|
||||||
|
|
||||||
bool LLAgent::leftButtonGrabbed() const
|
bool LLAgent::leftButtonGrabbed() const
|
||||||
{
|
{
|
||||||
const bool camera_mouse_look = gAgentCamera.cameraMouselook();
|
if (gAgentCamera.cameraMouselook())
|
||||||
return (!camera_mouse_look && mControlsTakenCount[CONTROL_LBUTTON_DOWN_INDEX] > 0)
|
{
|
||||||
|| (camera_mouse_look && mControlsTakenCount[CONTROL_ML_LBUTTON_DOWN_INDEX] > 0)
|
return mControlsTakenCount[CONTROL_ML_LBUTTON_DOWN_INDEX] > 0;
|
||||||
|| (!camera_mouse_look && mControlsTakenPassedOnCount[CONTROL_LBUTTON_DOWN_INDEX] > 0)
|
}
|
||||||
|| (camera_mouse_look && mControlsTakenPassedOnCount[CONTROL_ML_LBUTTON_DOWN_INDEX] > 0);
|
else
|
||||||
|
{
|
||||||
|
return mControlsTakenCount[CONTROL_LBUTTON_DOWN_INDEX] > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LLAgent::rotateGrabbed() const
|
bool LLAgent::rotateGrabbed() const
|
||||||
|
|
|
||||||
|
|
@ -1987,16 +1987,6 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(bool *hit_limit)
|
||||||
isConstrained = true;
|
isConstrained = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// JC - Could constrain camera based on parcel stuff here.
|
|
||||||
// LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(camera_position_global);
|
|
||||||
//
|
|
||||||
// if (regionp && !regionp->mParcelOverlay->isBuildCameraAllowed(regionp->getPosRegionFromGlobal(camera_position_global)))
|
|
||||||
// {
|
|
||||||
// camera_position_global = last_position_global;
|
|
||||||
//
|
|
||||||
// isConstrained = true;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't let camera go underground
|
// Don't let camera go underground
|
||||||
|
|
|
||||||
|
|
@ -1094,12 +1094,12 @@ void LLAgentWearables::setWearableOutfit(const LLInventoryItem::item_array_t& it
|
||||||
{
|
{
|
||||||
gAgentAvatarp->setCompositeUpdatesEnabled(true);
|
gAgentAvatarp->setCompositeUpdatesEnabled(true);
|
||||||
|
|
||||||
// If we have not yet declouded, we may want to use
|
// If we have not yet loaded core parts, we may want to use
|
||||||
// baked texture UUIDs sent from the first objectUpdate message
|
// baked texture UUIDs sent from the first objectUpdate message
|
||||||
// don't overwrite these. If we have already declouded, we've saved
|
// don't overwrite these. If we have parts already, we've saved
|
||||||
// these ids as the last known good textures and can invalidate without
|
// these texture ids as the last known good textures and can
|
||||||
// re-clouding.
|
// invalidate without having to recloud avatar.
|
||||||
if (!gAgentAvatarp->getIsCloud())
|
if (!gAgentAvatarp->getHasMissingParts())
|
||||||
{
|
{
|
||||||
gAgentAvatarp->invalidateAll();
|
gAgentAvatarp->invalidateAll();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -856,7 +856,7 @@ void LLWearableHoldingPattern::checkMissingWearables()
|
||||||
// was requested but none was found, create a default asset as a replacement.
|
// was requested but none was found, create a default asset as a replacement.
|
||||||
// In all other cases, don't do anything.
|
// In all other cases, don't do anything.
|
||||||
// For critical types (shape/hair/skin/eyes), this will keep the avatar as a cloud
|
// For critical types (shape/hair/skin/eyes), this will keep the avatar as a cloud
|
||||||
// due to logic in LLVOAvatarSelf::getIsCloud().
|
// due to logic in LLVOAvatarSelf::getHasMissingParts().
|
||||||
// For non-critical types (tatoo, socks, etc.) the wearable will just be missing.
|
// For non-critical types (tatoo, socks, etc.) the wearable will just be missing.
|
||||||
(requested_by_type[type] > 0) &&
|
(requested_by_type[type] > 0) &&
|
||||||
((type == LLWearableType::WT_PANTS) || (type == LLWearableType::WT_SHIRT) || (type == LLWearableType::WT_SKIRT)))
|
((type == LLWearableType::WT_PANTS) || (type == LLWearableType::WT_SHIRT) || (type == LLWearableType::WT_SKIRT)))
|
||||||
|
|
|
||||||
|
|
@ -268,6 +268,16 @@ using namespace LL;
|
||||||
#include "glib.h"
|
#include "glib.h"
|
||||||
#endif // (LL_LINUX) && LL_GTK
|
#endif // (LL_LINUX) && LL_GTK
|
||||||
|
|
||||||
|
#ifdef LL_DISCORD
|
||||||
|
#define DISCORDPP_IMPLEMENTATION
|
||||||
|
#include <discordpp.h>
|
||||||
|
static std::shared_ptr<discordpp::Client> gDiscordClient;
|
||||||
|
static uint64_t gDiscordTimestampsStart;
|
||||||
|
static std::string gDiscordActivityDetails;
|
||||||
|
static int32_t gDiscordPartyCurrentSize;
|
||||||
|
static int32_t gDiscordPartyMaxSize;
|
||||||
|
#endif
|
||||||
|
|
||||||
static LLAppViewerListener sAppViewerListener(LLAppViewer::instance);
|
static LLAppViewerListener sAppViewerListener(LLAppViewer::instance);
|
||||||
|
|
||||||
////// Windows-specific includes to the bottom - nasty defines in these pollute the preprocessor
|
////// Windows-specific includes to the bottom - nasty defines in these pollute the preprocessor
|
||||||
|
|
@ -1334,6 +1344,13 @@ bool LLAppViewer::frame()
|
||||||
|
|
||||||
bool LLAppViewer::doFrame()
|
bool LLAppViewer::doFrame()
|
||||||
{
|
{
|
||||||
|
#ifdef LL_DISCORD
|
||||||
|
{
|
||||||
|
LL_PROFILE_ZONE_NAMED("discord_callbacks");
|
||||||
|
discordpp::RunCallbacks();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
LL_RECORD_BLOCK_TIME(FTM_FRAME);
|
LL_RECORD_BLOCK_TIME(FTM_FRAME);
|
||||||
{
|
{
|
||||||
// and now adjust the visuals from previous frame.
|
// and now adjust the visuals from previous frame.
|
||||||
|
|
@ -2260,10 +2277,7 @@ void errorCallback(LLError::ELevel level, const std::string &error_string)
|
||||||
// Callback for LLError::LLUserWarningMsg
|
// Callback for LLError::LLUserWarningMsg
|
||||||
void errorHandler(const std::string& title_string, const std::string& message_string, S32 code)
|
void errorHandler(const std::string& title_string, const std::string& message_string, S32 code)
|
||||||
{
|
{
|
||||||
if (!message_string.empty())
|
// message is going to hang viewer, create marker first
|
||||||
{
|
|
||||||
OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK);
|
|
||||||
}
|
|
||||||
switch (code)
|
switch (code)
|
||||||
{
|
{
|
||||||
case LLError::LLUserWarningMsg::ERROR_OTHER:
|
case LLError::LLUserWarningMsg::ERROR_OTHER:
|
||||||
|
|
@ -2271,6 +2285,10 @@ void errorHandler(const std::string& title_string, const std::string& message_st
|
||||||
break;
|
break;
|
||||||
case LLError::LLUserWarningMsg::ERROR_BAD_ALLOC:
|
case LLError::LLUserWarningMsg::ERROR_BAD_ALLOC:
|
||||||
LLAppViewer::instance()->createErrorMarker(LAST_EXEC_BAD_ALLOC);
|
LLAppViewer::instance()->createErrorMarker(LAST_EXEC_BAD_ALLOC);
|
||||||
|
// When system run out of memory and errorHandler gets called from a thread,
|
||||||
|
// main thread might keep going while OSMessageBox freezes the caller.
|
||||||
|
// Todo: handle it better, but for now disconnect to avoid making things worse
|
||||||
|
gDisconnected = true;
|
||||||
break;
|
break;
|
||||||
case LLError::LLUserWarningMsg::ERROR_MISSING_FILES:
|
case LLError::LLUserWarningMsg::ERROR_MISSING_FILES:
|
||||||
LLAppViewer::instance()->createErrorMarker(LAST_EXEC_MISSING_FILES);
|
LLAppViewer::instance()->createErrorMarker(LAST_EXEC_MISSING_FILES);
|
||||||
|
|
@ -2278,6 +2296,10 @@ void errorHandler(const std::string& title_string, const std::string& message_st
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (!message_string.empty())
|
||||||
|
{
|
||||||
|
OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLAppViewer::initLoggingAndGetLastDuration()
|
void LLAppViewer::initLoggingAndGetLastDuration()
|
||||||
|
|
@ -3103,7 +3125,15 @@ bool LLAppViewer::initWindow()
|
||||||
.height(gSavedSettings.getU32("WindowHeight"))
|
.height(gSavedSettings.getU32("WindowHeight"))
|
||||||
.min_width(gSavedSettings.getU32("MinWindowWidth"))
|
.min_width(gSavedSettings.getU32("MinWindowWidth"))
|
||||||
.min_height(gSavedSettings.getU32("MinWindowHeight"))
|
.min_height(gSavedSettings.getU32("MinWindowHeight"))
|
||||||
|
#ifdef LL_DARWIN
|
||||||
|
// Setting it to true causes black screen with no UI displayed.
|
||||||
|
// Given that it's a DEBUG settings and application goes fullscreen
|
||||||
|
// on mac simply by expanding it, it was decided to not support/use
|
||||||
|
// this setting on mac.
|
||||||
|
.fullscreen(false)
|
||||||
|
#else // LL_DARWIN
|
||||||
.fullscreen(gSavedSettings.getBOOL("FullScreen"))
|
.fullscreen(gSavedSettings.getBOOL("FullScreen"))
|
||||||
|
#endif
|
||||||
.ignore_pixel_depth(ignorePixelDepth)
|
.ignore_pixel_depth(ignorePixelDepth)
|
||||||
.first_run(mIsFirstRun);
|
.first_run(mIsFirstRun);
|
||||||
|
|
||||||
|
|
@ -4289,8 +4319,8 @@ bool LLAppViewer::initCache()
|
||||||
const std::string cache_dir_name = gSavedSettings.getString("DiskCacheDirName");
|
const std::string cache_dir_name = gSavedSettings.getString("DiskCacheDirName");
|
||||||
|
|
||||||
const U32 MB = 1024 * 1024;
|
const U32 MB = 1024 * 1024;
|
||||||
const uintmax_t MIN_CACHE_SIZE = 256 * MB;
|
const uintmax_t MIN_CACHE_SIZE = 896 * MB;
|
||||||
const uintmax_t MAX_CACHE_SIZE = 9984ll * MB;
|
const uintmax_t MAX_CACHE_SIZE = 32768ll * MB;
|
||||||
const uintmax_t setting_cache_total_size = uintmax_t(gSavedSettings.getU32("CacheSize")) * MB;
|
const uintmax_t setting_cache_total_size = uintmax_t(gSavedSettings.getU32("CacheSize")) * MB;
|
||||||
const uintmax_t cache_total_size = llclamp(setting_cache_total_size, MIN_CACHE_SIZE, MAX_CACHE_SIZE);
|
const uintmax_t cache_total_size = llclamp(setting_cache_total_size, MIN_CACHE_SIZE, MAX_CACHE_SIZE);
|
||||||
const F64 disk_cache_percent = gSavedSettings.getF32("DiskCachePercentOfTotal");
|
const F64 disk_cache_percent = gSavedSettings.getF32("DiskCachePercentOfTotal");
|
||||||
|
|
@ -5694,9 +5724,31 @@ void LLAppViewer::forceErrorThreadCrash()
|
||||||
thread->start();
|
thread->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLAppViewer::initMainloopTimeout(const std::string& state, F32 secs)
|
void LLAppViewer::forceExceptionThreadCrash()
|
||||||
{
|
{
|
||||||
if(!mMainloopTimeout)
|
class LLCrashTestThread : public LLThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
LLCrashTestThread() : LLThread("Crash logging test thread")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void run()
|
||||||
|
{
|
||||||
|
const std::string exception_text = "This is a deliberate exception in a thread";
|
||||||
|
throw std::runtime_error(exception_text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LL_WARNS() << "This is a deliberate exception in a thread" << LL_ENDL;
|
||||||
|
LLCrashTestThread* thread = new LLCrashTestThread();
|
||||||
|
thread->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LLAppViewer::initMainloopTimeout(std::string_view state, F32 secs)
|
||||||
|
{
|
||||||
|
if (!mMainloopTimeout)
|
||||||
{
|
{
|
||||||
mMainloopTimeout = new LLWatchdogTimeout();
|
mMainloopTimeout = new LLWatchdogTimeout();
|
||||||
resumeMainloopTimeout(state, secs);
|
resumeMainloopTimeout(state, secs);
|
||||||
|
|
@ -5705,20 +5757,20 @@ void LLAppViewer::initMainloopTimeout(const std::string& state, F32 secs)
|
||||||
|
|
||||||
void LLAppViewer::destroyMainloopTimeout()
|
void LLAppViewer::destroyMainloopTimeout()
|
||||||
{
|
{
|
||||||
if(mMainloopTimeout)
|
if (mMainloopTimeout)
|
||||||
{
|
{
|
||||||
delete mMainloopTimeout;
|
delete mMainloopTimeout;
|
||||||
mMainloopTimeout = NULL;
|
mMainloopTimeout = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLAppViewer::resumeMainloopTimeout(const std::string& state, F32 secs)
|
void LLAppViewer::resumeMainloopTimeout(std::string_view state, F32 secs)
|
||||||
{
|
{
|
||||||
if(mMainloopTimeout)
|
if (mMainloopTimeout)
|
||||||
{
|
{
|
||||||
if(secs < 0.0f)
|
if (secs < 0.0f)
|
||||||
{
|
{
|
||||||
static LLCachedControl<F32> mainloop_timeout(gSavedSettings, "MainloopTimeoutDefault", 60);
|
static LLCachedControl<F32> mainloop_timeout(gSavedSettings, "MainloopTimeoutDefault", 60.f);
|
||||||
secs = mainloop_timeout;
|
secs = mainloop_timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5729,19 +5781,19 @@ void LLAppViewer::resumeMainloopTimeout(const std::string& state, F32 secs)
|
||||||
|
|
||||||
void LLAppViewer::pauseMainloopTimeout()
|
void LLAppViewer::pauseMainloopTimeout()
|
||||||
{
|
{
|
||||||
if(mMainloopTimeout)
|
if (mMainloopTimeout)
|
||||||
{
|
{
|
||||||
mMainloopTimeout->stop();
|
mMainloopTimeout->stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLAppViewer::pingMainloopTimeout(const std::string& state, F32 secs)
|
void LLAppViewer::pingMainloopTimeout(std::string_view state, F32 secs)
|
||||||
{
|
{
|
||||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_APP;
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_APP;
|
||||||
|
|
||||||
if(mMainloopTimeout)
|
if (mMainloopTimeout)
|
||||||
{
|
{
|
||||||
if(secs < 0.0f)
|
if (secs < 0.0f)
|
||||||
{
|
{
|
||||||
static LLCachedControl<F32> mainloop_timeout(gSavedSettings, "MainloopTimeoutDefault", 60);
|
static LLCachedControl<F32> mainloop_timeout(gSavedSettings, "MainloopTimeoutDefault", 60);
|
||||||
secs = mainloop_timeout;
|
secs = mainloop_timeout;
|
||||||
|
|
@ -5869,3 +5921,180 @@ void LLAppViewer::metricsSend(bool enable_reporting)
|
||||||
gViewerAssetStats->restart();
|
gViewerAssetStats->restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef LL_DISCORD
|
||||||
|
|
||||||
|
void LLAppViewer::initDiscordSocial()
|
||||||
|
{
|
||||||
|
gDiscordPartyCurrentSize = 1;
|
||||||
|
gDiscordPartyMaxSize = 0;
|
||||||
|
gDiscordTimestampsStart = time(nullptr);
|
||||||
|
gDiscordClient = std::make_shared<discordpp::Client>();
|
||||||
|
gDiscordClient->SetStatusChangedCallback([](discordpp::Client::Status status, discordpp::Client::Error, int32_t) {
|
||||||
|
if (status == discordpp::Client::Status::Ready)
|
||||||
|
{
|
||||||
|
updateDiscordActivity();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (gSavedSettings.getBOOL("EnableDiscord"))
|
||||||
|
{
|
||||||
|
auto credential = gSecAPIHandler->loadCredential("Discord");
|
||||||
|
if (credential.notNull())
|
||||||
|
{
|
||||||
|
gDiscordClient->UpdateToken(discordpp::AuthorizationTokenType::Bearer, credential->getAuthenticator()["token"].asString(), [](discordpp::ClientResult result) {
|
||||||
|
if (result.Successful())
|
||||||
|
gDiscordClient->Connect();
|
||||||
|
else
|
||||||
|
LL_WARNS("Discord") << result.Error() << LL_ENDL;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LL_WARNS("Discord") << "Integration was enabled, but no credentials. Disabling integration." << LL_ENDL;
|
||||||
|
gSavedSettings.setBOOL("EnableDiscord", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LLAppViewer::toggleDiscordIntegration(const LLSD& value)
|
||||||
|
{
|
||||||
|
static const uint64_t APPLICATION_ID = 1394782217405862001;
|
||||||
|
if (value.asBoolean())
|
||||||
|
{
|
||||||
|
discordpp::AuthorizationArgs args{};
|
||||||
|
args.SetClientId(APPLICATION_ID);
|
||||||
|
args.SetScopes(discordpp::Client::GetDefaultPresenceScopes());
|
||||||
|
auto codeVerifier = gDiscordClient->CreateAuthorizationCodeVerifier();
|
||||||
|
args.SetCodeChallenge(codeVerifier.Challenge());
|
||||||
|
gDiscordClient->Authorize(args, [codeVerifier](auto result, auto code, auto redirectUri) {
|
||||||
|
if (result.Successful())
|
||||||
|
{
|
||||||
|
gDiscordClient->GetToken(APPLICATION_ID, code, codeVerifier.Verifier(), redirectUri, [](discordpp::ClientResult result, std::string accessToken, std::string, discordpp::AuthorizationTokenType, int32_t, std::string) {
|
||||||
|
if (result.Successful())
|
||||||
|
{
|
||||||
|
gDiscordClient->UpdateToken(discordpp::AuthorizationTokenType::Bearer, accessToken, [accessToken](discordpp::ClientResult result) {
|
||||||
|
if (result.Successful())
|
||||||
|
{
|
||||||
|
LLSD authenticator = LLSD::emptyMap();
|
||||||
|
authenticator["token"] = accessToken;
|
||||||
|
gSecAPIHandler->saveCredential(gSecAPIHandler->createCredential("Discord", LLSD::emptyMap(), authenticator), true);
|
||||||
|
gDiscordClient->Connect();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LL_WARNS("Discord") << result.Error() << LL_ENDL;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LL_WARNS("Discord") << result.Error() << LL_ENDL;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LL_WARNS("Discord") << result.Error() << LL_ENDL;
|
||||||
|
gSavedSettings.setBOOL("EnableDiscord", false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gDiscordClient->Disconnect();
|
||||||
|
auto credential = gSecAPIHandler->loadCredential("Discord");
|
||||||
|
if (credential.notNull())
|
||||||
|
{
|
||||||
|
gDiscordClient->RevokeToken(APPLICATION_ID, credential->getAuthenticator()["token"].asString(), [](discordpp::ClientResult result) {
|
||||||
|
if (result.Successful())
|
||||||
|
LL_INFOS("Discord") << "Access token successfully revoked." << LL_ENDL;
|
||||||
|
else
|
||||||
|
LL_WARNS("Discord") << "No access token to revoke." << LL_ENDL;
|
||||||
|
});
|
||||||
|
auto cred = new LLCredential("Discord");
|
||||||
|
gSecAPIHandler->deleteCredential(cred);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LL_WARNS("Discord") << "Credentials are already nonexistent." << LL_ENDL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LLAppViewer::updateDiscordActivity()
|
||||||
|
{
|
||||||
|
LL_PROFILE_ZONE_SCOPED;
|
||||||
|
discordpp::Activity activity;
|
||||||
|
activity.SetType(discordpp::ActivityTypes::Playing);
|
||||||
|
discordpp::ActivityTimestamps timestamps;
|
||||||
|
timestamps.SetStart(gDiscordTimestampsStart);
|
||||||
|
activity.SetTimestamps(timestamps);
|
||||||
|
|
||||||
|
if (gAgent.getID() == LLUUID::null)
|
||||||
|
{
|
||||||
|
gDiscordClient->UpdateRichPresence(activity, [](discordpp::ClientResult) {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LLCachedControl<bool> show_details(gSavedSettings, "ShowDiscordActivityDetails", false);
|
||||||
|
if (show_details)
|
||||||
|
{
|
||||||
|
if (gDiscordActivityDetails.empty())
|
||||||
|
{
|
||||||
|
LLAvatarName av_name;
|
||||||
|
LLAvatarNameCache::get(gAgent.getID(), &av_name);
|
||||||
|
gDiscordActivityDetails = av_name.getUserName();
|
||||||
|
auto displayName = av_name.getDisplayName();
|
||||||
|
if (gDiscordActivityDetails != displayName)
|
||||||
|
gDiscordActivityDetails = displayName + " (" + gDiscordActivityDetails + ")";
|
||||||
|
}
|
||||||
|
activity.SetDetails(gDiscordActivityDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LLCachedControl<bool> show_state(gSavedSettings, "ShowDiscordActivityState", false);
|
||||||
|
if (show_state)
|
||||||
|
{
|
||||||
|
auto agent_pos_region = gAgent.getPositionAgent();
|
||||||
|
S32 pos_x = S32(agent_pos_region.mV[VX] + 0.5f);
|
||||||
|
S32 pos_y = S32(agent_pos_region.mV[VY] + 0.5f);
|
||||||
|
S32 pos_z = S32(agent_pos_region.mV[VZ] + 0.5f);
|
||||||
|
F32 velocity_mag_sq = gAgent.getVelocity().magVecSquared();
|
||||||
|
const F32 FLY_CUTOFF = 6.f;
|
||||||
|
const F32 FLY_CUTOFF_SQ = FLY_CUTOFF * FLY_CUTOFF;
|
||||||
|
const F32 WALK_CUTOFF = 1.5f;
|
||||||
|
const F32 WALK_CUTOFF_SQ = WALK_CUTOFF * WALK_CUTOFF;
|
||||||
|
if (velocity_mag_sq > FLY_CUTOFF_SQ)
|
||||||
|
{
|
||||||
|
pos_x -= pos_x % 4;
|
||||||
|
pos_y -= pos_y % 4;
|
||||||
|
}
|
||||||
|
else if (velocity_mag_sq > WALK_CUTOFF_SQ)
|
||||||
|
{
|
||||||
|
pos_x -= pos_x % 2;
|
||||||
|
pos_y -= pos_y % 2;
|
||||||
|
}
|
||||||
|
auto location = llformat("%s (%d, %d, %d)", gAgent.getRegion()->getName().c_str(), pos_x, pos_y, pos_z);
|
||||||
|
activity.SetState(location);
|
||||||
|
|
||||||
|
discordpp::ActivityParty party;
|
||||||
|
party.SetId(location);
|
||||||
|
party.SetCurrentSize(gDiscordPartyCurrentSize);
|
||||||
|
party.SetMaxSize(gDiscordPartyMaxSize);
|
||||||
|
activity.SetParty(party);
|
||||||
|
}
|
||||||
|
|
||||||
|
gDiscordClient->UpdateRichPresence(activity, [](discordpp::ClientResult) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void LLAppViewer::updateDiscordPartyCurrentSize(int32_t size)
|
||||||
|
{
|
||||||
|
gDiscordPartyCurrentSize = size;
|
||||||
|
updateDiscordActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LLAppViewer::updateDiscordPartyMaxSize(int32_t size)
|
||||||
|
{
|
||||||
|
gDiscordPartyMaxSize = size;
|
||||||
|
updateDiscordActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,7 @@ public:
|
||||||
virtual void forceErrorCoroprocedureCrash();
|
virtual void forceErrorCoroprocedureCrash();
|
||||||
virtual void forceErrorWorkQueueCrash();
|
virtual void forceErrorWorkQueueCrash();
|
||||||
virtual void forceErrorThreadCrash();
|
virtual void forceErrorThreadCrash();
|
||||||
|
virtual void forceExceptionThreadCrash();
|
||||||
|
|
||||||
// The list is found in app_settings/settings_files.xml
|
// The list is found in app_settings/settings_files.xml
|
||||||
// but since they are used explicitly in code,
|
// but since they are used explicitly in code,
|
||||||
|
|
@ -197,11 +198,11 @@ public:
|
||||||
// For thread debugging.
|
// For thread debugging.
|
||||||
// llstartup needs to control init.
|
// llstartup needs to control init.
|
||||||
// llworld, send_agent_pause() also controls pause/resume.
|
// llworld, send_agent_pause() also controls pause/resume.
|
||||||
void initMainloopTimeout(const std::string& state, F32 secs = -1.0f);
|
void initMainloopTimeout(std::string_view state, F32 secs = -1.0f);
|
||||||
void destroyMainloopTimeout();
|
void destroyMainloopTimeout();
|
||||||
void pauseMainloopTimeout();
|
void pauseMainloopTimeout();
|
||||||
void resumeMainloopTimeout(const std::string& state = "", F32 secs = -1.0f);
|
void resumeMainloopTimeout(std::string_view state = "", F32 secs = -1.0f);
|
||||||
void pingMainloopTimeout(const std::string& state, F32 secs = -1.0f);
|
void pingMainloopTimeout(std::string_view state, F32 secs = -1.0f);
|
||||||
|
|
||||||
// Handle the 'login completed' event.
|
// Handle the 'login completed' event.
|
||||||
// *NOTE:Mani Fix this for login abstraction!!
|
// *NOTE:Mani Fix this for login abstraction!!
|
||||||
|
|
@ -250,6 +251,14 @@ public:
|
||||||
// Note: mQuitRequested can be aborted by user.
|
// Note: mQuitRequested can be aborted by user.
|
||||||
void outOfMemorySoftQuit();
|
void outOfMemorySoftQuit();
|
||||||
|
|
||||||
|
#ifdef LL_DISCORD
|
||||||
|
static void initDiscordSocial();
|
||||||
|
static void toggleDiscordIntegration(const LLSD& value);
|
||||||
|
static void updateDiscordActivity();
|
||||||
|
static void updateDiscordPartyCurrentSize(int32_t size);
|
||||||
|
static void updateDiscordPartyMaxSize(int32_t size);
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool initWindow(); // Initialize the viewer's window.
|
virtual bool initWindow(); // Initialize the viewer's window.
|
||||||
virtual void initLoggingAndGetLastDuration(); // Initialize log files, logging system
|
virtual void initLoggingAndGetLastDuration(); // Initialize log files, logging system
|
||||||
|
|
|
||||||
|
|
@ -448,6 +448,7 @@ int APIENTRY WINMAIN(HINSTANCE hInstance,
|
||||||
|
|
||||||
// *FIX: global
|
// *FIX: global
|
||||||
gIconResource = MAKEINTRESOURCE(IDI_LL_ICON);
|
gIconResource = MAKEINTRESOURCE(IDI_LL_ICON);
|
||||||
|
gIconSmallResource = MAKEINTRESOURCE(IDI_LL_ICON_SMALL);
|
||||||
|
|
||||||
LLAppViewerWin32* viewer_app_ptr = new LLAppViewerWin32(ll_convert_wide_to_string(pCmdLine).c_str());
|
LLAppViewerWin32* viewer_app_ptr = new LLAppViewerWin32(ll_convert_wide_to_string(pCmdLine).c_str());
|
||||||
|
|
||||||
|
|
@ -816,6 +817,29 @@ bool LLAppViewerWin32::reportCrashToBugsplat(void* pExcepInfo)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LLAppViewerWin32::initWindow()
|
||||||
|
{
|
||||||
|
// This is a workaround/hotfix for a change in Windows 11 24H2 (and possibly later)
|
||||||
|
// Where the window width and height need to correctly reflect an available FullScreen size
|
||||||
|
if (gSavedSettings.getBOOL("FullScreen"))
|
||||||
|
{
|
||||||
|
DEVMODE dev_mode;
|
||||||
|
::ZeroMemory(&dev_mode, sizeof(DEVMODE));
|
||||||
|
dev_mode.dmSize = sizeof(DEVMODE);
|
||||||
|
if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dev_mode))
|
||||||
|
{
|
||||||
|
gSavedSettings.setU32("WindowWidth", dev_mode.dmPelsWidth);
|
||||||
|
gSavedSettings.setU32("WindowHeight", dev_mode.dmPelsHeight);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LL_WARNS("AppInit") << "Unable to set WindowWidth and WindowHeight for FullScreen mode" << LL_ENDL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LLAppViewer::initWindow();
|
||||||
|
}
|
||||||
|
|
||||||
void LLAppViewerWin32::initLoggingAndGetLastDuration()
|
void LLAppViewerWin32::initLoggingAndGetLastDuration()
|
||||||
{
|
{
|
||||||
LLAppViewer::initLoggingAndGetLastDuration();
|
LLAppViewer::initLoggingAndGetLastDuration();
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ public:
|
||||||
bool reportCrashToBugsplat(void* pExcepInfo) override;
|
bool reportCrashToBugsplat(void* pExcepInfo) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
bool initWindow() override; // Override to initialize the viewer's window.
|
||||||
void initLoggingAndGetLastDuration() override; // Override to clean stack_trace info.
|
void initLoggingAndGetLastDuration() override; // Override to clean stack_trace info.
|
||||||
void initConsole() override; // Initialize OS level debugging console.
|
void initConsole() override; // Initialize OS level debugging console.
|
||||||
bool initHardwareTest() override; // Win32 uses DX9 to test hardware.
|
bool initHardwareTest() override; // Win32 uses DX9 to test hardware.
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ LLFilePicker LLFilePicker::sInstance;
|
||||||
#define XML_FILTER L"XML files (*.xml)\0*.xml\0"
|
#define XML_FILTER L"XML files (*.xml)\0*.xml\0"
|
||||||
#define SLOBJECT_FILTER L"Objects (*.slobject)\0*.slobject\0"
|
#define SLOBJECT_FILTER L"Objects (*.slobject)\0*.slobject\0"
|
||||||
#define RAW_FILTER L"RAW files (*.raw)\0*.raw\0"
|
#define RAW_FILTER L"RAW files (*.raw)\0*.raw\0"
|
||||||
#define MODEL_FILTER L"Model files (*.dae)\0*.dae\0"
|
#define MODEL_FILTER L"Model files (*.dae, *.gltf, *.glb)\0*.dae;*.gltf;*.glb\0"
|
||||||
#define MATERIAL_FILTER L"GLTF Files (*.gltf; *.glb)\0*.gltf;*.glb\0"
|
#define MATERIAL_FILTER L"GLTF Files (*.gltf; *.glb)\0*.gltf;*.glb\0"
|
||||||
#define HDRI_FILTER L"HDRI Files (*.exr)\0*.exr\0"
|
#define HDRI_FILTER L"HDRI Files (*.exr)\0*.exr\0"
|
||||||
#define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0"
|
#define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0"
|
||||||
|
|
@ -217,6 +217,8 @@ bool LLFilePicker::setupFilter(ELoadFilter filter)
|
||||||
break;
|
break;
|
||||||
case FFLOAD_MODEL:
|
case FFLOAD_MODEL:
|
||||||
mOFN.lpstrFilter = MODEL_FILTER \
|
mOFN.lpstrFilter = MODEL_FILTER \
|
||||||
|
COLLADA_FILTER \
|
||||||
|
MATERIAL_FILTER \
|
||||||
L"\0";
|
L"\0";
|
||||||
break;
|
break;
|
||||||
case FFLOAD_MATERIAL:
|
case FFLOAD_MATERIAL:
|
||||||
|
|
@ -671,6 +673,8 @@ std::unique_ptr<std::vector<std::string>> LLFilePicker::navOpenFilterProc(ELoadF
|
||||||
case FFLOAD_HDRI:
|
case FFLOAD_HDRI:
|
||||||
allowedv->push_back("exr");
|
allowedv->push_back("exr");
|
||||||
case FFLOAD_MODEL:
|
case FFLOAD_MODEL:
|
||||||
|
allowedv->push_back("gltf");
|
||||||
|
allowedv->push_back("glb");
|
||||||
case FFLOAD_COLLADA:
|
case FFLOAD_COLLADA:
|
||||||
allowedv->push_back("dae");
|
allowedv->push_back("dae");
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* @file llfloateravatar.h
|
* @file llfloateravatarwelcomepack.cpp
|
||||||
* @author Leyla Farazha
|
* @author Callum Prentice (callum@lindenlab.com)
|
||||||
* @brief floater for the avatar changer
|
* @brief Floater container for the Avatar Welcome Pack we app
|
||||||
*
|
*
|
||||||
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
|
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
|
||||||
* Second Life Viewer Source Code
|
* Second Life Viewer Source Code
|
||||||
|
|
@ -27,17 +27,16 @@
|
||||||
|
|
||||||
#include "llviewerprecompiledheaders.h"
|
#include "llviewerprecompiledheaders.h"
|
||||||
|
|
||||||
#include "llfloateravatar.h"
|
#include "llfloateravatarwelcomepack.h"
|
||||||
#include "lluictrlfactory.h"
|
#include "lluictrlfactory.h"
|
||||||
#include "llmediactrl.h"
|
#include "llmediactrl.h"
|
||||||
|
|
||||||
|
LLFloaterAvatarWelcomePack::LLFloaterAvatarWelcomePack(const LLSD& key)
|
||||||
LLFloaterAvatar::LLFloaterAvatar(const LLSD& key)
|
|
||||||
: LLFloater(key)
|
: LLFloater(key)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
LLFloaterAvatar::~LLFloaterAvatar()
|
LLFloaterAvatarWelcomePack::~LLFloaterAvatarWelcomePack()
|
||||||
{
|
{
|
||||||
if (mAvatarPicker)
|
if (mAvatarPicker)
|
||||||
{
|
{
|
||||||
|
|
@ -47,15 +46,13 @@ LLFloaterAvatar::~LLFloaterAvatar()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LLFloaterAvatar::postBuild()
|
bool LLFloaterAvatarWelcomePack::postBuild()
|
||||||
{
|
{
|
||||||
mAvatarPicker = findChild<LLMediaCtrl>("avatar_picker_contents");
|
mAvatarPicker = findChild<LLMediaCtrl>("avatar_picker_contents");
|
||||||
if (mAvatarPicker)
|
if (mAvatarPicker)
|
||||||
{
|
{
|
||||||
mAvatarPicker->clearCache();
|
mAvatarPicker->clearCache();
|
||||||
}
|
}
|
||||||
enableResizeCtrls(true, true, false);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* @file llfloateravatar.h
|
* @file llfloateravatarwelcomepack.h
|
||||||
* @author Leyla Farazha
|
* @author Callum Prentice (callum@lindenlab.com)
|
||||||
* @brief floater for the avatar changer
|
* @brief Floater container for the Avatar Welcome Pack we app
|
||||||
*
|
*
|
||||||
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
|
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
|
||||||
* Second Life Viewer Source Code
|
* Second Life Viewer Source Code
|
||||||
|
|
@ -25,22 +25,21 @@
|
||||||
* $/LicenseInfo$
|
* $/LicenseInfo$
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef LL_FLOATER_AVATAR_H
|
#pragma once
|
||||||
#define LL_FLOATER_AVATAR_H
|
|
||||||
|
|
||||||
#include "llfloater.h"
|
#include "llfloater.h"
|
||||||
|
|
||||||
class LLMediaCtrl;
|
class LLMediaCtrl;
|
||||||
|
|
||||||
class LLFloaterAvatar:
|
class LLFloaterAvatarWelcomePack:
|
||||||
public LLFloater
|
public LLFloater
|
||||||
{
|
{
|
||||||
friend class LLFloaterReg;
|
friend class LLFloaterReg;
|
||||||
private:
|
|
||||||
LLFloaterAvatar(const LLSD& key);
|
private:
|
||||||
~LLFloaterAvatar();
|
LLFloaterAvatarWelcomePack(const LLSD& key);
|
||||||
|
~LLFloaterAvatarWelcomePack();
|
||||||
bool postBuild() override;
|
bool postBuild() override;
|
||||||
|
|
||||||
LLMediaCtrl* mAvatarPicker;
|
LLMediaCtrl* mAvatarPicker;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -179,7 +179,7 @@ void LLFloaterBvhPreview::setAnimCallbacks()
|
||||||
getChild<LLUICtrl>("ease_out_time")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateEaseOut, this, _1));
|
getChild<LLUICtrl>("ease_out_time")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateEaseOut, this, _1));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map <std::string, std::string> LLFloaterBvhPreview::getJointAliases()
|
std::map<std::string, std::string, std::less<>> LLFloaterBvhPreview::getJointAliases()
|
||||||
{
|
{
|
||||||
LLPointer<LLVOAvatar> av = (LLVOAvatar*)mAnimPreview->getDummyAvatar();
|
LLPointer<LLVOAvatar> av = (LLVOAvatar*)mAnimPreview->getDummyAvatar();
|
||||||
return av->getJointAliases();
|
return av->getJointAliases();
|
||||||
|
|
@ -252,7 +252,7 @@ bool LLFloaterBvhPreview::postBuild()
|
||||||
ELoadStatus load_status = E_ST_OK;
|
ELoadStatus load_status = E_ST_OK;
|
||||||
S32 line_number = 0;
|
S32 line_number = 0;
|
||||||
|
|
||||||
std::map<std::string, std::string> joint_alias_map = getJointAliases();
|
auto joint_alias_map = getJointAliases();
|
||||||
|
|
||||||
loaderp = new LLBVHLoader(file_buffer, load_status, line_number, joint_alias_map);
|
loaderp = new LLBVHLoader(file_buffer, load_status, line_number, joint_alias_map);
|
||||||
std::string status = getString(STATUS[load_status]);
|
std::string status = getString(STATUS[load_status]);
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ public:
|
||||||
S32 status, LLExtStat ext_status);
|
S32 status, LLExtStat ext_status);
|
||||||
private:
|
private:
|
||||||
void setAnimCallbacks() ;
|
void setAnimCallbacks() ;
|
||||||
std::map <std::string, std::string> getJointAliases();
|
std::map<std::string, std::string, std::less<>> getJointAliases();
|
||||||
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
#include "llimagetga.h"
|
#include "llimagetga.h"
|
||||||
#include "llimagejpeg.h"
|
#include "llimagejpeg.h"
|
||||||
#include "llimagepng.h"
|
#include "llimagepng.h"
|
||||||
|
#include "llimagej2c.h"
|
||||||
|
|
||||||
#include "llagent.h"
|
#include "llagent.h"
|
||||||
#include "llagentbenefits.h"
|
#include "llagentbenefits.h"
|
||||||
|
|
@ -43,6 +44,10 @@
|
||||||
#include "llrender.h"
|
#include "llrender.h"
|
||||||
#include "llface.h"
|
#include "llface.h"
|
||||||
#include "llfocusmgr.h"
|
#include "llfocusmgr.h"
|
||||||
|
#include "llfilesystem.h"
|
||||||
|
#include "llfloaterperms.h"
|
||||||
|
#include "llnotificationsutil.h"
|
||||||
|
#include "llstatusbar.h" // can_afford_transaction()
|
||||||
#include "lltextbox.h"
|
#include "lltextbox.h"
|
||||||
#include "lltoolmgr.h"
|
#include "lltoolmgr.h"
|
||||||
#include "llui.h"
|
#include "llui.h"
|
||||||
|
|
@ -52,6 +57,7 @@
|
||||||
#include "llvoavatar.h"
|
#include "llvoavatar.h"
|
||||||
#include "pipeline.h"
|
#include "pipeline.h"
|
||||||
#include "lluictrlfactory.h"
|
#include "lluictrlfactory.h"
|
||||||
|
#include "llviewermenufile.h" // upload_new_resource()
|
||||||
#include "llviewershadermgr.h"
|
#include "llviewershadermgr.h"
|
||||||
#include "llviewertexturelist.h"
|
#include "llviewertexturelist.h"
|
||||||
#include "llstring.h"
|
#include "llstring.h"
|
||||||
|
|
@ -140,7 +146,7 @@ bool LLFloaterImagePreview::postBuild()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getChild<LLUICtrl>("ok_btn")->setCommitCallback(boost::bind(&LLFloaterNameDesc::onBtnOK, this));
|
getChild<LLUICtrl>("ok_btn")->setCommitCallback(boost::bind(&LLFloaterImagePreview::onBtnOK, this));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -243,6 +249,59 @@ void LLFloaterImagePreview::clearAllPreviewTextures()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// onBtnOK()
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void LLFloaterImagePreview::onBtnOK()
|
||||||
|
{
|
||||||
|
getChildView("ok_btn")->setEnabled(false); // don't allow inadvertent extra uploads
|
||||||
|
|
||||||
|
S32 expected_upload_cost = getExpectedUploadCost();
|
||||||
|
if (can_afford_transaction(expected_upload_cost))
|
||||||
|
{
|
||||||
|
LL_INFOS() << "saving texture: " << mRawImagep->getWidth() << "x" << mRawImagep->getHeight() << LL_ENDL;
|
||||||
|
// gen a new uuid for this asset
|
||||||
|
LLTransactionID tid;
|
||||||
|
tid.generate();
|
||||||
|
LLAssetID new_asset_id = tid.makeAssetID(gAgent.getSecureSessionID());
|
||||||
|
|
||||||
|
LLPointer<LLImageJ2C> formatted = new LLImageJ2C;
|
||||||
|
|
||||||
|
if (formatted->encode(mRawImagep, 0.0f))
|
||||||
|
{
|
||||||
|
LLFileSystem fmt_file(new_asset_id, LLAssetType::AT_TEXTURE, LLFileSystem::WRITE);
|
||||||
|
fmt_file.write(formatted->getData(), formatted->getDataSize());
|
||||||
|
|
||||||
|
LLResourceUploadInfo::ptr_t assetUploadInfo(new LLResourceUploadInfo(
|
||||||
|
tid, LLAssetType::AT_TEXTURE,
|
||||||
|
getChild<LLUICtrl>("name_form")->getValue().asString(),
|
||||||
|
getChild<LLUICtrl>("description_form")->getValue().asString(),
|
||||||
|
0,
|
||||||
|
LLFolderType::FT_NONE, LLInventoryType::IT_NONE,
|
||||||
|
LLFloaterPerms::getNextOwnerPerms("Uploads"),
|
||||||
|
LLFloaterPerms::getGroupPerms("Uploads"),
|
||||||
|
LLFloaterPerms::getEveryonePerms("Uploads"),
|
||||||
|
expected_upload_cost
|
||||||
|
));
|
||||||
|
|
||||||
|
upload_new_resource(assetUploadInfo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LLNotificationsUtil::add("ErrorEncodingImage");
|
||||||
|
LL_WARNS() << "Error encoding image" << LL_ENDL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LLSD args;
|
||||||
|
args["COST"] = llformat("%d", expected_upload_cost);
|
||||||
|
LLNotificationsUtil::add("ErrorCannotAffordUpload", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeFloater(false);
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// draw()
|
// draw()
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
@ -364,19 +423,6 @@ bool LLFloaterImagePreview::loadImage(const std::string& src_filename)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
S32 max_width = gSavedSettings.getS32("max_texture_dimension_X");
|
|
||||||
S32 max_height = gSavedSettings.getS32("max_texture_dimension_Y");
|
|
||||||
|
|
||||||
if ((image_info.getWidth() > max_width) || (image_info.getHeight() > max_height))
|
|
||||||
{
|
|
||||||
LLStringUtil::format_map_t args;
|
|
||||||
args["WIDTH"] = llformat("%d", max_width);
|
|
||||||
args["HEIGHT"] = llformat("%d", max_height);
|
|
||||||
|
|
||||||
mImageLoadError = LLTrans::getString("texture_load_dimensions_error", args);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the image
|
// Load the image
|
||||||
LLPointer<LLImageFormatted> image = LLImageFormatted::createFromType(codec);
|
LLPointer<LLImageFormatted> image = LLImageFormatted::createFromType(codec);
|
||||||
if (image.isNull())
|
if (image.isNull())
|
||||||
|
|
@ -399,6 +445,46 @@ bool LLFloaterImagePreview::loadImage(const std::string& src_filename)
|
||||||
image->setLastError("Image files with less than 3 or more than 4 components are not supported.");
|
image->setLastError("Image files with less than 3 or more than 4 components are not supported.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// Downscale images to fit the max_texture_dimensions_*
|
||||||
|
S32 max_width = gSavedSettings.getS32("max_texture_dimension_X");
|
||||||
|
S32 max_height = gSavedSettings.getS32("max_texture_dimension_Y");
|
||||||
|
|
||||||
|
S32 orig_width = raw_image->getWidth();
|
||||||
|
S32 orig_height = raw_image->getHeight();
|
||||||
|
|
||||||
|
if (orig_width > max_width || orig_height > max_height)
|
||||||
|
{
|
||||||
|
// Calculate scale factors
|
||||||
|
F32 width_scale = (F32)max_width / (F32)orig_width;
|
||||||
|
F32 height_scale = (F32)max_height / (F32)orig_height;
|
||||||
|
F32 scale = llmin(width_scale, height_scale);
|
||||||
|
|
||||||
|
// Calculate new dimensions, preserving aspect ratio
|
||||||
|
S32 new_width = LLImageRaw::contractDimToPowerOfTwo(
|
||||||
|
llclamp((S32)llroundf(orig_width * scale), 4, max_width)
|
||||||
|
);
|
||||||
|
S32 new_height = LLImageRaw::contractDimToPowerOfTwo(
|
||||||
|
llclamp((S32)llroundf(orig_height * scale), 4, max_height)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!raw_image->scale(new_width, new_height))
|
||||||
|
{
|
||||||
|
LL_WARNS() << "Failed to scale image from "
|
||||||
|
<< orig_width << "x" << orig_height
|
||||||
|
<< " to " << new_width << "x" << new_height << LL_ENDL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inform the resident about the resized image
|
||||||
|
LLSD subs;
|
||||||
|
subs["[ORIGINAL_WIDTH]"] = orig_width;
|
||||||
|
subs["[ORIGINAL_HEIGHT]"] = orig_height;
|
||||||
|
subs["[NEW_WIDTH]"] = new_width;
|
||||||
|
subs["[NEW_HEIGHT]"] = new_height;
|
||||||
|
subs["[MAX_WIDTH]"] = max_width;
|
||||||
|
subs["[MAX_HEIGHT]"] = max_height;
|
||||||
|
LLNotificationsUtil::add("ImageUploadResized", subs);
|
||||||
|
}
|
||||||
|
|
||||||
raw_image->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT);
|
raw_image->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT);
|
||||||
mRawImagep = raw_image;
|
mRawImagep = raw_image;
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,8 @@ public:
|
||||||
|
|
||||||
void clearAllPreviewTextures();
|
void clearAllPreviewTextures();
|
||||||
|
|
||||||
|
void onBtnOK();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void onPreviewTypeCommit(LLUICtrl*,void*);
|
static void onPreviewTypeCommit(LLUICtrl*,void*);
|
||||||
void draw() override;
|
void draw() override;
|
||||||
|
|
|
||||||
|
|
@ -180,8 +180,15 @@ void LLFloaterMediaSettings::onClose(bool app_quitting)
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//static
|
//static
|
||||||
void LLFloaterMediaSettings::initValues( const LLSD& media_settings, bool editable )
|
void LLFloaterMediaSettings::initValues( const LLSD& media_settings, bool editable, bool has_media_info, bool multiple_media, bool multiple_valid_media)
|
||||||
{
|
{
|
||||||
|
if (!sInstance)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sInstance->mIdenticalHasMediaInfo = has_media_info;
|
||||||
|
sInstance->mMultipleMedia = multiple_media;
|
||||||
|
sInstance->mMultipleValidMedia = multiple_valid_media;
|
||||||
if (sInstance->hasFocus()) return;
|
if (sInstance->hasFocus()) return;
|
||||||
|
|
||||||
// Clear values
|
// Clear values
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ public:
|
||||||
static LLFloaterMediaSettings* getInstance();
|
static LLFloaterMediaSettings* getInstance();
|
||||||
static bool instanceExists();
|
static bool instanceExists();
|
||||||
static void apply();
|
static void apply();
|
||||||
static void initValues( const LLSD& media_settings , bool editable);
|
static void initValues( const LLSD& media_settings , bool editable, bool has_media_info, bool multiple_media, bool multiple_valid_media);
|
||||||
static void clearValues( bool editable);
|
static void clearValues( bool editable);
|
||||||
|
|
||||||
LLPanelMediaSettingsSecurity* getPanelSecurity(){return mPanelMediaSettingsSecurity;};
|
LLPanelMediaSettingsSecurity* getPanelSecurity(){return mPanelMediaSettingsSecurity;};
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@
|
||||||
#include "llcallbacklist.h"
|
#include "llcallbacklist.h"
|
||||||
#include "llviewertexteditor.h"
|
#include "llviewertexteditor.h"
|
||||||
#include "llviewernetwork.h"
|
#include "llviewernetwork.h"
|
||||||
|
#include "llmaterialeditor.h"
|
||||||
|
|
||||||
|
|
||||||
//static
|
//static
|
||||||
|
|
@ -619,11 +620,9 @@ void LLFloaterModelPreview::onJointListSelection()
|
||||||
LLPanel *panel = mTabContainer->getPanelByName("rigging_panel");
|
LLPanel *panel = mTabContainer->getPanelByName("rigging_panel");
|
||||||
LLScrollListCtrl *joints_list = panel->getChild<LLScrollListCtrl>("joints_list");
|
LLScrollListCtrl *joints_list = panel->getChild<LLScrollListCtrl>("joints_list");
|
||||||
LLScrollListCtrl *joints_pos = panel->getChild<LLScrollListCtrl>("pos_overrides_list");
|
LLScrollListCtrl *joints_pos = panel->getChild<LLScrollListCtrl>("pos_overrides_list");
|
||||||
LLScrollListCtrl *joints_scale = panel->getChild<LLScrollListCtrl>("scale_overrides_list");
|
|
||||||
LLTextBox *joint_pos_descr = panel->getChild<LLTextBox>("pos_overrides_descr");
|
LLTextBox *joint_pos_descr = panel->getChild<LLTextBox>("pos_overrides_descr");
|
||||||
|
|
||||||
joints_pos->deleteAllItems();
|
joints_pos->deleteAllItems();
|
||||||
joints_scale->deleteAllItems();
|
|
||||||
|
|
||||||
LLScrollListItem *selected = joints_list->getFirstSelected();
|
LLScrollListItem *selected = joints_list->getFirstSelected();
|
||||||
if (selected)
|
if (selected)
|
||||||
|
|
@ -1341,26 +1340,26 @@ void LLFloaterModelPreview::addStringToLog(const std::string& message, const LLS
|
||||||
{
|
{
|
||||||
std::string str;
|
std::string str;
|
||||||
switch (lod)
|
switch (lod)
|
||||||
{
|
{
|
||||||
case LLModel::LOD_IMPOSTOR: str = "LOD0 "; break;
|
case LLModel::LOD_IMPOSTOR: str = "LOD0 "; break;
|
||||||
case LLModel::LOD_LOW: str = "LOD1 "; break;
|
case LLModel::LOD_LOW: str = "LOD1 "; break;
|
||||||
case LLModel::LOD_MEDIUM: str = "LOD2 "; break;
|
case LLModel::LOD_MEDIUM: str = "LOD2 "; break;
|
||||||
case LLModel::LOD_PHYSICS: str = "PHYS "; break;
|
case LLModel::LOD_PHYSICS: str = "PHYS "; break;
|
||||||
case LLModel::LOD_HIGH: str = "LOD3 "; break;
|
case LLModel::LOD_HIGH: str = "LOD3 "; break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
LLStringUtil::format_map_t args_msg;
|
LLStringUtil::format_map_t args_msg;
|
||||||
LLSD::map_const_iterator iter = args.beginMap();
|
LLSD::map_const_iterator iter = args.beginMap();
|
||||||
LLSD::map_const_iterator end = args.endMap();
|
LLSD::map_const_iterator end = args.endMap();
|
||||||
for (; iter != end; ++iter)
|
for (; iter != end; ++iter)
|
||||||
{
|
{
|
||||||
args_msg[iter->first] = iter->second.asString();
|
args_msg[iter->first] = iter->second.asString();
|
||||||
}
|
}
|
||||||
str += sInstance->getString(message, args_msg);
|
str += sInstance->getString(message, args_msg);
|
||||||
sInstance->addStringToLogTab(str, flash);
|
sInstance->addStringToLogTab(str, flash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void LLFloaterModelPreview::addStringToLog(const std::string& str, bool flash)
|
void LLFloaterModelPreview::addStringToLog(const std::string& str, bool flash)
|
||||||
|
|
@ -1488,7 +1487,7 @@ void LLFloaterModelPreview::updateAvatarTab(bool highlight_overrides)
|
||||||
{
|
{
|
||||||
// Populate table
|
// Populate table
|
||||||
|
|
||||||
std::map<std::string, std::string> joint_alias_map;
|
std::map<std::string, std::string, std::less<>> joint_alias_map;
|
||||||
mModelPreview->getJointAliases(joint_alias_map);
|
mModelPreview->getJointAliases(joint_alias_map);
|
||||||
|
|
||||||
S32 conflicts = 0;
|
S32 conflicts = 0;
|
||||||
|
|
|
||||||
|
|
@ -366,6 +366,11 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key)
|
||||||
mCommitCallbackRegistrar.add("Pref.ClearLog", boost::bind(&LLConversationLog::onClearLog, &LLConversationLog::instance()));
|
mCommitCallbackRegistrar.add("Pref.ClearLog", boost::bind(&LLConversationLog::onClearLog, &LLConversationLog::instance()));
|
||||||
mCommitCallbackRegistrar.add("Pref.DeleteTranscripts", boost::bind(&LLFloaterPreference::onDeleteTranscripts, this));
|
mCommitCallbackRegistrar.add("Pref.DeleteTranscripts", boost::bind(&LLFloaterPreference::onDeleteTranscripts, this));
|
||||||
mCommitCallbackRegistrar.add("UpdateFilter", boost::bind(&LLFloaterPreference::onUpdateFilterTerm, this, false)); // <FS:ND/> Hook up for filtering
|
mCommitCallbackRegistrar.add("UpdateFilter", boost::bind(&LLFloaterPreference::onUpdateFilterTerm, this, false)); // <FS:ND/> Hook up for filtering
|
||||||
|
#ifdef LL_DISCORD
|
||||||
|
gSavedSettings.getControl("EnableDiscord")->getCommitSignal()->connect(boost::bind(&LLAppViewer::toggleDiscordIntegration, _2));
|
||||||
|
gSavedSettings.getControl("ShowDiscordActivityDetails")->getCommitSignal()->connect(boost::bind(&LLAppViewer::updateDiscordActivity));
|
||||||
|
gSavedSettings.getControl("ShowDiscordActivityState")->getCommitSignal()->connect(boost::bind(&LLAppViewer::updateDiscordActivity));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLFloaterPreference::processProperties( void* pData, EAvatarProcessorType type )
|
void LLFloaterPreference::processProperties( void* pData, EAvatarProcessorType type )
|
||||||
|
|
@ -523,6 +528,10 @@ bool LLFloaterPreference::postBuild()
|
||||||
getChild<LLComboBox>("language_combobox")->add("System default", LLSD("default"), ADD_TOP, true);
|
getChild<LLComboBox>("language_combobox")->add("System default", LLSD("default"), ADD_TOP, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef LL_DISCORD
|
||||||
|
getChild<LLTabContainer>("privacy_tab_container")->childDisable("privacy_preferences_discord");
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -207,14 +207,14 @@ void LLFloaterSettingsDebug::updateControl(LLControlVariable* controlp)
|
||||||
mSettingNameText->setToolTip(controlp->getName());
|
mSettingNameText->setToolTip(controlp->getName());
|
||||||
mComment->setVisible(true);
|
mComment->setVisible(true);
|
||||||
|
|
||||||
std::string old_text = mComment->getText();
|
|
||||||
std::string new_text = controlp->getComment();
|
std::string new_text = controlp->getComment();
|
||||||
// Don't setText if not nessesary, it will reset scroll
|
// Don't setText if not nessesary, it will reset scroll
|
||||||
// This is a debug UI that reads from xml, there might
|
// This is a debug UI that reads from xml, there might
|
||||||
// be use cases where comment changes, but not the name
|
// be use cases where comment changes, but not the name
|
||||||
if (old_text != new_text)
|
if (mOldText != new_text)
|
||||||
{
|
{
|
||||||
mComment->setText(controlp->getComment());
|
mComment->setText(controlp->getComment());
|
||||||
|
mOldText = new_text;
|
||||||
}
|
}
|
||||||
|
|
||||||
mValSpinner1->setMaxValue(F32_MAX);
|
mValSpinner1->setMaxValue(F32_MAX);
|
||||||
|
|
@ -467,6 +467,7 @@ void LLFloaterSettingsDebug::updateControl(LLControlVariable* controlp)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
mComment->setText(std::string("unknown"));
|
mComment->setText(std::string("unknown"));
|
||||||
|
mOldText = "unknown";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ protected:
|
||||||
LLColorSwatchCtrl* mColorSwatch = nullptr;
|
LLColorSwatchCtrl* mColorSwatch = nullptr;
|
||||||
|
|
||||||
std::string mSearchFilter;
|
std::string mSearchFilter;
|
||||||
|
std::string mOldText;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //LLFLOATERDEBUGSETTINGS_H
|
#endif //LLFLOATERDEBUGSETTINGS_H
|
||||||
|
|
|
||||||
|
|
@ -1042,7 +1042,9 @@ void LLFloaterUIPreview::getExecutablePath(const std::vector<std::string>& filen
|
||||||
{
|
{
|
||||||
CFStringRef executable_cfstr = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, CFSTR("CFBundleExecutable")); // get the name of the actual executable (e.g. TextEdit or firefox-bin)
|
CFStringRef executable_cfstr = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, CFSTR("CFBundleExecutable")); // get the name of the actual executable (e.g. TextEdit or firefox-bin)
|
||||||
int max_file_length = 256; // (max file name length is 255 in OSX)
|
int max_file_length = 256; // (max file name length is 255 in OSX)
|
||||||
char executable_buf[max_file_length];
|
|
||||||
|
// Xcode 26: VLAs are a clang extension. Just create the buffer and delete it after.
|
||||||
|
char *executable_buf = new char [max_file_length];
|
||||||
if(CFStringGetCString(executable_cfstr, executable_buf, max_file_length, kCFStringEncodingMacRoman)) // convert CFStringRef to char*
|
if(CFStringGetCString(executable_cfstr, executable_buf, max_file_length, kCFStringEncodingMacRoman)) // convert CFStringRef to char*
|
||||||
{
|
{
|
||||||
executable_path += std::string("/Contents/MacOS/") + std::string(executable_buf); // append path to executable directory and then executable name to exec path
|
executable_path += std::string("/Contents/MacOS/") + std::string(executable_buf); // append path to executable directory and then executable name to exec path
|
||||||
|
|
@ -1052,6 +1054,7 @@ void LLFloaterUIPreview::getExecutablePath(const std::vector<std::string>& filen
|
||||||
std::string warning = "Unable to get CString from CFString for executable path";
|
std::string warning = "Unable to get CString from CFString for executable path";
|
||||||
popupAndPrintWarning(warning);
|
popupAndPrintWarning(warning);
|
||||||
}
|
}
|
||||||
|
delete [] executable_buf;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,52 @@ public:
|
||||||
};
|
};
|
||||||
LLWorldMapHandler gWorldMapHandler;
|
LLWorldMapHandler gWorldMapHandler;
|
||||||
|
|
||||||
|
// handle secondlife:///app/worldmap_global/{GLOBAL_COORDS} URLs
|
||||||
|
class LLWorldMapGlobalHandler : public LLCommandHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LLWorldMapGlobalHandler() : LLCommandHandler("worldmap_global", UNTRUSTED_THROTTLE)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual bool canHandleUntrusted(
|
||||||
|
const LLSD& params,
|
||||||
|
const LLSD& query_map,
|
||||||
|
LLMediaCtrl* web,
|
||||||
|
const std::string& nav_type)
|
||||||
|
{
|
||||||
|
if (nav_type == NAV_TYPE_CLICKED
|
||||||
|
|| nav_type == NAV_TYPE_EXTERNAL)
|
||||||
|
{
|
||||||
|
// NAV_TYPE_EXTERNAL will be throttled
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handle(const LLSD& params,
|
||||||
|
const LLSD& query_map,
|
||||||
|
const std::string& grid,
|
||||||
|
LLMediaCtrl* web)
|
||||||
|
{
|
||||||
|
if (params.size() < 3)
|
||||||
|
{
|
||||||
|
LL_WARNS() << "Correct global coordinates are not provided." << LL_ENDL;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LLVector3d parcel_global_pos = LLVector3d(params[0].asInteger(), params[1].asInteger(), params[2].asInteger());
|
||||||
|
LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance();
|
||||||
|
if (!parcel_global_pos.isExactlyZero() && worldmap_instance)
|
||||||
|
{
|
||||||
|
worldmap_instance->trackLocation(parcel_global_pos);
|
||||||
|
LLFloaterReg::showInstance("world_map", "center");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
LLWorldMapGlobalHandler gWorldMapGlobalHandler;
|
||||||
|
|
||||||
// SocialMap handler secondlife:///app/maptrackavatar/id
|
// SocialMap handler secondlife:///app/maptrackavatar/id
|
||||||
class LLMapTrackAvatarHandler : public LLCommandHandler
|
class LLMapTrackAvatarHandler : public LLCommandHandler
|
||||||
{
|
{
|
||||||
|
|
@ -325,11 +371,9 @@ LLFloaterWorldMap::LLFloaterWorldMap(const LLSD& key)
|
||||||
mWaitingForTracker(false),
|
mWaitingForTracker(false),
|
||||||
mIsClosing(false),
|
mIsClosing(false),
|
||||||
mSetToUserPosition(true),
|
mSetToUserPosition(true),
|
||||||
|
mProcessingSearchUpdate(false),
|
||||||
mTrackedLocation(0.0,0.0,0.0),
|
mTrackedLocation(0.0,0.0,0.0),
|
||||||
mTrackedStatus(LLTracker::TRACKING_NOTHING),
|
mTrackedStatus(LLTracker::TRACKING_NOTHING),
|
||||||
mListFriendCombo(nullptr),
|
|
||||||
mListLandmarkCombo(nullptr),
|
|
||||||
mListSearchResults(nullptr),
|
|
||||||
mParcelInfoObserver(nullptr),
|
mParcelInfoObserver(nullptr),
|
||||||
mShowParcelInfo(false)
|
mShowParcelInfo(false)
|
||||||
{
|
{
|
||||||
|
|
@ -341,7 +385,7 @@ LLFloaterWorldMap::LLFloaterWorldMap(const LLSD& key)
|
||||||
mCommitCallbackRegistrar.add("WMap.Location", boost::bind(&LLFloaterWorldMap::onLocationCommit, this));
|
mCommitCallbackRegistrar.add("WMap.Location", boost::bind(&LLFloaterWorldMap::onLocationCommit, this));
|
||||||
mCommitCallbackRegistrar.add("WMap.AvatarCombo", boost::bind(&LLFloaterWorldMap::onAvatarComboCommit, this));
|
mCommitCallbackRegistrar.add("WMap.AvatarCombo", boost::bind(&LLFloaterWorldMap::onAvatarComboCommit, this));
|
||||||
mCommitCallbackRegistrar.add("WMap.Landmark", boost::bind(&LLFloaterWorldMap::onLandmarkComboCommit, this));
|
mCommitCallbackRegistrar.add("WMap.Landmark", boost::bind(&LLFloaterWorldMap::onLandmarkComboCommit, this));
|
||||||
mCommitCallbackRegistrar.add("WMap.SearchResult", boost::bind(&LLFloaterWorldMap::onCommitSearchResult, this));
|
mCommitCallbackRegistrar.add("WMap.SearchResult", [this](LLUICtrl* ctrl, const LLSD& data) { LLFloaterWorldMap::onCommitSearchResult(false); });
|
||||||
mCommitCallbackRegistrar.add("WMap.GoHome", boost::bind(&LLFloaterWorldMap::onGoHome, this));
|
mCommitCallbackRegistrar.add("WMap.GoHome", boost::bind(&LLFloaterWorldMap::onGoHome, this));
|
||||||
mCommitCallbackRegistrar.add("WMap.Teleport", boost::bind(&LLFloaterWorldMap::onClickTeleportBtn, this));
|
mCommitCallbackRegistrar.add("WMap.Teleport", boost::bind(&LLFloaterWorldMap::onClickTeleportBtn, this));
|
||||||
mCommitCallbackRegistrar.add("WMap.ShowTarget", boost::bind(&LLFloaterWorldMap::onShowTargetBtn, this));
|
mCommitCallbackRegistrar.add("WMap.ShowTarget", boost::bind(&LLFloaterWorldMap::onShowTargetBtn, this));
|
||||||
|
|
@ -383,32 +427,33 @@ bool LLFloaterWorldMap::postBuild()
|
||||||
mTeleportCoordSpinY = getChild<LLUICtrl>("teleport_coordinate_y");
|
mTeleportCoordSpinY = getChild<LLUICtrl>("teleport_coordinate_y");
|
||||||
mTeleportCoordSpinZ = getChild<LLUICtrl>("teleport_coordinate_z");
|
mTeleportCoordSpinZ = getChild<LLUICtrl>("teleport_coordinate_z");
|
||||||
|
|
||||||
LLComboBox *avatar_combo = getChild<LLComboBox>("friend combo");
|
mFriendCombo = getChild<LLComboBox>("friend combo");
|
||||||
avatar_combo->selectFirstItem();
|
mFriendCombo->selectFirstItem();
|
||||||
avatar_combo->setPrearrangeCallback( boost::bind(&LLFloaterWorldMap::onAvatarComboPrearrange, this) );
|
mFriendCombo->setPrearrangeCallback(boost::bind(&LLFloaterWorldMap::onAvatarComboPrearrange, this));
|
||||||
avatar_combo->setTextChangedCallback( boost::bind(&LLFloaterWorldMap::onComboTextEntry, this) );
|
mFriendCombo->setTextChangedCallback(boost::bind(&LLFloaterWorldMap::onComboTextEntry, this));
|
||||||
mListFriendCombo = dynamic_cast<LLCtrlListInterface *>(avatar_combo);
|
|
||||||
|
|
||||||
mLocationEditor = getChild<LLSearchEditor>("location");
|
mLocationEditor = getChild<LLSearchEditor>("location");
|
||||||
mLocationEditor->setFocusChangedCallback(boost::bind(&LLFloaterWorldMap::onLocationFocusChanged, this, _1));
|
mLocationEditor->setFocusChangedCallback(boost::bind(&LLFloaterWorldMap::onLocationFocusChanged, this, _1));
|
||||||
mLocationEditor->setTextChangedCallback( boost::bind(&LLFloaterWorldMap::onSearchTextEntry, this));
|
mLocationEditor->setTextChangedCallback(boost::bind(&LLFloaterWorldMap::onSearchTextEntry, this));
|
||||||
|
|
||||||
getChild<LLScrollListCtrl>("search_results")->setDoubleClickCallback( boost::bind(&LLFloaterWorldMap::onClickTeleportBtn, this));
|
mSearchResults = getChild<LLScrollListCtrl>("search_results");
|
||||||
mListSearchResults = childGetListInterface("search_results");
|
mSearchResults->setDoubleClickCallback(boost::bind(&LLFloaterWorldMap::onClickTeleportBtn, this));
|
||||||
|
|
||||||
LLComboBox *landmark_combo = getChild<LLComboBox>( "landmark combo");
|
mLandmarkCombo = getChild<LLComboBox>("landmark combo");
|
||||||
landmark_combo->selectFirstItem();
|
mLandmarkCombo->selectFirstItem();
|
||||||
landmark_combo->setPrearrangeCallback( boost::bind(&LLFloaterWorldMap::onLandmarkComboPrearrange, this) );
|
mLandmarkCombo->setPrearrangeCallback(boost::bind(&LLFloaterWorldMap::onLandmarkComboPrearrange, this));
|
||||||
landmark_combo->setTextChangedCallback( boost::bind(&LLFloaterWorldMap::onComboTextEntry, this) );
|
mLandmarkCombo->setTextChangedCallback(boost::bind(&LLFloaterWorldMap::onComboTextEntry, this));
|
||||||
mListLandmarkCombo = dynamic_cast<LLCtrlListInterface *>(landmark_combo);
|
|
||||||
|
|
||||||
mZoomSlider = getChild<LLSliderCtrl>("zoom slider");
|
mZoomSlider = getChild<LLSliderCtrl>("zoom slider");
|
||||||
F32 slider_zoom = mMapView->getZoom();
|
F32 slider_zoom = mMapView->getZoom();
|
||||||
mZoomSlider->setValue(slider_zoom);
|
mZoomSlider->setValue(slider_zoom);
|
||||||
|
|
||||||
|
mTrackCtrlsPanel = getChild<LLPanel>("layout_panel_4");
|
||||||
|
mSearchButton = getChild<LLButton>("DoSearch");
|
||||||
|
|
||||||
getChild<LLPanel>("expand_btn_panel")->setMouseDownCallback(boost::bind(&LLFloaterWorldMap::onExpandCollapseBtn, this));
|
getChild<LLPanel>("expand_btn_panel")->setMouseDownCallback(boost::bind(&LLFloaterWorldMap::onExpandCollapseBtn, this));
|
||||||
|
|
||||||
setDefaultBtn(NULL);
|
mTrackCtrlsPanel->setDefaultBtn(nullptr);
|
||||||
|
|
||||||
onChangeMaturity();
|
onChangeMaturity();
|
||||||
|
|
||||||
|
|
@ -608,7 +653,6 @@ void LLFloaterWorldMap::draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
mTeleportButton->setEnabled((bool)tracking_status);
|
mTeleportButton->setEnabled((bool)tracking_status);
|
||||||
// getChildView("Clear")->setEnabled((bool)tracking_status);
|
|
||||||
mShowDestinationButton->setEnabled((bool)tracking_status || LLWorldMap::getInstance()->isTracking());
|
mShowDestinationButton->setEnabled((bool)tracking_status || LLWorldMap::getInstance()->isTracking());
|
||||||
mCopySlurlButton->setEnabled((mSLURL.isValid()) );
|
mCopySlurlButton->setEnabled((mSLURL.isValid()) );
|
||||||
|
|
||||||
|
|
@ -700,19 +744,17 @@ void LLFloaterWorldMap::requestParcelInfo(const LLVector3d& pos_global, const LL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLFloaterWorldMap::trackAvatar( const LLUUID& avatar_id, const std::string& name )
|
void LLFloaterWorldMap::trackAvatar(const LLUUID& avatar_id, const std::string& name)
|
||||||
{
|
{
|
||||||
mShowParcelInfo = false;
|
mShowParcelInfo = false;
|
||||||
LLCtrlSelectionInterface *iface = childGetSelectionInterface("friend combo");
|
|
||||||
if (!iface) return;
|
|
||||||
|
|
||||||
buildAvatarIDList();
|
buildAvatarIDList();
|
||||||
if(iface->setCurrentByID(avatar_id) || gAgent.isGodlike())
|
if (mFriendCombo->setCurrentByID(avatar_id) || gAgent.isGodlike())
|
||||||
{
|
{
|
||||||
// *HACK: Adjust Z values automatically for liaisons & gods so
|
// *HACK: Adjust Z values automatically for liaisons & gods so
|
||||||
// they swoop down when they click on the map. Requested
|
// they swoop down when they click on the map. Requested
|
||||||
// convenience.
|
// convenience.
|
||||||
if(gAgent.isGodlike())
|
if (gAgent.isGodlike())
|
||||||
{
|
{
|
||||||
mTeleportCoordSpinZ->setValue(LLSD(200.f));
|
mTeleportCoordSpinZ->setValue(LLSD(200.f));
|
||||||
}
|
}
|
||||||
|
|
@ -728,52 +770,45 @@ void LLFloaterWorldMap::trackAvatar( const LLUUID& avatar_id, const std::string&
|
||||||
{
|
{
|
||||||
LLTracker::stopTracking(false);
|
LLTracker::stopTracking(false);
|
||||||
}
|
}
|
||||||
setDefaultBtn("Teleport");
|
mTrackCtrlsPanel->setDefaultBtn(mTeleportButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLFloaterWorldMap::trackLandmark( const LLUUID& landmark_item_id )
|
void LLFloaterWorldMap::trackLandmark(const LLUUID& landmark_item_id)
|
||||||
{
|
{
|
||||||
mShowParcelInfo = false;
|
mShowParcelInfo = false;
|
||||||
LLCtrlSelectionInterface *iface = childGetSelectionInterface("landmark combo");
|
|
||||||
if (!iface) return;
|
|
||||||
|
|
||||||
buildLandmarkIDLists();
|
buildLandmarkIDLists();
|
||||||
bool found = false;
|
bool found = false;
|
||||||
S32 idx;
|
S32 idx;
|
||||||
for (idx = 0; idx < mLandmarkItemIDList.size(); idx++)
|
for (idx = 0; idx < mLandmarkItemIDList.size(); idx++)
|
||||||
{
|
{
|
||||||
if ( mLandmarkItemIDList.at(idx) == landmark_item_id)
|
if (mLandmarkItemIDList.at(idx) == landmark_item_id)
|
||||||
{
|
{
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (found && iface->setCurrentByID( landmark_item_id ) )
|
if (found && mLandmarkCombo->setCurrentByID(landmark_item_id))
|
||||||
{
|
{
|
||||||
LLUUID asset_id = mLandmarkAssetIDList.at( idx );
|
LLUUID asset_id = mLandmarkAssetIDList.at(idx);
|
||||||
std::string name;
|
std::string name = mLandmarkCombo->getSimple();
|
||||||
LLComboBox* combo = getChild<LLComboBox>( "landmark combo");
|
|
||||||
if (combo) name = combo->getSimple();
|
|
||||||
mTrackedStatus = LLTracker::TRACKING_LANDMARK;
|
mTrackedStatus = LLTracker::TRACKING_LANDMARK;
|
||||||
LLTracker::trackLandmark(mLandmarkAssetIDList.at( idx ), // assetID
|
LLTracker::trackLandmark(mLandmarkAssetIDList.at(idx), // assetID
|
||||||
mLandmarkItemIDList.at( idx ), // itemID
|
mLandmarkItemIDList.at(idx), // itemID
|
||||||
name); // name
|
name); // name
|
||||||
|
|
||||||
if( asset_id != sHomeID )
|
if (asset_id != sHomeID)
|
||||||
{
|
{
|
||||||
// start the download process
|
// start the download process
|
||||||
gLandmarkList.getAsset( asset_id);
|
gLandmarkList.getAsset(asset_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have to download both region info and landmark data, so set busy. JC
|
|
||||||
// getWindow()->incBusyCount();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LLTracker::stopTracking(false);
|
LLTracker::stopTracking(false);
|
||||||
}
|
}
|
||||||
setDefaultBtn("Teleport");
|
mTrackCtrlsPanel->setDefaultBtn(mTeleportButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -782,7 +817,7 @@ void LLFloaterWorldMap::trackEvent(const LLItemInfo &event_info)
|
||||||
mShowParcelInfo = false;
|
mShowParcelInfo = false;
|
||||||
mTrackedStatus = LLTracker::TRACKING_LOCATION;
|
mTrackedStatus = LLTracker::TRACKING_LOCATION;
|
||||||
LLTracker::trackLocation(event_info.getGlobalPosition(), event_info.getName(), event_info.getToolTip(), LLTracker::LOCATION_EVENT);
|
LLTracker::trackLocation(event_info.getGlobalPosition(), event_info.getName(), event_info.getToolTip(), LLTracker::LOCATION_EVENT);
|
||||||
setDefaultBtn("Teleport");
|
mTrackCtrlsPanel->setDefaultBtn(mTeleportButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLFloaterWorldMap::trackGenericItem(const LLItemInfo &item)
|
void LLFloaterWorldMap::trackGenericItem(const LLItemInfo &item)
|
||||||
|
|
@ -790,11 +825,12 @@ void LLFloaterWorldMap::trackGenericItem(const LLItemInfo &item)
|
||||||
mShowParcelInfo = false;
|
mShowParcelInfo = false;
|
||||||
mTrackedStatus = LLTracker::TRACKING_LOCATION;
|
mTrackedStatus = LLTracker::TRACKING_LOCATION;
|
||||||
LLTracker::trackLocation(item.getGlobalPosition(), item.getName(), item.getToolTip(), LLTracker::LOCATION_ITEM);
|
LLTracker::trackLocation(item.getGlobalPosition(), item.getName(), item.getToolTip(), LLTracker::LOCATION_ITEM);
|
||||||
setDefaultBtn("Teleport");
|
mTrackCtrlsPanel->setDefaultBtn(mTeleportButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global)
|
void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global)
|
||||||
{
|
{
|
||||||
|
mProcessingSearchUpdate = false;
|
||||||
LLSimInfo* sim_info = LLWorldMap::getInstance()->simInfoFromPosGlobal(pos_global);
|
LLSimInfo* sim_info = LLWorldMap::getInstance()->simInfoFromPosGlobal(pos_global);
|
||||||
if (!sim_info)
|
if (!sim_info)
|
||||||
{
|
{
|
||||||
|
|
@ -804,7 +840,7 @@ void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global)
|
||||||
S32 world_x = S32(pos_global.mdV[0] / 256);
|
S32 world_x = S32(pos_global.mdV[0] / 256);
|
||||||
S32 world_y = S32(pos_global.mdV[1] / 256);
|
S32 world_y = S32(pos_global.mdV[1] / 256);
|
||||||
LLWorldMapMessage::getInstance()->sendMapBlockRequest(world_x, world_y, world_x, world_y, true);
|
LLWorldMapMessage::getInstance()->sendMapBlockRequest(world_x, world_y, world_x, world_y, true);
|
||||||
setDefaultBtn("");
|
mTrackCtrlsPanel->setDefaultBtn(nullptr);
|
||||||
|
|
||||||
// clicked on a non-region - turn off coord display
|
// clicked on a non-region - turn off coord display
|
||||||
enableTeleportCoordsDisplay( false );
|
enableTeleportCoordsDisplay( false );
|
||||||
|
|
@ -818,7 +854,7 @@ void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global)
|
||||||
LLTracker::stopTracking(false);
|
LLTracker::stopTracking(false);
|
||||||
LLWorldMap::getInstance()->setTracking(pos_global);
|
LLWorldMap::getInstance()->setTracking(pos_global);
|
||||||
LLWorldMap::getInstance()->setTrackingInvalid();
|
LLWorldMap::getInstance()->setTrackingInvalid();
|
||||||
setDefaultBtn("");
|
mTrackCtrlsPanel->setDefaultBtn(nullptr);
|
||||||
|
|
||||||
// clicked on a down region - turn off coord display
|
// clicked on a down region - turn off coord display
|
||||||
enableTeleportCoordsDisplay( false );
|
enableTeleportCoordsDisplay( false );
|
||||||
|
|
@ -849,7 +885,7 @@ void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global)
|
||||||
// we have a valid region - turn on coord display
|
// we have a valid region - turn on coord display
|
||||||
enableTeleportCoordsDisplay( true );
|
enableTeleportCoordsDisplay( true );
|
||||||
|
|
||||||
setDefaultBtn("Teleport");
|
mTrackCtrlsPanel->setDefaultBtn(mTeleportButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable/disable teleport destination coordinates
|
// enable/disable teleport destination coordinates
|
||||||
|
|
@ -934,7 +970,10 @@ void LLFloaterWorldMap::updateLocation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mProcessingSearchUpdate)
|
||||||
|
{
|
||||||
mLocationEditor->setValue(sim_name);
|
mLocationEditor->setValue(sim_name);
|
||||||
|
}
|
||||||
|
|
||||||
// refresh coordinate display to reflect where user clicked.
|
// refresh coordinate display to reflect where user clicked.
|
||||||
LLVector3d coord_pos = LLTracker::getTrackedPositionGlobal();
|
LLVector3d coord_pos = LLTracker::getTrackedPositionGlobal();
|
||||||
|
|
@ -964,7 +1003,7 @@ void LLFloaterWorldMap::trackURL(const std::string& region_name, S32 x_coord, S3
|
||||||
local_pos.mV[VZ] = (F32)z_coord;
|
local_pos.mV[VZ] = (F32)z_coord;
|
||||||
LLVector3d global_pos = sim_info->getGlobalPos(local_pos);
|
LLVector3d global_pos = sim_info->getGlobalPos(local_pos);
|
||||||
trackLocation(global_pos);
|
trackLocation(global_pos);
|
||||||
setDefaultBtn("Teleport");
|
mTrackCtrlsPanel->setDefaultBtn(mTeleportButton);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -1028,14 +1067,11 @@ void LLFloaterWorldMap::friendsChanged()
|
||||||
LLAvatarTracker& t = LLAvatarTracker::instance();
|
LLAvatarTracker& t = LLAvatarTracker::instance();
|
||||||
const LLUUID& avatar_id = t.getAvatarID();
|
const LLUUID& avatar_id = t.getAvatarID();
|
||||||
buildAvatarIDList();
|
buildAvatarIDList();
|
||||||
if(avatar_id.notNull())
|
if (avatar_id.notNull())
|
||||||
{
|
{
|
||||||
LLCtrlSelectionInterface *iface = childGetSelectionInterface("friend combo");
|
|
||||||
const LLRelationship* buddy_info = t.getBuddyInfo(avatar_id);
|
const LLRelationship* buddy_info = t.getBuddyInfo(avatar_id);
|
||||||
if(!iface ||
|
if (!mFriendCombo->setCurrentByID(avatar_id) ||
|
||||||
!iface->setCurrentByID(avatar_id) ||
|
(buddy_info && !buddy_info->isRightGrantedFrom(LLRelationship::GRANT_MAP_LOCATION)) || gAgent.isGodlike())
|
||||||
(buddy_info && !buddy_info->isRightGrantedFrom(LLRelationship::GRANT_MAP_LOCATION)) ||
|
|
||||||
gAgent.isGodlike())
|
|
||||||
{
|
{
|
||||||
LLTracker::stopTracking(false);
|
LLTracker::stopTracking(false);
|
||||||
}
|
}
|
||||||
|
|
@ -1045,15 +1081,12 @@ void LLFloaterWorldMap::friendsChanged()
|
||||||
// No longer really builds a list. Instead, just updates mAvatarCombo.
|
// No longer really builds a list. Instead, just updates mAvatarCombo.
|
||||||
void LLFloaterWorldMap::buildAvatarIDList()
|
void LLFloaterWorldMap::buildAvatarIDList()
|
||||||
{
|
{
|
||||||
LLCtrlListInterface *list = mListFriendCombo;
|
|
||||||
if (!list) return;
|
|
||||||
|
|
||||||
// Delete all but the "None" entry
|
// Delete all but the "None" entry
|
||||||
S32 list_size = list->getItemCount();
|
S32 list_size = mFriendCombo->getItemCount();
|
||||||
if (list_size > 1)
|
if (list_size > 1)
|
||||||
{
|
{
|
||||||
list->selectItemRange(1, -1);
|
mFriendCombo->selectItemRange(1, -1);
|
||||||
list->operateOnSelection(LLCtrlListInterface::OP_DELETE);
|
mFriendCombo->operateOnSelection(LLCtrlListInterface::OP_DELETE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all of the calling cards for avatar that are currently online
|
// Get all of the calling cards for avatar that are currently online
|
||||||
|
|
@ -1063,27 +1096,24 @@ void LLFloaterWorldMap::buildAvatarIDList()
|
||||||
LLCollectMappableBuddies::buddy_map_t::iterator end;
|
LLCollectMappableBuddies::buddy_map_t::iterator end;
|
||||||
it = collector.mMappable.begin();
|
it = collector.mMappable.begin();
|
||||||
end = collector.mMappable.end();
|
end = collector.mMappable.end();
|
||||||
for( ; it != end; ++it)
|
for (; it != end; ++it)
|
||||||
{
|
{
|
||||||
list->addSimpleElement((*it).second, ADD_BOTTOM, (*it).first);
|
mFriendCombo->addSimpleElement((*it).second, ADD_BOTTOM, (*it).first);
|
||||||
}
|
}
|
||||||
|
|
||||||
list->setCurrentByID( LLAvatarTracker::instance().getAvatarID() );
|
mFriendCombo->setCurrentByID(LLAvatarTracker::instance().getAvatarID());
|
||||||
list->selectFirstItem();
|
mFriendCombo->selectFirstItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void LLFloaterWorldMap::buildLandmarkIDLists()
|
void LLFloaterWorldMap::buildLandmarkIDLists()
|
||||||
{
|
{
|
||||||
LLCtrlListInterface *list = mListLandmarkCombo;
|
|
||||||
if (!list) return;
|
|
||||||
|
|
||||||
// Delete all but the "None" entry
|
// Delete all but the "None" entry
|
||||||
S32 list_size = list->getItemCount();
|
S32 list_size = mLandmarkCombo->getItemCount();
|
||||||
if (list_size > 1)
|
if (list_size > 1)
|
||||||
{
|
{
|
||||||
list->selectItemRange(1, -1);
|
mLandmarkCombo->selectItemRange(1, -1);
|
||||||
list->operateOnSelection(LLCtrlListInterface::OP_DELETE);
|
mLandmarkCombo->operateOnSelection(LLCtrlListInterface::OP_DELETE);
|
||||||
}
|
}
|
||||||
|
|
||||||
mLandmarkItemIDList.clear();
|
mLandmarkItemIDList.clear();
|
||||||
|
|
@ -1115,13 +1145,13 @@ void LLFloaterWorldMap::buildLandmarkIDLists()
|
||||||
{
|
{
|
||||||
LLInventoryItem* item = items.at(i);
|
LLInventoryItem* item = items.at(i);
|
||||||
|
|
||||||
list->addSimpleElement(item->getName(), ADD_BOTTOM, item->getUUID());
|
mLandmarkCombo->addSimpleElement(item->getName(), ADD_BOTTOM, item->getUUID());
|
||||||
|
|
||||||
mLandmarkAssetIDList.push_back( item->getAssetUUID() );
|
mLandmarkAssetIDList.push_back( item->getAssetUUID() );
|
||||||
mLandmarkItemIDList.push_back( item->getUUID() );
|
mLandmarkItemIDList.push_back( item->getUUID() );
|
||||||
}
|
}
|
||||||
|
|
||||||
list->selectFirstItem();
|
mLandmarkCombo->selectFirstItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1139,10 +1169,9 @@ F32 LLFloaterWorldMap::getDistanceToDestination(const LLVector3d &destination,
|
||||||
|
|
||||||
void LLFloaterWorldMap::clearLocationSelection(bool clear_ui, bool dest_reached)
|
void LLFloaterWorldMap::clearLocationSelection(bool clear_ui, bool dest_reached)
|
||||||
{
|
{
|
||||||
LLCtrlListInterface *list = mListSearchResults;
|
if (!dest_reached || (mSearchResults->getItemCount() == 1))
|
||||||
if (list && (!dest_reached || (list->getItemCount() == 1)))
|
|
||||||
{
|
{
|
||||||
list->operateOnAll(LLCtrlListInterface::OP_DELETE);
|
mSearchResults->operateOnAll(LLCtrlListInterface::OP_DELETE);
|
||||||
}
|
}
|
||||||
LLWorldMap::getInstance()->cancelTracking();
|
LLWorldMap::getInstance()->cancelTracking();
|
||||||
mCompletingRegionName = "";
|
mCompletingRegionName = "";
|
||||||
|
|
@ -1153,11 +1182,7 @@ void LLFloaterWorldMap::clearLandmarkSelection(bool clear_ui)
|
||||||
{
|
{
|
||||||
if (clear_ui || !childHasKeyboardFocus("landmark combo"))
|
if (clear_ui || !childHasKeyboardFocus("landmark combo"))
|
||||||
{
|
{
|
||||||
LLCtrlListInterface *list = mListLandmarkCombo;
|
mLandmarkCombo->selectByValue("None");
|
||||||
if (list)
|
|
||||||
{
|
|
||||||
list->selectByValue( "None" );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1167,10 +1192,9 @@ void LLFloaterWorldMap::clearAvatarSelection(bool clear_ui)
|
||||||
if (clear_ui || !childHasKeyboardFocus("friend combo"))
|
if (clear_ui || !childHasKeyboardFocus("friend combo"))
|
||||||
{
|
{
|
||||||
mTrackedStatus = LLTracker::TRACKING_NOTHING;
|
mTrackedStatus = LLTracker::TRACKING_NOTHING;
|
||||||
LLCtrlListInterface *list = mListFriendCombo;
|
if (mFriendCombo->getSelectedValue().asString() != "None")
|
||||||
if (list && list->getSelectedValue().asString() != "None")
|
|
||||||
{
|
{
|
||||||
list->selectByValue( "None" );
|
mFriendCombo->selectByValue("None");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1223,28 +1247,25 @@ void LLFloaterWorldMap::onGoHome()
|
||||||
{
|
{
|
||||||
gAgent.teleportHome();
|
gAgent.teleportHome();
|
||||||
closeFloater();
|
closeFloater();
|
||||||
|
mProcessingSearchUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void LLFloaterWorldMap::onLandmarkComboPrearrange( )
|
void LLFloaterWorldMap::onLandmarkComboPrearrange()
|
||||||
{
|
{
|
||||||
if( mIsClosing )
|
if (mIsClosing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LLCtrlListInterface *list = mListLandmarkCombo;
|
LLUUID current_choice = mLandmarkCombo->getCurrentID();
|
||||||
if (!list) return;
|
|
||||||
|
|
||||||
LLUUID current_choice = list->getCurrentID();
|
|
||||||
|
|
||||||
buildLandmarkIDLists();
|
buildLandmarkIDLists();
|
||||||
|
|
||||||
if( current_choice.isNull() || !list->setCurrentByID( current_choice ) )
|
if (current_choice.isNull() || !mLandmarkCombo->setCurrentByID(current_choice))
|
||||||
{
|
{
|
||||||
LLTracker::stopTracking(false);
|
LLTracker::stopTracking(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLFloaterWorldMap::onComboTextEntry()
|
void LLFloaterWorldMap::onComboTextEntry()
|
||||||
|
|
@ -1264,33 +1285,28 @@ void LLFloaterWorldMap::onSearchTextEntry( )
|
||||||
|
|
||||||
void LLFloaterWorldMap::onLandmarkComboCommit()
|
void LLFloaterWorldMap::onLandmarkComboCommit()
|
||||||
{
|
{
|
||||||
if( mIsClosing )
|
if (mIsClosing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LLCtrlListInterface *list = mListLandmarkCombo;
|
|
||||||
if (!list) return;
|
|
||||||
|
|
||||||
LLUUID asset_id;
|
LLUUID asset_id;
|
||||||
LLUUID item_id = list->getCurrentID();
|
LLUUID item_id = mLandmarkCombo->getCurrentID();
|
||||||
|
|
||||||
LLTracker::stopTracking(false);
|
LLTracker::stopTracking(false);
|
||||||
|
|
||||||
//RN: stopTracking() clears current combobox selection, need to reassert it here
|
// RN: stopTracking() clears current combobox selection, need to reassert it here
|
||||||
list->setCurrentByID(item_id);
|
mLandmarkCombo->setCurrentByID(item_id);
|
||||||
|
|
||||||
if( item_id.isNull() )
|
if (item_id.isNull()) {}
|
||||||
{
|
else if (item_id == sHomeID)
|
||||||
}
|
|
||||||
else if( item_id == sHomeID )
|
|
||||||
{
|
{
|
||||||
asset_id = sHomeID;
|
asset_id = sHomeID;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LLInventoryItem* item = gInventory.getItem( item_id );
|
LLInventoryItem* item = gInventory.getItem(item_id);
|
||||||
if( item )
|
if (item)
|
||||||
{
|
{
|
||||||
asset_id = item->getAssetUUID();
|
asset_id = item->getAssetUUID();
|
||||||
}
|
}
|
||||||
|
|
@ -1301,34 +1317,31 @@ void LLFloaterWorldMap::onLandmarkComboCommit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trackLandmark( item_id);
|
trackLandmark(item_id);
|
||||||
onShowTargetBtn();
|
onShowTargetBtn();
|
||||||
|
|
||||||
// Reset to user postion if nothing is tracked
|
// Reset to user postion if nothing is tracked
|
||||||
mSetToUserPosition = ( LLTracker::getTrackingStatus() == LLTracker::TRACKING_NOTHING );
|
mSetToUserPosition = (LLTracker::getTrackingStatus() == LLTracker::TRACKING_NOTHING);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void LLFloaterWorldMap::onAvatarComboPrearrange( )
|
void LLFloaterWorldMap::onAvatarComboPrearrange()
|
||||||
{
|
{
|
||||||
if( mIsClosing )
|
if (mIsClosing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LLCtrlListInterface *list = mListFriendCombo;
|
|
||||||
if (!list) return;
|
|
||||||
|
|
||||||
LLUUID current_choice;
|
LLUUID current_choice;
|
||||||
|
|
||||||
if( LLAvatarTracker::instance().haveTrackingInfo() )
|
if (LLAvatarTracker::instance().haveTrackingInfo())
|
||||||
{
|
{
|
||||||
current_choice = LLAvatarTracker::instance().getAvatarID();
|
current_choice = LLAvatarTracker::instance().getAvatarID();
|
||||||
}
|
}
|
||||||
|
|
||||||
buildAvatarIDList();
|
buildAvatarIDList();
|
||||||
|
|
||||||
if( !list->setCurrentByID( current_choice ) || current_choice.isNull() )
|
if (!mFriendCombo->setCurrentByID(current_choice) || current_choice.isNull())
|
||||||
{
|
{
|
||||||
LLTracker::stopTracking(false);
|
LLTracker::stopTracking(false);
|
||||||
}
|
}
|
||||||
|
|
@ -1336,26 +1349,21 @@ void LLFloaterWorldMap::onAvatarComboPrearrange( )
|
||||||
|
|
||||||
void LLFloaterWorldMap::onAvatarComboCommit()
|
void LLFloaterWorldMap::onAvatarComboCommit()
|
||||||
{
|
{
|
||||||
if( mIsClosing )
|
if (mIsClosing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LLCtrlListInterface *list = mListFriendCombo;
|
const LLUUID& new_avatar_id = mFriendCombo->getCurrentID();
|
||||||
if (!list) return;
|
|
||||||
|
|
||||||
const LLUUID& new_avatar_id = list->getCurrentID();
|
|
||||||
if (new_avatar_id.notNull())
|
if (new_avatar_id.notNull())
|
||||||
{
|
{
|
||||||
std::string name;
|
std::string name = mFriendCombo->getSimple();
|
||||||
LLComboBox* combo = getChild<LLComboBox>("friend combo");
|
|
||||||
if (combo) name = combo->getSimple();
|
|
||||||
trackAvatar(new_avatar_id, name);
|
trackAvatar(new_avatar_id, name);
|
||||||
onShowTargetBtn();
|
onShowTargetBtn();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // Reset to user postion if nothing is tracked
|
{ // Reset to user postion if nothing is tracked
|
||||||
mSetToUserPosition = ( LLTracker::getTrackingStatus() == LLTracker::TRACKING_NOTHING );
|
mSetToUserPosition = (LLTracker::getTrackingStatus() == LLTracker::TRACKING_NOTHING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1375,11 +1383,11 @@ void LLFloaterWorldMap::updateSearchEnabled()
|
||||||
if (childHasKeyboardFocus("location") &&
|
if (childHasKeyboardFocus("location") &&
|
||||||
mLocationEditor->getValue().asString().length() > 0)
|
mLocationEditor->getValue().asString().length() > 0)
|
||||||
{
|
{
|
||||||
setDefaultBtn("DoSearch");
|
mTrackCtrlsPanel->setDefaultBtn(mSearchButton);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setDefaultBtn(NULL);
|
mTrackCtrlsPanel->setDefaultBtn(nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1409,6 +1417,7 @@ void LLFloaterWorldMap::onLocationCommit()
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
mProcessingSearchUpdate = true;
|
||||||
|
|
||||||
LLStringUtil::toLower(str);
|
LLStringUtil::toLower(str);
|
||||||
mCompletingRegionName = str;
|
mCompletingRegionName = str;
|
||||||
|
|
@ -1430,6 +1439,7 @@ void LLFloaterWorldMap::onCoordinatesCommit()
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
mProcessingSearchUpdate = false;
|
||||||
|
|
||||||
S32 x_coord = (S32)mTeleportCoordSpinX->getValue().asReal();
|
S32 x_coord = (S32)mTeleportCoordSpinX->getValue().asReal();
|
||||||
S32 y_coord = (S32)mTeleportCoordSpinY->getValue().asReal();
|
S32 y_coord = (S32)mTeleportCoordSpinY->getValue().asReal();
|
||||||
|
|
@ -1443,6 +1453,7 @@ void LLFloaterWorldMap::onCoordinatesCommit()
|
||||||
void LLFloaterWorldMap::onClearBtn()
|
void LLFloaterWorldMap::onClearBtn()
|
||||||
{
|
{
|
||||||
mTrackedStatus = LLTracker::TRACKING_NOTHING;
|
mTrackedStatus = LLTracker::TRACKING_NOTHING;
|
||||||
|
mProcessingSearchUpdate = false;
|
||||||
LLTracker::stopTracking(true);
|
LLTracker::stopTracking(true);
|
||||||
LLWorldMap::getInstance()->cancelTracking();
|
LLWorldMap::getInstance()->cancelTracking();
|
||||||
mSLURL = LLSLURL(); // Clear the SLURL since it's invalid
|
mSLURL = LLSLURL(); // Clear the SLURL since it's invalid
|
||||||
|
|
@ -1459,6 +1470,7 @@ void LLFloaterWorldMap::onShowAgentBtn()
|
||||||
mMapView->setPanWithInterpTime(0, 0, false, 0.1f); // false == animate
|
mMapView->setPanWithInterpTime(0, 0, false, 0.1f); // false == animate
|
||||||
// Set flag so user's location will be displayed if not tracking anything else
|
// Set flag so user's location will be displayed if not tracking anything else
|
||||||
mSetToUserPosition = true;
|
mSetToUserPosition = true;
|
||||||
|
mProcessingSearchUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLFloaterWorldMap::onClickTeleportBtn()
|
void LLFloaterWorldMap::onClickTeleportBtn()
|
||||||
|
|
@ -1487,8 +1499,9 @@ void LLFloaterWorldMap::onExpandCollapseBtn()
|
||||||
|
|
||||||
std::string image_name = getString(toggle_collapse ? "expand_icon" : "collapse_icon");
|
std::string image_name = getString(toggle_collapse ? "expand_icon" : "collapse_icon");
|
||||||
std::string tooltip = getString(toggle_collapse ? "expand_tooltip" : "collapse_tooltip");
|
std::string tooltip = getString(toggle_collapse ? "expand_tooltip" : "collapse_tooltip");
|
||||||
getChild<LLIconCtrl>("expand_collapse_icon")->setImage(LLUI::getUIImage(image_name));
|
LLIconCtrl* expandCollapseIcon = getChild<LLIconCtrl>("expand_collapse_icon");
|
||||||
getChild<LLIconCtrl>("expand_collapse_icon")->setToolTip(tooltip);
|
expandCollapseIcon->setImage(LLUI::getUIImage(image_name));
|
||||||
|
expandCollapseIcon->setToolTip(tooltip);
|
||||||
getChild<LLPanel>("expand_btn_panel")->setToolTip(tooltip);
|
getChild<LLPanel>("expand_btn_panel")->setToolTip(tooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1613,6 +1626,12 @@ void LLFloaterWorldMap::teleport()
|
||||||
gAgent.teleportViaLocation( pos_global );
|
gAgent.teleportViaLocation( pos_global );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mProcessingSearchUpdate)
|
||||||
|
{
|
||||||
|
mProcessingSearchUpdate = false;
|
||||||
|
mTrackedSimName.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLFloaterWorldMap::flyToLandmark()
|
void LLFloaterWorldMap::flyToLandmark()
|
||||||
|
|
@ -1680,9 +1699,9 @@ void LLFloaterWorldMap::teleportToAvatar()
|
||||||
|
|
||||||
void LLFloaterWorldMap::flyToAvatar()
|
void LLFloaterWorldMap::flyToAvatar()
|
||||||
{
|
{
|
||||||
if( LLAvatarTracker::instance().haveTrackingInfo() )
|
if (LLAvatarTracker::instance().haveTrackingInfo())
|
||||||
{
|
{
|
||||||
gAgent.startAutoPilotGlobal( LLAvatarTracker::instance().getGlobalPos() );
|
gAgent.startAutoPilotGlobal(LLAvatarTracker::instance().getGlobalPos());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1693,8 +1712,7 @@ void LLFloaterWorldMap::updateSims(bool found_null_sim)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LLScrollListCtrl *list = getChild<LLScrollListCtrl>("search_results");
|
mSearchResults->operateOnAll(LLCtrlListInterface::OP_DELETE);
|
||||||
list->operateOnAll(LLCtrlListInterface::OP_DELETE);
|
|
||||||
|
|
||||||
auto name_length = mCompletingRegionName.length();
|
auto name_length = mCompletingRegionName.length();
|
||||||
|
|
||||||
|
|
@ -1722,7 +1740,7 @@ void LLFloaterWorldMap::updateSims(bool found_null_sim)
|
||||||
value["id"] = info->getName();
|
value["id"] = info->getName();
|
||||||
value["columns"][0]["column"] = "sim_name";
|
value["columns"][0]["column"] = "sim_name";
|
||||||
value["columns"][0]["value"] = info->getName();
|
value["columns"][0]["value"] = info->getName();
|
||||||
list->addElement(value);
|
mSearchResults->addElement(value);
|
||||||
num_results++;
|
num_results++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1737,21 +1755,24 @@ void LLFloaterWorldMap::updateSims(bool found_null_sim)
|
||||||
// if match found, highlight it and go
|
// if match found, highlight it and go
|
||||||
if (!match.isUndefined())
|
if (!match.isUndefined())
|
||||||
{
|
{
|
||||||
list->selectByValue(match);
|
mSearchResults->selectByValue(match);
|
||||||
|
mSearchResults->setFocus(true);
|
||||||
|
onCommitSearchResult(false /*fully commit the only option*/);
|
||||||
}
|
}
|
||||||
// else select first found item
|
// else let user decide
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
list->selectFirstItem();
|
mSearchResults->selectFirstItem();
|
||||||
|
mSearchResults->setFocus(true);
|
||||||
|
onCommitSearchResult(true /*don't update text field*/);
|
||||||
}
|
}
|
||||||
getChild<LLUICtrl>("search_results")->setFocus(true);
|
|
||||||
onCommitSearchResult();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// if we found nothing, say "none"
|
// if we found nothing, say "none"
|
||||||
list->setCommentText(LLTrans::getString("worldmap_results_none_found"));
|
mProcessingSearchUpdate = false;
|
||||||
list->operateOnAll(LLCtrlListInterface::OP_DESELECT);
|
mSearchResults->setCommentText(LLTrans::getString("worldmap_results_none_found"));
|
||||||
|
mSearchResults->operateOnAll(LLCtrlListInterface::OP_DESELECT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1763,13 +1784,9 @@ void LLFloaterWorldMap::onTeleportFinished()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LLFloaterWorldMap::onCommitSearchResult()
|
void LLFloaterWorldMap::onCommitSearchResult(bool from_search)
|
||||||
{
|
{
|
||||||
LLCtrlListInterface *list = mListSearchResults;
|
std::string sim_name = mSearchResults->getSelectedValue().asString();
|
||||||
if (!list) return;
|
|
||||||
|
|
||||||
LLSD selected_value = list->getSelectedValue();
|
|
||||||
std::string sim_name = selected_value.asString();
|
|
||||||
if (sim_name.empty())
|
if (sim_name.empty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -1785,7 +1802,7 @@ void LLFloaterWorldMap::onCommitSearchResult()
|
||||||
{
|
{
|
||||||
LLVector3d pos_global = info->getGlobalOrigin();
|
LLVector3d pos_global = info->getGlobalOrigin();
|
||||||
|
|
||||||
const F64 SIM_COORD_DEFAULT = 128.0;
|
constexpr F64 SIM_COORD_DEFAULT = 128.0;
|
||||||
LLVector3 pos_local(SIM_COORD_DEFAULT, SIM_COORD_DEFAULT, 0.0f);
|
LLVector3 pos_local(SIM_COORD_DEFAULT, SIM_COORD_DEFAULT, 0.0f);
|
||||||
|
|
||||||
// Did this value come from a trackURL() request?
|
// Did this value come from a trackURL() request?
|
||||||
|
|
@ -1798,9 +1815,15 @@ void LLFloaterWorldMap::onCommitSearchResult()
|
||||||
pos_global.mdV[VY] += (F64)pos_local.mV[VY];
|
pos_global.mdV[VY] += (F64)pos_local.mV[VY];
|
||||||
pos_global.mdV[VZ] = (F64)pos_local.mV[VZ];
|
pos_global.mdV[VZ] = (F64)pos_local.mV[VZ];
|
||||||
|
|
||||||
|
// Commiting search string automatically selects first item in the search list,
|
||||||
|
// in such case onCommitSearchResult shouldn't modify search string
|
||||||
|
if (!from_search)
|
||||||
|
{
|
||||||
mLocationEditor->setValue(sim_name);
|
mLocationEditor->setValue(sim_name);
|
||||||
|
}
|
||||||
trackLocation(pos_global);
|
trackLocation(pos_global);
|
||||||
setDefaultBtn("Teleport");
|
mProcessingSearchUpdate = from_search;
|
||||||
|
mTrackCtrlsPanel->setDefaultBtn(mTeleportButton);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ class LLCheckBoxCtrl;
|
||||||
class LLSliderCtrl;
|
class LLSliderCtrl;
|
||||||
class LLSpinCtrl;
|
class LLSpinCtrl;
|
||||||
class LLSearchEditor;
|
class LLSearchEditor;
|
||||||
|
class LLComboBox;
|
||||||
|
class LLScrollListCtrl;
|
||||||
|
|
||||||
class LLWorldMapParcelInfoObserver : public LLRemoteParcelInfoObserver
|
class LLWorldMapParcelInfoObserver : public LLRemoteParcelInfoObserver
|
||||||
{
|
{
|
||||||
|
|
@ -174,7 +176,7 @@ protected:
|
||||||
void onLocationFocusChanged( LLFocusableElement* ctrl );
|
void onLocationFocusChanged( LLFocusableElement* ctrl );
|
||||||
void onLocationCommit();
|
void onLocationCommit();
|
||||||
void onCoordinatesCommit();
|
void onCoordinatesCommit();
|
||||||
void onCommitSearchResult();
|
void onCommitSearchResult(bool from_search);
|
||||||
|
|
||||||
void onTeleportFinished();
|
void onTeleportFinished();
|
||||||
|
|
||||||
|
|
@ -211,6 +213,7 @@ private:
|
||||||
|
|
||||||
bool mIsClosing;
|
bool mIsClosing;
|
||||||
bool mSetToUserPosition;
|
bool mSetToUserPosition;
|
||||||
|
bool mProcessingSearchUpdate; // Don't update search string from what user set it to
|
||||||
|
|
||||||
LLVector3d mTrackedLocation;
|
LLVector3d mTrackedLocation;
|
||||||
LLTracker::ETrackingStatus mTrackedStatus;
|
LLTracker::ETrackingStatus mTrackedStatus;
|
||||||
|
|
@ -218,14 +221,11 @@ private:
|
||||||
LLUUID mTrackedAvatarID;
|
LLUUID mTrackedAvatarID;
|
||||||
LLSLURL mSLURL;
|
LLSLURL mSLURL;
|
||||||
|
|
||||||
LLCtrlListInterface * mListFriendCombo;
|
|
||||||
LLCtrlListInterface * mListLandmarkCombo;
|
|
||||||
LLCtrlListInterface * mListSearchResults;
|
|
||||||
|
|
||||||
LLButton* mTeleportButton = nullptr;
|
LLButton* mTeleportButton = nullptr;
|
||||||
LLButton* mShowDestinationButton = nullptr;
|
LLButton* mShowDestinationButton = nullptr;
|
||||||
LLButton* mCopySlurlButton = nullptr;
|
LLButton* mCopySlurlButton = nullptr;
|
||||||
LLButton* mGoHomeButton = nullptr;
|
LLButton* mGoHomeButton = nullptr;
|
||||||
|
LLButton* mSearchButton = nullptr;
|
||||||
|
|
||||||
LLCheckBoxCtrl* mPeopleCheck = nullptr;
|
LLCheckBoxCtrl* mPeopleCheck = nullptr;
|
||||||
LLCheckBoxCtrl* mInfohubCheck = nullptr;
|
LLCheckBoxCtrl* mInfohubCheck = nullptr;
|
||||||
|
|
@ -245,6 +245,13 @@ private:
|
||||||
|
|
||||||
LLSliderCtrl* mZoomSlider = nullptr;
|
LLSliderCtrl* mZoomSlider = nullptr;
|
||||||
|
|
||||||
|
LLComboBox* mLandmarkCombo = nullptr;
|
||||||
|
LLComboBox* mFriendCombo = nullptr;
|
||||||
|
|
||||||
|
LLScrollListCtrl* mSearchResults = nullptr;
|
||||||
|
|
||||||
|
LLPanel* mTrackCtrlsPanel = nullptr;
|
||||||
|
|
||||||
boost::signals2::connection mTeleportFinishConnection;
|
boost::signals2::connection mTeleportFinishConnection;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
#include "lldrawable.h"
|
#include "lldrawable.h"
|
||||||
#include "llviewerobjectlist.h"
|
#include "llviewerobjectlist.h"
|
||||||
#include "llviewercontrol.h"
|
#include "llviewercontrol.h"
|
||||||
|
#include "llvoavatarself.h"
|
||||||
#include "llrendersphere.h"
|
#include "llrendersphere.h"
|
||||||
#include "llselectmgr.h"
|
#include "llselectmgr.h"
|
||||||
#include "llglheaders.h"
|
#include "llglheaders.h"
|
||||||
|
|
@ -397,6 +398,21 @@ bool LLHUDEffectLookAt::setLookAt(ELookAtType target_type, LLViewerObject *objec
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static LLCachedControl<bool> enable_lookat_hints(gSavedSettings, "EnableLookAtTarget", true);
|
||||||
|
if (!enable_lookat_hints)
|
||||||
|
{
|
||||||
|
// Clear the effect so it doesn't linger around if it gets disabled
|
||||||
|
if (mTargetType != LOOKAT_TARGET_IDLE)
|
||||||
|
{
|
||||||
|
mTargetObject = gAgentAvatarp;
|
||||||
|
mTargetType = LOOKAT_TARGET_IDLE;
|
||||||
|
mTargetOffsetGlobal.set(2.f, 0.f, 0.f);
|
||||||
|
setDuration(3.f);
|
||||||
|
setNeedsSendToSim(true);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (target_type >= LOOKAT_NUM_TARGETS)
|
if (target_type >= LOOKAT_NUM_TARGETS)
|
||||||
{
|
{
|
||||||
LL_WARNS() << "Bad target_type " << (int)target_type << " - ignoring." << LL_ENDL;
|
LL_WARNS() << "Bad target_type " << (int)target_type << " - ignoring." << LL_ENDL;
|
||||||
|
|
@ -409,6 +425,29 @@ bool LLHUDEffectLookAt::setLookAt(ELookAtType target_type, LLViewerObject *objec
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static LLCachedControl<bool> limit_lookat_hints(gSavedSettings, "LimitLookAtTarget", true);
|
||||||
|
// Don't affect the look at if object is gAgentAvatarp (cursor head follow)
|
||||||
|
if (limit_lookat_hints && object != gAgentAvatarp)
|
||||||
|
{
|
||||||
|
// If it is a object
|
||||||
|
if (object)
|
||||||
|
{
|
||||||
|
position += object->getRenderPosition();
|
||||||
|
object = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
LLVector3 agentHeadPosition = gAgentAvatarp->mHeadp->getWorldPosition();
|
||||||
|
float dist = (float)dist_vec(agentHeadPosition, position);
|
||||||
|
|
||||||
|
static LLCachedControl<F32> limit_lookat_hints_distance(gSavedSettings, "LimitLookAtTargetDistance", 2.0f);
|
||||||
|
if (dist > limit_lookat_hints_distance)
|
||||||
|
{
|
||||||
|
LLVector3 headOffset = position - agentHeadPosition;
|
||||||
|
headOffset *= limit_lookat_hints_distance / dist;
|
||||||
|
position.setVec(agentHeadPosition + headOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
F32 current_time = mTimer.getElapsedTimeF32();
|
F32 current_time = mTimer.getElapsedTimeF32();
|
||||||
|
|
||||||
// type of lookat behavior or target object has changed
|
// type of lookat behavior or target object has changed
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue