diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c0a28d6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,58 @@ +--- +# Dependabot keeps SkySimulation dependency drift visible while CI validates every update. +version: 2 + +updates: + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + timezone: "Europe/Amsterdam" + open-pull-requests-limit: 10 + labels: + - "dependencies" + - "gradle" + commit-message: + prefix: "deps" + include: "scope" + groups: + jme-runtime-stack: + patterns: + - "org.jmonkeyengine:*" + - "com.github.stephengold:*" + test-and-tools: + patterns: + - "junit:junit" + - "org.jcommander:*" + - "org.apache.commons:*" + - "de.undercouch.download" + lua-runtime: + patterns: + - "org.luaj:*" + ui-example-stack: + patterns: + - "com.github.nifty-gui:*" + - "com.github.stephengold:jme3-utilities-nifty" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "tuesday" + time: "09:00" + timezone: "Europe/Amsterdam" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "ci" + include: "scope" + groups: + github-actions: + patterns: + - "actions/*" + - "gradle/actions*" + - "softprops/action-gh-release" diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 5ee5dd7..f7499ee 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -1,43 +1,70 @@ --- -# GitHub Actions workflow for commits pushed to the SkyControl repo - all branches +name: CI -name: CI at GitHub -on: [push] +on: + push: + branches: + - '**' + pull_request: + branches: + - '**' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: sky-ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - Java17-Linux: - if: contains(toJson(github.event.commits), '[ci skip] ') == false - runs-on: ubuntu-24.04 - timeout-minutes: 3 - steps: - - uses: actions/setup-java@v5 - with: - distribution: 'zulu' - java-version: 17 - - uses: actions/checkout@v7 - - uses: gradle/actions/wrapper-validation@v6 - - run: ./gradlew build --console=plain --stacktrace - - Java21-MacOS: - if: contains(toJson(github.event.commits), '[ci skip] ') == false - runs-on: macos-26 - timeout-minutes: 10 + validate: + name: ${{ matrix.name }} + if: ${{ !contains(github.event.head_commit.message || '', '[ci skip]') }} + runs-on: ${{ matrix.os }} + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + include: + - name: Linux Java 17 + os: ubuntu-24.04 + java: '17' + command: ./gradlew --no-daemon clean build packageLocal --console=plain --stacktrace + shell: bash + - name: Windows Java 17 + os: windows-2025 + java: '17' + command: gradlew.bat --no-daemon clean build packageLocal --console=plain --stacktrace + shell: cmd + steps: - - uses: actions/setup-java@v5 + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v4 with: - distribution: 'zulu' - java-version: 21 - - uses: actions/checkout@v7 - - run: ./gradlew build --console=plain --stacktrace - - Java26-Windows: - if: contains(toJson(github.event.commits), '[ci skip] ') == false - runs-on: windows-2025 - steps: - - uses: actions/setup-java@v5 + distribution: temurin + java-version: ${{ matrix.java }} + + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@v4 + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build, test, checkstyle, and local package + shell: ${{ matrix.shell }} + run: ${{ matrix.command }} + + - name: Upload problem reports + if: failure() + uses: actions/upload-artifact@v4 with: - distribution: 'liberica' - java-version: 26 - - uses: actions/checkout@v7 - - run: ./gradlew build --console=plain --stacktrace - shell: bash + name: reports-${{ matrix.name }} + if-no-files-found: ignore + path: | + build/reports/** + **/build/reports/** + **/build/test-results/** diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..bca3d33 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,67 @@ +name: Release + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + version: + description: 'Release version without the v prefix, for example 1.0.0' + required: true + default: '1.0.0' + +permissions: + contents: write + packages: write + +jobs: + release: + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Resolve version + id: version + shell: bash + run: | + if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then + VERSION="${GITHUB_REF_NAME#v}" + else + VERSION="${{ github.event.inputs.version }}" + fi + echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" + echo "Resolved release version: ${VERSION}" + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + cache: gradle + + - name: Build and test + shell: cmd + run: gradlew.bat --no-daemon clean build packageLocal --console=plain --stacktrace -PskySimulationVersion=${{ steps.version.outputs.version }} + + - name: Publish to GitHub Packages + shell: cmd + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gradlew.bat --no-daemon publishGithubPackage --console=plain --stacktrace -PskySimulationVersion=${{ steps.version.outputs.version }} + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.version.outputs.version }} + name: SkySimulation v${{ steps.version.outputs.version }} + body_path: RELEASE_NOTES_v${{ steps.version.outputs.version }}.md + generate_release_notes: true + files: | + SkyLibrary/build/libs/sky-simulation-${{ steps.version.outputs.version }}.jar + SkyLibrary/build/libs/sky-simulation-${{ steps.version.outputs.version }}-sources.jar + SkyLibrary/build/libs/sky-simulation-${{ steps.version.outputs.version }}-javadoc.jar + SkyLibrary/build/libs/sky-simulation-${{ steps.version.outputs.version }}.pom + SkyLibrary/build/libs/sky-simulation-${{ steps.version.outputs.version }}.module diff --git a/.gitignore b/.gitignore index 947b088..fefc134 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ /SkyAssets/src/main/resources/bsc5.dat.gz /SkyExamples/private/ /SkyExamples/Written Assets/ -/SkyLibrary/src/main/resources/Textures/skies/ # Ignore Gradle's project-specific cache directory: /.gradle/ diff --git a/PACKAGE.md b/PACKAGE.md new file mode 100644 index 0000000..4d37d8d --- /dev/null +++ b/PACKAGE.md @@ -0,0 +1,36 @@ +# Take Some SkySimulation package + +Internal Maven/Gradle package coordinates: + +```kotlin +implementation("dev.takesome:sky-simulation:1.4.3") +``` + +Install the package into Maven Local: + +```bat +gradlew.bat packageLocal +``` + +Use it from another Gradle project: + +```kotlin +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation("dev.takesome:sky-simulation:1.4.3-SNAPSHOT") +} +``` + +The Java package namespace remains compatible with SkyControl: + +```java +import jme3utilities.sky.SkyControl; +import jme3utilities.sky.SkyAtmosphere; +``` + +This avoids breaking existing code while publishing the artifact under Take Some +coordinates. diff --git a/README.md b/README.md index 1387331..1604943 100644 --- a/README.md +++ b/README.md @@ -1,381 +1,194 @@ -SkyControl logo +# SkySimulation -[The SkyControl Project][skycontrol] provides a sky-simulation library for -[the jMonkeyEngine (JME) game engine][jme]. +

+ SkySimulation banner +

-It contains 3 subprojects: +

+ CI + Release + Version + Java + Artifact + License +

-1. SkyLibrary: the SkyControl runtime library and its automated tests -2. SkyExamples: example applications -3. SkyAssets: generate textures included in the library +**SkySimulation** is the Take Some() sky and atmosphere package for Java / jMonkeyEngine worlds. -Complete source code (in Java) is provided under -[a 3-clause BSD license][license]. +We are **Take Some()** — a small engineering team building game-runtime infrastructure: simulation systems, rendering tools, world technology, and reusable engine modules for our own projects. +This repository turns the original SkyControl-style sky runtime into a Take Some() package with practical controls for modern game worlds: atmospheric tuning, sun and moon texture customization, cloud lighting, stars, horizon haze, and release-ready Gradle publishing. - +## What we are building -## Contents of this document +We are building a reusable world-simulation layer for our games and engine experiments: -+ [Important features](#features) -+ [How to add SkyControl to an existing project](#add) -+ [How to build SkyControl from source](#build) -+ [Downloads](#downloads) -+ [Conventions](#conventions) -+ [External links](#links) -+ [History](#history) -+ [Acknowledgments](#acks) +- dynamic sky, sun, moon, stars, haze, and cloud layers; +- configurable atmospheric lighting profiles; +- texture customization for both the sun and the moon; +- lighting synchronization for ambient light, directional light, bloom, shadows, and viewport background color; +- package distribution through Maven Local and GitHub Packages; +- a foundation for richer worlds, weather, time-of-day systems, and future Take Some() engine modules. +The public Java namespace currently remains compatible with SkyControl: - +```java +import jme3utilities.sky.SkyControl; +import jme3utilities.sky.SkyAtmosphere; +``` -## Important features +That keeps existing code usable while the artifact itself is published under Take Some() coordinates. -+ sun, moon, stars, horizon haze, and up to 6 cloud layers -+ compatible with static backgrounds such as cube maps -+ high resolution textures are provided -- or customize with your own textures -+ compatible with effects such as `SimpleWater`, shadows, bloom, and cartoon edges -+ continuous and reversible motion and blending of cloud layers -+ option to foreshorten clouds near the horizon -+ continuous and reversible motion of sun, moon, and stars based on time of day -+ updater to synchronize lighting and shadows with sun, moon, and clouds -+ continuous scaling of sun, moon, and clouds -+ option for continuously variable phase of the moon -+ demonstration apps and online tutorial provided -+ complete source code provided under FreeBSD license -[Jump to the table of contents](#toc) +## Source layout +The public API stays in the stable `jme3utilities.sky` namespace: - +```text +SkyControl +SkyControlCore +SkyMaterial / SkyMaterialCore +SkyAtmosphere +CloudLayer +SunAndStars +Updater +``` -## How to add SkyControl to an existing project +Implementation helpers are grouped by responsibility in focused packages: -Adding SkyControl to an existing [jMonkeyEngine][jme] project should be -a simple 6-step process: +```text +jme3utilities.sky.atmosphere atmospheric parsing and lighting math +jme3utilities.sky.material texture sampling helpers +jme3utilities.sky.scene scene-graph naming helpers +jme3utilities.sky.update live updater application helpers +``` - 1. Add SkyControl and its dependencies to the classpath. - 2. Disable any existing sky which might interfere with SkyControl. - 3. Add a `SkyControl` instance to some node in the scene graph. - 4. Configure the `SkyControl` instance. - 5. Enable the `SkyControl` instance. - 6. Test and tune as necessary. +Some helper classes remain package-private in `jme3utilities.sky` when moving +those classes would force us to expose constructors or state that should stay +encapsulated. Public compatibility is preferred over cosmetic directory moves. -The SkyControl Library depends on -the standard "jme3-effects" library from jMonkeyEngine and -[the Heart Library][heart], -which in turn depends on -the standard "jme3-core" library. +## Cloud weather and generated sky materials -For projects built using [Maven] or [Gradle], it is sufficient to add a -dependency on the SkyControl Library. -The build tool should automatically resolve the remaining dependencies. +Version `1.4.3` extracts weather subscription dispatch from the environment runtime, reduces per-event allocation during weather notifications, and keeps cloud/weather runtime state observable for gameplay systems. -### Gradle-built projects +- `SkyCloudPreset` provides `CLEAR`, `FAIR`, `OVERCAST`, `WISPY`, `CLOUDY`, `RAIN`, `STORM`, and `NIMBUS`. +- `SkyControl.setCloudPreset(preset, seconds)` changes weather by fading current layers out, swapping alpha/normal/scale/motion while invisible, and fading target layers in. +- DDS resources live under `SkyLibrary/src/main/resources/Textures/skies/clouds/presets`. +- `cloud-weather-presets.json` records managed presets, raw bundles, DDS inventory, SHA-256 values, transition policy, and generated material/shader settings. +- `:SkyAssets:skyMaterials` generates `dome02`, `dome06`, `dome20`, `dome22`, `dome60`, and `dome66` MatDefs and GLSL shaders. +- BC5/ATI2 DDS normal maps are supported through the first-party fallback loader and are mapped to `Image.Format.RGTC2`. -Add to the project’s "build.gradle" or "build.gradle.kts" file: +Visual smoke test: + +```bat +gradlew.bat :SkyExamples:SkyCloudWeatherSmoke +``` - repositories { - mavenCentral() - } - dependencies { - implementation("com.github.stephengold:SkyControl:1.1.0") - } -For some older versions of Gradle, -it's necessary to replace `implementation` with `compile`. - -### Maven-built projects - -Add to the project’s "pom.xml" file: - - - - mvnrepository - https://repo1.maven.org/maven2/ - - - - - com.github.stephengold - SkyControl - 1.1.0 - - -### Ant-built projects +## Package -For projects built using [Ant], download the SkyControl and [Heart] -libraries from GitHub: +### Release package + +```kotlin +repositories { + mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/Take-Some/SkySimulation") + credentials { + username = findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") + password = findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") + } + } +} -+ https://github.com/stephengold/SkyControl/releases/tag/latest -+ https://github.com/stephengold/Heart/releases/tag/8.3.2 +dependencies { + implementation("dev.takesome:sky-simulation:1.4.3") +} +``` -You'll definitely want both class jars -and probably the "-sources" and "-javadoc" jars as well. +### Local development package -Open the project's properties in the IDE (JME SDK or NetBeans): +First install the package into Maven Local: -1. Right-click on the project (not its assets) in the "Projects" window. -2. Select "Properties" to open the "Project Properties" dialog. -3. Under "Categories:" select "Libraries". -4. Click on the "Compile" tab. -5. Add the [Heart] class jar: - + Click on the "Add JAR/Folder" button. - + Navigate to the download folder. - + Select the "Heart-8.3.2.jar" file. - + Click on the "Open" button. -6. (optional) Add jars for javadoc and sources: - + Click on the "Edit" button. - + Click on the "Browse..." button to the right of "Javadoc:" - + Select the "Heart-8.3.2-javadoc.jar" file. - + Click on the "Open" button. - + Click on the "Browse..." button to the right of "Sources:" - + Select the "Heart-8.3.2-sources.jar" file. - + Click on the "Open" button again. - + Click on the "OK" button to close the "Edit Jar Reference" dialog. -7. Similarly, add the SkyControl jar(s). -8. Click on the "OK" button to exit the "Project Properties" dialog. +```bat +gradlew.bat packageLocal +``` -[Jump to the table of contents](#toc) +Then use it from another Gradle project: +```kotlin +repositories { + mavenLocal() + mavenCentral() +} - +dependencies { + implementation("dev.takesome:sky-simulation:1.4.3-SNAPSHOT") +} +``` -## How to build SkyControl from source +## Build -1. Install a [Java Development Kit (JDK)][adoptium], - version 17 or higher, - if you don't already have one. -2. Point the `JAVA_HOME` environment variable to your JDK installation: - (In other words, set it to the path of a directory/folder - containing a "bin" that contains a Java executable. - That path might look something like - "C:\Program Files\Eclipse Adoptium\jdk-17.0.3.7-hotspot" - or "/usr/lib/jvm/java-17-openjdk-amd64/" or - "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home" .) - + using Bash or Zsh: `export JAVA_HOME="` *path to installation* `"` - + using [Fish]: `set -g JAVA_HOME "` *path to installation* `"` - + using Windows Command Prompt: `set JAVA_HOME="` *path to installation* `"` - + using PowerShell: `$env:JAVA_HOME = '` *path to installation* `'` -3. Download and extract the SkyControl source code from GitHub: - + using [Git]: - + `git clone https://github.com/stephengold/SkyControl.git` - + `cd SkyControl` - + `git checkout -b latest 1.1.0` - + using a web browser: - + browse to [the latest release][latest] - + follow the "Source code (zip)" link - + save the ZIP file - + extract the contents of the saved ZIP file - + `cd` to the extracted directory/folder -4. Run the [Gradle] wrapper: - + using Bash or Fish or PowerShell or Zsh: `./gradlew build` - + using Windows Command Prompt: `.\gradlew build` +Requirements: -After a successful build, -Maven artifacts will be found in "SkyLibrary/build/libs". +- JDK 17+ +- Gradle wrapper from this repository -You can install the artifacts to your local Maven repository: -+ using Bash or Fish or PowerShell or Zsh: `./gradlew install` -+ using Windows Command Prompt: `.\gradlew install` +Common commands: -You can restore the project to a pristine state: -+ using Bash or Fish or PowerShell or Zsh: `./gradlew clean` -+ using Windows Command Prompt: `.\gradlew clean` +```bat +gradlew.bat :SkyLibrary:compileJava +gradlew.bat :SkyLibrary:checkstyleMain +gradlew.bat packageLocal +``` -[Jump to the table of contents](#toc) +Build release artifacts locally: +```bat +gradlew.bat :SkyLibrary:assemble -PskySimulationVersion=1.4.3 +``` - +Artifacts are generated in: -## Downloads +```text +SkyLibrary/build/libs +``` -Newer releases (since v0.9.27) can be downloaded from -[GitHub](https://github.com/stephengold/SkyControl/releases). +## Release -Older releases (v0.9.0 through v0.9.26) can be downloaded from -[the Jme3-utilities Project](https://github.com/stephengold/jme3-utilities/releases). +A GitHub release is produced by pushing a version tag: -Newer Maven artifacts (since v0.9.30) are available from -[MavenCentral](https://central.sonatype.com/artifact/com.github.stephengold/SkyControl/1.1.0/versions). +```bat +git tag -a v1.4.3 -m "SkySimulation v1.4.3" +git push origin v1.4.3 +``` -Old Maven artifacts (v0.9.25 through v0.9.29) are available from JCenter. +The release workflow publishes the Maven package to GitHub Packages and attaches the JAR, sources, Javadoc, POM, and Gradle module metadata to the GitHub Release. -[Jump to the table of contents](#toc) +## Modules +- `SkyLibrary` — runtime library and public API. +- `SkyAssets` — generated sky, star, sun, moon, cloud, and haze textures. +- `SkyExamples` — example applications and test scenes. - +## Current Take Some() additions -## Conventions - -Package names begin with `jme3utilities.sky` - -Both the source code and the pre-built libraries are compatible with JDK 8. - -In the default world coordinate system: - -+ the `+X` axis points toward the northern horizon -+ the `+Y` axis points up (toward the zenith) -+ the `+Z` axis points toward the eastern horizon - -However, these axis assignments can be overridden using `SunAndStars.setAxes()`. - -[Jump to the table of contents](#toc) - - - - -## External links - -+ November 2013 [SkyControl demo video](https://www.youtube.com/watch?v=FsJRM6tr3oQ) -+ January 2014 [SkyControl update video](https://www.youtube.com/watch?v=gE4wxgBIkaw) -+ A [driving simulator](https://github.com/stephengold/jme-vehicles) - that uses SkyControl. -+ A [flight simulation game](https://github.com/ZoltanTheHun/SkyHussars) - that uses SkyControl. +- `SkyAtmosphere` profile for atmospheric lighting and realism tuning. +- `earthlike-atmosphere.properties` default tuning preset. +- Explicit `setSunTexture(...)` and `setMoonTexture(...)` APIs. +- `clearMoonTexture()` for returning to phase-preset moon textures. +- GitHub Packages publishing under `dev.takesome:sky-simulation`. +- Release workflow for GitHub Releases. +- `SkyCloudPreset` weather presets with gradual transitions. +- DDS cloud preset resources plus `cloud-weather-presets.json` registry. +- Generated sky MatDefs/GLSL via `:SkyAssets:skyMaterials`. +- BC5/ATI2 DDS normal-map fallback loader for cloud normals. +- Smooth atmosphere transitions for gradient style, sunset intensity, and halo intensity. +- Game-facing weather subscriptions for exact weather ids, storm-like states, precipitation thresholds, wind thresholds, and all weather changes. +- Expanded logging for Lua weather ABI loading, cloud preset transitions, generated sky material creation, and compressed DDS/BC cloud texture metadata. -[Jump to the table of contents](#toc) +## Attribution +SkySimulation is based on the SkyControl project lineage and keeps the BSD-style license terms from the original codebase. Take Some() maintains this fork for our own engine/runtime work while preserving compatibility where it matters. - - -## History - -The evolution of this project is chronicled in -[its release log][log]. - -SkyControl has its roots in SkyDome by Cris (aka "t0neg0d"). - -From November 2013 to September 2015, -SkyControl was part of the Jme3-utilities Project at -[Google Code](https://code.google.com/archive/). - -From September 2015 to August 2020, -SkyControl was part of the Jme3-utilities Project at -[GitHub](https://github.com/stephengold/jme3-utilities). - -Since August 2020, SkyControl has been a separate project, hosted at -[GitHub][skycontrol]. - -Old (2014) versions of SkyControl can still be found in -[the jMonkeyEngine-Contributions Project](https://github.com/jMonkeyEngine-Contributions/SkyControl). - -[Jump to the table of contents](#toc) - - - - -## Acknowledgments - -Like most projects, the SkyControl Project builds on the work of many who -have gone before. I therefore acknowledge the following -artists and software developers: - -+ Cris (aka "t0neg0d") for creating SkyDome (which provided both an inspiration - and a starting point for SkyControl) and also for encouraging me to run with - it ... thank you yet again! -+ Paul Speed, for helpful insights which got me unstuck during debugging -+ Rémy Bouquet (aka "nehon") for many helpful insights -+ Alexandr Brui (aka "javasabr") for a solving a problem with the - de-serialization of `SkyControl` -+ the brave souls who volunteered to be alpha testers for SkyControl, including: - + Davis Rollman - + "Lockhead" - + Jonatan Dahl - + Mindaugas (aka "eraslt") - + Thomas Kluge - + "pixelapp" - + Roger (aka "stenb") -+ the beta testers for SkyControl, including: - + "madjack" - + Benjamin D. - + "Fissll" - + Davis Rollman -+ users who found and reported bugs in later versions: - + Rami Manaf - + Anton Starastsin (aka "Antonystar") -+ the creators of (and contributors to) the following software: - + Adobe Photoshop Elements - + the Ant build tool - + the [Blender] 3-D animation suite - + the [Checkstyle] tool - + the [FindBugs] source-code analyzer - + the [Firefox] and [Chrome] web browsers - + Gimp, the GNU Image Manipulation Program - + the [Git] revision-control system and GitK commit viewer - + the [GitKraken] client - + the [Gradle] build tool - + Guava core libraries for Java - + the [IntelliJ IDEA][idea] and [NetBeans] integrated development environments - + the [Java] compiler, standard doclet, and runtime environment - + the JCommander Java framework - + [jMonkeyEngine][jme] and the jME3 Software Development Kit - + the [Linux Mint][mint] operating system - + [LWJGL], the Lightweight Java Game Library - + the [Markdown] document-conversion tool - + the [Meld] visual merge tool - + Microsoft Windows - + the [Nifty] graphical user-interface library - + [Open Broadcaster Software Studio][obs] - + the PMD source-code analyzer - + Alex Peterson's Spacescape tool - + the Subversion revision-control systems - + the [WinMerge] differencing and merging tool - -Many of SkyControl's assets were based on the works of others who licensed their -works under liberal terms or contributed them to the public domain. -For this I thank: - -+ Cris (aka "t0neg0d") -+ Jacques Descloitres, MODIS Rapid Response Team, NASA/GSFC -+ Tom Ruen - -I am grateful to [GitHub], [Sonatype], [JFrog], [YouTube], and [Imgur] -for providing free hosting for this project -and many other open-source projects. - -I'm also grateful to Quinn (for lending me one of her microphones) and finally -my dear Holly, for keeping me sane. - -If I've misattributed anything or left anyone out, please let me know, so I can -correct the situation: sgold@sonic.net - -[Jump to the table of contents](#toc) - - -[adoptium]: https://adoptium.net/releases.html "Adoptium Project" -[ant]: https://ant.apache.org "Apache Ant Project" -[blender]: https://docs.blender.org "Blender Project" -[bsd3]: https://opensource.org/licenses/BSD-3-Clause "3-Clause BSD License" -[checkstyle]: https://checkstyle.org "Checkstyle" -[chrome]: https://www.google.com/chrome "Chrome" -[elements]: https://www.adobe.com/products/photoshop-elements.html "Photoshop Elements" -[findbugs]: http://findbugs.sourceforge.net "FindBugs Project" -[firefox]: https://www.mozilla.org/en-US/firefox "Firefox" -[fish]: https://fishshell.com/ "Fish command-line shell" -[git]: https://git-scm.com "Git" -[github]: https://github.com "GitHub" -[gitkraken]: https://www.gitkraken.com "GitKraken client" -[gradle]: https://gradle.org "Gradle Project" -[heart]: https://github.com/stephengold/Heart "Heart Project" -[idea]: https://www.jetbrains.com/idea/ "IntelliJ IDEA" -[imgur]: https://imgur.com/ "Imgur" -[java]: https://en.wikipedia.org/wiki/Java_(programming_language) "Java programming language" -[jfrog]: https://www.jfrog.com "JFrog" -[jme]: https://jmonkeyengine.org "jMonkeyEngine Project" -[latest]: https://github.com/stephengold/SkyControl/releases/latest "latest release" -[license]: https://github.com/stephengold/SkyControl/blob/master/LICENSE "SkyControl license" -[log]: https://github.com/stephengold/SkyControl/blob/master/SkyLibrary/release-notes.md "release log" -[lwjgl]: https://www.lwjgl.org "Lightweight Java Game Library" -[markdown]: https://daringfireball.net/projects/markdown "Markdown Project" -[maven]: https://maven.apache.org "Maven Project" -[meld]: https://meldmerge.org "Meld merge tool" -[mint]: https://linuxmint.com "Linux Mint Project" -[netbeans]: https://netbeans.org "NetBeans Project" -[nifty]: http://nifty-gui.github.io/nifty-gui "Nifty GUI Project" -[obs]: https://obsproject.com "Open Broadcaster Software Project" -[skycontrol]: https://github.com/stephengold/SkyControl "SkyControl Project" -[sonatype]: https://www.sonatype.com "Sonatype" -[utilities]: https://github.com/stephengold/jme3-utilities "Jme3-utilities Project" -[winmerge]: https://winmerge.org "WinMerge Project" -[youtube]: https://www.youtube.com/ "YouTube" +See `LICENSE` and source headers for license details. diff --git a/RELEASE_NOTES_v1.1.0.md b/RELEASE_NOTES_v1.1.0.md new file mode 100644 index 0000000..2d0f654 --- /dev/null +++ b/RELEASE_NOTES_v1.1.0.md @@ -0,0 +1,3 @@ +# SkySimulation v1.1.0 + +Cloud weather, generated sky materials, DDS presets, and BC5 cloud normal-map support. diff --git a/RELEASE_NOTES_v1.4.0.md b/RELEASE_NOTES_v1.4.0.md new file mode 100644 index 0000000..3943b2a --- /dev/null +++ b/RELEASE_NOTES_v1.4.0.md @@ -0,0 +1,59 @@ +# SkySimulation v1.4.0 + +SkySimulation 1.4.0 turns the package into a Lua-driven sky runtime bridge for Take Some / Helix / NOESIS style engine integration. + +## Highlights + +- Added a game-facing runtime layer with environment snapshots, weather metrics, lighting snapshots, and world clock support. +- Added Lua weather ABI support through `helix/lua/sky/weather.lua`. +- Added typed data-driven weather presets through `SkyCloudPresetDefinition`, `SkyCloudPresetRegistry`, and `SkyCloudPresetLoader`. +- Added Lua boot/config ABI support through `helix/lua/sky/default-sky.lua`. +- Added typed `SkySimulationConfig` loading and application for atmosphere profile, initial weather, clock, stars mode, lower dome, cloud flattening, cloud Y offset, and integration defaults. +- Added command ABI support through `helix/lua/sky/commands.lua` and `SkyCommandBus`. +- Added commands: `sky.weather.set`, `sky.weather.list`, `sky.clock.setTime`, `sky.clock.advance`, `sky.environment.snapshot`, and `sky.config.reload`. +- Added `weatherId()` to `SkyEnvironmentSnapshot` for stable external runtime snapshots. +- Preserved the existing `jme3utilities.sky` public namespace and direct Java APIs while adding Lua ABI control paths. +- Strengthened release validation: the release workflow now runs `clean build packageLocal` before publishing. + +## Runtime ABI resources + +```text +helix/lua/sky/weather.lua +helix/lua/sky/default-sky.lua +helix/lua/sky/commands.lua +``` + +## Java entry points + +```java +SkySimulationConfig config = + SkySimulationConfigLoader.loadDefault(assetManager); + +SkyControl skyControl = config.createControl(assetManager, camera); + +SkyCommandBus commandBus = new SkyCommandBus(assetManager, skyControl); +commandBus.execute(SkyCommandIds.weatherSet, "STORM", "90"); +commandBus.execute(SkyCommandIds.clockSetTime, "18.5"); +SkyCommandResult snapshot = + commandBus.execute(SkyCommandIds.environmentSnapshot); +``` + +## Package coordinates + +```kotlin +implementation("dev.takesome:sky-simulation:1.4.0") +``` + +For local development snapshots: + +```kotlin +implementation("dev.takesome:sky-simulation:1.4.0-SNAPSHOT") +``` + +## Validation + +Release validation was run with: + +```bat +gradlew.bat clean build packageLocal -PskySimulationVersion=1.4.0 +``` diff --git a/RELEASE_NOTES_v1.4.1.md b/RELEASE_NOTES_v1.4.1.md new file mode 100644 index 0000000..2256d3c --- /dev/null +++ b/RELEASE_NOTES_v1.4.1.md @@ -0,0 +1,48 @@ +# SkySimulation v1.4.1 + +SkySimulation 1.4.1 adds runtime-controllable atmospheric gradient styling for Helix / NOESIS driven worlds. + +## Highlights + +- Add physically-inspired horizon gradient weighting for sunrise, sunset, and low-altitude moonlight. +- Add `SkyGradientStyle` presets: `REALISTIC`, `CINEMATIC`, and `FANTASY`. +- Add `SkyAtmosphere` runtime controls for `gradientStyle`, `sunsetIntensity`, `sunHaloIntensity`, and `moonHaloIntensity`. +- Add Lua config ABI fields under `atmosphere` for gradient style and halo/sunset intensities. +- Add Lua gradient presets in `helix/lua/sky/default-sky.lua`. +- Add runtime atmosphere command ABI entries in `helix/lua/sky/commands.lua`: + - `sky.atmosphere.setGradient` + - `sky.atmosphere.setSunsetIntensity` + - `sky.atmosphere.setSunHaloIntensity` + - `sky.atmosphere.setMoonHaloIntensity` +- Extend `SkyCommandBus` so NOESIS / Helix can change atmospheric style at runtime without rebuilding Java. +- Improve GitHub CI for push, pull request, and manual validation on Java 17 Linux and Windows. +- Harden release workflow by using `--no-daemon` for Gradle commands. + +## Runtime command example + +```lua +sky.atmosphere.setGradient("FANTASY") +sky.atmosphere.setSunsetIntensity(2.0) +sky.atmosphere.setSunHaloIntensity(1.7) +sky.atmosphere.setMoonHaloIntensity(1.8) +``` + +## Package coordinates + +```kotlin +implementation("dev.takesome:sky-simulation:1.4.1") +``` + +For local development builds: + +```kotlin +implementation("dev.takesome:sky-simulation:1.4.1-SNAPSHOT") +``` + +## Validation + +Validated with: + +```bat +gradlew.bat --no-daemon clean build packageLocal --console=plain --stacktrace -PskySimulationVersion=1.4.1 +``` diff --git a/RELEASE_NOTES_v1.4.2.md b/RELEASE_NOTES_v1.4.2.md new file mode 100644 index 0000000..383b118 --- /dev/null +++ b/RELEASE_NOTES_v1.4.2.md @@ -0,0 +1,32 @@ +# SkySimulation v1.4.2 + +SkySimulation 1.4.2 improves runtime control and observability for atmosphere and weather systems. + +## Highlights + +- Added smooth runtime atmosphere transitions for gradient style, sunset intensity, sun halo intensity, and moon halo intensity. +- Extended the atmosphere command ABI with optional `transitionSeconds` arguments for live cinematic changes. +- Added typed game-facing weather subscriptions through `SkyEnvironmentRuntime`. +- Added subscription filters for exact weather ids, built-in presets, storm-like states, precipitation thresholds, cloudiness thresholds, wind thresholds, and all weather changes. +- Added cancellable `SkyWeatherSubscription` handles and immutable `SkyWeatherEvent` payloads. +- Expanded logging for Lua weather ABI loading, preset parsing, weather state changes, cloud transition lifecycle, generated sky material creation, and cloud normal-map application. +- Added compressed DDS/BC texture diagnostics for cloud normals: dimensions, mip levels, FourCC/code, jME image format, and block byte size. +- Added tests for atmosphere transitions and weather subscriptions. + +## Coordinates + +```kotlin +implementation("dev.takesome:sky-simulation:1.4.2") +``` + +Local development snapshot: + +```kotlin +implementation("dev.takesome:sky-simulation:1.4.2-SNAPSHOT") +``` + +## Validation + +```bat +gradlew.bat :SkyLibrary:test --no-daemon +``` diff --git a/RELEASE_NOTES_v1.4.3.md b/RELEASE_NOTES_v1.4.3.md new file mode 100644 index 0000000..935cb14 --- /dev/null +++ b/RELEASE_NOTES_v1.4.3.md @@ -0,0 +1,27 @@ +# SkySimulation v1.4.3 + +SkySimulation 1.4.3 improves the runtime architecture around game-facing weather events and prepares the package for safer growth behind the protected `master` branch. + +## Highlights + +- Extracted weather subscription management from `SkyEnvironmentRuntime` into `SkyWeatherSubscriptionRegistry`. +- Reduced weather-event dispatch overhead by avoiding per-event `ArrayList` snapshots and active-list `contains()` checks. +- Preserved the existing typed weather subscription API, cancellable handles, listener isolation, and current-state replay behavior. +- Kept runtime weather observability for gameplay, UI, AI, fog-of-war, and audio systems. +- Added release documentation for the `1.4.3` package coordinates. + +## Coordinates + +```kotlin +implementation("dev.takesome:sky-simulation:1.4.3") +``` + +Local development snapshot: + +```kotlin +implementation("dev.takesome:sky-simulation:1.4.3-SNAPSHOT") +``` + +## Validation + +- `gradlew.bat :SkyLibrary:test --no-daemon --console=plain --stacktrace` diff --git a/SkyAssets/build.gradle b/SkyAssets/build.gradle index 18a0adf..51c0254 100644 --- a/SkyAssets/build.gradle +++ b/SkyAssets/build.gradle @@ -8,9 +8,12 @@ plugins { alias(libs.plugins.download) // to retrieve files from URLs } +apply from: file('skyMaterials.gradle') + ext { bsc = 'src/main/resources/bsc5.dat' skies = '../SkyLibrary/src/main/resources/Textures/skies' + skyResources = '../SkyLibrary/src/main/resources' } sourceSets.main.java { @@ -21,6 +24,7 @@ sourceSets.main.java { dependencies { implementation(libs.heart) implementation(libs.jcommander) + implementation(libs.luaj.jse) implementation(libs.jme3.core) implementation(libs.jme3.effects) @@ -52,6 +56,10 @@ tasks.register('cleanDownload', Delete) { delete file("${bsc}.gz") } +// Register tasks to generate generated sky material definitions/shaders: + +processResources.dependsOn('skyMaterials') + // Register tasks to generate sky textures: tasks.register('skyTextures') { @@ -61,7 +69,13 @@ tasks.register('skyTextures') { description = 'generate texture assets distributed with SkyControl' } tasks.register('cleanSkyTextures', Delete) { - delete fileTree(dir: skies) + delete file("$skies/clouds/clear.png") + delete file("$skies/clouds/fbm.png") + delete file("$skies/clouds/overcast.png") + delete fileTree(dir: "$skies/moon-nonviral") + delete fileTree(dir: "$skies/ramps") + delete fileTree(dir: "$skies/star-maps") + delete fileTree(dir: "$skies/suns") } tasks.register('clouds', JavaExec) { diff --git a/SkyAssets/skyMaterials.gradle b/SkyAssets/skyMaterials.gradle new file mode 100644 index 0000000..0c6393d --- /dev/null +++ b/SkyAssets/skyMaterials.gradle @@ -0,0 +1,306 @@ +// Gradle support for generated sky material definitions and shaders. + +ext.skyDomeProfiles = [ + [name: 'dome02', objects: 0, clouds: 2], + [name: 'dome06', objects: 0, clouds: 6], + [name: 'dome20', objects: 2, clouds: 0], + [name: 'dome22', objects: 2, clouds: 2], + [name: 'dome60', objects: 6, clouds: 0], + [name: 'dome66', objects: 6, clouds: 6], +] + +tasks.register('skyMaterials') { + description = 'generate sky material definitions and shaders' + outputs.dir("$skyResources/MatDefs/skies") + outputs.dir("$skyResources/Shaders/skies") + + doLast { + def profiles = project.ext.skyDomeProfiles + profiles.each { profile -> + String name = profile.name + int numObjects = profile.objects + int numClouds = profile.clouds + writeGenerated("$skyResources/MatDefs/skies/$name/${name}.j3md", + makeMatDef(name, numObjects, numClouds)) + writeGenerated("$skyResources/Shaders/skies/$name/${name}.vert", + makeVertexShader(name, numObjects, numClouds)) + writeGenerated("$skyResources/Shaders/skies/$name/${name}.frag", + makeFragmentShader(name, numObjects, numClouds, false)) + writeGenerated("$skyResources/Shaders/skies/$name/${name}glow.frag", + makeFragmentShader(name, numObjects, numClouds, true)) + } + } +} + +void writeGenerated(String path, String text) { + File target = file(path) + target.parentFile.mkdirs() + target.text = text.replace('\n', System.lineSeparator()) +} + +String makeMatDef(String name, int objects, int clouds) { + StringBuilder b = new StringBuilder() + b << generatedLine('//') + b << "// A generated material for SkyMaterial: $objects objects and " + b << "$clouds cloud layers.\n\n" + b << "MaterialDef $name {\n" + b << ' MaterialParameters {\n' + b << ' Color ClearColor\n' + b << ' Color ClearGlow\n' + b << ' Vector2 TopCoord\n\n' + b << ' Texture2D StarsColorMap\n' + for (int i = 0; i < objects; ++i) { + b << "\n Color Object${i}Color\n" + b << " Color Object${i}Glow\n" + b << "\tTexture2D Object${i}ColorMap\n" + b << "\tVector2 Object${i}Center\n" + b << "\tVector2 Object${i}TransformU\n" + b << "\tVector2 Object${i}TransformV\n" + } + for (int i = 0; i < clouds; ++i) { + b << "\n Color Clouds${i}Color\n" + b << " Color Clouds${i}Glow\n" + b << " Float Clouds${i}Scale : 1.0\n" + b << "\tTexture2D Clouds${i}AlphaMap\n" + b << "\tTexture2D Clouds${i}NormalMap\n" + b << "\tVector2 Clouds${i}Offset\n" + } + b << '\n\tColor HazeColor\n' + b << ' Color HazeGlow\n' + b << '\tTexture2D HazeAlphaMap\n' + b << ' }\n\n' + b << makeTechnique(name, objects, clouds, false) + b << '\n' + b << makeTechnique(name, objects, clouds, true) + b << '}\n' + return b.toString() +} + +String makeTechnique(String name, int objects, int clouds, boolean glow) { + StringBuilder b = new StringBuilder() + b << (glow ? ' Technique Glow {\n' : ' Technique {\n') + b << ' Defines {\n' + if (!glow) { + b << '\t HAS_STARS : StarsColorMap\n' + } + for (int i = 0; i < objects; ++i) { + b << "\t HAS_OBJECT${i} : Object${i}ColorMap\n" + } + for (int i = 0; i < clouds; ++i) { + b << "\t HAS_CLOUDS${i} : Clouds${i}AlphaMap\n" + b << "\t HAS_CLOUDS${i}_NORMAL : Clouds${i}NormalMap\n" + } + b << '\t HAS_HAZE : HazeAlphaMap\n' + b << ' }\n' + String frag = glow ? "${name}glow.frag" : "${name}.frag" + b << " FragmentShader GLSL300 GLSL150 GLSL100: " + b << "Shaders/skies/${name}/${frag}\n" + b << " VertexShader GLSL300 GLSL150 GLSL100: " + b << "Shaders/skies/$name/${name}.vert\n" + b << ' WorldParameters {\n' + b << ' WorldViewProjectionMatrix\n' + b << ' }\n' + b << ' }\n' + return b.toString() +} + +String makeVertexShader(String name, int objects, int clouds) { + StringBuilder b = new StringBuilder() + b << generatedLine('/*') + b << "/*\n * generated vertex shader used by ${name}.j3md\n */\n" + b << '#import "Common/ShaderLib/GLSLCompat.glsllib"\n' + b << 'attribute vec2 inTexCoord;\n' + b << 'attribute vec3 inPosition;\n' + b << 'uniform mat4 g_WorldViewProjectionMatrix;\n' + b << 'uniform vec2 m_TopCoord;\n' + b << 'varying vec2 skyTexCoord;\n' + appendObjectDecls(b, objects, true) + appendCloudDecls(b, clouds, true, false) + b << '\nvoid main(){\n' + b << ' skyTexCoord = inTexCoord;\n' + if (clouds > 0) { + b << ' /*\n' + b << ' * Keep cloud coordinates consistent with material sampling.\n' + b << ' */\n' + } + for (int i = 0; i < clouds; ++i) { + b << " #ifdef HAS_CLOUDS${i}\n" + b << " clouds${i}Coord = inTexCoord " + b << "* m_Clouds${i}Scale + m_Clouds${i}Offset;\n" + b << ' #endif\n' + } + for (int i = 0; i < objects; ++i) { + b << " #ifdef HAS_OBJECT${i}\n" + b << " object${i}Offset = inTexCoord - m_Object${i}Center;\n" + b << " object${i}Coord.x = " + b << "dot(m_Object${i}TransformU, object${i}Offset);\n" + b << " object${i}Coord.y = " + b << "dot(m_Object${i}TransformV, object${i}Offset);\n" + b << " object${i}Coord += m_TopCoord;\n" + b << ' #endif\n' + } + b << '\n gl_Position = g_WorldViewProjectionMatrix ' + b << '* vec4(inPosition, 1);\n' + b << '}\n' + return b.toString() +} + +String makeFragmentShader(String name, int objects, int clouds, boolean glow) { + StringBuilder b = new StringBuilder() + b << generatedLine('/*') + String kind = glow ? 'glow fragment shader' : 'fragment shader' + b << "/*\n * generated $kind used by ${name}.j3md\n */\n" + b << '#import "Common/ShaderLib/GLSLCompat.glsllib"\n' + b << (glow ? 'uniform vec4 m_ClearGlow;\n' : 'uniform vec4 m_ClearColor;\n') + b << 'varying vec2 skyTexCoord;\n' + if (!glow) { + b << '\n#ifdef HAS_STARS\n' + b << ' uniform sampler2D m_StarsColorMap;\n' + b << '#endif\n' + } + appendObjectDecls(b, objects, false) + appendHazeDecls(b, glow) + appendCloudDecls(b, clouds, false, glow) + b << mixColorsFunction() + b << '\nvoid main() {\n' + if (glow) { + b << ' vec4 stars = vec4(0.0);\n' + } else { + b << ' #ifdef HAS_STARS\n' + b << ' vec4 stars = texture2D(m_StarsColorMap, skyTexCoord);\n' + b << ' #else\n' + b << ' vec4 stars = vec4(0.0);\n' + b << ' #endif\n' + } + b << ' vec4 objects = vec4(0.0);\n' + appendObjectMain(b, objects, glow) + b << '\n vec4 color = mixColors(stars, objects);\n\n' + b << (glow ? ' vec4 clear = m_ClearGlow;\n' : ' vec4 clear = m_ClearColor;\n') + appendHazeMain(b, glow) + b << ' color = mixColors(color, clear);\n' + b << ' color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb)' + b << ' * clear.a;\n' + appendCloudMain(b, clouds, glow) + b << '\n\tgl_FragColor = color;\n' + b << '}\n' + return b.toString() +} + +void appendObjectDecls(StringBuilder b, int objects, boolean vertex) { + for (int i = 0; i < objects; ++i) { + b << "\n#ifdef HAS_OBJECT${i}\n" + if (vertex) { + b << " uniform vec2 m_Object${i}Center;\n" + b << " uniform vec2 m_Object${i}TransformU;\n" + b << " uniform vec2 m_Object${i}TransformV;\n" + b << " varying vec2 object${i}Coord;\n" + b << " varying vec2 object${i}Offset;\n" + } else { + b << " uniform vec4 m_Object${i}Color;\n" + b << "\tuniform sampler2D m_Object${i}ColorMap;\n" + b << "\tvarying vec2 object${i}Coord;\n" + } + b << '#endif\n' + } +} + +void appendCloudDecls(StringBuilder b, int clouds, boolean vertex, + boolean glow) { + for (int i = 0; i < clouds; ++i) { + b << "\n#ifdef HAS_CLOUDS${i}\n" + if (vertex) { + b << " uniform float m_Clouds${i}Scale;\n" + b << " uniform vec2 m_Clouds${i}Offset;\n" + b << " varying vec2 clouds${i}Coord;\n" + } else { + b << " uniform sampler2D m_Clouds${i}AlphaMap;\n" + b << " uniform vec4 m_Clouds${i}${glow ? 'Glow' : 'Color'};\n" + b << "\tvarying vec2 clouds${i}Coord;\n" + } + b << '#endif\n' + if (!vertex) { + b << "#ifdef HAS_CLOUDS${i}_NORMAL\n" + b << " uniform sampler2D m_Clouds${i}NormalMap;\n" + b << '#endif\n' + } + } +} + +void appendHazeDecls(StringBuilder b, boolean glow) { + b << '\n#ifdef HAS_HAZE\n' + b << ' uniform sampler2D m_HazeAlphaMap;\n' + b << " uniform vec4 m_Haze${glow ? 'Glow' : 'Color'};\n" + b << '#endif\n' +} + +void appendObjectMain(StringBuilder b, int objects, boolean glow) { + for (int i = 0; i < objects; ++i) { + String objectColor = glow ? "m_Object${i}Glow" : "m_Object${i}Color" + b << "\n #ifdef HAS_OBJECT${i}\n" + b << " if (floor(object${i}Coord.s) == 0.0 &&\n" + b << " floor(object${i}Coord.t) == 0.0) {\n" + b << " vec4 object${i} = $objectColor;\n" + b << " object${i} *= " + b << "texture2D(m_Object${i}ColorMap, object${i}Coord);\n" + if (i == 0) { + b << " objects = object${i};\n" + } else { + b << " objects = mixColors(objects, object${i});\n" + } + b << ' }\n' + b << '\t#endif\n' + } +} + +void appendHazeMain(StringBuilder b, boolean glow) { + String hazeColor = glow ? 'm_HazeGlow' : 'm_HazeColor' + b << '\t#ifdef HAS_HAZE\n' + b << " vec4 haze = $hazeColor;\n" + b << ' haze.a *= texture2D(m_HazeAlphaMap, skyTexCoord).r;\n' + b << '\t clear = mixColors(clear, haze);\n' + b << '\t#endif\n' +} + +void appendCloudMain(StringBuilder b, int clouds, boolean glow) { + for (int i = 0; i < clouds; ++i) { + String cloudColor = glow ? "m_Clouds${i}Glow" : "m_Clouds${i}Color" + b << "\n\t#ifdef HAS_CLOUDS${i}\n" + b << " vec4 clouds${i} = $cloudColor;\n" + b << "\t\tclouds${i}.a *= " + b << "texture2D(m_Clouds${i}AlphaMap, clouds${i}Coord).r;\n" + b << "\t\t#ifdef HAS_CLOUDS${i}_NORMAL\n" + b << "\t\t\tvec3 normal${i} = " + b << "texture2D(m_Clouds${i}NormalMap, clouds${i}Coord).xyz;\n" + b << "\t\t\tnormal${i} = normal${i} * 2.0 - 1.0;\n" + b << "\t\t\tfloat relief${i} = clamp(0.90 + normal${i}.z " + b << "* 0.15, 0.75, 1.15);\n" + b << "\t\t\tclouds${i}.rgb *= relief${i};\n" + b << '\t\t#endif\n' + b << " color = mixColors(color, clouds${i});\n" + b << ' #endif\n' + } +} + +String mixColorsFunction() { + return ''' +vec4 mixColors(vec4 color0, vec4 color1) { + vec4 result; + float a0 = color0.a * (1.0 - color1.a); + result.a = a0 + color1.a; + if (result.a > 0.0) { + result.rgb = (a0 * color0.rgb + color1.a * color1.rgb) + / result.a; + } else { + result.rgb = vec3(0.0); + } + return result; +} +''' +} + +String generatedLine(String commentStyle) { + if (commentStyle == '//') { + return '// Generated by :SkyAssets:skyMaterials. Do not edit by hand.\n' + } + return '/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */\n\n' +} diff --git a/SkyAssets/src/main/java/jme3utilities/sky/textures/BrightStarCatalogReader.java b/SkyAssets/src/main/java/jme3utilities/sky/textures/BrightStarCatalogReader.java new file mode 100644 index 0000000..c6946c3 --- /dev/null +++ b/SkyAssets/src/main/java/jme3utilities/sky/textures/BrightStarCatalogReader.java @@ -0,0 +1,399 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.textures; + +import com.jme3.math.FastMath; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.Collection; +import java.util.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.math.MyMath; +import jme3utilities.sky.Constants; + +/** + * Reader for the ASCII Yale Bright Star Catalog. + * + * @author Take Some + */ +final class BrightStarCatalogReader { + + /** + * Mutable counters gathered while reading a catalog. + */ + final private static class CatalogStats { + /** Number of duplicate entries. */ + int duplicateEntries; + /** Number of missed entries. */ + int missedEntries; + /** Next expected entry id. */ + int nextEntry = 1; + /** Number of catalog entries read. */ + int readEntries; + /** Number of skipped entries. */ + int skippedEntries; + } + + /** + * Exception for unexpected invalid data in a catalog entry. + */ + final private static class InvalidEntryException extends Exception { + /** serialization identifier. */ + final static long serialVersionUID = 1L; + + /** + * Instantiate the exception. + * + * @param message descriptive text + */ + InvalidEntryException(String message) { + super(message); + } + } + + /** + * Exception for an invalid apparent magnitude in a catalog entry. + */ + final private static class InvalidMagnitudeException extends Exception { + /** serialization identifier. */ + final static long serialVersionUID = 1L; + } + + /** Expected id of the last entry in the catalog. */ + final private static int lastEntryExpected = 9_110; + /** Number of degrees from equator to pole. */ + final private static int maxDeclination = 90; + /** Maximum apparent magnitude of all stars in the catalog. */ + final private static float maxMagnitude = 7.96f; + /** Number of minutes in an hour or degree. */ + final private static int maxMinutes = 60; + /** Number of seconds in a minute. */ + final private static int maxSeconds = 60; + /** Minimum apparent magnitude of all stars in the catalog. */ + final private static float minMagnitude = -1.47f; + /** Earth's rate of rotation, in radians per sidereal hour. */ + final private static float radiansPerHour + = FastMath.TWO_PI / Constants.hoursPerDay; + /** Message logger for this class. */ + final private static Logger logger + = Logger.getLogger(BrightStarCatalogReader.class.getName()); + + /** + * Hidden constructor. + */ + private BrightStarCatalogReader() { + // do nothing + } + + /** + * Read the star catalog and collect valid stars. + * + * @param catalogFilePath filesystem path to the catalog file (not null) + * @return new collection of valid stars + */ + static Collection read(String catalogFilePath) { + Collection result = new TreeSet<>(); + File catalogFile = new File(catalogFilePath); + FileReader fileReader = null; + BufferedReader bufferedReader = null; + try { + fileReader = new FileReader(catalogFile); + bufferedReader = new BufferedReader(fileReader); + read(bufferedReader, catalogFilePath, result); + } catch (FileNotFoundException exception) { + logger.log(Level.SEVERE, "unable to open {0}", + MyString.quote(catalogFilePath)); + throw new RuntimeException(exception); + } catch (IOException exception) { + logger.log(Level.SEVERE, "unable to read {0}", + MyString.quote(catalogFilePath)); + } catch (InvalidEntryException exception) { + logger.log(Level.SEVERE, "", exception); + } finally { + close(fileReader, bufferedReader, catalogFilePath); + } + + return result; + } + + /** + * Close catalog readers. + * + * @param fileReader file reader, or null + * @param bufferedReader buffered reader, or null + * @param catalogFilePath catalog path for logging (not null) + */ + private static void close(FileReader fileReader, + BufferedReader bufferedReader, String catalogFilePath) { + try { + if (fileReader != null) { + fileReader.close(); + } + if (bufferedReader != null) { + bufferedReader.close(); + } + } catch (IOException exception) { + logger.log(Level.WARNING, "unable to close {0}", + MyString.quote(catalogFilePath)); + } + } + + /** + * Extract a star's declination from a catalog entry. + * + * @param line text line read from the catalog (not null) + * @return angle north of the celestial equator, in degrees + */ + private static float declination(String line) + throws InvalidEntryException { + assert line != null; + + String dd = line.substring(83, 86); + String mm = line.substring(86, 88); + String ss = line.substring(88, 90); + logger.log(Level.FINE, "{0}d {1}m {2}s", new Object[]{dd, mm, ss}); + + int degrees = Integer.parseInt(dd); + if (degrees < -maxDeclination || degrees > maxDeclination) { + throw new InvalidEntryException( + "dec degrees should be between -90 and 90, inclusive"); + } + int minutes = Integer.parseInt(mm); + if (minutes < 0 || minutes >= maxMinutes) { + throw new InvalidEntryException( + "dec minutes should be between 0 and 59, inclusive"); + } + float seconds = Float.parseFloat(ss); + if (seconds < 0f || seconds >= maxSeconds) { + throw new InvalidEntryException( + "dec seconds should be between 0 and 59, inclusive"); + } + + float result; + if (degrees > 0) { + result = degrees + minutes / 60f + seconds / 3600f; + } else { + result = degrees - minutes / 60f - seconds / 3600f; + } + + assert result >= -maxDeclination : result; + assert result <= maxDeclination : result; + logger.log(Level.FINE, "result = {0}", result); + return result; + } + + /** + * Read catalog lines and build up the collection of stars. + * + * @param bufferedReader reader to use (not null) + * @param catalogFilePath catalog path for logging (not null) + * @param result collection to populate (not null) + */ + private static void read(BufferedReader bufferedReader, + String catalogFilePath, Collection result) + throws IOException, InvalidEntryException { + assert bufferedReader != null; + assert result != null; + + CatalogStats stats = new CatalogStats(); + for (;;) { + String textLine = bufferedReader.readLine(); + if (textLine == null) { + break; + } + logger.log(Level.FINE, "{0}", textLine); + if (textLine.length() < 5) { + continue; + } + String actualPrefix = textLine.substring(0, 4); + if (!actualPrefix.matches("[ ]*[0-9]+")) { + continue; + } + ++stats.readEntries; + + int actualEntry = Integer.parseInt(actualPrefix.trim()); + if (actualEntry > stats.nextEntry) { + logger.log(Level.FINE, "missed entries #{0} through #{1}", + new Object[]{stats.nextEntry, actualEntry - 1}); + stats.nextEntry = actualEntry; + stats.missedEntries += actualEntry - stats.nextEntry; + + } else if (actualEntry < stats.nextEntry) { + logger.log(Level.WARNING, + "skipped entry due to duplicate id #{0}", + actualEntry); + ++stats.skippedEntries; + continue; + } + + assert actualEntry == stats.nextEntry : stats.nextEntry; + Star star = null; + try { + star = readStar(textLine, stats.nextEntry); + + } catch (InvalidMagnitudeException exception) { + logger.log(Level.FINE, + "skipped entry #{0} due to invalid magnitude", + stats.nextEntry); + ++stats.skippedEntries; + } + if (star != null) { + if (result.contains(star)) { + logger.log(Level.FINE, "entry #{0} is a duplicate", + stats.nextEntry); + ++stats.duplicateEntries; + } else { + boolean success = result.add(star); + assert success : stats.nextEntry; + } + } + ++stats.nextEntry; + } + + logStats(catalogFilePath, stats, result.size()); + } + + /** + * Construct a new star based on a catalog entry. + * + * @param textLine line of text read from the catalog (not null) + * @param entryId entry id (≥1) + * @return new star + */ + private static Star readStar(String textLine, int entryId) + throws InvalidEntryException, InvalidMagnitudeException { + assert textLine != null; + assert entryId >= 1 : entryId; + + if (textLine.length() < 107) { + throw new InvalidEntryException("catalog entry is too short"); + } + String magnitudeText = textLine.substring(102, 107); + logger.log(Level.FINE, "mag={0}", magnitudeText); + + if (magnitudeText.equals(" ")) { + throw new InvalidMagnitudeException(); + } + float apparentMagnitude; + try { + apparentMagnitude = Float.parseFloat(magnitudeText); + } catch (NumberFormatException exception) { + logger.log(Level.WARNING, "entry #{0} has invalid magnitude {1}", + new Object[]{entryId, MyString.quote(magnitudeText)}); + throw new InvalidMagnitudeException(); + } + if (apparentMagnitude < minMagnitude + || apparentMagnitude > maxMagnitude) { + logger.log(Level.WARNING, "entry #{0} has invalid magnitude {1}", + new Object[]{entryId, MyString.quote(magnitudeText)}); + throw new InvalidMagnitudeException(); + } + + float declinationDegrees = declination(textLine); + float declination = MyMath.toRadians(declinationDegrees); + float rightAscension = rightAscensionHours(textLine) * radiansPerHour; + + Star result = new Star(rightAscension, declination, apparentMagnitude); + + return result; + } + + /** + * Log catalog read statistics. + * + * @param catalogFilePath catalog path (not null) + * @param stats catalog counters (not null) + * @param collectedStars number of collected stars + */ + private static void logStats(String catalogFilePath, CatalogStats stats, + int collectedStars) { + int lastEntryRead = stats.nextEntry - 1; + if (lastEntryRead != lastEntryExpected) { + logger.log(Level.WARNING, + "expected last entry to be #{0} but it was actually #{1}", + new Object[]{lastEntryExpected, lastEntryRead}); + } + if (stats.missedEntries > 0) { + logger.log(Level.WARNING, "missed {0} entries", + stats.missedEntries); + } + logger.log(Level.INFO, "read {0} catalog entries from {1}", + new Object[]{stats.readEntries, catalogFilePath}); + if (stats.duplicateEntries > 0) { + logger.log(Level.WARNING, "{0} duplicate entries", + stats.duplicateEntries); + } + if (stats.skippedEntries > 0) { + logger.log(Level.WARNING, "{0} entries skipped", + stats.skippedEntries); + } + logger.log(Level.INFO, "collected {0} stars", collectedStars); + } + + /** + * Extract a star's right ascension from a catalog entry. + * + * @param line text line read from the catalog (not null) + * @return angle east of the March equinox, in hours + */ + private static float rightAscensionHours(String line) + throws InvalidEntryException { + assert line != null; + + String hh = line.substring(75, 77); + String mm = line.substring(77, 79); + String ss = line.substring(79, 83); + logger.log(Level.FINE, "{0}:{1}:{2}", new Object[]{hh, mm, ss}); + + int hours = Integer.parseInt(hh); + if (hours < 0 || hours >= Constants.hoursPerDay) { + throw new InvalidEntryException( + "RA hours should be between 0 and 23, inclusive"); + } + int minutes = Integer.parseInt(mm); + if (minutes < 0 || minutes >= maxMinutes) { + throw new InvalidEntryException( + "RA minutes should be between 0 and 59, inclusive"); + } + float seconds = Float.parseFloat(ss); + if (seconds < 0f || seconds >= maxSeconds) { + throw new InvalidEntryException( + "RA seconds should be between 0 and 59, inclusive"); + } + + float result = hours + minutes / 60f + seconds / 3600f; + + assert result >= 0f : result; + assert result < Constants.hoursPerDay : result; + logger.log(Level.FINE, "result = {0}", result); + return result; + } +} diff --git a/SkyAssets/src/main/java/jme3utilities/sky/textures/MakeStarMaps.java b/SkyAssets/src/main/java/jme3utilities/sky/textures/MakeStarMaps.java index 50c37e3..963f2e6 100644 --- a/SkyAssets/src/main/java/jme3utilities/sky/textures/MakeStarMaps.java +++ b/SkyAssets/src/main/java/jme3utilities/sky/textures/MakeStarMaps.java @@ -28,29 +28,15 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.IOException; import java.util.Collection; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import jme3utilities.Heart; -import jme3utilities.MyAsset; import jme3utilities.MyString; import jme3utilities.math.MyMath; -import jme3utilities.math.MyQuaternion; -import jme3utilities.math.MyVector3f; -import jme3utilities.mesh.DomeMesh; import jme3utilities.sky.Constants; /** @@ -61,86 +47,14 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * @author Stephen Gold sgold@sonic.net */ final class MakeStarMaps { - // ************************************************************************* - // throwables - - /** - * exception to indicate unexpected invalid data in a catalog entry - */ - final private static class InvalidEntryException extends Exception { - - final static long serialVersionUID = 1L; - - /** - * Instantiate the exception. - * - * @param message descriptive text - */ - InvalidEntryException(String message) { - super(message); - } - } - - /** - * exception to indicate an invalid apparent magnitude in a catalog entry: - * such entries can be ignored - */ - final private static class InvalidMagnitudeException extends Exception { - - final static long serialVersionUID = 1L; - } // ************************************************************************* // constants and loggers - /** - * luminosity of the faintest stars to include - */ - final private static float luminosityCutoff = 0.1f; - /** - * maximum (dimmest) apparent magnitude of all stars in the catalog - */ - final private static float maxMagnitude = 7.96f; - /** - * minimum (brightest) apparent magnitude of all stars in the catalog - */ - final private static float minMagnitude = -1.47f; - /** - * luminosity ratio between successive stellar magnitudes (5th root of 100) - */ - final private static float pogsonsRatio = FastMath.pow(100f, 0.2f); /** * Earth's rate of rotation (radians per sidereal hour) */ final private static float radiansPerHour = FastMath.TWO_PI / Constants.hoursPerDay; - /** - * expected id of the last entry in the catalog - */ - final private static int lastEntryExpected = 9_110; - /** - * number of degrees from equator to pole - */ - final private static int maxDeclination = 90; - /** - * number of minutes in an hour or degree - */ - final private static int maxMinutes = 60; - /** - * number of seconds in a minute - */ - final private static int maxSeconds = 60; - /** - * number of points per ellipse - */ - final private static int ellipseNumPoints = 32; - /** - * x-coordinates used to draw an ellipse - */ - final private static int[] ellipseXs = new int[ellipseNumPoints]; - /** - * y-coordinates used to draw an ellipse - */ - final private static int[] ellipseYs = new int[ellipseNumPoints]; /** * message logger for this class */ @@ -187,10 +101,6 @@ final private static class InvalidMagnitudeException extends Exception { * stars read from the catalog */ final private static Collection stars = new TreeSet<>(); - /** - * sample dome mesh for calculating texture coordinates - */ - final private static DomeMesh domeMesh = new DomeMesh(3, 2); /** * name of preset */ @@ -260,159 +170,6 @@ public static void main(String[] arguments) { // ************************************************************************* // private methods - /** - * Calculate the texture coordinates of a point that lies in the specified - * direction from the center of the cube. - * - * @param direction (length>0, unaffected) - * @param faceIndex (≥0, <6) which face of the cube - * @return a new vector, or null if direction is too far outside the face - */ - private static Vector2f cubeUV(Vector3f direction, int faceIndex) { - assert direction != null; - assert !MyVector3f.isZero(direction); - assert faceIndex >= 0 : faceIndex; - assert faceIndex < 6 : faceIndex; - - Vector3f faceDir = MyAsset.copyFaceDirection(faceIndex); - Vector3f norm = direction.normalize(); - float dot = faceDir.dot(norm); - if (dot < 0.5f) { // way outside the face - return null; - } - - // project outward to the plane of the face - norm.divideLocal(dot); - - // convert to texture coordinates - Vector3f uDir = MyAsset.copyUDirection(faceIndex); - Vector3f vDir = MyAsset.copyVDirection(faceIndex); - float u = 0.5f * (1f + uDir.dot(norm)); - float v = 0.5f * (1f + vDir.dot(norm)); - Vector2f uv = new Vector2f(u, v); - - return uv; - } - - /** - * Extract a star's declination from a catalog entry. - * - * @param line of text read from the catalog (not null) - * @return angle north of the celestial equator (in degrees, ≤90, - * ≥-90) - */ - private static float declination(String line) - throws InvalidEntryException { - assert line != null; - - // Extract declination components from the line of text. - String dd = line.substring(83, 86); - String mm = line.substring(86, 88); - String ss = line.substring(88, 90); - logger.log(Level.FINE, "{0}d {1}m {2}s", new Object[]{dd, mm, ss}); - - // sanity checks - int degrees = Integer.parseInt(dd); - if (degrees < -maxDeclination || degrees > maxDeclination) { - throw new InvalidEntryException( - "dec degrees should be between -90 and 90, inclusive"); - } - int minutes = Integer.parseInt(mm); - if (minutes < 0 || minutes >= maxMinutes) { - throw new InvalidEntryException( - "dec minutes should be between 0 and 59, inclusive"); - } - float seconds = Float.parseFloat(ss); - if (seconds < 0f || seconds >= maxSeconds) { - throw new InvalidEntryException( - "dec seconds should be between 0 and 59, inclusive"); - } - - // Convert to an angle. - float result; // in degrees - if (degrees > 0) { - result = degrees + minutes / 60f + seconds / 3600f; - } else { - result = degrees - minutes / 60f - seconds / 3600f; - } - - assert result >= -maxDeclination : result; - assert result <= maxDeclination : result; - logger.log(Level.FINE, "result = {0}", result); - return result; - } - - /** - * Generate 6 starry sky texture maps for a cube. - * - * @param latitude radians north of the equator (≤Pi/2, ≥-Pi/2) - * @param siderealTime radians since sidereal midnight (<2*Pi, ≥0) - * @param textureSize size of each texture map (pixels per side, >2) - * @return new instance - */ - private static RenderedImage[] generateCubeMap( - float latitude, float siderealTime, int textureSize) { - assert latitude >= -FastMath.HALF_PI : latitude; - assert latitude <= FastMath.HALF_PI : latitude; - assert siderealTime >= 0f : siderealTime; - assert siderealTime < FastMath.TWO_PI : siderealTime; - assert textureSize > 2 : textureSize; - - // Create a blank, grayscale buffered image for each texture map. - BufferedImage[] maps = new BufferedImage[6]; - for (int faceIndex = 0; faceIndex < 6; ++faceIndex) { - maps[faceIndex] = new BufferedImage( - textureSize, textureSize, BufferedImage.TYPE_BYTE_GRAY); - } - - // Plot individual stars on the images, starting with the faintest. - int plotCount = 0; - for (Star star : stars) { - boolean success = plotStarOnCube( - maps, star, latitude, siderealTime, textureSize); - if (success) { - ++plotCount; - } - } - logger.log(Level.FINE, "plotted {0} stars", plotCount); - - return maps; - } - - /** - * Generate a starry sky texture map for a dome. - * - * @param latitude radians north of the equator (≤Pi/2, ≥-Pi/2) - * @param siderealTime radians since sidereal midnight (<2*Pi, ≥0) - * @param textureSize size of the texture map (pixels per side, >2) - * @return new instance - */ - private static RenderedImage generateDomeMap( - float latitude, float siderealTime, int textureSize) { - assert latitude >= -FastMath.HALF_PI : latitude; - assert latitude <= FastMath.HALF_PI : latitude; - assert siderealTime >= 0f : siderealTime; - assert siderealTime < FastMath.TWO_PI : siderealTime; - assert textureSize > 2 : textureSize; - - // Create a blank, grayscale buffered image for the texture map. - BufferedImage map = new BufferedImage( - textureSize, textureSize, BufferedImage.TYPE_BYTE_GRAY); - - // Plot individual stars on the image, starting with the faintest. - int plotCount = 0; - for (Star star : stars) { - boolean success = plotStarOnDome( - map, star, latitude, siderealTime, textureSize); - if (success) { - ++plotCount; - } - } - logger.log(Level.FINE, "plotted {0} stars", plotCount); - - return map; - } - /** * Generate starry sky texture map(s) for the specified preset. * @@ -435,8 +192,8 @@ private static void generateMap(StarMapPreset preset) { float siderealTime = siderealHour * radiansPerHour; if (forCube) { // Generate 6 texture maps for a cube. - RenderedImage[] images = generateCubeMap(latitude, siderealTime, - textureSize); + RenderedImage[] images = StarMapRenderer.generateCubeMap( + stars, latitude, siderealTime, textureSize); assert images.length == 6 : images.length; for (int faceIndex = 0; faceIndex < 6; ++faceIndex) { String filePath = String.format("%s/%s/%s_%s%d.png", @@ -451,8 +208,8 @@ private static void generateMap(StarMapPreset preset) { } } else { // Generate a texture map for a dome. - RenderedImage image = generateDomeMap(latitude, siderealTime, - textureSize); + RenderedImage image = StarMapRenderer.generateDomeMap( + stars, latitude, siderealTime, textureSize); String filePath = String.format("%s/%s.png", outputDirPath, preset.textureFileName()); try { @@ -463,627 +220,12 @@ private static void generateMap(StarMapPreset preset) { } } - /** - * Plot a four-pointed star shape on a texture map. - * - * @param map texture map (not null) - * @param luminosity star's relative luminosity (in terms of pure white - * pixels, ≤37, >0) - * @param textureSize size of the texture map (pixels per side, >2) - * @param uv star's texture coordinates (not null) - * @return true if the star was successfully plotted, otherwise false - */ - private static boolean plot4PointStar(BufferedImage map, float luminosity, - int textureSize, Vector2f uv) { - assert luminosity > 0f : luminosity; - assert luminosity <= 37f : luminosity; - assert textureSize > 2 : textureSize; - assert uv != null; - /* - * Convert the star's luminosity into a shape and pixel color. - * - * The shape must be big enough to ensure that the pixels will not be - * oversaturated. For instance, a star with luminosity=4.1 - * must fill at least 5 pixels. - */ - int minPixels = (int) FastMath.ceil(luminosity); - assert minPixels >= 1 : minPixels; - /* - * Star shapes consist of a square portion (up to 5x5 pixels) - * plus optional rays. Rays are used only with odd-sized squares; - * they add either 1 or 3 pixels to each side of the square. - * In other words, they add either 4 or 12 pixels. - */ - int raySize; - int squareSize; - if (minPixels == 1) { - raySize = 0; - squareSize = 1; - } else if (minPixels <= 4) { - raySize = 0; - squareSize = 2; - } else if (minPixels <= 5) { - raySize = 1; - squareSize = 1; - } else if (minPixels <= 9) { - raySize = 0; - squareSize = 3; - } else if (minPixels <= 13) { - raySize = 1; - squareSize = 3; - } else if (minPixels <= 16) { - raySize = 0; - squareSize = 4; - } else if (minPixels <= 21) { - raySize = 3; - squareSize = 3; - } else if (minPixels <= 29) { - raySize = 1; - squareSize = 5; - } else if (minPixels <= 37) { - raySize = 3; - squareSize = 5; - } else { - logger.log(Level.SEVERE, "no shape contains {0} pixels", minPixels); - return false; - } - int numPixels = squareSize * squareSize + 4 * raySize; - assert numPixels >= minPixels : minPixels; - int brightness = Math.round(255f * luminosity / numPixels); - assert brightness >= 0 : brightness; - assert brightness <= 255 : brightness; - // TODO tint based on spectral type - Color color = new Color(brightness, brightness, brightness); - /* - * Convert the texture coordinates into (x, y) image coordinates of - * the square's upper-left pixel. - */ - float u = uv.x; - float v = uv.y; - float cornerOffset = 0.5f * (squareSize - 1); - int x = Math.round(u * textureSize - cornerOffset); - int y = Math.round(v * textureSize - cornerOffset); - - // Plot the star onto the texture map. - Graphics2D graphics = map.createGraphics(); - graphics.setColor(color); - graphics.fillRect(x, y, squareSize, squareSize); - if (raySize == 0) { - return true; - } - - assert MyMath.isOdd(squareSize) : squareSize; - int halfSize = (squareSize - 1) / 2; - switch (raySize) { - case 1: - graphics.fillRect(x - 1, y + halfSize, 1, 1); - graphics.fillRect(x + halfSize, y - 1, 1, 1); - graphics.fillRect(x + halfSize, y + squareSize, 1, 1); - graphics.fillRect(x + squareSize, y + halfSize, 1, 1); - break; - - case 3: - graphics.fillRect(x - 1, y + halfSize - 1, 1, 3); - graphics.fillRect(x + halfSize - 1, y - 1, 3, 1); - graphics.fillRect(x + halfSize - 1, y + squareSize, 3, 1); - graphics.fillRect(x + squareSize, y + halfSize - 1, 1, 3); - break; - - default: - assert false : raySize; - } - return true; - } - - /** - * Draw an ellipse -- a circle stretched to compensate for UV distortion - * near the rim of the dome. - * - * @param map texture map (not null) - * @param luminosity star's relative luminosity (in terms of pure white - * pixels, >0) - * @param textureSize size of the texture map (pixels per side, >2) - * @param uv star's texture coordinates (not null) - */ - private static void plotEllipseForDome(BufferedImage map, float luminosity, - int textureSize, Vector2f uv) { - assert map != null; - assert luminosity > 0f : luminosity; - assert textureSize > 2 : textureSize; - assert uv != null; - float u = uv.x; - float v = uv.y; - assert u >= Constants.uvMin : u; - assert u <= Constants.uvMax : u; - assert v >= Constants.uvMin : v; - assert v <= Constants.uvMax : v; - - Vector2f offset = uv.subtract(Constants.topUV); - float topDist = offset.length(); - float xDir; - float yDir; - if (topDist > 0f) { - xDir = offset.x / topDist; - yDir = offset.y / topDist; - } else { - xDir = 1f; - yDir = 0f; - } - float stretchFactor = 1f - + Constants.stretchCoefficient * topDist * topDist; - float a = FastMath.sqrt(luminosity * stretchFactor / FastMath.PI); - float b = a / stretchFactor; - - for (int i = 0; i < ellipseNumPoints; ++i) { - float theta = FastMath.TWO_PI * i / ellipseNumPoints; - float da = a * FastMath.cos(theta); - float db = b * FastMath.sin(theta); - float dx = db * xDir + da * yDir; - float dy = db * yDir - da * xDir; - int x = Math.round(u * textureSize + dx); - int y = Math.round(v * textureSize + dy); - ellipseXs[i] = x; - ellipseYs[i] = y; - } - Graphics2D graphics = map.createGraphics(); - graphics.setColor(Color.WHITE); // TODO tint based on spectral type - graphics.fillPolygon(ellipseXs, ellipseYs, ellipseNumPoints); - } - - /** - * Draw an ellipse -- a circle stretched to compensate for UV distortion - * near the edges of the quad. - * - * @param map texture map (not null) - * @param luminosity star's relative luminosity (>0) - * @param textureSize size of the texture map (pixels per side, >2) - * @param worldDirection the star's world coordinates (length=1) - * @param faceIndex which face of the cube (≥0, <6) - */ - private static void plotEllipseForQuad(BufferedImage map, float luminosity, - int textureSize, Vector3f worldDirection, int faceIndex) { - assert map != null; - assert luminosity > 0f : luminosity; - assert textureSize > 2 : textureSize; - assert worldDirection != null; - assert faceIndex >= 0 : faceIndex; - assert faceIndex < 6 : faceIndex; - - Vector3f basis1 = worldDirection.clone(); - Vector3f basis2 = new Vector3f(); - Vector3f basis3 = new Vector3f(); - MyVector3f.generateBasis(basis1, basis2, basis3); - float numPixels = textureSize * textureSize; - float area = luminosity / numPixels; - float r = 1.2f * FastMath.sqrt(area); - - Vector3f p = new Vector3f(); - for (int i = 0; i < ellipseNumPoints; ++i) { - float theta = FastMath.TWO_PI * i / ellipseNumPoints; - float rCos = r * FastMath.cos(theta); - float rSin = r * FastMath.sin(theta); - p.scaleAdd(rCos, basis2, basis1); - p.scaleAdd(rSin, basis3, p); - Vector2f uv = cubeUV(p, faceIndex); - if (uv == null) { - return; - } - int x = Math.round(uv.x * textureSize); - int y = Math.round(uv.y * textureSize); - ellipseXs[i] = x; - ellipseYs[i] = y; - } - Graphics2D graphics = map.createGraphics(); - graphics.setColor(Color.WHITE); // TODO tint based on spectral type - graphics.fillPolygon(ellipseXs, ellipseYs, ellipseNumPoints); - } - - /** - * Plot a star's position at the specified time onto a cube. - * - * @param maps texture maps for 6 cube faces (not null, modified) - * @param star star to plot (not null) - * @param latitude radians north of the equator (≤Pi/2, ≥-Pi/2) - * @param siderealTime radians since sidereal midnight (<2*Pi, ≥0) - * @param textureSize size of the texture map (pixels per side, >2) - * @return true if the star was successfully plotted, otherwise false - */ - private static boolean plotStarOnCube(BufferedImage[] maps, Star star, - float latitude, float siderealTime, int textureSize) { - assert maps != null; - assert maps.length == 6 : maps.length; - assert star != null; - assert latitude >= -FastMath.HALF_PI : latitude; - assert latitude <= FastMath.HALF_PI : latitude; - assert siderealTime >= 0f : siderealTime; - assert siderealTime < FastMath.TWO_PI : siderealTime; - assert textureSize > 2 : textureSize; - - Vector3f equatorial = star.getEquatorialLocation(siderealTime); - /* - * Convert equatorial coordinates to world coordinates, where: - * +X points to the north horizon - * +Y points to the zenith - * +Z points to the east horizon - * - * The conversion consists of a (latitude - Pi/2) rotation about the Y - * (east) axis followed by permutation of the axes. - */ - float coLatitude = FastMath.HALF_PI - latitude; - Quaternion rotation = new Quaternion(); - rotation.fromAngleNormalAxis(-coLatitude, Vector3f.UNIT_Y); - Vector3f rotated = MyQuaternion.rotate(rotation, equatorial, null); - assert rotated.isUnitVector() : rotated; - Vector3f world = new Vector3f(-rotated.x, rotated.z, rotated.y); - - float apparentMagnitude = star.getApparentMagnitude(); - boolean success = plotStarOnCube(maps, apparentMagnitude, - textureSize, world); - - return success; - } - - /** - * Plot a star onto a texture map for a cube. - * - * @param maps texture maps for 6 cube faces (not null, modified) - * @param apparentMagnitude the star's brightness (log scale) - * @param textureSize size of the texture map (pixels per side, <2) - * @param worldDirection the star's world coordinates (length=1) - * @return true if the star was successfully plotted, otherwise false - */ - private static boolean plotStarOnCube(BufferedImage[] maps, - float apparentMagnitude, int textureSize, Vector3f worldDirection) { - assert maps != null; - assert maps.length == 6 : maps.length; - assert worldDirection != null; - assert worldDirection.isUnitVector() : worldDirection; - assert textureSize > 2 : textureSize; - - // Convert apparent magnitude to relative luminosity. - float resolution = textureSize / 2_048f; - float luminosity0 = 100f * resolution * resolution; - float luminosity = luminosity0 * 1.5f - * FastMath.pow(pogsonsRatio, -apparentMagnitude); - if (luminosity < luminosityCutoff) { - return false; - } - - boolean success = false; - for (int faceIndex = 0; faceIndex < 6; ++faceIndex) { - /* - * Convert world direction to texture coordinates on this - * face of the cube. - */ - Vector2f uv = cubeUV(worldDirection, faceIndex); - if (uv != null) { - BufferedImage map = maps[faceIndex]; - - if (luminosity <= 37f) { - boolean success2 = plot4PointStar(map, luminosity, - textureSize, uv); - if (success2) { - success = true; - } - } else { - plotEllipseForQuad(map, luminosity, textureSize, - worldDirection, faceIndex); - success = true; - } - } - } - return success; - } - - /** - * Plot a star's position at the specified time onto a texture map for a - * dome. - * - * @param map texture map (not null, modified) - * @param star star to plot (not null) - * @param latitude radians north of the equator (≤Pi/2, ≥-Pi/2) - * @param siderealTime radians since sidereal midnight (<2*Pi, ≥0) - * @param textureSize size of the texture map (pixels per side, >2) - * @return true if the star was successfully plotted, otherwise false - */ - private static boolean plotStarOnDome(BufferedImage map, Star star, - float latitude, float siderealTime, int textureSize) { - assert map != null; - assert star != null; - assert latitude >= -FastMath.HALF_PI : latitude; - assert latitude <= FastMath.HALF_PI : latitude; - assert siderealTime >= 0f : siderealTime; - assert siderealTime < FastMath.TWO_PI : siderealTime; - assert textureSize > 2 : textureSize; - - Vector3f equatorial = star.getEquatorialLocation(siderealTime); - /* - * Convert equatorial coordinates to world coordinates, where: - * +X points to the north horizon - * +Y points to the zenith - * +Z points to the east horizon - * - * The conversion consists of a (latitude - Pi/2) rotation about the Y - * (east) axis followed by permutation of the axes. - */ - float coLatitude = FastMath.HALF_PI - latitude; - Quaternion rotation = new Quaternion(); - rotation.fromAngleNormalAxis(-coLatitude, Vector3f.UNIT_Y); - Vector3f rotated = MyQuaternion.rotate(rotation, equatorial, null); - assert rotated.isUnitVector() : rotated; - if (rotated.z < 0f) { // The star lies below the horizon, so skip it. - return false; - } - Vector3f world = new Vector3f(-rotated.x, rotated.z, rotated.y); - - float apparentMagnitude = star.getApparentMagnitude(); - boolean success = plotStarOnDome( - map, apparentMagnitude, textureSize, world); - - return success; - } - - /** - * Plot a star on a texture map for a dome. - * - * @param map texture map (not null) - * @param apparentMagnitude the star's brightness - * @param textureSize size of the texture map (pixels per side, <2) - * @param worldDirection the star's world coordinates (length=1) - * @return true if the star was successfully plotted, otherwise false - */ - private static boolean plotStarOnDome(BufferedImage map, - float apparentMagnitude, int textureSize, Vector3f worldDirection) { - assert map != null; - assert worldDirection != null; - assert worldDirection.isUnitVector() : worldDirection; - assert textureSize > 2 : textureSize; - - // Convert apparent magnitude to relative luminosity. - float resolution = textureSize / 2_048f; - float luminosity0 = 37f * resolution * resolution; - float luminosity - = luminosity0 * FastMath.pow(pogsonsRatio, -apparentMagnitude); - if (luminosity < luminosityCutoff) { - return false; - } - - // Convert world direction to texture coordinates on a dome. - Vector2f uv = domeMesh.directionUV(worldDirection); - - if (luminosity <= 37f) { - boolean success = plot4PointStar(map, luminosity, textureSize, uv); - return success; - } - plotEllipseForDome(map, luminosity, textureSize, uv); - return true; - } - /** * Read the star catalog and add each valid star to the collection. */ private static void readCatalog() { - File catalogFile = new File(catalogFilePath); - FileReader fileReader = null; - BufferedReader bufferedReader = null; - try { - fileReader = new FileReader(catalogFile); - bufferedReader = new BufferedReader(fileReader); - readCatalog(bufferedReader); - } catch (FileNotFoundException exception) { - logger.log(Level.SEVERE, "unable to open {0}", - MyString.quote(catalogFilePath)); - throw new RuntimeException(exception); - } catch (IOException exception) { - logger.log(Level.SEVERE, "unable to read {0}", - MyString.quote(catalogFilePath)); - } catch (InvalidEntryException exception) { - logger.log(Level.SEVERE, "", exception); - } finally { - try { - if (fileReader != null) { - fileReader.close(); - } - if (bufferedReader != null) { - bufferedReader.close(); - } - } catch (IOException exception) { - logger.log(Level.WARNING, "unable to close {0}", - MyString.quote(catalogFilePath)); - } - } - } - - /** - * Read the catalog line by line and use the data therein to build up the - * collection of stars. - * - * @param bufferedReader the reader to use (not null) - */ - private static void readCatalog(BufferedReader bufferedReader) - throws IOException, InvalidEntryException { - assert bufferedReader != null; - - int duplicateEntries = 0; - int nextEntry = 1; - int missedEntries = 0; - int readEntries = 0; - int skippedEntries = 0; - for (;;) { - String textLine; - textLine = bufferedReader.readLine(); - if (textLine == null) { - // Might have reached the end of the catalog file. - break; - } - logger.log(Level.FINE, "{0}", textLine); - /* - * If the line does not resemble a catalog entry, - * then silently ignore it. - */ - if (textLine.length() < 5) { - continue; - } - String actualPrefix = textLine.substring(0, 4); - if (!actualPrefix.matches("[ ]*[0-9]+")) { - continue; - } - ++readEntries; - - // Cope with missing/duplicate entry ids. - int actualEntry = Integer.parseInt(actualPrefix.trim()); - if (actualEntry > nextEntry) { - logger.log(Level.FINE, "missed entries #{0} through #{1}", - new Object[]{nextEntry, actualEntry - 1}); - nextEntry = actualEntry; - missedEntries += actualEntry - nextEntry; - - } else if (actualEntry < nextEntry) { - logger.log(Level.WARNING, - "skipped entry due to duplicate id #{0}", - actualEntry); - ++skippedEntries; - continue; - } - - assert actualEntry == nextEntry : nextEntry; - Star star = null; - try { - star = readStar(textLine, nextEntry); - - } catch (InvalidMagnitudeException exception) { - logger.log(Level.FINE, - "skipped entry #{0} due to invalid magnitude", - nextEntry); - ++skippedEntries; - } - if (star != null) { - if (stars.contains(star)) { - logger.log(Level.FINE, "entry #{0} is a duplicate", - nextEntry); - ++duplicateEntries; - } else { - boolean success = stars.add(star); - assert success : nextEntry; - } - } - ++nextEntry; - } - - // Verify that the entire catalog was read. - int lastEntryRead = nextEntry - 1; - if (lastEntryRead != lastEntryExpected) { - logger.log(Level.WARNING, - "expected last entry to be #{0} but it was actually #{1}", - new Object[]{lastEntryExpected, lastEntryRead}); - } - - // Log statistics. - if (missedEntries > 0) { - logger.log(Level.WARNING, "missed {0} entries", missedEntries); - } - logger.log(Level.INFO, "read {0} catalog entries from {1}", - new Object[]{readEntries, catalogFilePath}); - if (duplicateEntries > 0) { - logger.log(Level.WARNING, "{0} duplicate entries", - duplicateEntries); - } - if (skippedEntries > 0) { - logger.log(Level.WARNING, "{0} entries skipped", skippedEntries); - } - logger.log(Level.INFO, "collected {0} stars", stars.size()); - } - - /** - * Construct a new star based on a catalog entry. - * - * @param textLine line of text read from the catalog (not null) - * @param entryId (≥1) - * @return new instance - */ - private static Star readStar(String textLine, int entryId) - throws InvalidEntryException, InvalidMagnitudeException { - assert textLine != null; - assert entryId >= 1 : entryId; - - // Extract the apparent magnitude field from the line of text. - if (textLine.length() < 107) { - throw new InvalidEntryException("catalog entry is too short"); - } - String magnitudeText = textLine.substring(102, 107); - logger.log(Level.FINE, "mag={0}", magnitudeText); - - // sanity checks on the magnitude - if (magnitudeText.equals(" ")) { - throw new InvalidMagnitudeException(); - } - float apparentMagnitude; - try { - apparentMagnitude = Float.parseFloat(magnitudeText); - } catch (NumberFormatException exception) { - logger.log(Level.WARNING, "entry #{0} has invalid magnitude {1}", - new Object[]{entryId, MyString.quote(magnitudeText)}); - throw new InvalidMagnitudeException(); - } - if (apparentMagnitude < minMagnitude - || apparentMagnitude > maxMagnitude) { - logger.log(Level.WARNING, "entry #{0} has invalid magnitude {1}", - new Object[]{entryId, MyString.quote(magnitudeText)}); - throw new InvalidMagnitudeException(); - } - /* - * Compute the star's equatorial coordinates - * and convert them to radians. - */ - float declinationDegrees = declination(textLine); - float declination = MyMath.toRadians(declinationDegrees); - float rightAscension = rightAscensionHours(textLine) * radiansPerHour; - - // Instantiate the star. - Star result = new Star(rightAscension, declination, apparentMagnitude); - - return result; + stars.clear(); + stars.addAll(BrightStarCatalogReader.read(catalogFilePath)); } - /** - * Extract a star's right ascension from a catalog entry. - * - * @param line of text read from the catalog (not null) - * @return angle east of the March equinox (in hours, <24, ≥0) - */ - private static float rightAscensionHours(String line) - throws InvalidEntryException { - assert line != null; - - // Extract right ascension components from the line of text. - String hh = line.substring(75, 77); - String mm = line.substring(77, 79); - String ss = line.substring(79, 83); - logger.log(Level.FINE, "{0}:{1}:{2}", new Object[]{hh, mm, ss}); - - // sanity checks - int hours = Integer.parseInt(hh); - if (hours < 0 || hours >= Constants.hoursPerDay) { - throw new InvalidEntryException( - "RA hours should be between 0 and 23, inclusive"); - } - int minutes = Integer.parseInt(mm); - if (minutes < 0 || minutes >= maxMinutes) { - throw new InvalidEntryException( - "RA minutes should be between 0 and 59, inclusive"); - } - float seconds = Float.parseFloat(ss); - if (seconds < 0f || seconds >= maxSeconds) { - throw new InvalidEntryException( - "RA seconds should be between 0 and 59, inclusive"); - } - - // Convert to an angle. - float result = hours + minutes / 60f + seconds / 3600f; // in hours - - assert result >= 0f : result; - assert result < Constants.hoursPerDay : result; - logger.log(Level.FINE, "result = {0}", result); - return result; - } } diff --git a/SkyAssets/src/main/java/jme3utilities/sky/textures/StarMapPlotter.java b/SkyAssets/src/main/java/jme3utilities/sky/textures/StarMapPlotter.java new file mode 100644 index 0000000..ab6f4cb --- /dev/null +++ b/SkyAssets/src/main/java/jme3utilities/sky/textures/StarMapPlotter.java @@ -0,0 +1,347 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.textures; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyVector3f; +import jme3utilities.mesh.DomeMesh; +import jme3utilities.sky.Constants; + +/** + * Draws stars onto dome and cube star-map textures. + * + * @author Take Some + */ +final class StarMapPlotter { + /** Luminosity of the faintest stars to draw. */ + final private static float luminosityCutoff = 0.1f; + /** Number of points per ellipse. */ + final private static int ellipseNumPoints = 32; + /** Luminosity ratio between successive stellar magnitudes. */ + final private static float pogsonsRatio = FastMath.pow(100f, 0.2f); + /** Sample dome mesh for calculating texture coordinates. */ + final private static DomeMesh domeMesh = new DomeMesh(3, 2); + /** x-coordinates used to draw an ellipse. */ + final private static int[] ellipseXs = new int[ellipseNumPoints]; + /** y-coordinates used to draw an ellipse. */ + final private static int[] ellipseYs = new int[ellipseNumPoints]; + /** Message logger for this class. */ + final private static Logger logger + = Logger.getLogger(StarMapPlotter.class.getName()); + + /** + * Hidden constructor. + */ + private StarMapPlotter() { + // do nothing + } + + /** + * Plot a star's position at the specified time onto a cube. + * + * @param maps texture maps for 6 cube faces (not null, modified) + * @param star star to plot (not null) + * @param latitude radians north of the equator (≤Pi/2, ≥-Pi/2) + * @param siderealTime radians since sidereal midnight (<2*Pi, ≥0) + * @param textureSize size of the texture map (pixels per side, >2) + * @return true if the star was plotted, otherwise false + */ + static boolean plotOnCube(BufferedImage[] maps, Star star, + float latitude, float siderealTime, int textureSize) { + Vector3f world = StarMapProjection.worldDirection( + star, latitude, siderealTime); + float apparentMagnitude = star.getApparentMagnitude(); + boolean result = plotOnCube( + maps, apparentMagnitude, textureSize, world); + + return result; + } + + /** + * Plot a star's position at the specified time onto a dome. + * + * @param map texture map (not null, modified) + * @param star star to plot (not null) + * @param latitude radians north of the equator (≤Pi/2, ≥-Pi/2) + * @param siderealTime radians since sidereal midnight (<2*Pi, ≥0) + * @param textureSize size of the texture map (pixels per side, >2) + * @return true if the star was plotted, otherwise false + */ + static boolean plotOnDome(BufferedImage map, Star star, + float latitude, float siderealTime, int textureSize) { + Vector3f world = StarMapProjection.worldDirection( + star, latitude, siderealTime); + if (world.y < 0f) { + return false; + } + + float apparentMagnitude = star.getApparentMagnitude(); + boolean result = plotOnDome( + map, apparentMagnitude, textureSize, world); + + return result; + } + + /** + * Plot a four-pointed star shape on a texture map. + * + * @param map texture map (not null) + * @param luminosity star luminosity in white-pixel units (≤37, >0) + * @param textureSize texture size in pixels per side (>2) + * @param uv star texture coordinates (not null) + * @return true if the star was plotted, otherwise false + */ + private static boolean plot4PointStar(BufferedImage map, float luminosity, + int textureSize, Vector2f uv) { + assert luminosity > 0f : luminosity; + assert luminosity <= 37f : luminosity; + assert textureSize > 2 : textureSize; + assert uv != null; + + int minPixels = (int) FastMath.ceil(luminosity); + assert minPixels >= 1 : minPixels; + int raySize; + int squareSize; + if (minPixels == 1) { + raySize = 0; + squareSize = 1; + } else if (minPixels <= 4) { + raySize = 0; + squareSize = 2; + } else if (minPixels <= 5) { + raySize = 1; + squareSize = 1; + } else if (minPixels <= 9) { + raySize = 0; + squareSize = 3; + } else if (minPixels <= 13) { + raySize = 1; + squareSize = 3; + } else if (minPixels <= 16) { + raySize = 0; + squareSize = 4; + } else if (minPixels <= 21) { + raySize = 3; + squareSize = 3; + } else if (minPixels <= 29) { + raySize = 1; + squareSize = 5; + } else if (minPixels <= 37) { + raySize = 3; + squareSize = 5; + } else { + logger.log(Level.SEVERE, "no shape contains {0} pixels", minPixels); + return false; + } + + int numPixels = squareSize * squareSize + 4 * raySize; + assert numPixels >= minPixels : minPixels; + int brightness = Math.round(255f * luminosity / numPixels); + Color color = new Color(brightness, brightness, brightness); + float cornerOffset = 0.5f * (squareSize - 1); + int x = Math.round(uv.x * textureSize - cornerOffset); + int y = Math.round(uv.y * textureSize - cornerOffset); + + Graphics2D graphics = map.createGraphics(); + graphics.setColor(color); + graphics.fillRect(x, y, squareSize, squareSize); + if (raySize == 0) { + return true; + } + + assert MyMath.isOdd(squareSize) : squareSize; + int halfSize = (squareSize - 1) / 2; + switch (raySize) { + case 1: + graphics.fillRect(x - 1, y + halfSize, 1, 1); + graphics.fillRect(x + halfSize, y - 1, 1, 1); + graphics.fillRect(x + halfSize, y + squareSize, 1, 1); + graphics.fillRect(x + squareSize, y + halfSize, 1, 1); + break; + case 3: + graphics.fillRect(x - 1, y + halfSize - 1, 1, 3); + graphics.fillRect(x + halfSize - 1, y - 1, 3, 1); + graphics.fillRect(x + halfSize - 1, y + squareSize, 3, 1); + graphics.fillRect(x + squareSize, y + halfSize - 1, 1, 3); + break; + default: + assert false : raySize; + } + return true; + } + + /** + * Draw a dome-corrected ellipse onto a texture map. + * + * @param map texture map (not null) + * @param luminosity star luminosity (>0) + * @param textureSize texture size in pixels per side (>2) + * @param uv star texture coordinates (not null) + */ + private static void plotEllipseForDome(BufferedImage map, float luminosity, + int textureSize, Vector2f uv) { + assert map != null; + assert luminosity > 0f : luminosity; + assert textureSize > 2 : textureSize; + assert uv != null; + + Vector2f offset = uv.subtract(Constants.topUV); + float topDist = offset.length(); + float xDir; + float yDir; + if (topDist > 0f) { + xDir = offset.x / topDist; + yDir = offset.y / topDist; + } else { + xDir = 1f; + yDir = 0f; + } + float stretchFactor = 1f + + Constants.stretchCoefficient * topDist * topDist; + float a = FastMath.sqrt(luminosity * stretchFactor / FastMath.PI); + float b = a / stretchFactor; + + for (int i = 0; i < ellipseNumPoints; ++i) { + float theta = FastMath.TWO_PI * i / ellipseNumPoints; + float da = a * FastMath.cos(theta); + float db = b * FastMath.sin(theta); + float dx = db * xDir + da * yDir; + float dy = db * yDir - da * xDir; + ellipseXs[i] = Math.round(uv.x * textureSize + dx); + ellipseYs[i] = Math.round(uv.y * textureSize + dy); + } + Graphics2D graphics = map.createGraphics(); + graphics.setColor(Color.WHITE); + graphics.fillPolygon(ellipseXs, ellipseYs, ellipseNumPoints); + } + + /** + * Draw a cube-face corrected ellipse onto a texture map. + * + * @param map texture map (not null) + * @param luminosity star luminosity (>0) + * @param textureSize texture size in pixels per side (>2) + * @param worldDirection star world direction (length=1) + * @param faceIndex cube face index (≥0, <6) + */ + private static void plotEllipseForQuad(BufferedImage map, float luminosity, + int textureSize, Vector3f worldDirection, int faceIndex) { + Vector3f basis1 = worldDirection.clone(); + Vector3f basis2 = new Vector3f(); + Vector3f basis3 = new Vector3f(); + MyVector3f.generateBasis(basis1, basis2, basis3); + float numPixels = textureSize * textureSize; + float area = luminosity / numPixels; + float r = 1.2f * FastMath.sqrt(area); + + Vector3f p = new Vector3f(); + for (int i = 0; i < ellipseNumPoints; ++i) { + float theta = FastMath.TWO_PI * i / ellipseNumPoints; + p.scaleAdd(r * FastMath.cos(theta), basis2, basis1); + p.scaleAdd(r * FastMath.sin(theta), basis3, p); + Vector2f uv = StarMapProjection.cubeUV(p, faceIndex); + if (uv == null) { + return; + } + ellipseXs[i] = Math.round(uv.x * textureSize); + ellipseYs[i] = Math.round(uv.y * textureSize); + } + Graphics2D graphics = map.createGraphics(); + graphics.setColor(Color.WHITE); + graphics.fillPolygon(ellipseXs, ellipseYs, ellipseNumPoints); + } + + /** + * Plot a star onto cube texture maps. + * + * @param maps texture maps for 6 cube faces (not null, modified) + * @param apparentMagnitude star brightness + * @param textureSize texture size in pixels per side (>2) + * @param worldDirection star world direction (length=1) + * @return true if the star was plotted, otherwise false + */ + private static boolean plotOnCube(BufferedImage[] maps, + float apparentMagnitude, int textureSize, Vector3f worldDirection) { + float resolution = textureSize / 2_048f; + float luminosity0 = 100f * resolution * resolution; + float luminosity = luminosity0 * 1.5f + * FastMath.pow(pogsonsRatio, -apparentMagnitude); + if (luminosity < luminosityCutoff) { + return false; + } + + boolean result = false; + for (int faceIndex = 0; faceIndex < 6; ++faceIndex) { + Vector2f uv = StarMapProjection.cubeUV(worldDirection, faceIndex); + if (uv != null) { + BufferedImage map = maps[faceIndex]; + if (luminosity <= 37f) { + result |= plot4PointStar(map, luminosity, textureSize, uv); + } else { + plotEllipseForQuad(map, luminosity, textureSize, + worldDirection, faceIndex); + result = true; + } + } + } + return result; + } + + /** + * Plot a star onto a dome texture map. + * + * @param map texture map (not null, modified) + * @param apparentMagnitude star brightness + * @param textureSize texture size in pixels per side (>2) + * @param worldDirection star world direction (length=1) + * @return true if the star was plotted, otherwise false + */ + private static boolean plotOnDome(BufferedImage map, + float apparentMagnitude, int textureSize, Vector3f worldDirection) { + float resolution = textureSize / 2_048f; + float luminosity0 = 37f * resolution * resolution; + float luminosity + = luminosity0 * FastMath.pow(pogsonsRatio, -apparentMagnitude); + if (luminosity < luminosityCutoff) { + return false; + } + + Vector2f uv = domeMesh.directionUV(worldDirection); + if (luminosity <= 37f) { + return plot4PointStar(map, luminosity, textureSize, uv); + } + plotEllipseForDome(map, luminosity, textureSize, uv); + return true; + } +} diff --git a/SkyAssets/src/main/java/jme3utilities/sky/textures/StarMapProjection.java b/SkyAssets/src/main/java/jme3utilities/sky/textures/StarMapProjection.java new file mode 100644 index 0000000..11fa558 --- /dev/null +++ b/SkyAssets/src/main/java/jme3utilities/sky/textures/StarMapProjection.java @@ -0,0 +1,107 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.textures; + +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import jme3utilities.MyAsset; +import jme3utilities.math.MyQuaternion; +import jme3utilities.math.MyVector3f; + +/** + * Coordinate conversion helpers for star-map generation. + * + * @author Take Some + */ +final class StarMapProjection { + /** + * Hidden constructor. + */ + private StarMapProjection() { + // do nothing + } + + /** + * Calculate texture coordinates for a direction on one cube face. + * + * @param direction direction from the cube center (length >0, + * unaffected) + * @param faceIndex cube face index (≥0, <6) + * @return new vector, or null when outside the face + */ + static Vector2f cubeUV(Vector3f direction, int faceIndex) { + assert direction != null; + assert !MyVector3f.isZero(direction); + assert faceIndex >= 0 : faceIndex; + assert faceIndex < 6 : faceIndex; + + Vector3f faceDir + = MyAsset.copyFaceDirection(faceIndex); + Vector3f norm = direction.normalize(); + float dot = faceDir.dot(norm); + if (dot < 0.5f) { + return null; + } + + norm.divideLocal(dot); + Vector3f uDir = MyAsset.copyUDirection(faceIndex); + Vector3f vDir = MyAsset.copyVDirection(faceIndex); + float u = 0.5f * (1f + uDir.dot(norm)); + float v = 0.5f * (1f + vDir.dot(norm)); + Vector2f result = new Vector2f(u, v); + + return result; + } + + /** + * Convert a star's equatorial coordinates to world coordinates. + * + * @param star star to convert (not null) + * @param latitude radians north of the equator (≤Pi/2, ≥-Pi/2) + * @param siderealTime radians since sidereal midnight (<2*Pi, ≥0) + * @return new unit vector in world coordinates + */ + static Vector3f worldDirection( + Star star, float latitude, float siderealTime) { + assert star != null; + assert latitude >= -FastMath.HALF_PI : latitude; + assert latitude <= FastMath.HALF_PI : latitude; + assert siderealTime >= 0f : siderealTime; + assert siderealTime < FastMath.TWO_PI : siderealTime; + + Vector3f equatorial = star.getEquatorialLocation(siderealTime); + float coLatitude = FastMath.HALF_PI - latitude; + Quaternion rotation = new Quaternion(); + rotation.fromAngleNormalAxis(-coLatitude, Vector3f.UNIT_Y); + Vector3f rotated = MyQuaternion.rotate(rotation, equatorial, null); + assert rotated.isUnitVector() : rotated; + Vector3f result = new Vector3f(-rotated.x, rotated.z, rotated.y); + + return result; + } +} diff --git a/SkyAssets/src/main/java/jme3utilities/sky/textures/StarMapRenderer.java b/SkyAssets/src/main/java/jme3utilities/sky/textures/StarMapRenderer.java new file mode 100644 index 0000000..27e02c1 --- /dev/null +++ b/SkyAssets/src/main/java/jme3utilities/sky/textures/StarMapRenderer.java @@ -0,0 +1,122 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.textures; + +import com.jme3.math.FastMath; +import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; +import java.util.Collection; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Renders complete dome and cube star maps from catalog stars. + * + * @author Take Some + */ +final class StarMapRenderer { + /** Message logger for this class. */ + final private static Logger logger + = Logger.getLogger(StarMapRenderer.class.getName()); + + /** + * Hidden constructor. + */ + private StarMapRenderer() { + // do nothing + } + + /** + * Generate 6 starry sky texture maps for a cube. + * + * @param stars stars to plot (not null) + * @param latitude radians north of the equator (≤Pi/2, ≥-Pi/2) + * @param siderealTime radians since sidereal midnight (<2*Pi, ≥0) + * @param textureSize size of each texture map (pixels per side, >2) + * @return new array of 6 images + */ + static RenderedImage[] generateCubeMap(Collection stars, + float latitude, float siderealTime, int textureSize) { + assert stars != null; + assert latitude >= -FastMath.HALF_PI : latitude; + assert latitude <= FastMath.HALF_PI : latitude; + assert siderealTime >= 0f : siderealTime; + assert siderealTime < FastMath.TWO_PI : siderealTime; + assert textureSize > 2 : textureSize; + + BufferedImage[] maps = new BufferedImage[6]; + for (int faceIndex = 0; faceIndex < 6; ++faceIndex) { + maps[faceIndex] = new BufferedImage( + textureSize, textureSize, BufferedImage.TYPE_BYTE_GRAY); + } + + int plotCount = 0; + for (Star star : stars) { + boolean success = StarMapPlotter.plotOnCube( + maps, star, latitude, siderealTime, textureSize); + if (success) { + ++plotCount; + } + } + logger.log(Level.FINE, "plotted {0} stars", plotCount); + + return maps; + } + + /** + * Generate a starry sky texture map for a dome. + * + * @param stars stars to plot (not null) + * @param latitude radians north of the equator (≤Pi/2, ≥-Pi/2) + * @param siderealTime radians since sidereal midnight (<2*Pi, ≥0) + * @param textureSize size of the texture map (pixels per side, >2) + * @return new image + */ + static RenderedImage generateDomeMap(Collection stars, + float latitude, float siderealTime, int textureSize) { + assert stars != null; + assert latitude >= -FastMath.HALF_PI : latitude; + assert latitude <= FastMath.HALF_PI : latitude; + assert siderealTime >= 0f : siderealTime; + assert siderealTime < FastMath.TWO_PI : siderealTime; + assert textureSize > 2 : textureSize; + + BufferedImage map = new BufferedImage( + textureSize, textureSize, BufferedImage.TYPE_BYTE_GRAY); + + int plotCount = 0; + for (Star star : stars) { + boolean success = StarMapPlotter.plotOnDome( + map, star, latitude, siderealTime, textureSize); + if (success) { + ++plotCount; + } + } + logger.log(Level.FINE, "plotted {0} stars", plotCount); + + return map; + } +} diff --git a/SkyExamples/build.gradle b/SkyExamples/build.gradle index 81c6499..a32e22d 100644 --- a/SkyExamples/build.gradle +++ b/SkyExamples/build.gradle @@ -119,3 +119,7 @@ tasks.register('TestSkyControlWater', JavaExec) { tasks.register('TestSkyMaterial', JavaExec) { mainClass = 'jme3utilities.sky.test.TestSkyMaterial' } + +tasks.register('SkyCloudWeatherSmoke', JavaExec) { + mainClass = 'jme3utilities.sky.test.SkyCloudWeatherSmoke' +} diff --git a/SkyExamples/src/main/java/jme3utilities/sky/test/SkyCloudWeatherSmoke.java b/SkyExamples/src/main/java/jme3utilities/sky/test/SkyCloudWeatherSmoke.java new file mode 100644 index 0000000..187a51a --- /dev/null +++ b/SkyExamples/src/main/java/jme3utilities/sky/test/SkyCloudWeatherSmoke.java @@ -0,0 +1,143 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.test; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.system.AppSettings; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.sky.SkyControl; +import jme3utilities.sky.StarsOption; +import jme3utilities.sky.Updater; +import jme3utilities.sky.cloud.SkyCloudPreset; + +/** + * Visual smoke test for cloud weather presets and generated cloud shaders. + * + * @author Take Some + */ +final class SkyCloudWeatherSmoke extends SimpleApplication { + /** Message logger for this class. */ + final private static Logger logger + = Logger.getLogger(SkyCloudWeatherSmoke.class.getName()); + /** Transition duration between presets. */ + final private static float transitionSeconds = 1.2f; + /** Application time accumulator. */ + private float elapsed; + /** Next transition slot. */ + private int transitionIndex; + /** Sky control under test. */ + private SkyControl skyControl; + + /** + * Main entry point. + * + * @param arguments ignored command-line arguments + */ + public static void main(String[] arguments) { + logger.setLevel(Level.INFO); + SkyCloudWeatherSmoke application = new SkyCloudWeatherSmoke(); + application.setPauseOnLostFocus(false); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setTitle("SkyCloudWeatherSmoke"); + settings.setResolution(960, 540); + application.setSettings(settings); + application.setShowSettings(false); + application.start(); + } + + /** Initialize the scene. */ + @Override + public void simpleInitApp() { + flyCam.setMoveSpeed(25f); + cam.setLocation(new Vector3f(0f, 3f, 8f)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + + AmbientLight ambientLight = new AmbientLight(); + DirectionalLight mainLight = new DirectionalLight(); + rootNode.addLight(ambientLight); + rootNode.addLight(mainLight); + + float cloudFlattening = 0.9f; + boolean bottomDome = true; + this.skyControl = new SkyControl( + assetManager, cam, cloudFlattening, StarsOption.TwoDomes, + bottomDome); + rootNode.addControl(skyControl); + skyControl.setEnabled(true); + skyControl.setCloudModulation(true); + skyControl.getSunAndStars().setHour(14f); + skyControl.setCloudPreset(SkyCloudPreset.WISPY, 0f); + + Updater updater = skyControl.getUpdater(); + updater.addViewPort(viewPort); + updater.setAmbientLight(ambientLight); + updater.setMainLight(mainLight); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects); + bloom.setBloomIntensity(1.5f); + fpp.addFilter(bloom); + viewPort.addProcessor(fpp); + updater.addBloomFilter(bloom); + + logger.info("Started WISPY preset"); + } + + /** + * Update the smoke-test timeline. + * + * @param tpf time per frame in seconds + */ + @Override + public void simpleUpdate(float tpf) { + elapsed += tpf; + if (transitionIndex == 0 && elapsed >= 2f) { + skyControl.setCloudPreset(SkyCloudPreset.RAIN, transitionSeconds); + logger.info("Transition to RAIN"); + ++transitionIndex; + } else if (transitionIndex == 1 && elapsed >= 5f) { + skyControl.setCloudPreset(SkyCloudPreset.STORM, transitionSeconds); + logger.info("Transition to STORM"); + ++transitionIndex; + } else if (transitionIndex == 2 && elapsed >= 8f) { + skyControl.setCloudPreset(SkyCloudPreset.WISPY, transitionSeconds); + logger.info("Transition back to WISPY"); + ++transitionIndex; + } else if (transitionIndex == 3 && elapsed >= 12f) { + logger.info("Cloud weather visual smoke completed."); + stop(true); + ++transitionIndex; + } + } +} diff --git a/SkyLibrary/build.gradle b/SkyLibrary/build.gradle index a2a7bbc..33ea89e 100644 --- a/SkyLibrary/build.gradle +++ b/SkyLibrary/build.gradle @@ -1,4 +1,4 @@ -// Gradle script to build and publish the SkyLibrary subproject of SkyControl +// Gradle script to build and publish the SkyLibrary package // Note: "common.gradle" in the root project contains additional initialization // for this project. This initialization is applied in the "build.gradle" @@ -11,19 +11,21 @@ plugins { } ext { - group = 'com.github.stephengold' - artifact = 'SkyControl' - version = skycontrolVersion + group = rootProject.ext.skySimulationGroup + artifact = rootProject.ext.skySimulationArtifact + version = rootProject.ext.skySimulationVersion baseName = "${artifact}-${version}" // for artifacts - websiteUrl = 'https://github.com/stephengold/SkyControl' + websiteUrl = 'https://github.com/Take-Some/SkySimulation' } -processResources.dependsOn(':SkyAssets:skyTextures') +processResources.dependsOn(':SkyAssets:skyTextures', ':SkyAssets:skyMaterials') dependencies { api(libs.jme3.effects) api(libs.heart) + implementation(libs.luaj.jse) + testImplementation(libs.jme3.desktop) testImplementation(libs.junit4) } @@ -32,11 +34,11 @@ dependencies { tasks.register('install') { dependsOn 'publishMavenPublicationToMavenLocal' - description = 'Installs the Maven artifacts to the local repository.' + description = 'Installs the Take Some SkySimulation package to Maven Local.' } tasks.register('release') { dependsOn 'publishMavenPublicationToCentralRepository' - description = 'Stages the Maven artifacts to the Central Publisher Portal.' + description = 'Stages the Maven artifacts to the configured repository.' } jar { @@ -95,12 +97,11 @@ publishing { from components.java groupId = project.ext.group pom { - description = 'A sky-simulation library for jMonkeyEngine' + description = 'Take Some internal sky-simulation library for jMonkeyEngine' developers { developer { - email = 'sgold@sonic.net' - id = 'stephengold' - name = 'Stephen Gold' + id = 'takesome' + name = 'Take Some' } } licenses { @@ -112,8 +113,8 @@ publishing { } name = project.ext.group + ':' + artifact scm { - connection = 'scm:git:git://github.com/stephengold/SkyControl.git' - developerConnection = 'scm:git:ssh://github.com:stephengold/SkyControl.git' + connection = 'scm:git:https://github.com/Take-Some/SkySimulation.git' + developerConnection = 'scm:git:https://github.com/Take-Some/SkySimulation.git' url = project.ext.websiteUrl + '/tree/master' } url = project.ext.websiteUrl @@ -134,13 +135,27 @@ publishing { name = 'Central' url = 'https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/' } + maven { + name = 'GitHubPackages' + url = uri('https://maven.pkg.github.com/Take-Some/SkySimulation') + credentials { + username = findProperty('gpr.user') ?: System.getenv('GITHUB_ACTOR') + password = findProperty('gpr.key') ?: System.getenv('GITHUB_TOKEN') + } + } } } generateMetadataFileForMavenPublication.dependsOn('pom') publishMavenPublicationToMavenLocal.dependsOn('assemble') publishMavenPublicationToMavenLocal.doLast { - println 'installed locally as ' + baseName + println 'installed locally as ' + project.ext.group + ':' \ + + artifact + ':' + project.ext.version +} +tasks.register('publishGithubPackage') { + dependsOn 'publishMavenPublicationToGitHubPackagesRepository' + description = 'Publishes the Take Some SkySimulation package to GitHub Packages.' } +publishMavenPublicationToGitHubPackagesRepository.dependsOn('assemble') publishMavenPublicationToCentralRepository.dependsOn('assemble') // Register tasks to sign the Maven artifacts for publication: diff --git a/SkyLibrary/release-notes.md b/SkyLibrary/release-notes.md index c5f9b39..a51ca96 100644 --- a/SkyLibrary/release-notes.md +++ b/SkyLibrary/release-notes.md @@ -1,5 +1,47 @@ # release log for the SkyControl library and related software +## Version 1.4.3 released on 27 June 2026 + +Notable changes: ++ extract weather subscription dispatcher from the environment runtime ++ reduce weather event dispatch allocation and active subscription scans ++ preserve weather listener replay, cancellation, and failure isolation behavior ++ update package coordinates and release documentation for version 1.4.3 + +## Version 1.4.2 released on 27 June 2026 + +Notable changes: ++ add smooth runtime atmosphere transitions for gradient style, sunset intensity, sun halo, and moon halo controls ++ add optional `transitionSeconds` arguments to atmosphere command ABI entries ++ add typed game-facing weather subscription API with listeners, filters, events, and cancellable subscription handles ++ add built-in filters for weather id, built-in preset, storm-like states, precipitation, cloudiness, wind, and all weather changes ++ add expanded logging for Lua weather ABI loading, cloud preset parsing, cloud transition lifecycle, material generation, and cloud normal-map application ++ add compressed DDS/BC texture diagnostics for dimensions, mip levels, FourCC, image format, and block size ++ add tests for atmosphere transitions and weather subscriptions + +## Version 1.4.1 released on 25 June 2026 + +Notable changes: ++ add atmospheric gradient style presets (`REALISTIC`, `CINEMATIC`, `FANTASY`) ++ add runtime sunset, sun-halo, and moon-halo intensity controls ++ add Lua config ABI fields for atmospheric gradient styling ++ add command ABI entries for live atmosphere gradient changes ++ add SkyCommandBus support for runtime atmosphere commands ++ improve sunrise, sunset, sun glow, and moon glow gradient math ++ harden GitHub CI for push, pull request, and release validation + +## Version 1.4.0 released on 25 June 2026 + +Notable changes: ++ add Lua weather ABI (`helix/lua/sky/weather.lua`) ++ add data-driven weather preset definitions and registry loader ++ add game-facing environment snapshots and world clock ++ add Lua SkySimulation config ABI (`helix/lua/sky/default-sky.lua`) ++ add typed SkySimulation config loading and application ++ add command ABI (`helix/lua/sky/commands.lua`) and command bus ++ add commands for weather, clock, environment snapshot, and config reload ++ run full build and local package validation for releases + ## Version 1.1.0 released on 18 October 2024 Notable changes: diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/CloudLayer.java b/SkyLibrary/src/main/java/jme3utilities/sky/CloudLayer.java index 8eac187..cae586c 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/CloudLayer.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/CloudLayer.java @@ -210,12 +210,22 @@ public void setTexture(String assetPath, float scale) { material.setCloudsScale(layerIndex, scale); } + + /** + * Add, replace, or clear this layer's normal-map texture. + * + * @param assetPath asset path to the normal map, or null to clear it + */ + public void setNormalMap(String assetPath) { + material.setCloudsNormalMap(layerIndex, assetPath); + } + /** * Update this layer's texture offset in the material. * * @param time (in seconds) */ - void updateOffset(float time) { + public void updateOffset(float time) { float u = u0 + time * uRate; float v = v0 + time * vRate; material.setCloudsOffset(layerIndex, u, v); diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/Constants.java b/SkyLibrary/src/main/java/jme3utilities/sky/Constants.java index d029881..d6e9275 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/Constants.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/Constants.java @@ -74,7 +74,7 @@ final public class Constants { /** * UV distance from top to rim (<0.5, >0) */ - final static float uvScale = 0.44f; + final public static float uvScale = 0.44f; /** * coefficient used to compute the stretchFactor for objects projected onto * a DomeMesh @@ -106,6 +106,6 @@ private Constants() { * @return branch and revision (not null, not empty) */ public static String versionShort() { - return "master 1.1.1-SNAPSHOT"; + return "master 1.4.3"; } } diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/SkyAtmosphere.java b/SkyLibrary/src/main/java/jme3utilities/sky/SkyAtmosphere.java new file mode 100644 index 0000000..92ea243 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/SkyAtmosphere.java @@ -0,0 +1,925 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.ColorRGBA; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; +import java.io.IOException; +import java.util.Locale; +import java.util.Properties; +import jme3utilities.Validate; +import jme3utilities.sky.atmosphere.SkyAtmosphereProperties; +import jme3utilities.sky.atmosphere.SkyGradientStyle; + +/** + * Mutable atmospheric tuning profile used by SkyControl. + *

+ * The profile intentionally remains renderer-independent: it does not replace + * the existing dome shaders, but provides physically-inspired controls for + * daylight extinction, horizon warming, twilight length, cloud brightness, + * moonlight, starlight, ambient balance, bloom, and shadows. + *

+ * All color components are linear-space multipliers as expected by jME lights. + * Runtime code may either mutate the instance returned by + * {@link SkyControl#getAtmosphere()} or install a copied profile by invoking + * {@link SkyControl#setAtmosphere(SkyAtmosphere)}. + * + * @author Take Some + */ +public class SkyAtmosphere implements JmeCloneable, Savable { + // ************************************************************************* + // fields + + /** + * Strength of solar extinction through low atmosphere. + */ + private float airMassStrength = 0.85f; + /** + * Multiplier applied to ambient light computed from sky/cloud color. + */ + private float ambientScale = 1f; + /** + * Multiplier applied to generated bloom intensity. + */ + private float bloomScale = 1f; + /** + * Height of the sun where horizon color shift fades out. + */ + private float colorShiftAltitude = 0.35f; + /** + * Daytime cloud brightness multiplier. + */ + private float cloudDayBrightness = 0.95f; + /** + * Moonlight boost added to nighttime cloud brightness. + */ + private float cloudMoonBoost = 0.6f; + /** + * Minimum nighttime cloud brightness. + */ + private float cloudNight = 0.22f; + /** + * Height of the sun where daylight is considered fully established. + */ + private float fullDayAltitude = 0.25f; + /** + * Strength of low-altitude haze coloration. + */ + private float hazeStrength = 1f; + /** + * Upper clamp for generated bloom intensity. + */ + private float maxBloomIntensity = 1.7f; + /** + * Lower clamp for solar transmission at the horizon. + */ + private float minSunTransmission = 0.08f; + /** + * Multiplier applied to computed shadow intensity. + */ + private float shadowContrast = 1f; + /** + * Strength of warm color shift near sunrise and sunset. + */ + private float sunsetWarmth = 1f; + /** + * Art direction style for gradients and halos. + */ + private SkyGradientStyle gradientStyle = SkyGradientStyle.CINEMATIC; + /** + * Runtime color-shift style scale. + */ + private float colorScale = SkyGradientStyle.CINEMATIC.colorScale(); + /** + * Runtime halo/glow style scale. + */ + private float haloScale = SkyGradientStyle.CINEMATIC.haloScale(); + /** + * Runtime horizon gradient style scale. + */ + private float horizonScale = SkyGradientStyle.CINEMATIC.horizonScale(); + /** + * Moon halo intensity multiplier. + */ + private float moonHaloIntensity = 1f; + /** + * Sun halo intensity multiplier. + */ + private float sunHaloIntensity = 1f; + /** + * Sunset gradient intensity multiplier. + */ + private float sunsetIntensity = 1f; + /** + * Twilight reach expressed as sine of solar depression below horizon. + */ + private float twilightLimit = 0.12f; + /** + * Full-moon light color. + */ + private ColorRGBA moonLight + = new ColorRGBA(0.25f, 0.28f, 0.42f, Constants.alphaMax); + /** + * Moonless-night light color. + */ + private ColorRGBA starLight + = new ColorRGBA(0.015f, 0.018f, 0.025f, Constants.alphaMax); + /** + * Full sunlight color before atmospheric extinction. + */ + private ColorRGBA sunLight + = new ColorRGBA(0.95f, 0.92f, 0.82f, Constants.alphaMax); + /** + * Horizon color around sunrise and sunset. + */ + private ColorRGBA twilightColor + = new ColorRGBA(0.95f, 0.36f, 0.12f, Constants.alphaMax); + // ************************************************************************* + // constructors + + /** + * Instantiate an Earth-like atmospheric profile. + */ + public SkyAtmosphere() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Apply overrides from Java properties. + *

+ * Recognized color keys use comma-separated components: r,g,b or r,g,b,a. + * Recognized keys are: airMassStrength, ambientScale, bloomScale, + * colorShiftAltitude, cloudDayBrightness, cloudMoonBoost, + * cloudNight, fullDayAltitude, gradientStyle, hazeStrength, + * maxBloomIntensity, minSunTransmission, moonHaloIntensity, + * shadowContrast, sunHaloIntensity, sunsetIntensity, sunsetWarmth, + * twilightLimit, moonLight, starLight, sunLight, and twilightColor. + * + * @param properties source properties (not null) + */ + public void apply(Properties properties) { + Validate.nonNull(properties, "properties"); + + setAirMassStrength(SkyAtmosphereProperties.readFloat(properties, + "airMassStrength", airMassStrength)); + setAmbientScale(SkyAtmosphereProperties.readFloat( + properties, "ambientScale", ambientScale)); + setBloomScale(SkyAtmosphereProperties.readFloat( + properties, "bloomScale", bloomScale)); + setColorShiftAltitude(SkyAtmosphereProperties.readFloat(properties, + "colorShiftAltitude", colorShiftAltitude)); + setCloudDayBrightness(SkyAtmosphereProperties.readFloat(properties, + "cloudDayBrightness", cloudDayBrightness)); + setCloudMoonBoost(SkyAtmosphereProperties.readFloat(properties, + "cloudMoonBoost", cloudMoonBoost)); + setCloudNight(SkyAtmosphereProperties.readFloat(properties, + "cloudNight", cloudNight)); + setFullDayAltitude(SkyAtmosphereProperties.readFloat(properties, + "fullDayAltitude", fullDayAltitude)); + setHazeStrength(SkyAtmosphereProperties.readFloat( + properties, "hazeStrength", hazeStrength)); + setMaxBloomIntensity(SkyAtmosphereProperties.readFloat(properties, + "maxBloomIntensity", maxBloomIntensity)); + setMinSunTransmit(SkyAtmosphereProperties.readFloat(properties, + "minSunTransmission", minSunTransmission)); + setShadowContrast(SkyAtmosphereProperties.readFloat(properties, + "shadowContrast", shadowContrast)); + setGradientStyle(readGradientStyle(properties)); + setMoonHaloIntensity(SkyAtmosphereProperties.readFloat( + properties, "moonHaloIntensity", moonHaloIntensity)); + setSunHaloIntensity(SkyAtmosphereProperties.readFloat( + properties, "sunHaloIntensity", sunHaloIntensity)); + setSunsetIntensity(SkyAtmosphereProperties.readFloat( + properties, "sunsetIntensity", sunsetIntensity)); + setSunsetWarmth(SkyAtmosphereProperties.readFloat( + properties, "sunsetWarmth", sunsetWarmth)); + setTwilightLimit(SkyAtmosphereProperties.readFloat( + properties, "twilightLimit", twilightLimit)); + + setMoonLight(SkyAtmosphereProperties.readColor( + properties, "moonLight", moonLight)); + setStarLight(SkyAtmosphereProperties.readColor( + properties, "starLight", starLight)); + setSunLight(SkyAtmosphereProperties.readColor( + properties, "sunLight", sunLight)); + setTwilightColor(SkyAtmosphereProperties.readColor( + properties, "twilightColor", twilightColor)); + } + + /** + * Copy this profile. + * + * @return a new profile equivalent to this one + */ + public SkyAtmosphere copy() { + SkyAtmosphere result = new SkyAtmosphere(); + result.copyFrom(this); + return result; + } + + /** + * Copy the moonlight color. + * + * @param storeResult storage for the result (modified if not null) + * @return copied color + */ + public ColorRGBA copyMoonLight(ColorRGBA storeResult) { + ColorRGBA result = copyColor(moonLight, storeResult); + return result; + } + + /** + * Copy the starlight color. + * + * @param storeResult storage for the result (modified if not null) + * @return copied color + */ + public ColorRGBA copyStarLight(ColorRGBA storeResult) { + ColorRGBA result = copyColor(starLight, storeResult); + return result; + } + + /** + * Copy the sunlight color. + * + * @param storeResult storage for the result (modified if not null) + * @return copied color + */ + public ColorRGBA copySunLight(ColorRGBA storeResult) { + ColorRGBA result = copyColor(sunLight, storeResult); + return result; + } + + /** + * Copy the twilight color. + * + * @param storeResult storage for the result (modified if not null) + * @return copied color + */ + public ColorRGBA copyTwilightColor(ColorRGBA storeResult) { + ColorRGBA result = copyColor(twilightColor, storeResult); + return result; + } + + /** + * Copy all tunables from another profile. + * + * @param source source profile (not null, unaffected) + */ + final public void copyFrom(SkyAtmosphere source) { + Validate.nonNull(source, "source"); + + this.airMassStrength = source.airMassStrength; + this.ambientScale = source.ambientScale; + this.bloomScale = source.bloomScale; + this.colorShiftAltitude = source.colorShiftAltitude; + this.cloudDayBrightness = source.cloudDayBrightness; + this.cloudMoonBoost = source.cloudMoonBoost; + this.cloudNight = source.cloudNight; + this.fullDayAltitude = source.fullDayAltitude; + this.hazeStrength = source.hazeStrength; + this.maxBloomIntensity = source.maxBloomIntensity; + this.minSunTransmission = source.minSunTransmission; + this.shadowContrast = source.shadowContrast; + this.gradientStyle = source.gradientStyle; + this.colorScale = source.colorScale; + this.haloScale = source.haloScale; + this.horizonScale = source.horizonScale; + this.moonHaloIntensity = source.moonHaloIntensity; + this.sunHaloIntensity = source.sunHaloIntensity; + this.sunsetIntensity = source.sunsetIntensity; + this.sunsetWarmth = source.sunsetWarmth; + this.twilightLimit = source.twilightLimit; + this.moonLight = source.moonLight.clone(); + this.starLight = source.starLight.clone(); + this.sunLight = source.sunLight.clone(); + this.twilightColor = source.twilightColor.clone(); + } + + /** + * Return the strength of solar extinction through low atmosphere. + * + * @return extinction strength (≥0) + */ + public float getAirMassStrength() { + return airMassStrength; + } + + /** + * Return the ambient light scale. + * + * @return multiplier (≥0) + */ + public float getAmbientScale() { + return ambientScale; + } + + /** + * Return the bloom scale. + * + * @return multiplier (≥0) + */ + public float getBloomScale() { + return bloomScale; + } + + /** + * Return the altitude where warm horizon shift fades out. + * + * @return sine of solar altitude (>0, ≤1) + */ + public float getColorShiftAltitude() { + return colorShiftAltitude; + } + + /** + * Return the daytime cloud brightness multiplier. + * + * @return multiplier (≥0) + */ + public float getCloudDayBrightness() { + return cloudDayBrightness; + } + + /** + * Return the moonlight boost applied to clouds at night. + * + * @return multiplier (≥0) + */ + public float getCloudMoonBoost() { + return cloudMoonBoost; + } + + /** + * Return the minimum cloud brightness at night. + * + * @return multiplier (≥0) + */ + public float getCloudNight() { + return cloudNight; + } + + /** + * Return the altitude where daylight is fully established. + * + * @return sine of solar altitude (>0, ≤1) + */ + public float getFullDayAltitude() { + return fullDayAltitude; + } + + /** + * Return the runtime color-shift style scale. + * + * @return multiplier + */ + public float getColorScale() { + return colorScale; + } + + /** + * Return the gradient art-direction style. + * + * @return gradient style + */ + public SkyGradientStyle getGradientStyle() { + return gradientStyle; + } + + /** + * Return the runtime halo/glow style scale. + * + * @return multiplier + */ + public float getHaloScale() { + return haloScale; + } + + /** + * Return the runtime horizon gradient style scale. + * + * @return multiplier + */ + public float getHorizonScale() { + return horizonScale; + } + + /** + * Return the strength of low-altitude haze coloration. + * + * @return fraction (≤1, ≥0) + */ + public float getHazeStrength() { + return hazeStrength; + } + + /** + * Return the moon halo intensity multiplier. + * + * @return multiplier (≥0) + */ + public float getMoonHaloIntensity() { + return moonHaloIntensity; + } + + /** + * Return the bloom intensity clamp. + * + * @return intensity (≥0) + */ + public float getMaxBloomIntensity() { + return maxBloomIntensity; + } + + /** + * Return the lower clamp for solar transmission. + * + * @return fraction (≤1, ≥0) + */ + public float getMinSunTransmit() { + return minSunTransmission; + } + + /** + * Return the shadow contrast multiplier. + * + * @return multiplier (≥0) + */ + public float getShadowContrast() { + return shadowContrast; + } + + /** + * Return the sun halo intensity multiplier. + * + * @return multiplier (≥0) + */ + public float getSunHaloIntensity() { + return sunHaloIntensity; + } + + /** + * Return the sunset gradient intensity multiplier. + * + * @return multiplier (≥0) + */ + public float getSunsetIntensity() { + return sunsetIntensity; + } + + /** + * Return the strength of warm sunrise/sunset coloration. + * + * @return fraction (≤1, ≥0) + */ + public float getSunsetWarmth() { + return sunsetWarmth; + } + + /** + * Return twilight reach below the horizon. + * + * @return sine of solar depression (>0, ≤1) + */ + public float getTwilightLimit() { + return twilightLimit; + } + + /** + * Alter the strength of solar extinction through low atmosphere. + * + * @param strength desired extinction strength (≥0) + */ + public void setAirMassStrength(float strength) { + Validate.nonNegative(strength, "strength"); + this.airMassStrength = strength; + } + + /** + * Alter the ambient light scale. + * + * @param scale desired multiplier (≥0) + */ + public void setAmbientScale(float scale) { + Validate.nonNegative(scale, "scale"); + this.ambientScale = scale; + } + + /** + * Alter the bloom scale. + * + * @param scale desired multiplier (≥0) + */ + public void setBloomScale(float scale) { + Validate.nonNegative(scale, "scale"); + this.bloomScale = scale; + } + + /** + * Alter the altitude where warm horizon shift fades out. + * + * @param altitude sine of solar altitude (>0, ≤1) + */ + public void setColorShiftAltitude(float altitude) { + SkyAtmosphereProperties.validatePosFraction(altitude, "altitude"); + this.colorShiftAltitude = altitude; + } + + /** + * Alter the daytime cloud brightness multiplier. + * + * @param brightness desired multiplier (≥0) + */ + public void setCloudDayBrightness(float brightness) { + Validate.nonNegative(brightness, "brightness"); + this.cloudDayBrightness = brightness; + } + + /** + * Alter the moonlight boost applied to clouds at night. + * + * @param boost desired multiplier (≥0) + */ + public void setCloudMoonBoost(float boost) { + Validate.nonNegative(boost, "boost"); + this.cloudMoonBoost = boost; + } + + /** + * Alter the minimum cloud brightness at night. + * + * @param brightness desired multiplier (≥0) + */ + public void setCloudNight(float brightness) { + Validate.nonNegative(brightness, "brightness"); + this.cloudNight = brightness; + } + + /** + * Alter the altitude where daylight is fully established. + * + * @param altitude sine of solar altitude (>0, ≤1) + */ + public void setFullDayAltitude(float altitude) { + SkyAtmosphereProperties.validatePosFraction(altitude, "altitude"); + this.fullDayAltitude = altitude; + } + + /** + * Alter the gradient art-direction style. + * + * @param style desired style (not null) + */ + public void setGradientStyle(SkyGradientStyle style) { + Validate.nonNull(style, "style"); + this.gradientStyle = style; + setGradientScales(style.horizonScale(), style.colorScale(), + style.haloScale()); + } + + /** + * Alter runtime gradient style scales. + * + * @param horizon desired horizon scale (≥0) + * @param color desired color scale (≥0) + * @param halo desired halo scale (≥0) + */ + public void setGradientScales(float horizon, float color, float halo) { + Validate.nonNegative(horizon, "horizon"); + Validate.nonNegative(color, "color"); + Validate.nonNegative(halo, "halo"); + + this.horizonScale = horizon; + this.colorScale = color; + this.haloScale = halo; + } + + /** + * Alter the strength of low-altitude haze coloration. + * + * @param strength desired fraction (≤1, ≥0) + */ + public void setHazeStrength(float strength) { + Validate.fraction(strength, "strength"); + this.hazeStrength = strength; + } + + /** + * Alter the moon halo intensity multiplier. + * + * @param intensity desired multiplier (≥0) + */ + public void setMoonHaloIntensity(float intensity) { + Validate.nonNegative(intensity, "intensity"); + this.moonHaloIntensity = intensity; + } + + /** + * Alter the bloom intensity clamp. + * + * @param intensity desired intensity (≥0) + */ + public void setMaxBloomIntensity(float intensity) { + Validate.nonNegative(intensity, "intensity"); + this.maxBloomIntensity = intensity; + } + + /** + * Alter the lower clamp for solar transmission. + * + * @param fraction desired fraction (≤1, ≥0) + */ + public void setMinSunTransmit(float fraction) { + Validate.fraction(fraction, "fraction"); + this.minSunTransmission = fraction; + } + + /** + * Alter the full-moon light color. + * + * @param color desired color (not null, unaffected) + */ + public void setMoonLight(ColorRGBA color) { + Validate.nonNull(color, "color"); + this.moonLight = color.clone(); + } + + /** + * Alter the shadow contrast multiplier. + * + * @param contrast desired multiplier (≥0) + */ + public void setShadowContrast(float contrast) { + Validate.nonNegative(contrast, "contrast"); + this.shadowContrast = contrast; + } + + /** + * Alter the moonless-night light color. + * + * @param color desired color (not null, unaffected) + */ + public void setStarLight(ColorRGBA color) { + Validate.nonNull(color, "color"); + this.starLight = color.clone(); + } + + /** + * Alter the sun halo intensity multiplier. + * + * @param intensity desired multiplier (≥0) + */ + public void setSunHaloIntensity(float intensity) { + Validate.nonNegative(intensity, "intensity"); + this.sunHaloIntensity = intensity; + } + + /** + * Alter full sunlight color before atmospheric extinction. + * + * @param color desired color (not null, unaffected) + */ + public void setSunLight(ColorRGBA color) { + Validate.nonNull(color, "color"); + this.sunLight = color.clone(); + } + + /** + * Alter sunset gradient intensity. + * + * @param intensity desired multiplier (≥0) + */ + public void setSunsetIntensity(float intensity) { + Validate.nonNegative(intensity, "intensity"); + this.sunsetIntensity = intensity; + } + + /** + * Alter the strength of warm sunrise/sunset coloration. + * + * @param strength desired fraction (≤1, ≥0) + */ + public void setSunsetWarmth(float strength) { + Validate.fraction(strength, "strength"); + this.sunsetWarmth = strength; + } + + /** + * Alter twilight reach below the horizon. + * + * @param limit sine of solar depression (>0, ≤1) + */ + public void setTwilightLimit(float limit) { + SkyAtmosphereProperties.validatePosFraction(limit, "limit"); + this.twilightLimit = limit; + } + + /** + * Alter horizon color around sunrise and sunset. + * + * @param color desired color (not null, unaffected) + */ + public void setTwilightColor(ColorRGBA color) { + Validate.nonNull(color, "color"); + this.twilightColor = color.clone(); + } + // ************************************************************************* + // JmeCloneable methods + + /** + * Convert this shallow-cloned profile into a deep-cloned one. + * + * @param cloner active cloner + * @param original the profile from which this profile was shallow-cloned + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + this.moonLight = cloner.clone(moonLight); + this.starLight = cloner.clone(starLight); + this.sunLight = cloner.clone(sunLight); + this.twilightColor = cloner.clone(twilightColor); + } + + /** + * Create a shallow clone for the JME cloner. + * + * @return a new instance (not null) + */ + @Override + public SkyAtmosphere jmeClone() { + try { + SkyAtmosphere clone = (SkyAtmosphere) clone(); + return clone; + } catch (CloneNotSupportedException exception) { + throw new RuntimeException(exception); + } + } + // ************************************************************************* + // Object methods + + /** + * Clone this profile. + * + * @return new profile equivalent to this one + * @throws CloneNotSupportedException from Object.clone() + */ + @Override + public SkyAtmosphere clone() throws CloneNotSupportedException { + SkyAtmosphere clone = (SkyAtmosphere) super.clone(); + clone.moonLight = moonLight.clone(); + clone.starLight = starLight.clone(); + clone.sunLight = sunLight.clone(); + clone.twilightColor = twilightColor.clone(); + return clone; + } + // ************************************************************************* + // Savable methods + + /** + * De-serialize this profile, for example when loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); + + airMassStrength = capsule.readFloat("airMassStrength", 0.85f); + ambientScale = capsule.readFloat("ambientScale", 1f); + bloomScale = capsule.readFloat("bloomScale", 1f); + colorShiftAltitude = capsule.readFloat("colorShiftAltitude", 0.35f); + cloudDayBrightness = capsule.readFloat("cloudDayBrightness", 0.95f); + cloudMoonBoost = capsule.readFloat("cloudMoonBoost", 0.6f); + cloudNight = capsule.readFloat("cloudNight", 0.22f); + fullDayAltitude = capsule.readFloat("fullDayAltitude", 0.25f); + hazeStrength = capsule.readFloat("hazeStrength", 1f); + maxBloomIntensity = capsule.readFloat("maxBloomIntensity", 1.7f); + minSunTransmission + = capsule.readFloat("minSunTransmission", 0.08f); + shadowContrast = capsule.readFloat("shadowContrast", 1f); + SkyGradientStyle style = capsule.readEnum("gradientStyle", + SkyGradientStyle.class, SkyGradientStyle.CINEMATIC); + setGradientStyle(style); + moonHaloIntensity = capsule.readFloat("moonHaloIntensity", 1f); + sunHaloIntensity = capsule.readFloat("sunHaloIntensity", 1f); + sunsetIntensity = capsule.readFloat("sunsetIntensity", 1f); + sunsetWarmth = capsule.readFloat("sunsetWarmth", 1f); + twilightLimit = capsule.readFloat("twilightLimit", 0.12f); + moonLight = (ColorRGBA) capsule.readSavable( + "moonLight", + new ColorRGBA(0.25f, 0.28f, 0.42f, Constants.alphaMax)); + starLight = (ColorRGBA) capsule.readSavable( + "starLight", + new ColorRGBA(0.015f, 0.018f, 0.025f, Constants.alphaMax)); + sunLight = (ColorRGBA) capsule.readSavable( + "sunLight", + new ColorRGBA(0.95f, 0.92f, 0.82f, Constants.alphaMax)); + twilightColor = (ColorRGBA) capsule.readSavable( + "twilightColor", + new ColorRGBA(0.95f, 0.36f, 0.12f, Constants.alphaMax)); + } + + /** + * Serialize this profile, for example when saving to a J3O file. + * + * @param exporter (not null) + * @throws IOException from exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(airMassStrength, "airMassStrength", 0.85f); + capsule.write(ambientScale, "ambientScale", 1f); + capsule.write(bloomScale, "bloomScale", 1f); + capsule.write(colorShiftAltitude, "colorShiftAltitude", 0.35f); + capsule.write(cloudDayBrightness, "cloudDayBrightness", 0.95f); + capsule.write(cloudMoonBoost, "cloudMoonBoost", 0.6f); + capsule.write(cloudNight, "cloudNight", 0.22f); + capsule.write(fullDayAltitude, "fullDayAltitude", 0.25f); + capsule.write(hazeStrength, "hazeStrength", 1f); + capsule.write(maxBloomIntensity, "maxBloomIntensity", 1.7f); + capsule.write(minSunTransmission, "minSunTransmission", 0.08f); + capsule.write(shadowContrast, "shadowContrast", 1f); + capsule.write(gradientStyle, "gradientStyle", + SkyGradientStyle.CINEMATIC); + capsule.write(moonHaloIntensity, "moonHaloIntensity", 1f); + capsule.write(sunHaloIntensity, "sunHaloIntensity", 1f); + capsule.write(sunsetIntensity, "sunsetIntensity", 1f); + capsule.write(sunsetWarmth, "sunsetWarmth", 1f); + capsule.write(twilightLimit, "twilightLimit", 0.12f); + capsule.write(moonLight, "moonLight", null); + capsule.write(starLight, "starLight", null); + capsule.write(sunLight, "sunLight", null); + capsule.write(twilightColor, "twilightColor", null); + } + // ************************************************************************* + // private methods + + /** + * Read gradient style from properties. + * + * @param properties source properties (not null) + * @return parsed or current style + */ + private SkyGradientStyle readGradientStyle(Properties properties) { + String text = properties.getProperty("gradientStyle"); + SkyGradientStyle result; + if (text == null) { + result = gradientStyle; + } else { + String key = text.trim().toUpperCase(Locale.ROOT); + result = SkyGradientStyle.valueOf(key); + } + return result; + } + + /** + * Copy a color. + * + * @param source source color (not null) + * @param storeResult storage for the result (modified if not null) + * @return copied color + */ + private static ColorRGBA copyColor( + ColorRGBA source, ColorRGBA storeResult) { + ColorRGBA result = (storeResult == null) + ? new ColorRGBA() : storeResult; + result.set(source); + return result; + } + + +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/SkyCloudLayerFactory.java b/SkyLibrary/src/main/java/jme3utilities/sky/SkyCloudLayerFactory.java new file mode 100644 index 0000000..afd0f9e --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/SkyCloudLayerFactory.java @@ -0,0 +1,55 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky; + +/** + * Factory for cloud-layer runtime objects. + * + * @author Take Some + */ +final class SkyCloudLayerFactory { + /** + * Hidden constructor. + */ + private SkyCloudLayerFactory() { + // do nothing + } + + /** + * Create cloud-layer runtime objects. + * + * @param material cloud material (not null) + * @return new cloud-layer array + */ + static CloudLayer[] createLayers(SkyMaterial material) { + CloudLayer[] result = new CloudLayer[SkyControlCore.numCloudLayers]; + for (int layer = 0; layer < SkyControlCore.numCloudLayers; ++layer) { + result[layer] = new CloudLayer(material, layer); + } + + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/SkyControl.java b/SkyLibrary/src/main/java/jme3utilities/sky/SkyControl.java index c4263b2..c6a98d3 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/SkyControl.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/SkyControl.java @@ -26,7 +26,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE package jme3utilities.sky; import com.jme3.asset.AssetManager; -import com.jme3.asset.AssetNotFoundException; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; @@ -37,7 +36,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; -import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.texture.Texture; import com.jme3.util.clone.Cloner; @@ -46,8 +44,21 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import java.util.logging.Logger; import jme3utilities.Validate; import jme3utilities.math.MyColor; -import jme3utilities.math.MyMath; import jme3utilities.mesh.DomeMesh; +import jme3utilities.sky.atmosphere.SkyAtmosphereTransitionRuntime; +import jme3utilities.sky.atmosphere.SkyGradientStyle; +import jme3utilities.sky.cloud.SkyCloudPreset; +import jme3utilities.sky.cloud.SkyCloudPresetDefinition; +import jme3utilities.sky.control.SkyBaseColorRuntime; +import jme3utilities.sky.control.SkyCloudTransmissionRuntime; +import jme3utilities.sky.control.SkyLightSelectionRuntime; +import jme3utilities.sky.control.SkyLightingOutputRuntime; +import jme3utilities.sky.control.SkyMoonRuntime; +import jme3utilities.sky.control.SkyObjectLightingRuntime; +import jme3utilities.sky.runtime.SkyEnvironmentRuntime; +import jme3utilities.sky.runtime.SkyLightingSnapshot; +import jme3utilities.sky.runtime.SkyLightingState; +import jme3utilities.sky.runtime.SkyWorldClock; /** * Simple control to simulate a dynamic sky using assets and techniques derived @@ -84,31 +95,6 @@ public class SkyControl extends SkyControlCore { // ************************************************************************* // constants and loggers - /** - * light color and intensity for full moonlight: bluish gray - */ - final private static ColorRGBA moonLight - = new ColorRGBA(0.4f, 0.4f, 0.6f, Constants.alphaMax); - /** - * light color and intensity for moonless night: nearly black - */ - final private static ColorRGBA starLight - = new ColorRGBA(0.03f, 0.03f, 0.03f, Constants.alphaMax); - /** - * light color and intensity for full sunlight: yellowish white - */ - final private static ColorRGBA sunLight - = new ColorRGBA(0.8f, 0.8f, 0.75f, Constants.alphaMax); - /** - * color blended in around sunrise and sunset: ruddy orange - */ - final private static ColorRGBA twilight - = new ColorRGBA(0.6f, 0.3f, 0.15f, Constants.alphaMax); - /** - * extent of the twilight periods before sunrise and after sunset, expressed - * as the sine of the sun's angle below the horizon (≤1, ≥0) - */ - final private static float limitOfTwilight = 0.1f; /** * object index for the moon */ @@ -169,6 +155,22 @@ public class SkyControl extends SkyControlCore { * lights, shadows, and viewports to update */ private Updater updater = null; + /** + * Game-facing environment runtime. + */ + private SkyEnvironmentRuntime environmentRuntime = null; + /** atmospheric lighting and realism profile */ + private SkyAtmosphere atmosphere = new SkyAtmosphere(); + /** Smooth runtime atmosphere gradient transition. */ + private SkyAtmosphereTransitionRuntime atmoTransition + = new SkyAtmosphereTransitionRuntime(); + /** custom moon texture asset path, or null to use the phase preset */ + private String moonAssetPath = null; + /** custom moon texture object, or null for phase preset or path */ + private Texture moonColorMap = null; + /** Latest lighting snapshot exposed to game/runtime code. */ + private SkyLightingSnapshot lastLightSnapshot + = SkyLightingSnapshot.empty(); // ************************************************************************* // constructors @@ -177,6 +179,7 @@ public class SkyControl extends SkyControlCore { */ protected SkyControl() { super(); + this.environmentRuntime = createEnvRuntime(); } /** @@ -202,6 +205,7 @@ public SkyControl( this.sunAndStars = new SunAndStars(); this.updater = new Updater(); + this.environmentRuntime = createEnvRuntime(); setPhase(phase); setSunStyle("Textures/skies/suns/hazy-disc.png"); @@ -262,6 +266,90 @@ public Updater getUpdater() { return updater; } + /** + * Access the game-facing sky environment runtime. + * + * @return pre-existing runtime + */ + public SkyEnvironmentRuntime environment() { + ensureEnvRuntime(); + return environmentRuntime; + } + + /** + * Access the atmospheric tuning profile. + * + * @return the pre-existing mutable profile (not null) + */ + public SkyAtmosphere getAtmosphere() { + assert atmosphere != null; + return atmosphere; + } + + /** + * Replace the atmospheric tuning profile. + *

+ * The specified profile is copied, so later caller-side mutation will not + * alter this control. + * + * @param newAtmosphere desired profile (not null, unaffected) + */ + public void setAtmosphere(SkyAtmosphere newAtmosphere) { + Validate.nonNull(newAtmosphere, "atmosphere"); + this.atmosphere = newAtmosphere.copy(); + this.atmoTransition = new SkyAtmosphereTransitionRuntime(); + } + + /** + * Transition the atmosphere gradient style. + * + * @param style target style (not null) + * @param seconds transition duration in seconds (≥0) + */ + public void setGradientStyle(SkyGradientStyle style, float seconds) { + atmoTransition.transitionStyle(atmosphere, style, seconds); + } + + /** + * Transition moon halo intensity. + * + * @param intensity target intensity (≥0) + * @param seconds transition duration in seconds (≥0) + */ + public void setMoonHaloIntensity(float intensity, float seconds) { + atmoTransition.transitionMoon(atmosphere, intensity, seconds); + } + + /** + * Transition sun halo intensity. + * + * @param intensity target intensity (≥0) + * @param seconds transition duration in seconds (≥0) + */ + public void setSunHaloIntensity(float intensity, float seconds) { + atmoTransition.transitionSun(atmosphere, intensity, seconds); + } + + /** + * Transition sunset intensity. + * + * @param intensity target intensity (≥0) + * @param seconds transition duration in seconds (≥0) + */ + public void setSunsetIntensity(float intensity, float seconds) { + atmoTransition.transitionSunset(atmosphere, intensity, seconds); + } + + /** + * Copy the latest lighting output snapshot. + * + * @return copied lighting snapshot + */ + public SkyLightingSnapshot lightingSnapshot() { + ensureEnvRuntime(); + return lastLightSnapshot.copy(); + } + /** * Calculate the angular diameter of the moon. * @@ -283,11 +371,10 @@ public float lunarDiameter() { * storeResult or a new vector) */ public Vector3f moonDirection(Vector3f storeResult) { - float solarLongitude = sunAndStars.getSolarLongitude(); - float celestialLongitude = solarLongitude + longitudeDifference; - celestialLongitude = MyMath.modulo(celestialLongitude, FastMath.TWO_PI); - Vector3f result = sunAndStars.convertToWorld( - lunarLatitude, celestialLongitude, storeResult); + float longitudeDifference = moonLongitudeDifference(); + float lunarLatitude = moonLatitude(); + Vector3f result = SkyMoonRuntime.direction(sunAndStars, + longitudeDifference, lunarLatitude, storeResult); return result; } @@ -302,6 +389,31 @@ public void setCloudModulation(boolean newValue) { this.cloudModulationFlag = newValue; } + /** + * Transition cloud layers to a weather preset and update environment state. + * + * @param preset target cloud preset (not null) + * @param seconds transition duration in seconds (≥0) + */ + @Override + public void setCloudPreset(SkyCloudPreset preset, float seconds) { + ensureEnvRuntime(); + environmentRuntime.setWeather(preset, seconds); + } + + /** + * Transition cloud layers to a data-driven weather preset and update state. + * + * @param definition target cloud preset definition (not null) + * @param seconds transition duration in seconds (≥0) + */ + @Override + public void setCloudPreset(SkyCloudPresetDefinition definition, + float seconds) { + ensureEnvRuntime(); + environmentRuntime.setWeather(definition, seconds); + } + /** * Alter the daytime clear-sky color. * @@ -342,12 +454,51 @@ public void setMoonRenderer(GlobeRenderer newRenderer) { this.moonRenderer = newRenderer; if (moonRenderer.isEnabled()) { + this.moonAssetPath = null; + this.moonColorMap = null; Texture dynamicTexture = moonRenderer.getTexture(); SkyMaterial topMaterial = getTopMaterial(); topMaterial.addObject(moonIndex, dynamicTexture); } } + /** Remove any custom moon texture and return to phase-preset images. */ + public void clearMoonTexture() { + this.moonAssetPath = null; + this.moonColorMap = null; + setPhase(phase); + } + + /** + * Alter the moon's color map texture using an asset path. + * + * @param assetPath path to the texture asset (not null, not empty) + */ + public void setMoonTexture(String assetPath) { + Validate.nonEmpty(assetPath, "asset path"); + this.moonAssetPath = assetPath; + this.moonColorMap = null; + if (moonRenderer != null) { + moonRenderer.setEnabled(false); + } + applyMoonTexture(); + } + + /** + * Alter the moon's color map texture using a pre-loaded texture. + * + * @param colorMap texture to apply (not null) + */ + public void setMoonTexture(Texture colorMap) { + Validate.nonNull(colorMap, "texture"); + this.moonAssetPath = null; + this.moonColorMap = colorMap; + if (moonRenderer != null) { + moonRenderer.setEnabled(false); + } + applyMoonTexture(); + } + /** * Alter the phase of the moon to a pre-set value. * @@ -355,7 +506,7 @@ public void setMoonRenderer(GlobeRenderer newRenderer) { */ final public void setPhase(LunarPhase newPreset) { if (newPreset == LunarPhase.CUSTOM) { - setPhase(longitudeDifference, lunarLatitude); + setPhase(moonLongitudeDifference(), moonLatitude()); return; } @@ -364,15 +515,11 @@ final public void setPhase(LunarPhase newPreset) { } this.phase = newPreset; if (newPreset != null) { - this.longitudeDifference = newPreset.longitudeDifference(); - SkyMaterial topMaterial = getTopMaterial(); - - String assetPath = newPreset.imagePath(""); - try { - topMaterial.addObject(moonIndex, assetPath); - } catch (AssetNotFoundException exception) { - assetPath = newPreset.imagePath("-nonviral"); - topMaterial.addObject(moonIndex, assetPath); + setMoonLongDiff(newPreset.longitudeDifference()); + if (!applyMoonTexture()) { + SkyMaterial topMaterial = getTopMaterial(); + SkyMoonRuntime.applyPresetTexture( + topMaterial, moonIndex, newPreset); } } } @@ -394,8 +541,9 @@ public void setPhase(float longitudeDifference, float lunarLatitude) { moonRenderer.setEnabled(true); this.phase = LunarPhase.CUSTOM; - this.longitudeDifference = longitudeDifference; - this.lunarLatitude = lunarLatitude; + setCelestialPhase(longitudeDifference, lunarLatitude); + this.moonAssetPath = null; + this.moonColorMap = null; Texture dynamicTexture = moonRenderer.getTexture(); SkyMaterial topMaterial = getTopMaterial(); @@ -421,28 +569,79 @@ public void setSolarDiameter(float newDiameter) { /** * Alter the sun's color map. * - * @param assetPath to new color map (not null) + * @param assetPath path to the texture asset (not null, not empty) */ final public void setSunStyle(String assetPath) { - Validate.nonNull(assetPath, "path"); + setSunTexture(assetPath); + } + /** + * Alter the sun's color map texture using an asset path. + * + * @param assetPath path to the texture asset (not null, not empty) + */ + public void setSunTexture(String assetPath) { + Validate.nonEmpty(assetPath, "asset path"); SkyMaterial topMaterial = getTopMaterial(); topMaterial.addObject(sunIndex, assetPath); } + /** + * Alter the sun's color map texture using a pre-loaded texture. + * + * @param colorMap texture to apply (not null) + */ + public void setSunTexture(Texture colorMap) { + Validate.nonNull(colorMap, "texture"); + setObjectTexture(sunIndex, colorMap); + } + /** * Calculate the angular diameter of the sun. * * @return diameter (in radians, <Pi, >0) */ public float solarDiameter() { - float result = moonScale * Constants.discDiameter * FastMath.HALF_PI + float result = sunScale * Constants.discDiameter * FastMath.HALF_PI / Constants.uvScale; assert result > 0f : result; assert result < FastMath.PI : result; return result; } + + /** + * Update cloud colors using the atmospheric profile. + *

+ * Subclasses overriding this method should preserve the contract of + * returning the color used for ambient-light estimation and should update + * every configured cloud layer. + * + * @param baseColor source color (not null, unaffected) + * @param sunUp true if the sun is above the horizon, otherwise false + * @param moonUp true if the moon is above the horizon, otherwise false + * @return new color used for ambient-light estimation + */ + @Override + protected ColorRGBA updateCloudsColor( + ColorRGBA baseColor, boolean sunUp, boolean moonUp) { + assert baseColor != null; + + ColorRGBA cloudsColor = MyColor.saturate(baseColor); + float brightness = atmosphere.getCloudDayBrightness(); + if (!sunUp) { + brightness = atmosphere.getCloudNight(); + if (moonUp) { + brightness += atmosphere.getCloudMoonBoost() + * getMoonIllumination(); + } + } + cloudsColor.multLocal(brightness); + setCloudLayersColor(cloudsColor); + + return cloudsColor; + } + // ************************************************************************* // SkyControlCore methods @@ -469,10 +668,21 @@ public SkyControl clone() throws CloneNotSupportedException { public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); + final SkyControl originalControl = (SkyControl) original; this.colorDay = cloner.clone(colorDay); this.moonRenderer = cloner.clone(moonRenderer); + this.moonColorMap = cloner.clone(moonColorMap); + this.atmosphere = cloner.clone(atmosphere); this.sunAndStars = cloner.clone(sunAndStars); this.updater = cloner.clone(updater); + this.lastLightSnapshot + = originalControl.lastLightSnapshot.copy(); + this.environmentRuntime = createEnvRuntime(); + if (originalControl.environmentRuntime != null) { + this.environmentRuntime.restoreWeather( + originalControl.environmentRuntime.weather()); + this.environmentRuntime.updateLighting(lastLightSnapshot); + } } /** @@ -483,6 +693,7 @@ public void cloneFields(Cloner cloner, Object original) { @Override public void controlUpdate(float tpf) { super.controlUpdate(tpf); + atmoTransition.update(atmosphere, tpf); updateAll(); } @@ -501,11 +712,20 @@ public void read(JmeImporter importer) throws IOException { this.colorDay = (ColorRGBA) ic.readSavable( "colorDay", new ColorRGBA(0.4f, 0.6f, 1f, Constants.alphaMax)); this.moonScale = ic.readFloat("moonScale", 0.02f); + this.moonAssetPath = ic.readString("moonAssetPath", null); + this.moonColorMap = (Texture) ic.readSavable("moonColorMap", null); + this.atmosphere = (SkyAtmosphere) ic.readSavable( + "atmosphere", new SkyAtmosphere()); this.sunScale = ic.readFloat("sunScale", 0.08f); // moon renderer not serialized this.phase = ic.readEnum("phase", LunarPhase.class, LunarPhase.FULL); this.sunAndStars = (SunAndStars) ic.readSavable("sunAndStars", null); this.updater = (Updater) ic.readSavable("updater", null); + this.lastLightSnapshot = SkyLightingSnapshot.empty(); + this.environmentRuntime = createEnvRuntime(); + if (sunAndStars != null) { + environmentRuntime.clock().setTimeOfDay(sunAndStars.getHour()); + } } /** @@ -523,6 +743,9 @@ public void write(JmeExporter exporter) throws IOException { oc.write(colorDay, "colorDay", new ColorRGBA(0.4f, 0.6f, 1f, Constants.alphaMax)); oc.write(moonScale, "moonScale", 0.02f); + oc.write(moonAssetPath, "moonAssetPath", null); + oc.write(moonColorMap, "moonColorMap", null); + oc.write(atmosphere, "atmosphere", null); oc.write(sunScale, "sunScale", 0.08f); // moon renderer not serialized oc.write(phase, "phase", LunarPhase.FULL); @@ -533,117 +756,77 @@ public void write(JmeExporter exporter) throws IOException { // private methods /** - * Compute where mainDirection intersects the cloud dome in the dome's local - * coordinates, accounting for the dome's flattening and vertical offset. + * Apply a cloud preset using the inherited visual cloud runtime. * - * @param mainDirection (unit vector with non-negative y-component) - * @return new unit vector + * @param preset target cloud preset + * @param seconds transition duration in seconds */ - private Vector3f intersectCloudDome(Vector3f mainDirection) { - assert mainDirection != null; - assert mainDirection.isUnitVector() : mainDirection; - assert mainDirection.y >= 0f : mainDirection; - - double cosSquared - = MyMath.sumOfSquares(mainDirection.x, mainDirection.z); - if (cosSquared == 0.0) { - // Special case when the main light is directly overhead. - return new Vector3f(0f, 1f, 0f); - } + private void applyCloudPresetToCore(SkyCloudPreset preset, float seconds) { + super.setCloudPreset(preset, seconds); + } - float deltaY; - float semiMinorAxis; - Geometry cloudsOnlyDome = getCloudsOnlyDome(); - if (cloudsOnlyDome == null) { - deltaY = 0f; - semiMinorAxis = 1f; - } else { - Vector3f offset = cloudsOnlyDome.getLocalTranslation(); - assert offset.x == 0f : offset; - assert offset.y <= 0f : offset; - assert offset.z == 0f : offset; - deltaY = offset.y; - - Vector3f scale = cloudsOnlyDome.getLocalScale(); - assert scale.x == 1f : scale; - assert scale.y > 0f : scale; - assert scale.z == 1f : scale; - semiMinorAxis = scale.y; - } - /* - * Solve for the most positive root of a quadratic equation - * in w = sqrt(x^2 + z^2). Use double precision arithmetic. - */ - double cosAltitude = Math.sqrt(cosSquared); - double tanAltitude = mainDirection.y / cosAltitude; - double smaSquared = semiMinorAxis * semiMinorAxis; - double a = tanAltitude * tanAltitude + smaSquared; - assert a > 0.0 : a; - double b = -2.0 * deltaY * tanAltitude; - double c = deltaY * deltaY - smaSquared; - double discriminant = MyMath.discriminant(a, b, c); - assert discriminant >= 0.0 : discriminant; - double w = (-b + Math.sqrt(discriminant)) / (2.0 * a); - - double distance = w / cosAltitude; - if (distance > 1.0) { // Squash rounding errors. - distance = 1.0; - } - float x = (float) (mainDirection.x * distance); - float y = (float) MyMath.circle(w); - float z = (float) (mainDirection.z * distance); - Vector3f result = new Vector3f(x, y, z); + /** + * Apply a data-driven cloud preset using the inherited runtime. + * + * @param definition target cloud preset definition + * @param seconds transition duration in seconds + */ + private void applyCloudPresetToCore(SkyCloudPresetDefinition definition, + float seconds) { + super.setCloudPreset(definition, seconds); + } - assert result.isUnitVector() : result; + /** + * Create the game-facing environment runtime. + * + * @return new runtime + */ + private SkyEnvironmentRuntime createEnvRuntime() { + SkyEnvironmentRuntime result = new SkyEnvironmentRuntime( + new SkyWorldClock.TimeApplier() { + @Override + public void applyTimeOfDay(float timeOfDayHours) { + if (sunAndStars != null) { + sunAndStars.setHour(timeOfDayHours); + } + } + }, + new SkyEnvironmentRuntime.WeatherApplier() { + @Override + public void applyWeather( + SkyCloudPreset preset, float seconds) { + applyCloudPresetToCore(preset, seconds); + } + + @Override + public void applyWeather( + SkyCloudPresetDefinition definition, + float seconds) { + applyCloudPresetToCore(definition, seconds); + } + }); return result; } /** - * Compute the clockwise (left-handed) rotation of the moon's texture - * relative to the sky's texture. - * - * @param longitude the moon's celestial longitude (in radians east of the - * March equinox) - * @param uvCenter texture coordinates of the moon's center (not null) - * @return new unit vector with its x-component equal to the cosine of the - * rotation angle and its y-component equal to the sine of the rotation - * angle + * Ensure the environment runtime exists. */ - private Vector2f lunarRotation(float longitude, Vector2f uvCenter) { - assert uvCenter != null; - /* - * Compute UV coordinates for 0.01 radians north of the center - * of the moon. - */ - DomeMesh topMesh = getTopMesh(); - float latitude = lunarLatitude + 0.01f; - if (latitude <= FastMath.HALF_PI) { - Vector3f north - = sunAndStars.convertToWorld(latitude, longitude, null); - Vector2f uvNorth = topMesh.directionUV(north); - if (uvNorth != null) { - Vector2f offset = uvNorth.subtract(uvCenter); - assert offset.length() > 0f : offset; - Vector2f result = offset.normalize(); - return result; - } - } - /* - * Compute UV coordinates for 0.01 radians south of the center - * of the moon. - */ - latitude = lunarLatitude - 0.01f; - assert latitude >= -FastMath.HALF_PI : lunarLatitude; - Vector3f south = sunAndStars.convertToWorld(latitude, longitude, null); - Vector2f uvSouth = topMesh.directionUV(south); - if (uvSouth != null) { - Vector2f offset = uvCenter.subtract(uvSouth); - assert offset.length() > 0f : offset; - Vector2f result = offset.normalize(); - return result; + private void ensureEnvRuntime() { + if (environmentRuntime == null) { + environmentRuntime = createEnvRuntime(); } - assert false : south; - return null; + } + + /** + * Apply the custom moon texture, if one has been specified. + * + * @return true if a custom texture was applied, otherwise false + */ + private boolean applyMoonTexture() { + SkyMaterial topMaterial = getTopMaterial(); + boolean result = SkyMoonRuntime.applyTexture( + topMaterial, moonIndex, moonAssetPath, moonColorMap); + return result; } /** @@ -657,7 +840,8 @@ private void updateAll() { */ Vector3f sunDirection = updateSun(); ColorRGBA clearColor = colorDay.clone(); - clearColor.a = FastMath.saturate(1f + sunDirection.y / limitOfTwilight); + float twilightLimit = atmosphere.getTwilightLimit(); + clearColor.a = FastMath.saturate(1f + sunDirection.y / twilightLimit); SkyMaterial topMaterial = getTopMaterial(); topMaterial.setClearColor(clearColor); @@ -678,61 +862,28 @@ private void updateAll() { * @param moonDirection world direction to the moon (length=1 or null) */ private void updateLighting(Vector3f sunDirection, Vector3f moonDirection) { - assert sunDirection != null; - assert sunDirection.isUnitVector() : sunDirection; - if (moonDirection != null) { - assert moonDirection.isUnitVector() : moonDirection; - } - - float sineSolarAltitude = sunDirection.y; - float sineLunarAltitude; - if (moonDirection != null) { - sineLunarAltitude = moonDirection.y; - } else { - sineLunarAltitude = -1f; - } - updateObjectColors(sineSolarAltitude, sineLunarAltitude); - - // Determine the world direction to the main light source. - boolean moonUp = sineLunarAltitude >= 0f; - boolean sunUp = sineSolarAltitude >= 0f; float moonWeight = getMoonIllumination(); - Vector3f mainDirection; - if (sunUp) { - mainDirection = sunDirection; - } else if (moonUp && moonWeight > 0f) { - assert moonDirection != null; - mainDirection = moonDirection; - } else { - mainDirection = starlightDirection; - } - assert mainDirection.isUnitVector() : mainDirection; - assert mainDirection.y >= 0f : mainDirection; + SkyLightSelectionRuntime.Result lightSelection + = SkyLightSelectionRuntime.select( + sunDirection, moonDirection, moonWeight, + starlightDirection); + float sineSolarAltitude = lightSelection.sineSolarAltitude(); + float sineLunarAltitude = lightSelection.sineLunarAltitude(); + boolean moonUp = lightSelection.moonUp(); + boolean sunUp = lightSelection.sunUp(); + Vector3f mainDirection = lightSelection.mainDirection(); + + SkyMaterial topMaterial = getTopMaterial(); + SkyObjectLightingRuntime.updateObjects( + topMaterial, atmosphere, sineSolarAltitude, sineLunarAltitude); /* * Determine the base color (applied to horizon haze, bottom dome, and - * viewport backgrounds) using the sun's altitude: - * + sunlight when ssa >= 0.25, - * + twilight when ssa = 0, - * + blend of moonlight and starlight when ssa <= -0.04, - * with linearly interpolated transitions. + * viewport backgrounds) using the sun's altitude. */ - ColorRGBA baseColor; - if (sunUp) { - float dayWeight = FastMath.saturate(sineSolarAltitude / 0.25f); - baseColor = MyColor.interpolateLinear( - dayWeight, twilight, sunLight); - } else { - ColorRGBA blend; - if (moonUp && moonWeight > 0f) { - blend = MyColor.interpolateLinear( - moonWeight, starLight, moonLight); - } else { - blend = starLight; - } - float nightWeight = FastMath.saturate(-sineSolarAltitude / 0.04f); - baseColor = MyColor.interpolateLinear(nightWeight, twilight, blend); - } - SkyMaterial topMaterial = getTopMaterial(); + SkyBaseColorRuntime.Result baseResult + = SkyBaseColorRuntime.compute( + atmosphere, sineSolarAltitude, moonUp, moonWeight); + ColorRGBA baseColor = baseResult.baseColor(); topMaterial.setHazeColor(baseColor); Material bottomMaterial = getBottomMaterial(); if (bottomMaterial != null) { @@ -741,62 +892,34 @@ private void updateLighting(Vector3f sunDirection, Vector3f moonDirection) { ColorRGBA cloudsColor = updateCloudsColor(baseColor, sunUp, moonUp); - // Determine what fraction of the main light passes through the clouds. - float transmit; - if (cloudModulationFlag && (sunUp || moonUp && moonWeight > 0f)) { - // Modulate light intensity as clouds pass in front. - Vector3f intersection = intersectCloudDome(mainDirection); - DomeMesh cloudsMesh = getCloudsMesh(); - Vector2f texCoord = cloudsMesh.directionUV(intersection); - SkyMaterial cloudsMaterial = getCloudsMaterial(); - transmit = cloudsMaterial.getTransmission(texCoord); - - } else { - transmit = 1f; - } - - // Determine the color and intensity of the main light. - ColorRGBA main; - if (sunUp) { - /* - * By day, the main light has the base color, modulated by - * clouds and the cube root of the sine of the sun's altitude. - */ - float sunFactor = transmit * MyMath.cubeRoot(sineSolarAltitude); - main = baseColor.mult(sunFactor); - - } else if (moonUp) { - /* - * By night, the main light is a blend of moonlight and starlight, - * with the moon's portion modulated by clouds and the moon's phase. - */ - float moonFactor = transmit * moonWeight; - main = MyColor.interpolateLinear(moonFactor, starLight, moonLight); - - } else { - main = starLight.clone(); - } - /* - * The ambient light color is based on the clouds color; - * its intensity is modulated by the "slack" left by - * strongest component of the main light. - */ - float slack = 1f - MyMath.max(main.r, main.g, main.b); - assert slack >= 0f : slack; - ColorRGBA ambient = cloudsColor.mult(slack); - /* - * Compute the recommended shadow intensity as the fraction of - * the total directional light. - */ - float mainAmount = main.r + main.g + main.b; - float ambientAmount = ambient.r + ambient.g + ambient.b; - float totalAmount = mainAmount + ambientAmount; - assert totalAmount > 0f : totalAmount; - float shadowIntensity = FastMath.saturate(mainAmount / totalAmount); - - // Determine the recommended bloom intensity using the sun's altitude. - float bloomIntensity = 6f * sineSolarAltitude; - bloomIntensity = FastMath.clamp(bloomIntensity, 0f, 1.7f); + SkyCloudTransmissionRuntime.Input transmissionInput + = new SkyCloudTransmissionRuntime.Input( + cloudModulationFlag, sunUp, moonUp, moonWeight, + mainDirection); + SkyCloudTransmissionRuntime.Resources transmissionResources + = new SkyCloudTransmissionRuntime.Resources( + getCloudsOnlyDome(), getCloudsMesh(), + getCloudsMaterial()); + float transmit = SkyCloudTransmissionRuntime.transmission( + transmissionInput, transmissionResources); + + SkyLightingOutputRuntime.Input outputInput + = new SkyLightingOutputRuntime.Input( + atmosphere, lightSelection, baseResult, cloudsColor, + moonWeight, transmit); + SkyLightingOutputRuntime.Result output + = SkyLightingOutputRuntime.compute(outputInput); + ColorRGBA ambient = output.ambientColor(); + ColorRGBA main = output.mainDirectionalColor(); + float bloomIntensity = output.bloomIntensity(); + float shadowIntensity = output.shadowIntensity(); + + SkyLightingState lightingState = new SkyLightingState( + bloomIntensity, shadowIntensity, sunUp, moonUp); + lastLightSnapshot = new SkyLightingSnapshot( + ambient, baseColor, main, mainDirection, lightingState); + ensureEnvRuntime(); + environmentRuntime.updateLighting(lastLightSnapshot); updater.update(ambient, baseColor, main, bloomIntensity, shadowIntensity, mainDirection); @@ -809,68 +932,17 @@ private void updateLighting(Vector3f sunDirection, Vector3f moonDirection) { * is hidden */ private Vector3f updateMoon() { - if (phase == null) { - SkyMaterial topMaterial = getTopMaterial(); - topMaterial.hideObject(moonIndex); - return null; - } - if (phase == LunarPhase.CUSTOM) { - assert moonRenderer != null; - float intensity; - intensity = 2f + FastMath.abs(longitudeDifference - FastMath.PI); - moonRenderer.setLightIntensity(intensity); - moonRenderer.setPhase(longitudeDifference, lunarLatitude); - } - - // Compute the UV coordinates of the center of the moon. - float solarLongitude = sunAndStars.getSolarLongitude(); - float celestialLongitude = solarLongitude + longitudeDifference; - celestialLongitude = MyMath.modulo(celestialLongitude, FastMath.TWO_PI); - Vector3f worldDirection = sunAndStars.convertToWorld( - lunarLatitude, celestialLongitude, null); - DomeMesh topMesh = getTopMesh(); - Vector2f uvCenter = topMesh.directionUV(worldDirection); - SkyMaterial topMaterial = getTopMaterial(); - if (uvCenter != null) { - Vector2f rotation = lunarRotation(celestialLongitude, uvCenter); - // Reveal the object and update its texture transform. - topMaterial.setObjectTransform( - moonIndex, uvCenter, moonScale, rotation); - } else { - topMaterial.hideObject(moonIndex); - } - - return worldDirection; - } - - /** - * Update the colors of the sun and moon based on their altitudes. - * - * @param sineSolarAltitude (≤1, &ge:-1) - * @param sineLunarAltitude (≤1, &ge:-1) - */ - private void updateObjectColors( - float sineSolarAltitude, float sineLunarAltitude) { - assert sineSolarAltitude <= 1f : sineSolarAltitude; - assert sineSolarAltitude >= -1f : sineSolarAltitude; - assert sineLunarAltitude <= 1f : sineLunarAltitude; - assert sineLunarAltitude >= -1f : sineLunarAltitude; + DomeMesh topMesh = getTopMesh(); + float longitudeDifference = moonLongitudeDifference(); + float lunarLatitude = moonLatitude(); + SkyMoonRuntime.MoonUpdateState state + = new SkyMoonRuntime.MoonUpdateState(moonRenderer, moonIndex, + longitudeDifference, lunarLatitude, moonScale); + Vector3f result = SkyMoonRuntime.updateMoon( + topMaterial, topMesh, sunAndStars, phase, state); - // Update the sun's color. - float green = FastMath.saturate(3f * sineSolarAltitude); - float blue = FastMath.saturate(sineSolarAltitude - 0.1f); - ColorRGBA sunColor = new ColorRGBA(1f, green, blue, Constants.alphaMax); - SkyMaterial topMaterial = getTopMaterial(); - topMaterial.setObjectColor(sunIndex, sunColor); - topMaterial.setObjectGlow(sunIndex, sunColor); - - // Update the moon's color. - green = FastMath.saturate(2f * sineLunarAltitude + 0.6f); - blue = FastMath.saturate(5f * sineLunarAltitude + 0.1f); - ColorRGBA moonColor - = new ColorRGBA(1f, green, blue, Constants.alphaMax); - topMaterial.setObjectColor(moonIndex, moonColor); + return result; } /** diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/SkyControlCore.java b/SkyLibrary/src/main/java/jme3utilities/sky/SkyControlCore.java index 0f1c0d8..584395c 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/SkyControlCore.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/SkyControlCore.java @@ -30,7 +30,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; -import com.jme3.export.Savable; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; @@ -39,8 +38,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import com.jme3.renderer.Camera; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; -import com.jme3.renderer.queue.RenderQueue.Bucket; -import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.Spatial; @@ -49,12 +46,18 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; -import jme3utilities.MyAsset; import jme3utilities.MySpatial; import jme3utilities.SubtreeControl; import jme3utilities.Validate; import jme3utilities.math.MyColor; import jme3utilities.mesh.DomeMesh; +import jme3utilities.sky.cloud.SkyCloudPreset; +import jme3utilities.sky.cloud.SkyCloudPresetDefinition; +import jme3utilities.sky.control.SkyCelestialState; +import jme3utilities.sky.control.SkyCloudRuntime; +import jme3utilities.sky.material.SkyMaterialFactory; +import jme3utilities.sky.scene.SkyDomeFactory; +import jme3utilities.sky.scene.SkySceneLookup; /** * Core fields and methods of a SubtreeControl to simulate a dynamic sky. @@ -85,58 +88,15 @@ public class SkyControlCore extends SubtreeControl { * maximum number of cloud layers */ final public static int numCloudLayers = 6; - /** - * number of samples in each longitudinal arc of a major dome, including - * both its top and its rim (≥2) - */ - final private static int numLongitudinalSamples = 16; - /** - * number of samples around the rim of a dome (≥3) - */ - final private static int numRimSamples = 60; /** * message logger for this class */ final private static Logger logger = Logger.getLogger(SkyControlCore.class.getName()); - /** - * reusable mesh for smooth, inward-facing domes - */ - final private static DomeMesh hemisphereMesh - = new DomeMesh(numRimSamples, numLongitudinalSamples, - Constants.topU, Constants.topV, Constants.uvScale, true); /** * local copy of {@link com.jme3.math.Quaternion#IDENTITY} */ final private static Quaternion rotationIdentity = new Quaternion(); - /** - * name for the bottom geometry - */ - final private static String bottomName = "bottom"; - /** - * name for the clouds-only geometry - */ - final private static String cloudsName = "clouds"; - /** - * name for the stars node - */ - final private static String starsNodeName = "stars node"; - /** - * name for the top geometry - */ - final private static String topName = "top"; - /** - * local copy of {@link com.jme3.math.Vector3f#UNIT_X} - */ - final private static Vector3f unitX = new Vector3f(1f, 0f, 0f); - /** - * local copy of {@link com.jme3.math.Vector3f#UNIT_Z} - */ - final private static Vector3f unitZ = new Vector3f(0f, 0f, 1f); - /** - * negative Y-axis - */ - final private static Vector3f negativeUnitY = new Vector3f(0f, -1f, 0f); // ************************************************************************* // fields @@ -164,28 +124,13 @@ public class SkyControlCore extends SubtreeControl { */ private Camera camera; /** - * information about individual cloud layers - TODO privatize + * Runtime state and update logic for cloud layers. */ - protected CloudLayer[] cloudLayers; + private SkyCloudRuntime cloudRuntime; /** - * simulation time for cloud layer animations (initially 0, may be negative) + * Runtime state for the moon's latitude and phase angle. */ - private float cloudsAnimationTime = 0f; - /** - * rate of motion for cloud layer animations (default is 1, may be negative) - */ - private float cloudsRate = 1f; - /** - * the difference in celestial longitude (lambda) between the moon and the - * sun (in radians, measured eastward from the sun, default is Pi) - */ - protected float longitudeDifference = FastMath.PI; // TODO privatize - /** - * the moon's celestial latitude (beta, in radians, measured north from the - * ecliptic, ≥-Pi/2, ≤Pi/2, default is 0f, realistic range is -0.09 to - * 0.09) - */ - protected float lunarLatitude = 0f; // TODO privatize + private SkyCelestialState celestialState = new SkyCelestialState(); /** * how stars are rendered: set by constructor */ @@ -201,7 +146,8 @@ protected SkyControlCore() { this.bottomDomeFlag = false; this.starsOption = StarsOption.TopDome; this.camera = null; - this.cloudLayers = null; + this.cloudRuntime = null; + this.celestialState = new SkyCelestialState(); } /** @@ -238,48 +184,25 @@ public SkyControlCore( this.starsOption = starsOption; this.bottomDomeFlag = bottomDomeFlag; - // Create and initialize the sky material for sun, moon, and haze. - int topObjects = 2; // a sun and a moon - boolean cloudDomeFlag = cloudFlattening != 0f; - int topCloudLayers = cloudDomeFlag ? 0 : numCloudLayers; - SkyMaterial topMaterial - = new SkyMaterial(assetManager, topObjects, topCloudLayers); - topMaterial.initialize(); - topMaterial.addHaze(); - if (starsOption == StarsOption.TopDome) { - topMaterial.addStars(); - } - - SkyMaterial cloudsMaterial; - if (cloudDomeFlag) { - // Create and initialize a separate sky material for clouds only. - int numObjects = 0; - cloudsMaterial - = new SkyMaterial(assetManager, numObjects, numCloudLayers); - cloudsMaterial.initialize(); - cloudsMaterial.getAdditionalRenderState().setDepthWrite(false); - cloudsMaterial.setClearColor(ColorRGBA.BlackNoAlpha); - } else { - cloudsMaterial = topMaterial; - } - - // Initialize the cloud layers. - this.cloudLayers = new CloudLayer[numCloudLayers]; - for (int layerIndex = 0; layerIndex < numCloudLayers; ++layerIndex) { - this.cloudLayers[layerIndex] - = new CloudLayer(cloudsMaterial, layerIndex); - } - - Material bottomMaterial; - if (bottomDomeFlag) { - bottomMaterial = MyAsset.createUnshadedMaterial(assetManager); - } else { - bottomMaterial = null; + boolean separateCloudDome = cloudFlattening != 0f; + SkyMaterial topMaterial = SkyMaterialFactory.createTop( + assetManager, separateCloudDome, starsOption); + SkyMaterial cloudsMaterial = SkyMaterialFactory.createClouds( + assetManager, separateCloudDome, topMaterial); + CloudLayer[] cloudLayers + = SkyCloudLayerFactory.createLayers(cloudsMaterial); + this.cloudRuntime = new SkyCloudRuntime(cloudLayers); + Material bottomMaterial = SkyMaterialFactory.createBottom( + assetManager, bottomDomeFlag); + Node subtreeNode = SkyDomeFactory.createSubtree(cloudFlattening, + bottomDomeFlag, topMaterial, bottomMaterial, cloudsMaterial); + setSubtree(subtreeNode); + Node starsNode = SkyDomeFactory.createDefaultStars( + assetManager, starsOption); + if (starsNode != null) { + SkyDomeFactory.attachStars(subtreeNode, starsNode); } - createSpatials( - cloudFlattening, topMaterial, bottomMaterial, cloudsMaterial); - assert !isEnabled(); } // ************************************************************************* @@ -314,10 +237,8 @@ public void clearStarMaps() { public CloudLayer getCloudLayer(int layerIndex) { Validate.inRange( layerIndex, "cloud layer index", 0, numCloudLayers - 1); - CloudLayer layer = cloudLayers[layerIndex]; - - assert layer != null; - return layer; + CloudLayer result = cloudRuntime.getLayer(layerIndex); + return result; } /** @@ -326,7 +247,8 @@ public CloudLayer getCloudLayer(int layerIndex) { * @return multiple of the default rate (may be negative) */ public float getCloudsRate() { - return cloudsRate; + float result = cloudRuntime.rate(); + return result; } /** @@ -352,7 +274,8 @@ public float getCloudsYOffset() { * @return radians east of the sun */ public float getLongitudeDifference() { - return longitudeDifference; + float result = celestialState.longitudeDifference(); + return result; } /** @@ -361,7 +284,8 @@ public float getLongitudeDifference() { * @return radians north of the ecliptic (≥-Pi/2, ≤Pi/2) */ public float getLunarLatitude() { - return lunarLatitude; + float result = celestialState.lunarLatitude(); + return result; } /** @@ -372,19 +296,8 @@ public float getLunarLatitude() { * contribution */ public float getMoonIllumination() { - float fullAngle = FastMath.abs(longitudeDifference - FastMath.PI); - if (lunarLatitude != 0f) { - float cos = FastMath.cos(fullAngle) * FastMath.cos(lunarLatitude); - fullAngle = FastMath.acos(cos); - } - assert fullAngle >= 0f : fullAngle; - assert fullAngle <= FastMath.PI : fullAngle; - - float weight = 1f - FastMath.saturate(fullAngle * 0.6f); - - assert weight >= 0f : weight; - assert weight <= 1f : weight; - return weight; + float result = celestialState.moonIllumination(); + return result; } /** @@ -419,9 +332,7 @@ public void setCamera(Camera camera) { public void setCloudiness(float newAlpha) { Validate.fraction(newAlpha, "alpha"); - for (int layer = 0; layer < numCloudLayers; ++layer) { - cloudLayers[layer].setOpacity(newAlpha); - } + cloudRuntime.setCloudiness(newAlpha); } /** @@ -430,7 +341,44 @@ public void setCloudiness(float newAlpha) { * @param newRate multiple of the default rate (may be negative, default=1) */ public void setCloudsRate(float newRate) { - this.cloudsRate = newRate; + cloudRuntime.setRate(newRate); + } + + /** + * Transition cloud layers to a weather preset. + *

+ * Textures are swapped only after the affected layers fade out, then the + * target layers fade back in. + * + * @param preset target cloud preset (not null) + * @param seconds transition duration in seconds (≥0) + */ + public void setCloudPreset(SkyCloudPreset preset, float seconds) { + Validate.nonNull(preset, "preset"); + if (!(seconds >= 0f)) { + throw new IllegalArgumentException("duration must be non-negative"); + } + + cloudRuntime.transitionTo(preset, seconds); + } + + /** + * Transition cloud layers to a data-driven weather preset. + *

+ * Textures are swapped only after the affected layers fade out, then the + * target layers fade back in. + * + * @param definition target cloud preset definition (not null) + * @param seconds transition duration in seconds (≥0) + */ + public void setCloudPreset(SkyCloudPresetDefinition definition, + float seconds) { + Validate.nonNull(definition, "definition"); + if (!(seconds >= 0f)) { + throw new IllegalArgumentException("duration must be non-negative"); + } + + cloudRuntime.transitionTo(definition, seconds); } /** @@ -496,13 +444,13 @@ public void setStabilizeFlag(boolean newState) { final public void setStarMaps(String assetName) { Validate.nonEmpty(assetName, "asset name"); - Node starNode; switch (starsOption) { case Cube: + case TwoDomes: removeStarsNode(); - starNode = MyAsset.createStarMapQuads(assetManager, assetName); - starNode.setName(starsNodeName); - ((Node) getSubtree()).attachChildAt(starNode, 0); + Node starNode = SkyDomeFactory.createStars( + assetManager, starsOption, assetName); + SkyDomeFactory.attachStars((Node) getSubtree(), starNode); break; case TopDome: @@ -510,12 +458,6 @@ final public void setStarMaps(String assetName) { topMaterial.addStars(assetName); break; - case TwoDomes: - removeStarsNode(); - starNode = createStarMapDomes(assetName); - ((Node) getSubtree()).attachChildAt(starNode, 0); - break; - default: throw new IllegalStateException("option = " + starsOption); } @@ -554,8 +496,7 @@ public void setTopVerticalAngle(float newAngle) { */ protected Geometry getBottomDome() { Node subtreeNode = (Node) getSubtree(); - Geometry bottomDome - = (Geometry) MySpatial.findChild(subtreeNode, bottomName); + Geometry bottomDome = SkySceneLookup.bottomDome(subtreeNode); return bottomDome; } @@ -567,10 +508,7 @@ protected Geometry getBottomDome() { */ protected Material getBottomMaterial() { Geometry bottomDome = getBottomDome(); - if (bottomDome == null) { - return null; - } - Material bottomMaterial = bottomDome.getMaterial(); + Material bottomMaterial = SkySceneLookup.material(bottomDome); return bottomMaterial; } @@ -582,10 +520,7 @@ protected Material getBottomMaterial() { */ protected DomeMesh getBottomMesh() { Geometry bottomDome = getBottomDome(); - if (bottomDome == null) { - return null; - } - DomeMesh bottomMesh = (DomeMesh) bottomDome.getMesh(); + DomeMesh bottomMesh = SkySceneLookup.domeMesh(bottomDome); return bottomMesh; } @@ -597,8 +532,7 @@ protected DomeMesh getBottomMesh() { */ protected Geometry getCloudsOnlyDome() { Node subtreeNode = (Node) getSubtree(); - Geometry cloudsOnlyDome - = (Geometry) MySpatial.findChild(subtreeNode, cloudsName); + Geometry cloudsOnlyDome = SkySceneLookup.cloudsOnlyDome(subtreeNode); return cloudsOnlyDome; } @@ -647,7 +581,7 @@ protected DomeMesh getCloudsMesh() { */ protected Node getStarsNode() { Node subtreeNode = (Node) getSubtree(); - Node starsNode = (Node) MySpatial.findChild(subtreeNode, starsNodeName); + Node starsNode = SkySceneLookup.starsNode(subtreeNode); return starsNode; } @@ -659,7 +593,7 @@ protected Node getStarsNode() { */ protected Geometry getTopDome() { Node subtreeNode = (Node) getSubtree(); - Geometry topDome = (Geometry) MySpatial.findChild(subtreeNode, topName); + Geometry topDome = SkySceneLookup.topDome(subtreeNode); assert topDome != null; return topDome; @@ -672,9 +606,8 @@ protected Geometry getTopDome() { */ protected SkyMaterial getTopMaterial() { Geometry topDome = getTopDome(); - SkyMaterial topMaterial = (SkyMaterial) topDome.getMaterial(); + SkyMaterial topMaterial = SkySceneLookup.skyMaterial(topDome); - assert topMaterial != null; return topMaterial; } @@ -685,7 +618,7 @@ protected SkyMaterial getTopMaterial() { */ protected DomeMesh getTopMesh() { Geometry topDome = getTopDome(); - DomeMesh topMesh = (DomeMesh) topDome.getMesh(); + DomeMesh topMesh = SkySceneLookup.domeMesh(topDome); assert topMesh != null; return topMesh; @@ -714,12 +647,59 @@ protected ColorRGBA updateCloudsColor( } cloudsColor.multLocal(cloudBrightness); } - for (int layer = 0; layer < numCloudLayers; ++layer) { - cloudLayers[layer].setColor(cloudsColor); - } + setCloudLayersColor(cloudsColor); return cloudsColor; } + + /** + * Apply a color to all cloud layers. + * + * @param color desired cloud color (not null, unaffected) + */ + protected void setCloudLayersColor(ColorRGBA color) { + cloudRuntime.setColor(color); + } + + /** + * Return the moon's longitude difference. + * + * @return radians east of the sun + */ + protected float moonLongitudeDifference() { + float result = celestialState.longitudeDifference(); + return result; + } + + /** + * Return the moon's lunar latitude. + * + * @return radians north of the ecliptic + */ + protected float moonLatitude() { + float result = celestialState.lunarLatitude(); + return result; + } + + /** + * Alter the moon's longitude difference. + * + * @param longitudeDifference radians east of the sun + */ + protected void setMoonLongDiff(float longitudeDifference) { + celestialState.setLongitudeDifference(longitudeDifference); + } + + /** + * Alter the moon's lunar latitude and phase angle. + * + * @param longitudeDifference radians east of the sun + * @param lunarLatitude radians north of the ecliptic + */ + protected void setCelestialPhase(float longitudeDifference, + float lunarLatitude) { + celestialState.setPhase(longitudeDifference, lunarLatitude); + } // ************************************************************************* // SubtreeControl methods @@ -747,7 +727,8 @@ public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); this.camera = cloner.clone(camera); - this.cloudLayers = cloner.clone(cloudLayers); + this.cloudRuntime.cloneFields(cloner); + this.celestialState = celestialState.copy(); } /** @@ -764,7 +745,7 @@ public void controlUpdate(float updateInterval) { return; } - updateClouds(updateInterval); + cloudRuntime.update(updateInterval); // Translate the sky node to center the sky on the camera. Vector3f cameraLocation = camera.getLocation(); @@ -806,14 +787,8 @@ public void read(JmeImporter importer) throws IOException { this.starsOption = ic.readEnum("starsOption", StarsOption.class, starsOption); /* camera not serialized */ - Savable[] sav = ic.readSavableArray("cloudLayers", null); - this.cloudLayers = new CloudLayer[sav.length]; - System.arraycopy(sav, 0, cloudLayers, 0, sav.length); - - this.cloudsAnimationTime = ic.readFloat("cloudsAnimationTime", 0f); - this.cloudsRate = ic.readFloat("cloudsRelativeSpeed", 1f); - this.lunarLatitude = ic.readFloat("lunarLatitude", 0f); - this.longitudeDifference = ic.readFloat("phaseAngle", FastMath.PI); + this.cloudRuntime = SkyCloudRuntime.read(ic); + this.celestialState = SkyCelestialState.read(ic); } /** @@ -843,118 +818,12 @@ public void write(JmeExporter exporter) throws IOException { oc.write(stabilizeFlag, "stabilizeFlag", false); oc.write(starsOption, "starsOption", StarsOption.TopDome); /* camera not serialized */ - oc.write(cloudLayers, "cloudLayers", null); - oc.write(cloudsAnimationTime, "cloudsAnimationTime", 0f); - oc.write(cloudsRate, "cloudsRelativeSpeed", 1f); - oc.write(lunarLatitude, "lunarLatitude", 0f); - oc.write(longitudeDifference, "phaseAngle", FastMath.PI); + cloudRuntime.write(oc); + celestialState.write(oc); } // ************************************************************************* // private methods - /** - * Create and initialize the sky node and all its dome geometries. - * - * @param cloudFlattening the oblateness (ellipticity) of the dome with the - * clouds (≥ 0, <1, 0 → no flattening (hemisphere), 1 → - * maximum flattening - * @param topMaterial (not null) - * @param bottomMaterial (may be null) - * @param cloudsMaterial (not null) - */ - private void createSpatials(float cloudFlattening, Material topMaterial, - Material bottomMaterial, Material cloudsMaterial) { - // Create a Node to parent the dome geometries. - Node subtreeNode = new Node("sky node"); - subtreeNode.setQueueBucket(Bucket.Sky); - subtreeNode.setShadowMode(ShadowMode.Off); - setSubtree(subtreeNode); - /* - * Attach geometries to the sky node from the outside in - * because they'll be rendered in that order. - */ - switch (starsOption) { - case Cube: - setStarMaps("equator"); - break; - - case TwoDomes: - setStarMaps("Textures/skies/star-maps"); - break; - - default: - } - Geometry topDome = new Geometry(topName, hemisphereMesh.clone()); - subtreeNode.attachChild(topDome); - topDome.setMaterial(topMaterial); - - if (bottomDomeFlag) { - DomeMesh bottomMesh = new DomeMesh(numRimSamples, 2, Constants.topU, - Constants.topV, Constants.uvScale, true); - Geometry bottomDome = new Geometry(bottomName, bottomMesh); - subtreeNode.attachChild(bottomDome); - - Quaternion upsideDown = new Quaternion(); - upsideDown.lookAt(unitX, negativeUnitY); - bottomDome.setLocalRotation(upsideDown); - bottomDome.setMaterial(bottomMaterial); - } - - if (cloudsMaterial != topMaterial) { - assert cloudFlattening > 0f : cloudFlattening; - assert cloudFlattening < 1f : cloudFlattening; - - Geometry cloudsOnlyDome = new Geometry(cloudsName, hemisphereMesh); - subtreeNode.attachChild(cloudsOnlyDome); - /* - * Flatten the clouds-only dome in order to foreshorten clouds - * near the horizon -- even if cloudYOffset=0. - */ - float yScale = 1f - cloudFlattening; - cloudsOnlyDome.setLocalScale(1f, yScale, 1f); - cloudsOnlyDome.setMaterial(cloudsMaterial); - } - } - - /** - * Load a star map onto a sphere formed by 2 domes, one for the northern - * hemisphere and one for the southern hemisphere. - * - * @param assetPath path to an asset folder containing northern.png and - * southern.png (not null) - * @return a new, orphan node - */ - private Node createStarMapDomes(String assetPath) { - assert assetPath != null; - - Node starNode = new Node(starsNodeName); - - Geometry northGeometry = new Geometry("northern stars", hemisphereMesh); - starNode.attachChild(northGeometry); - String northAssetPath = assetPath + "/northern.png"; - Material northMaterial - = MyAsset.createUnshadedMaterial(assetManager, northAssetPath); - northGeometry.setMaterial(northMaterial); - - Quaternion orientNorth = new Quaternion(); - orientNorth.fromAngleAxis(-FastMath.HALF_PI, unitZ); - northGeometry.setLocalRotation(orientNorth); - - Geometry southGeometry - = new Geometry("southern stars", hemisphereMesh); - starNode.attachChild(southGeometry); - String southAssetPath = assetPath + "/southern.png"; - Material southMaterial - = MyAsset.createUnshadedMaterial(assetManager, southAssetPath); - southGeometry.setMaterial(southMaterial); - - Quaternion orientSouth = new Quaternion(); - orientSouth.fromAngleAxis(FastMath.HALF_PI, unitZ); - southGeometry.setLocalRotation(orientSouth); - - return starNode; - } - /** * Remove the stars node (if it exists) from the scene graph. */ @@ -966,17 +835,4 @@ private void removeStarsNode() { } } - /** - * Update the cloud layers. (Invoked once per frame.) - * - * @param updateInterval time interval between updates (in seconds, ≥0) - */ - private void updateClouds(float updateInterval) { - assert updateInterval >= 0f : updateInterval; - - this.cloudsAnimationTime += updateInterval * cloudsRate; - for (int layer = 0; layer < numCloudLayers; ++layer) { - cloudLayers[layer].updateOffset(cloudsAnimationTime); - } - } } diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/SkyMaterialCore.java b/SkyLibrary/src/main/java/jme3utilities/sky/SkyMaterialCore.java index 91339e8..09c18d6 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/SkyMaterialCore.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/SkyMaterialCore.java @@ -25,26 +25,27 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE */ package jme3utilities.sky; +import com.jme3.asset.AssetLoadException; import com.jme3.asset.AssetManager; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; -import com.jme3.export.Savable; import com.jme3.material.MatParam; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; import com.jme3.math.Vector2f; -import com.jme3.texture.Image; import com.jme3.texture.Texture; -import com.jme3.texture.image.ImageRaster; import java.io.IOException; -import java.util.Locale; +import java.util.logging.Level; import java.util.logging.Logger; import jme3utilities.MyAsset; import jme3utilities.Validate; -import jme3utilities.math.MyMath; +import jme3utilities.sky.material.SkyCloudMaterialSlots; +import jme3utilities.sky.material.SkyDdsTextureLoader; +import jme3utilities.sky.material.SkyMaterialParamNames; +import jme3utilities.sky.material.SkyObjectMaterialSlots; +import jme3utilities.sky.material.SkyObjectTransform; /** * Core fields and methods of a material for a dynamic sky dome. @@ -73,28 +74,15 @@ public class SkyMaterialCore extends Material { */ protected AssetManager assetManager; // TODO privatize /** - * maximum opacity of each cloud layer (≤1, ≥0) + * state and raster cache for cloud material slots: set by constructor or + * read(). */ - private float[] cloudAlphas; + private SkyCloudMaterialSlots cloudSlots; /** - * scale factors of cloud layers (each >0) + * state for astronomical object material slots: set by constructor or + * read(). */ - private float[] cloudScales; - /** - * scale factors of astronomical objects (each >0) - */ - private float[] objectScales; - /** - * image of each cloud layer - *

- * Since ImageRaster does not implement Savable, these are retained for use - * by write(). - */ - private Image[] cloudImages; - /** - * cached rasterization of each cloud layer - */ - private ImageRaster[] cloudsRaster; + private SkyObjectMaterialSlots objectSlots; /** * maximum number of cloud layers (≥0) */ @@ -103,18 +91,6 @@ public class SkyMaterialCore extends Material { * maximum number of astronomical objects (≥0) */ protected int maxObjects; // TODO privatize - /** - * UV offset of each cloud layer - */ - private Vector2f[] cloudOffsets; - /** - * sky texture coordinates of the center of each astronomical object - */ - private Vector2f[] objectCenters; - /** - * rotation vectors of astronomical objects (each may be null) - */ - private Vector2f[] objectRotations; // ************************************************************************* // constructors @@ -123,17 +99,11 @@ public class SkyMaterialCore extends Material { */ protected SkyMaterialCore() { this.assetManager = null; - this.cloudAlphas = null; - this.cloudImages = null; - this.cloudScales = null; - this.cloudsRaster = null; - this.cloudOffsets = null; + this.cloudSlots = null; this.maxCloudLayers = 0; this.maxObjects = 0; - this.objectCenters = null; - this.objectRotations = null; - this.objectScales = null; + this.objectSlots = null; } /** @@ -157,15 +127,9 @@ public SkyMaterialCore(AssetManager assetManager, String assetPath, this.maxObjects = maxObjects; this.maxCloudLayers = maxCloudLayers; - this.cloudAlphas = new float[maxCloudLayers]; - this.cloudImages = new Image[maxCloudLayers]; - this.cloudOffsets = new Vector2f[maxCloudLayers]; - this.cloudsRaster = new ImageRaster[maxCloudLayers]; - this.cloudScales = new float[maxCloudLayers]; + this.cloudSlots = new SkyCloudMaterialSlots(maxCloudLayers); - this.objectCenters = new Vector2f[maxObjects]; - this.objectRotations = new Vector2f[maxObjects]; - this.objectScales = new float[maxObjects]; + this.objectSlots = new SkyObjectMaterialSlots(maxObjects); } // ************************************************************************* // new methods exposed @@ -186,22 +150,48 @@ public void addClouds(int layerIndex, String assetPath) { = MyAsset.loadTexture(assetManager, assetPath, mipmaps); alphaMap.setWrap(Texture.WrapMode.Repeat); String parameterName - = String.format(Locale.ROOT, "Clouds%dAlphaMap", layerIndex); + = SkyMaterialParamNames.cloudAlphaMap(layerIndex); setTexture(parameterName, alphaMap); - boolean firstTime = (cloudsRaster[layerIndex] == null); - Image image = alphaMap.getImage(); - this.cloudImages[layerIndex] = image; - this.cloudsRaster[layerIndex] = ImageRaster.create(image); + boolean firstTime = cloudSlots.addAlphaMap(layerIndex, alphaMap); if (firstTime) { - this.cloudOffsets[layerIndex] = new Vector2f(); setCloudsColor(layerIndex, ColorRGBA.White); setCloudsOffset(layerIndex, 0f, 0f); setCloudsScale(layerIndex, 1f); } } + + /** + * Add, replace, or clear a cloud-layer normal map. + * + * @param layerIndex (<maxCloudLayers, ≥0) + * @param assetPath asset path to the normal map, or null to clear it + */ + public void setCloudsNormalMap(int layerIndex, String assetPath) { + validateLayerIndex(layerIndex); + requireCloudLayerAdded(layerIndex); + + String parameterName + = SkyMaterialParamNames.cloudNormalMap(layerIndex); + if (assetPath == null) { + clearParam(parameterName); + logger.log(Level.FINE, + "cloud normal map cleared: layer={0}, parameter={1}", + new Object[]{layerIndex, parameterName}); + } else { + Validate.nonEmpty(assetPath, "asset path"); + Texture normalMap = loadNormalMap(assetPath); + normalMap.setWrap(Texture.WrapMode.Repeat); + setTexture(parameterName, normalMap); + logger.log(Level.FINE, + "cloud normal map applied: layer={0}, parameter={1}, path={2}, image={3}", + new Object[]{layerIndex, parameterName, assetPath, + normalMap.getImage()}); + } + } + /** * Add an astronomical object to this material using the specified color * map. If the object already exists, its color map is updated. @@ -214,12 +204,10 @@ public void addObject(int objectIndex, Texture colorMap) { Validate.nonNull(colorMap, "texture"); String parameterName - = String.format(Locale.ROOT, "Object%dColorMap", objectIndex); + = SkyMaterialParamNames.objectColorMap(objectIndex); setTexture(parameterName, colorMap); - if (objectCenters[objectIndex] == null) { - this.objectCenters[objectIndex] = new Vector2f(); - this.objectRotations[objectIndex] = new Vector2f(); + if (objectSlots.addObject(objectIndex)) { setObjectColor(objectIndex, ColorRGBA.White); setObjectGlow(objectIndex, ColorRGBA.Black); setObjectTransform(objectIndex, Constants.topUV, 1f, null); @@ -235,14 +223,12 @@ public void addObject(int objectIndex, Texture colorMap) { */ public ColorRGBA copyCloudsColor(int layerIndex) { validateLayerIndex(layerIndex); - if (cloudsRaster[layerIndex] == null) { - throw new IllegalStateException("layer not yet added"); - } + requireCloudLayerAdded(layerIndex); String parameterName - = String.format(Locale.ROOT, "Clouds%dColor", layerIndex); + = SkyMaterialParamNames.cloudColor(layerIndex); ColorRGBA color = copyColor(parameterName); - color.a = cloudAlphas[layerIndex]; + color.a = cloudSlots.alpha(layerIndex); return color; } @@ -256,12 +242,10 @@ public ColorRGBA copyCloudsColor(int layerIndex) { */ public ColorRGBA copyCloudsGlow(int layerIndex) { validateLayerIndex(layerIndex); - if (cloudsRaster[layerIndex] == null) { - throw new IllegalStateException("layer not yet added"); - } + requireCloudLayerAdded(layerIndex); String parameterName - = String.format(Locale.ROOT, "Clouds%dGlow", layerIndex); + = SkyMaterialParamNames.cloudGlow(layerIndex); ColorRGBA color = copyColor(parameterName); return color; @@ -276,12 +260,10 @@ public ColorRGBA copyCloudsGlow(int layerIndex) { */ public Vector2f copyCloudsOffset(int layerIndex) { validateLayerIndex(layerIndex); - if (cloudsRaster[layerIndex] == null) { - throw new IllegalStateException("layer not yet added"); - } + requireCloudLayerAdded(layerIndex); - Vector2f offset = cloudOffsets[layerIndex]; - return offset.clone(); + Vector2f result = cloudSlots.copyOffset(layerIndex); + return result; } /** @@ -308,12 +290,10 @@ public ColorRGBA copyColor(String name) { */ public ColorRGBA copyObjectColor(int objectIndex) { validateObjectIndex(objectIndex); - if (objectCenters[objectIndex] == null) { - throw new IllegalStateException("object not yet added"); - } + requireObjectAdded(objectIndex); String parameterName - = String.format(Locale.ROOT, "Object%dColor", objectIndex); + = SkyMaterialParamNames.objectColor(objectIndex); ColorRGBA color = copyColor(parameterName); return color; @@ -328,12 +308,10 @@ public ColorRGBA copyObjectColor(int objectIndex) { */ public ColorRGBA copyObjectGlow(int objectIndex) { validateObjectIndex(objectIndex); - if (objectCenters[objectIndex] == null) { - throw new IllegalStateException("object not yet added"); - } + requireObjectAdded(objectIndex); String parameterName - = String.format(Locale.ROOT, "Object%dGlow", objectIndex); + = SkyMaterialParamNames.objectGlow(objectIndex); ColorRGBA color = copyColor(parameterName); return color; @@ -349,12 +327,10 @@ public ColorRGBA copyObjectGlow(int objectIndex) { */ public Vector2f copyObjectOffset(int objectIndex) { validateObjectIndex(objectIndex); - if (objectCenters[objectIndex] == null) { - throw new IllegalStateException("object not yet added"); - } + requireObjectAdded(objectIndex); - Vector2f offset = objectCenters[objectIndex]; - return offset.clone(); + Vector2f result = objectSlots.copyCenter(objectIndex); + return result; } /** @@ -367,16 +343,10 @@ public Vector2f copyObjectOffset(int objectIndex) { */ public Vector2f copyObjectRotation(int objectIndex) { validateObjectIndex(objectIndex); - if (objectCenters[objectIndex] == null) { - throw new IllegalStateException("object not yet added"); - } + requireObjectAdded(objectIndex); - Vector2f vector = objectRotations[objectIndex]; - if (vector == null) { - return null; - } else { - return vector.clone(); - } + Vector2f result = objectSlots.copyRotation(objectIndex); + return result; } /** @@ -402,12 +372,10 @@ public Vector2f copyVector2(String name) { */ public float getCloudsScale(int layerIndex) { validateLayerIndex(layerIndex); - if (cloudsRaster[layerIndex] == null) { - throw new IllegalStateException("layer not yet added"); - } + requireCloudLayerAdded(layerIndex); String parameterName - = String.format(Locale.ROOT, "Clouds%dScale", layerIndex); + = SkyMaterialParamNames.cloudScale(layerIndex); float result = getFloat(parameterName); assert result > 0f : result; @@ -424,11 +392,9 @@ public float getCloudsScale(int layerIndex) { */ public float getObjectScale(int objectIndex) { validateObjectIndex(objectIndex); - if (objectCenters[objectIndex] == null) { - throw new IllegalStateException("object not yet added"); - } + requireObjectAdded(objectIndex); - float result = objectScales[objectIndex]; + float result = objectSlots.scale(objectIndex); assert result > 0f : result; return result; @@ -456,11 +422,9 @@ public float getFloat(String name) { */ public float getTransmission(int objectIndex) { validateObjectIndex(objectIndex); + requireObjectAdded(objectIndex); - Vector2f center = objectCenters[objectIndex]; - if (center == null) { - throw new IllegalStateException("object not yet added"); - } + Vector2f center = objectSlots.copyCenter(objectIndex); float result = getTransmission(center); return result; @@ -476,16 +440,7 @@ public float getTransmission(int objectIndex) { public float getTransmission(Vector2f skyCoordinates) { Validate.nonNull(skyCoordinates, "coordinates"); - float result = 1f; - for (int layerIndex = 0; layerIndex < maxCloudLayers; ++layerIndex) { - if (cloudsRaster[layerIndex] != null) { - float transparency = transparency(layerIndex, skyCoordinates); - result *= transparency; - } - } - - assert result >= Constants.alphaMin : result; - assert result <= Constants.alphaMax : result; + float result = cloudSlots.transmission(skyCoordinates); return result; } @@ -500,22 +455,20 @@ public float getTransmission(Vector2f skyCoordinates) { */ public void hideObject(int objectIndex) { validateObjectIndex(objectIndex); - if (objectCenters[objectIndex] == null) { - throw new IllegalStateException("object not yet added"); - } + requireObjectAdded(objectIndex); String objectParameterName - = String.format(Locale.ROOT, "Object%dCenter", objectIndex); + = SkyMaterialParamNames.objectCenter(objectIndex); setVector2(objectParameterName, hidden); - objectCenters[objectIndex].set(hidden); + objectSlots.hide(objectIndex, hidden); // Scale down the object to occupy only a few pixels in texture space. float scale = 1000f; String transformUParameterName - = String.format(Locale.ROOT, "Object%dTransformU", objectIndex); + = SkyMaterialParamNames.objectTransformU(objectIndex); setVector2(transformUParameterName, new Vector2f(scale, scale)); String transformVParameterName - = String.format(Locale.ROOT, "Object%dTransformV", objectIndex); + = SkyMaterialParamNames.objectTransformV(objectIndex); setVector2(transformVParameterName, new Vector2f(scale, scale)); } @@ -528,14 +481,12 @@ public void hideObject(int objectIndex) { public void setCloudsColor(int layerIndex, ColorRGBA newColor) { validateLayerIndex(layerIndex); Validate.nonNull(newColor, "color"); - if (cloudsRaster[layerIndex] == null) { - throw new IllegalStateException("layer not yet added"); - } + requireCloudLayerAdded(layerIndex); String parameterName - = String.format(Locale.ROOT, "Clouds%dColor", layerIndex); + = SkyMaterialParamNames.cloudColor(layerIndex); setColor(parameterName, newColor.clone()); - this.cloudAlphas[layerIndex] = newColor.a; + cloudSlots.setAlpha(layerIndex, newColor.a); } /** @@ -547,12 +498,10 @@ public void setCloudsColor(int layerIndex, ColorRGBA newColor) { public void setCloudsGlow(int layerIndex, ColorRGBA newColor) { validateLayerIndex(layerIndex); Validate.nonNull(newColor, "color"); - if (cloudsRaster[layerIndex] == null) { - throw new IllegalStateException("layer not yet added"); - } + requireCloudLayerAdded(layerIndex); String parameterName - = String.format(Locale.ROOT, "Clouds%dGlow", layerIndex); + = SkyMaterialParamNames.cloudGlow(layerIndex); setColor(parameterName, newColor.clone()); } @@ -565,18 +514,13 @@ public void setCloudsGlow(int layerIndex, ColorRGBA newColor) { */ public void setCloudsOffset(int layerIndex, float newU, float newV) { validateLayerIndex(layerIndex); - if (cloudsRaster[layerIndex] == null) { - throw new IllegalStateException("layer not yet added"); - } + requireCloudLayerAdded(layerIndex); - float uOffset = MyMath.modulo(newU, 1f); - float vOffset = MyMath.modulo(newV, 1f); - Vector2f offset = new Vector2f(uOffset, vOffset); + Vector2f offset = cloudSlots.setOffset(layerIndex, newU, newV); String parameterName - = String.format(Locale.ROOT, "Clouds%dOffset", layerIndex); + = SkyMaterialParamNames.cloudOffset(layerIndex); setVector2(parameterName, offset); - cloudOffsets[layerIndex].set(offset); } /** @@ -588,14 +532,12 @@ public void setCloudsOffset(int layerIndex, float newU, float newV) { public void setCloudsScale(int layerIndex, float newScale) { validateLayerIndex(layerIndex); Validate.positive(newScale, "scale"); - if (cloudsRaster[layerIndex] == null) { - throw new IllegalStateException("layer not yet added"); - } + requireCloudLayerAdded(layerIndex); String parameterName - = String.format(Locale.ROOT, "Clouds%dScale", layerIndex); + = SkyMaterialParamNames.cloudScale(layerIndex); setFloat(parameterName, newScale); - this.cloudScales[layerIndex] = newScale; + cloudSlots.setScale(layerIndex, newScale); } /** @@ -607,12 +549,10 @@ public void setCloudsScale(int layerIndex, float newScale) { public void setObjectColor(int objectIndex, ColorRGBA newColor) { validateObjectIndex(objectIndex); Validate.nonNull(newColor, "color"); - if (objectCenters[objectIndex] == null) { - throw new IllegalStateException("object not yet added"); - } + requireObjectAdded(objectIndex); String parameterName - = String.format(Locale.ROOT, "Object%dColor", objectIndex); + = SkyMaterialParamNames.objectColor(objectIndex); setColor(parameterName, newColor.clone()); } @@ -625,12 +565,10 @@ public void setObjectColor(int objectIndex, ColorRGBA newColor) { public void setObjectGlow(int objectIndex, ColorRGBA newColor) { validateObjectIndex(objectIndex); Validate.nonNull(newColor, "color"); - if (objectCenters[objectIndex] == null) { - throw new IllegalStateException("object not yet added"); - } + requireObjectAdded(objectIndex); String parameterName - = String.format(Locale.ROOT, "Object%dGlow", objectIndex); + = SkyMaterialParamNames.objectGlow(objectIndex); setColor(parameterName, newColor.clone()); } @@ -653,86 +591,25 @@ public void setObjectTransform(int objectIndex, Vector2f centerUV, if (newRotate != null) { Validate.nonZero(newRotate, "rotation vector"); } - if (objectCenters[objectIndex] == null) { - throw new IllegalStateException("object not yet added"); - } + requireObjectAdded(objectIndex); // Record transform parameters for save(). - this.objectCenters[objectIndex] = centerUV.clone(); - if (newRotate == null) { - this.objectRotations[objectIndex] = null; - } else { - this.objectRotations[objectIndex] = newRotate.clone(); - } - this.objectScales[objectIndex] = newScale; + objectSlots.setTransform(objectIndex, centerUV, newScale, newRotate); String objectParameterName - = String.format(Locale.ROOT, "Object%dCenter", objectIndex); + = SkyMaterialParamNames.objectCenter(objectIndex); setVector2(objectParameterName, centerUV); - Vector2f offset = centerUV.subtract(Constants.topUV); - float topDist = offset.length(); - /* - * The texture coordinate transforms are broken into pairs of - * vectors because there is no Matrix2f class. - */ - Vector2f transformU = new Vector2f(); - Vector2f transformV = new Vector2f(); - Vector2f tU = new Vector2f(); - Vector2f tV = new Vector2f(); - - if (topDist > 0f) { - /* - * Stretch the image horizontally to compensate for UV distortion - * near the horizon. - */ - float a = offset.x / topDist; - float b = offset.y / topDist; - tU.set(b, -a); - tV.set(a, b); - - float stretchFactor - = 1f + Constants.stretchCoefficient * topDist * topDist; - tU.divideLocal(stretchFactor); - - if (newRotate != null) { - transformU.set(tU.x * b + tV.x * a, tU.y * b + tV.y * a); - transformV.set(tV.x * b - tU.x * a, tV.y * b - tU.y * a); - } else { - transformU.set(tU); - transformV.set(tV); - } - - } else { - // No UV distortion at the top of the dome. - transformU.set(1f, 0f); - transformV.set(0f, 1f); - } - - if (newRotate != null) { - // Rotate so top is toward the north horizon. - tU.set(transformV); - tV.set(-transformU.x, -transformU.y); - - // Rotate by newRotate. - Vector2f norm = newRotate.normalize(); - transformU.set(tU.x * norm.x + tV.x * norm.y, - tU.y * norm.x + tV.y * norm.y); - transformV.set(tV.x * norm.x - tU.x * norm.y, - tV.y * norm.x - tU.y * norm.y); - } - - // Scale by newScale. - transformU.divideLocal(newScale); - transformV.divideLocal(newScale); + SkyObjectTransform transform + = SkyObjectTransform.from(centerUV, newScale, newRotate); String transformUParameterName - = String.format(Locale.ROOT, "Object%dTransformU", objectIndex); - setVector2(transformUParameterName, transformU); + = SkyMaterialParamNames.objectTransformU(objectIndex); + setVector2(transformUParameterName, transform.copyTransformU()); String transformVParameterName - = String.format(Locale.ROOT, "Object%dTransformV", objectIndex); - setVector2(transformVParameterName, transformV); + = SkyMaterialParamNames.objectTransformV(objectIndex); + setVector2(transformVParameterName, transform.copyTransformV()); } // ************************************************************************* // new protected methods @@ -773,43 +650,16 @@ public void read(JmeImporter importer) throws IOException { InputCapsule capsule = importer.getCapsule(this); // cloud layers - this.cloudAlphas = capsule.readFloatArray("cloudAlphas", null); - - Savable[] sav = capsule.readSavableArray("cloudImages", null); - this.cloudImages = new Image[sav.length]; - System.arraycopy(sav, 0, cloudImages, 0, sav.length); - - sav = capsule.readSavableArray("cloudOffsets", null); - this.cloudOffsets = new Vector2f[sav.length]; - System.arraycopy(sav, 0, cloudOffsets, 0, sav.length); - - this.cloudScales = capsule.readFloatArray("cloudScales", null); + this.cloudSlots = SkyCloudMaterialSlots.read(capsule); // astronomical objects - sav = capsule.readSavableArray("objectCenters", null); - this.objectCenters = new Vector2f[sav.length]; - System.arraycopy(sav, 0, objectCenters, 0, sav.length); - - sav = capsule.readSavableArray("objectRotations", null); - this.objectRotations = new Vector2f[sav.length]; - System.arraycopy(sav, 0, objectRotations, 0, sav.length); - - this.objectScales = capsule.readFloatArray("objectScales", null); + this.objectSlots = SkyObjectMaterialSlots.read(capsule); // cached values this.assetManager = importer.getAssetManager(); - this.maxCloudLayers = cloudImages.length; - this.maxObjects = objectCenters.length; - - this.cloudsRaster = new ImageRaster[maxCloudLayers]; - for (int layerIndex = 0; layerIndex < maxCloudLayers; ++layerIndex) { - Image image = cloudImages[layerIndex]; - if (image == null) { - this.cloudsRaster[layerIndex] = null; - } else { - this.cloudsRaster[layerIndex] = ImageRaster.create(image); - } - } + this.maxCloudLayers = cloudSlots.count(); + this.maxObjects = objectSlots.count(); + } /** @@ -824,92 +674,64 @@ public void write(JmeExporter exporter) throws IOException { OutputCapsule capsule = exporter.getCapsule(this); - capsule.write(cloudAlphas, "cloudAlphas", null); - capsule.write(cloudImages, "cloudImages", null); - capsule.write(cloudOffsets, "cloudOffsets", null); - capsule.write(cloudScales, "cloudScales", null); + cloudSlots.write(capsule); - capsule.write(objectCenters, "objectCenters", null); - capsule.write(objectRotations, "objectRotations", null); - capsule.write(objectScales, "objectScales", null); + objectSlots.write(capsule); } + + /** + * Load a cloud normal map, including BC5 DDS fallback. + * + * @param assetPath texture asset path (not null, not empty) + * @return new texture + */ + private Texture loadNormalMap(String assetPath) { + assert assetPath != null; + + boolean mipmaps = false; + Texture result; + try { + logger.log(Level.FINER, + "loading cloud normal map through AssetManager: {0}", + assetPath); + result = MyAsset.loadTexture(assetManager, assetPath, mipmaps); + } catch (AssetLoadException exception) { + if (!assetPath.toLowerCase(java.util.Locale.ROOT) + .endsWith(".dds")) { + throw exception; + } + logger.log(Level.INFO, + "using internal compressed texture reader for cloud normal map: {0}", + assetPath); + result = SkyDdsTextureLoader.loadTexture(assetManager, assetPath); + } + + return result; + } + // ************************************************************************* // private methods /** - * Estimate how much light is transmitted through an indexed cloud layer at - * the specified texture coordinates. + * Verify that the specified cloud layer has been added. * - * @param layerIndex (<maxCloudLayers, ≥0) - * @param skyCoordinates (unaffected, not null) - * @return fraction of light transmitted (≤1, ≥0) + * @param layerIndex cloud layer index + * @throws IllegalStateException if the layer has not been added */ - private float transparency(int layerIndex, Vector2f skyCoordinates) { - assert layerIndex >= 0 : layerIndex; - assert layerIndex < maxCloudLayers : layerIndex; - assert skyCoordinates != null; - assert cloudsRaster[layerIndex] != null : layerIndex; - - Vector2f coord = skyCoordinates.mult(cloudScales[layerIndex]); - coord.addLocal(cloudOffsets[layerIndex]); - coord.x = MyMath.modulo(coord.x, Constants.uvMax); - coord.y = MyMath.modulo(coord.y, Constants.uvMax); - float opacity = sampleRed(cloudsRaster[layerIndex], coord); - opacity *= cloudAlphas[layerIndex]; - float result = Constants.alphaMax - opacity; - - assert result >= Constants.alphaMin : result; - assert result <= Constants.alphaMax : result; - return result; + private void requireCloudLayerAdded(int layerIndex) { + cloudSlots.requireAdded(layerIndex); } /** - * Sample the red component of a rasterized texture at the specified - * coordinates. - * - * @param colorImage the texture to sample (not null, unaffected) - * @param uv texture coordinates to sample (not null, each component <1 - * and ≥0, unaffected) - * @return red intensity (≤1, ≥0) - */ - private static float sampleRed(ImageRaster colorImage, Vector2f uv) { - assert colorImage != null; - assert uv != null; - float u = uv.x; - float v = uv.y; - assert u >= Constants.uvMin : uv; - assert u < Constants.uvMax : uv; - assert v >= Constants.uvMin : uv; - assert v < Constants.uvMax : uv; - - int width = colorImage.getWidth(); - float x = u * width; - int x0 = (int) FastMath.floor(x); - float xFraction1 = x - x0; - float xFraction0 = 1 - xFraction1; - int x1 = (x0 + 1) % width; - - int height = colorImage.getHeight(); - float y = v * width; - int y0 = (int) FastMath.floor(y); - float yFraction1 = y - y0; - float yFraction0 = 1 - yFraction1; - int y1 = (y0 + 1) % height; - - // Access the red values of the four nearest pixels. - float r00 = colorImage.getPixel(x0, y0).r; - float r01 = colorImage.getPixel(x1, y0).r; - float r10 = colorImage.getPixel(x0, y1).r; - float r11 = colorImage.getPixel(x1, y1).r; - - // Sample using bidirectional linear interpolation. - float result = r00 * xFraction0 * yFraction0 - + r01 * xFraction0 * yFraction1 - + r10 * xFraction1 * yFraction0 - + r11 * xFraction1 * yFraction1; - - assert result >= Constants.alphaMin : result; - assert result <= Constants.alphaMax : result; - return result; + * Verify that the specified astronomical object has been added. + * + * @param objectIndex astronomical object index + * @throws IllegalStateException if the object has not been added + */ + private void requireObjectAdded(int objectIndex) { + objectSlots.requireAdded(objectIndex); } + + + } diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/Updater.java b/SkyLibrary/src/main/java/jme3utilities/sky/Updater.java index 41e0b20..d595289 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/Updater.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/Updater.java @@ -47,6 +47,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import java.util.logging.Logger; import jme3utilities.Validate; import jme3utilities.ViewPortListener; +import jme3utilities.sky.update.UpdaterApplier; /** * Component of SkyControl to keep track of all the lights, shadows, and @@ -455,33 +456,14 @@ void update(ColorRGBA ambientColor, ColorRGBA backgroundColor, this.direction.set(direction); } - if (mainLight != null) { - ColorRGBA color = mainColor.mult(mainMultiplier); - mainLight.setColor(color); - /* - * The direction of the main light is the direction in which it - * propagates, which is the opposite of the direction to the - * light source. - */ - Vector3f propagationDirection = direction.negate(); - mainLight.setDirection(propagationDirection); - } - if (ambientLight != null) { - ColorRGBA color = ambientColor.mult(ambientMultiplier); - ambientLight.setColor(color); - } - for (BloomFilter filter : bloomFilters) { - filter.setBloomIntensity(bloomIntensity); - } - for (AbstractShadowFilter filter : shadowFilters) { - filter.setShadowIntensity(shadowIntensity); - } - for (AbstractShadowRenderer renderer : shadowRenderers) { - renderer.setShadowIntensity(shadowIntensity); - } - for (ViewPort viewPort : viewPorts) { - viewPort.setBackgroundColor(backgroundColor); - } + UpdaterApplier.applyMain( + mainLight, mainColor, mainMultiplier, direction); + UpdaterApplier.applyAmbient( + ambientLight, ambientColor, ambientMultiplier); + UpdaterApplier.applyBloom(bloomFilters, bloomIntensity); + UpdaterApplier.applyShadowFilters(shadowFilters, shadowIntensity); + UpdaterApplier.applyShadowRenderers(shadowRenderers, shadowIntensity); + UpdaterApplier.applyViewPorts(viewPorts, backgroundColor); } // ************************************************************************* // JmeCloneable methods diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/SkyAtmosphereProperties.java b/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/SkyAtmosphereProperties.java new file mode 100644 index 0000000..1dfd9ff --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/SkyAtmosphereProperties.java @@ -0,0 +1,106 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.atmosphere; + +import com.jme3.math.ColorRGBA; +import java.util.Properties; + +/** + * Parsing and validation helpers for SkyAtmosphere property profiles. + * + * @author Take Some + */ +public final class SkyAtmosphereProperties { + /** + * Hidden constructor. + */ + private SkyAtmosphereProperties() { + // do nothing + } + + /** + * Read a color override from properties. + * + * @param properties source properties (not null) + * @param key property key (not null) + * @param fallback fallback color (not null) + * @return parsed or fallback color + */ + public static ColorRGBA readColor( + Properties properties, String key, ColorRGBA fallback) { + String text = properties.getProperty(key); + if (text == null) { + return fallback; + } + + String[] components = text.split(","); + if (components.length != 3 && components.length != 4) { + throw new IllegalArgumentException( + key + " should contain r,g,b or r,g,b,a"); + } + float r = Float.parseFloat(components[0].trim()); + float g = Float.parseFloat(components[1].trim()); + float b = Float.parseFloat(components[2].trim()); + float a = (components.length == 4) + ? Float.parseFloat(components[3].trim()) : fallback.a; + ColorRGBA result = new ColorRGBA(r, g, b, a); + + return result; + } + + /** + * Read a float override from properties. + * + * @param properties source properties (not null) + * @param key property key (not null) + * @param fallback fallback value + * @return parsed or fallback value + */ + public static float readFloat( + Properties properties, String key, float fallback) { + String text = properties.getProperty(key); + if (text == null) { + return fallback; + } + float result = Float.parseFloat(text.trim()); + + return result; + } + + /** + * Validate a positive fraction. + * + * @param value value to validate + * @param description value description (not null) + */ + public static void validatePosFraction(float value, String description) { + if (!(value > 0f && value <= 1f)) { + throw new IllegalArgumentException( + description + " should be greater than 0" + + " and no more than 1"); + } + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/SkyAtmosphereTransitionRuntime.java b/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/SkyAtmosphereTransitionRuntime.java new file mode 100644 index 0000000..1cea391 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/SkyAtmosphereTransitionRuntime.java @@ -0,0 +1,236 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.atmosphere; + +import com.jme3.math.FastMath; +import jme3utilities.Validate; +import jme3utilities.sky.SkyAtmosphere; + +/** + * Runtime interpolator for atmosphere gradient styling. + * + * @author Take Some + */ +final public class SkyAtmosphereTransitionRuntime { + /** True while a transition is active. */ + private boolean active; + /** Transition duration in seconds. */ + private float duration; + /** Elapsed transition time. */ + private float elapsed; + /** Initial color style scale. */ + private float fromColor; + /** Initial halo style scale. */ + private float fromHalo; + /** Initial horizon style scale. */ + private float fromHorizon; + /** Initial moon halo intensity. */ + private float fromMoon; + /** Initial sun halo intensity. */ + private float fromSun; + /** Initial sunset intensity. */ + private float fromSunset; + /** Target color style scale. */ + private float toColor; + /** Target halo style scale. */ + private float toHalo; + /** Target horizon style scale. */ + private float toHorizon; + /** Target moon halo intensity. */ + private float toMoon; + /** Target sun halo intensity. */ + private float toSun; + /** Target sunset intensity. */ + private float toSunset; + + /** + * Test whether a transition is active. + * + * @return true if active, otherwise false + */ + public boolean isActive() { + return active; + } + + /** + * Start a gradient preset transition. + * + * @param atmosphere target atmosphere (not null) + * @param style target gradient style (not null) + * @param seconds transition duration in seconds (≥0) + */ + public void transitionStyle(SkyAtmosphere atmosphere, + SkyGradientStyle style, float seconds) { + Validate.nonNull(atmosphere, "atmosphere"); + Validate.nonNull(style, "style"); + Validate.nonNegative(seconds, "seconds"); + + toHorizon = style.horizonScale(); + toColor = style.colorScale(); + toHalo = style.haloScale(); + toSunset = style.presetSunset(); + toSun = style.presetSunHalo(); + toMoon = style.presetMoonHalo(); + start(atmosphere, seconds); + atmosphere.setGradientStyle(style); + apply(atmosphere, active ? 0f : 1f); + } + + /** + * Start a moon halo intensity transition. + * + * @param atmosphere target atmosphere (not null) + * @param intensity target intensity (≥0) + * @param seconds transition duration in seconds (≥0) + */ + public void transitionMoon(SkyAtmosphere atmosphere, float intensity, + float seconds) { + Validate.nonNull(atmosphere, "atmosphere"); + Validate.nonNegative(intensity, "intensity"); + Validate.nonNegative(seconds, "seconds"); + + toHorizon = atmosphere.getHorizonScale(); + toColor = atmosphere.getColorScale(); + toHalo = atmosphere.getHaloScale(); + toSunset = atmosphere.getSunsetIntensity(); + toSun = atmosphere.getSunHaloIntensity(); + toMoon = intensity; + start(atmosphere, seconds); + } + + /** + * Start a sun halo intensity transition. + * + * @param atmosphere target atmosphere (not null) + * @param intensity target intensity (≥0) + * @param seconds transition duration in seconds (≥0) + */ + public void transitionSun(SkyAtmosphere atmosphere, float intensity, + float seconds) { + Validate.nonNull(atmosphere, "atmosphere"); + Validate.nonNegative(intensity, "intensity"); + Validate.nonNegative(seconds, "seconds"); + + toHorizon = atmosphere.getHorizonScale(); + toColor = atmosphere.getColorScale(); + toHalo = atmosphere.getHaloScale(); + toSunset = atmosphere.getSunsetIntensity(); + toSun = intensity; + toMoon = atmosphere.getMoonHaloIntensity(); + start(atmosphere, seconds); + } + + /** + * Start a sunset intensity transition. + * + * @param atmosphere target atmosphere (not null) + * @param intensity target intensity (≥0) + * @param seconds transition duration in seconds (≥0) + */ + public void transitionSunset(SkyAtmosphere atmosphere, float intensity, + float seconds) { + Validate.nonNull(atmosphere, "atmosphere"); + Validate.nonNegative(intensity, "intensity"); + Validate.nonNegative(seconds, "seconds"); + + toHorizon = atmosphere.getHorizonScale(); + toColor = atmosphere.getColorScale(); + toHalo = atmosphere.getHaloScale(); + toSunset = intensity; + toSun = atmosphere.getSunHaloIntensity(); + toMoon = atmosphere.getMoonHaloIntensity(); + start(atmosphere, seconds); + } + + /** + * Advance the active transition. + * + * @param atmosphere target atmosphere (not null) + * @param interval update interval in seconds (≥0) + */ + public void update(SkyAtmosphere atmosphere, float interval) { + Validate.nonNull(atmosphere, "atmosphere"); + Validate.nonNegative(interval, "interval"); + if (!active) { + return; + } + + elapsed += interval; + float weight = FastMath.saturate(elapsed / duration); + apply(atmosphere, weight); + if (weight >= 1f) { + active = false; + } + } + + /** + * Apply an interpolation weight. + * + * @param atmosphere target atmosphere + * @param weight interpolation weight + */ + private void apply(SkyAtmosphere atmosphere, float weight) { + float horizon = lerp(fromHorizon, toHorizon, weight); + float color = lerp(fromColor, toColor, weight); + float halo = lerp(fromHalo, toHalo, weight); + atmosphere.setGradientScales(horizon, color, halo); + atmosphere.setSunsetIntensity(lerp(fromSunset, toSunset, weight)); + atmosphere.setSunHaloIntensity(lerp(fromSun, toSun, weight)); + atmosphere.setMoonHaloIntensity(lerp(fromMoon, toMoon, weight)); + } + + /** + * Linear interpolation. + * + * @param start start value + * @param end end value + * @param weight interpolation weight + * @return interpolated value + */ + private static float lerp(float start, float end, float weight) { + float result = start + (end - start) * weight; + return result; + } + + /** + * Start a transition to the current target fields. + * + * @param atmosphere target atmosphere + * @param seconds transition duration + */ + private void start(SkyAtmosphere atmosphere, float seconds) { + fromHorizon = atmosphere.getHorizonScale(); + fromColor = atmosphere.getColorScale(); + fromHalo = atmosphere.getHaloScale(); + fromSunset = atmosphere.getSunsetIntensity(); + fromSun = atmosphere.getSunHaloIntensity(); + fromMoon = atmosphere.getMoonHaloIntensity(); + duration = seconds; + elapsed = 0f; + active = seconds > 0f; + apply(atmosphere, active ? 0f : 1f); + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/SkyGradientStyle.java b/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/SkyGradientStyle.java new file mode 100644 index 0000000..b5cb82e --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/SkyGradientStyle.java @@ -0,0 +1,129 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.atmosphere; + +/** + * Strength presets for atmospheric gradients and astronomical halos. + * + * @author Take Some + */ +public enum SkyGradientStyle { + /** Conservative real-world inspired gradients. */ + REALISTIC(0.70f, 0.65f, 0.65f, 0.75f, 0.75f, 0.65f), + /** Expressive cinematic gradients for game visuals. */ + CINEMATIC(1.00f, 1.00f, 1.00f, 1.25f, 1.15f, 1.10f), + /** Strong fantasy gradients for unreal skies. */ + FANTASY(1.55f, 1.40f, 1.45f, 1.80f, 1.65f, 1.55f); + + /** Color-shift multiplier. */ + final private float colorScale; + /** Preset moon halo intensity. */ + final private float presetMoonHalo; + /** Preset sun halo intensity. */ + final private float presetSunHalo; + /** Preset sunset intensity. */ + final private float presetSunset; + /** Halo/glow multiplier. */ + final private float haloScale; + /** Horizon gradient multiplier. */ + final private float horizonScale; + + /** + * Instantiate a preset. + * + * @param horizonScale horizon multiplier + * @param colorScale color multiplier + * @param haloScale halo multiplier + * @param presetSunset preset sunset intensity + * @param presetSunHalo preset sun halo intensity + * @param presetMoonHalo preset moon halo intensity + */ + SkyGradientStyle(float horizonScale, float colorScale, float haloScale, + float presetSunset, float presetSunHalo, + float presetMoonHalo) { + this.horizonScale = horizonScale; + this.colorScale = colorScale; + this.haloScale = haloScale; + this.presetSunset = presetSunset; + this.presetSunHalo = presetSunHalo; + this.presetMoonHalo = presetMoonHalo; + } + + + /** + * Return preset moon halo intensity. + * + * @return multiplier + */ + public float presetMoonHalo() { + return presetMoonHalo; + } + + /** + * Return preset sun halo intensity. + * + * @return multiplier + */ + public float presetSunHalo() { + return presetSunHalo; + } + + /** + * Return preset sunset intensity. + * + * @return multiplier + */ + public float presetSunset() { + return presetSunset; + } + + /** + * Return the color-shift multiplier. + * + * @return multiplier + */ + public float colorScale() { + return colorScale; + } + + /** + * Return the halo/glow multiplier. + * + * @return multiplier + */ + public float haloScale() { + return haloScale; + } + + /** + * Return the horizon gradient multiplier. + * + * @return multiplier + */ + public float horizonScale() { + return horizonScale; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/SkyLightingModel.java b/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/SkyLightingModel.java new file mode 100644 index 0000000..522099f --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/SkyLightingModel.java @@ -0,0 +1,209 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.atmosphere; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import jme3utilities.sky.SkyAtmosphere; + +/** + * Physically-inspired sky lighting calculations shared by SkyControl. + * + * @author Take Some + */ +public final class SkyLightingModel { + /** + * Hidden constructor. + */ + private SkyLightingModel() { + // do nothing + } + + /** + * Approximate solar transmission through the atmosphere. + * + * @param atmosphere active atmospheric profile (not null) + * @param sineSolarAltitude sine of the solar altitude + * @return transmission fraction between 0 and 1 + */ + public static float airMassTransmission( + SkyAtmosphere atmosphere, float sineSolarAltitude) { + float altitude = FastMath.clamp(sineSolarAltitude, 0.01f, 1f); + float opticalMass = 1f / (altitude + 0.15f + * (float) Math.pow(altitude + 0.1f, 1.25)); + float extinction = (float) Math.exp(-0.18f + * atmosphere.getAirMassStrength() * (opticalMass - 1f)); + float result = FastMath.clamp(extinction, + atmosphere.getMinSunTransmit(), 1f); + + return result; + } + + /** + * Compute daylight color after approximate atmospheric extinction. + * + * @param atmosphere active atmospheric profile (not null) + * @param sineSolarAltitude sine of the solar altitude + * @return new color + */ + public static ColorRGBA daylightColor( + SkyAtmosphere atmosphere, float sineSolarAltitude) { + ColorRGBA result = atmosphere.copySunLight(null); + float transmission = airMassTransmission(atmosphere, sineSolarAltitude); + float warmWeight = 1f - smoothStep( + sineSolarAltitude / atmosphere.getColorShiftAltitude()); + float horizonWeight = horizonWeight( + sineSolarAltitude, atmosphere.getTwilightLimit()); + float styleScale = atmosphere.getColorScale(); + float shift = warmWeight * atmosphere.getSunsetWarmth() + * atmosphere.getSunsetIntensity() * styleScale; + float amber = horizonWeight * atmosphere.getHazeStrength() + * atmosphere.getSunsetWarmth() + * atmosphere.getSunsetIntensity() * styleScale; + + result.r *= 1f + 0.28f * amber; + result.g *= 1f - 0.28f * shift + 0.10f * amber; + result.b *= 1f - 0.70f * shift - 0.22f * amber; + result.multLocal(transmission); + + return result; + } + + /** + * Compute a horizon/twilight gradient color. + * + * @param atmosphere active atmospheric profile (not null) + * @param sineSolarAltitude sine of the solar altitude + * @return new color + */ + public static ColorRGBA horizonColor( + SkyAtmosphere atmosphere, float sineSolarAltitude) { + ColorRGBA result = atmosphere.copyTwilightColor(null); + float horizonWeight = horizonWeight( + sineSolarAltitude, atmosphere.getTwilightLimit()); + float duskWeight = smoothStep( + -sineSolarAltitude / atmosphere.getTwilightLimit()); + float strength = atmosphere.getSunsetWarmth() + * atmosphere.getSunsetIntensity() + * atmosphere.getHazeStrength() + * atmosphere.getHorizonScale(); + float amber = horizonWeight * strength; + float violet = duskWeight * horizonWeight * strength; + + result.r *= 1f + 0.35f * amber + 0.12f * violet; + result.g *= 1f + 0.06f * amber - 0.18f * violet; + result.b *= 1f - 0.26f * amber + 0.42f * violet; + + return result; + } + + /** + * Compute the bell-shaped strength of horizon gradients. + * + * @param sineAltitude sine of altitude above horizon + * @param twilightLimit twilight reach below horizon + * @return gradient strength + */ + public static float horizonWeight(float sineAltitude, float twilightLimit) { + float distance = Math.abs(sineAltitude) / twilightLimit; + float result = 1f - smoothStep(distance); + return result; + } + + /** + * Compute lunar color after low-altitude horizon tint. + * + * @param atmosphere active atmospheric profile (not null) + * @param sineLunarAltitude sine of lunar altitude + * @return new color + */ + public static ColorRGBA lunarColor( + SkyAtmosphere atmosphere, float sineLunarAltitude) { + ColorRGBA result = atmosphere.copyMoonLight(null); + float horizonWeight = horizonWeight( + sineLunarAltitude, atmosphere.getTwilightLimit()); + float shift = horizonWeight * atmosphere.getSunsetWarmth() + * atmosphere.getSunsetIntensity() + * atmosphere.getColorScale(); + + result.r *= 1f + 0.10f * shift; + result.g *= 1f - 0.10f * shift; + result.b *= 1f - 0.25f * shift; + + return result; + } + + /** + * Compute moon halo/glow color. + * + * @param atmosphere active atmospheric profile (not null) + * @param sineLunarAltitude sine of lunar altitude + * @return new color + */ + public static ColorRGBA moonGlowColor( + SkyAtmosphere atmosphere, float sineLunarAltitude) { + ColorRGBA result = lunarColor(atmosphere, sineLunarAltitude); + float horizonWeight = horizonWeight( + sineLunarAltitude, atmosphere.getTwilightLimit()); + float glow = 0.45f + 0.35f * horizonWeight + * atmosphere.getHaloScale(); + result.multLocal(glow * atmosphere.getMoonHaloIntensity()); + return result; + } + + /** + * Compute sun halo/glow color. + * + * @param atmosphere active atmospheric profile (not null) + * @param sineSolarAltitude sine of solar altitude + * @return new color + */ + public static ColorRGBA sunGlowColor( + SkyAtmosphere atmosphere, float sineSolarAltitude) { + ColorRGBA result = daylightColor(atmosphere, sineSolarAltitude); + float horizonWeight = horizonWeight( + sineSolarAltitude, atmosphere.getTwilightLimit()); + float glow = 1.15f + 1.10f * horizonWeight + * atmosphere.getSunsetWarmth() + * atmosphere.getSunsetIntensity() + * atmosphere.getHaloScale(); + result.multLocal(glow * atmosphere.getSunHaloIntensity()); + return result; + } + + /** + * Smoothly remap a value from 0..1 to 0..1. + * + * @param input input value + * @return smoothed fraction + */ + public static float smoothStep(float input) { + float x = FastMath.saturate(input); + float result = x * x * (3f - 2f * x); + + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/package-info.java b/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/package-info.java new file mode 100644 index 0000000..6246509 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/atmosphere/package-info.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Atmospheric profile parsing and lighting calculations. + */ +package jme3utilities.sky.atmosphere; diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudAssets.java b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudAssets.java new file mode 100644 index 0000000..b3ab416 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudAssets.java @@ -0,0 +1,108 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.cloud; + +/** + * Asset paths for built-in and optional cloud textures. + *

+ * jME can load DDS textures through the regular AssetManager pipeline, so the + * preset paths intentionally point at DDS files where richer assets are used. + * + * @author Take Some + */ +final public class SkyCloudAssets { + /** Classpath directory for current built-in cloud textures. */ + final public static String directory = "Textures/skies/clouds"; + /** Classpath directory for optional weather-preset cloud textures. */ + final public static String presetDirectory = directory + "/presets"; + + /** Resource-side cloud weather preset registry. */ + final public static String registry + = presetDirectory + "/cloud-weather-presets.json"; + + /** Runtime Lua ABI cloud weather preset registry. */ + final public static String luaRegistry = "helix/lua/sky/weather.lua"; + + /** Existing clear fallback alpha map. */ + final public static String clear = directory + "/clear.png"; + /** Existing procedural FBM alpha map. */ + final public static String fbm = directory + "/fbm.png"; + /** Existing overcast fallback alpha map. */ + final public static String overcast = directory + "/overcast.png"; + + /** Optional wispy cirrocumulus alpha map. */ + final public static String wispyCirrocumulus + = presetDirectory + "/wispy/skyhat_cirrocumulus01_ap.dds"; + /** Optional wispy cloud alpha map. */ + final public static String wisps + = presetDirectory + "/wispy/wisps_ap.dds"; + /** Optional cloudy base alpha map. */ + final public static String cloudyBase + = presetDirectory + "/cloudy/trialap.dds"; + /** Optional shared marble/detail alpha map. */ + final public static String marbleDetail + = presetDirectory + "/cloudy/cloudhat_marble02_ap.dds"; + /** Optional rain alpha map. */ + final public static String rain + = presetDirectory + "/rain/skyhat_rain02_ap.dds"; + /** Optional storm alpha map. */ + final public static String storm + = presetDirectory + "/storm/stormclouds_ap.dds"; + /** Optional nimbus alpha map. */ + final public static String nimbus + = presetDirectory + "/nimbus/final_nimbusclouds_ap.dds"; + + + /** Optional wispy cirrocumulus normal map. */ + final public static String wispyCirrocumulusNormal + = presetDirectory + "/wispy/skyhat_cirrocumulus01_nrm.dds"; + /** Optional wispy cloud normal map. */ + final public static String wispsNormal + = presetDirectory + "/wispy/wisps_nrm.dds"; + /** Optional cloudy base normal map. */ + final public static String cloudyBaseNormal + = presetDirectory + "/cloudy/trialn.dds"; + /** Optional shared detail normal map. */ + final public static String cloudyDetailNormal + = presetDirectory + "/cloudy/detail1_nrm.dds"; + /** Optional rain normal map. */ + final public static String rainNormal + = presetDirectory + "/rain/skyhat_rain02_n2.dds"; + /** Optional storm cloud normal map. */ + final public static String stormNormal + = presetDirectory + "/storm/stormclouds_nrm.dds"; + /** Optional storm rain normal map. */ + final public static String stormRainNormal + = presetDirectory + "/storm/skyhat_rain02_n2.dds"; + /** Optional nimbus normal map. */ + final public static String nimbusNormal + = presetDirectory + "/nimbus/final_nimbusclouds_n.dds"; + + /** Hidden constructor. */ + private SkyCloudAssets() { + // do nothing + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudLayerSpec.java b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudLayerSpec.java new file mode 100644 index 0000000..6fe0184 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudLayerSpec.java @@ -0,0 +1,147 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.cloud; + +import jme3utilities.Validate; + +/** + * Target state for one cloud layer in a weather preset. + * + * @author Take Some + */ +final public class SkyCloudLayerSpec { + /** A transparent clear layer. */ + final public static SkyCloudLayerSpec clear + = new SkyCloudLayerSpec(SkyCloudAssets.clear, 0f, 1f, 0f, 0f); + + /** Asset path to the alpha map. */ + final private String alphaMap; + /** Optional asset path to the normal map. */ + final private String normalMap; + /** Target opacity. */ + final private float opacity; + /** Target texture scale. */ + final private float scale; + /** U-axis motion rate. */ + final private float uRate; + /** V-axis motion rate. */ + final private float vRate; + + /** + * Instantiate a layer target. + * + * @param alphaMap asset path to the alpha map (not null, not empty) + * @param opacity target opacity (≤1, ≥0) + * @param scale texture scale (>0) + * @param uRate U-axis motion rate + * @param vRate V-axis motion rate + */ + public SkyCloudLayerSpec(String alphaMap, float opacity, float scale, + float uRate, float vRate) { + this(alphaMap, null, opacity, scale, uRate, vRate); + } + + /** + * Instantiate a layer target with a normal map. + * + * @param alphaMap asset path to the alpha map (not null, not empty) + * @param normalMap asset path to the normal map, or null + * @param opacity target opacity (≤1, ≥0) + * @param scale texture scale (>0) + * @param uRate U-axis motion rate + * @param vRate V-axis motion rate + */ + public SkyCloudLayerSpec(String alphaMap, String normalMap, float opacity, + float scale, float uRate, float vRate) { + Validate.nonEmpty(alphaMap, "alpha map"); + if (normalMap != null) { + Validate.nonEmpty(normalMap, "normal map"); + } + Validate.fraction(opacity, "opacity"); + Validate.positive(scale, "scale"); + + this.alphaMap = alphaMap; + this.normalMap = normalMap; + this.opacity = opacity; + this.scale = scale; + this.uRate = uRate; + this.vRate = vRate; + } + + /** + * Return the alpha-map asset path. + * + * @return asset path + */ + public String alphaMap() { + return alphaMap; + } + + /** + * Return the optional normal-map asset path. + * + * @return asset path, or null + */ + public String normalMap() { + return normalMap; + } + + /** + * Return the target opacity. + * + * @return opacity fraction + */ + public float opacity() { + return opacity; + } + + /** + * Return the texture scale. + * + * @return scale factor + */ + public float scale() { + return scale; + } + + /** + * Return the U-axis motion rate. + * + * @return motion rate in cycles per second + */ + public float uRate() { + return uRate; + } + + /** + * Return the V-axis motion rate. + * + * @return motion rate in cycles per second + */ + public float vRate() { + return vRate; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPreset.java b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPreset.java new file mode 100644 index 0000000..50231d7 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPreset.java @@ -0,0 +1,147 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.cloud; + +/** + * Cloud weather presets built from up to six cloud layers. + * + * @author Take Some + */ +public enum SkyCloudPreset { + /** No visible clouds. */ + CLEAR(SkyCloudLayerSpec.clear), + /** Existing procedural fair-weather cloud setup. */ + FAIR( + layer(SkyCloudAssets.fbm, 0.35f, 1.50f, -0.0005f, 0.0030f), + layer(SkyCloudAssets.fbm, 0.18f, 2.15f, 0.0003f, 0.0010f)), + /** Existing overcast fallback. */ + OVERCAST( + layer(SkyCloudAssets.overcast, 0.72f, 1.00f, 0.0000f, 0.0003f), + layer(SkyCloudAssets.fbm, 0.20f, 2.40f, 0.0002f, 0.0010f)), + /** Optional high-altitude wispy cloud setup. */ + WISPY( + layer(SkyCloudAssets.wispyCirrocumulus, + SkyCloudAssets.wispyCirrocumulusNormal, 0.28f, 1.30f, + 0.0001f, 0.0012f), + layer(SkyCloudAssets.wisps, SkyCloudAssets.wispsNormal, 0.16f, + 2.20f, -0.0002f, 0.0020f)), + /** Optional varied cloudy setup. */ + CLOUDY( + layer(SkyCloudAssets.cloudyBase, + SkyCloudAssets.cloudyBaseNormal, 0.52f, 1.20f, + -0.0003f, 0.0018f), + layer(SkyCloudAssets.marbleDetail, + SkyCloudAssets.cloudyDetailNormal, 0.22f, 3.00f, + 0.0004f, 0.0024f)), + /** Optional rainy setup. */ + RAIN( + layer(SkyCloudAssets.rain, SkyCloudAssets.rainNormal, 0.68f, + 1.10f, -0.0003f, 0.0026f), + layer(SkyCloudAssets.cloudyBase, + SkyCloudAssets.cloudyBaseNormal, 0.35f, 2.00f, + 0.0002f, 0.0018f), + layer(SkyCloudAssets.marbleDetail, + SkyCloudAssets.cloudyDetailNormal, 0.18f, 3.50f, + 0.0004f, 0.0030f)), + /** Optional storm setup. */ + STORM( + layer(SkyCloudAssets.storm, SkyCloudAssets.stormNormal, 0.85f, + 1.00f, -0.0004f, 0.0032f), + layer(SkyCloudAssets.rain, SkyCloudAssets.stormRainNormal, 0.42f, + 1.80f, 0.0002f, 0.0026f), + layer(SkyCloudAssets.marbleDetail, + SkyCloudAssets.cloudyDetailNormal, 0.26f, 4.00f, + 0.0005f, 0.0035f)), + /** Optional nimbus setup. */ + NIMBUS( + layer(SkyCloudAssets.nimbus, SkyCloudAssets.nimbusNormal, 0.76f, + 1.10f, -0.0004f, 0.0020f), + layer(SkyCloudAssets.marbleDetail, + SkyCloudAssets.cloudyDetailNormal, 0.22f, 3.50f, + 0.0003f, 0.0024f)); + + /** Layer targets, in material-layer order. */ + final private SkyCloudLayerSpec[] layers; + + /** + * Instantiate a preset. + * + * @param layers layer targets (not null, aliased) + */ + SkyCloudPreset(SkyCloudLayerSpec... layers) { + this.layers = layers; + } + + /** + * Access the target state for a layer. + * + * @param layerIndex cloud layer index + * @return layer target, or clear if the preset has no layer there + */ + public SkyCloudLayerSpec layer(int layerIndex) { + SkyCloudLayerSpec result = SkyCloudLayerSpec.clear; + if (layerIndex < layers.length) { + result = layers[layerIndex]; + } + + return result; + } + + /** + * Create a layer target. + * + * @param alphaMap asset path to the alpha map + * @param opacity target opacity + * @param scale texture scale + * @param uRate U-axis motion rate + * @param vRate V-axis motion rate + * @return new layer target + */ + private static SkyCloudLayerSpec layer(String alphaMap, float opacity, + float scale, float uRate, float vRate) { + SkyCloudLayerSpec result + = new SkyCloudLayerSpec(alphaMap, opacity, scale, uRate, vRate); + return result; + } + + /** + * Create a layer target with a normal map. + * + * @param alphaMap asset path to the alpha map + * @param normalMap asset path to the normal map + * @param opacity target opacity + * @param scale texture scale + * @param uRate U-axis motion rate + * @param vRate V-axis motion rate + * @return new layer target + */ + private static SkyCloudLayerSpec layer(String alphaMap, String normalMap, + float opacity, float scale, float uRate, float vRate) { + SkyCloudLayerSpec result = new SkyCloudLayerSpec( + alphaMap, normalMap, opacity, scale, uRate, vRate); + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPresetDefinition.java b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPresetDefinition.java new file mode 100644 index 0000000..4896c45 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPresetDefinition.java @@ -0,0 +1,226 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.cloud; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import jme3utilities.Validate; +import jme3utilities.sky.SkyControlCore; +import jme3utilities.sky.runtime.SkyWeatherMetrics; + +/** + * Data-driven cloud/weather preset definition. + * + * @author Take Some + */ +final public class SkyCloudPresetDefinition { + /** Built-in enum alias, or null for ABI/custom presets. */ + final private SkyCloudPreset builtIn; + /** Human-readable description. */ + final private String description; + /** Default transition duration. */ + final private float defaultSeconds; + /** Stable preset id. */ + final private String id; + /** Cloud layer targets. */ + final private List layers; + /** Game-facing weather metrics. */ + final private SkyWeatherMetrics metrics; + + /** + * Instantiate a preset definition. + * + * @param id stable preset id (not null, not empty) + * @param description human-readable description (not null) + * @param defaultSeconds default transition duration (≥0) + * @param layers cloud layer targets (not null, copied) + * @param metrics game-facing weather metrics (not null, unaffected) + */ + public SkyCloudPresetDefinition(String id, String description, + float defaultSeconds, List layers, + SkyWeatherMetrics metrics) { + this(id, description, defaultSeconds, layers, metrics, null); + } + + /** + * Instantiate a preset definition. + * + * @param id stable preset id (not null, not empty) + * @param description human-readable description (not null) + * @param defaultSeconds default transition duration (≥0) + * @param layers cloud layer targets (not null, copied) + * @param metrics game-facing weather metrics (not null, unaffected) + * @param builtIn built-in enum alias, or null + */ + private SkyCloudPresetDefinition(String id, String description, + float defaultSeconds, List layers, + SkyWeatherMetrics metrics, SkyCloudPreset builtIn) { + Validate.nonEmpty(id, "id"); + Validate.nonNull(description, "description"); + Validate.nonNegative(defaultSeconds, "seconds"); + Validate.nonNull(layers, "layers"); + Validate.nonNull(metrics, "metrics"); + + this.id = id; + this.description = description; + this.defaultSeconds = defaultSeconds; + this.layers = Collections.unmodifiableList( + new ArrayList(layers)); + this.metrics = metrics.copy(); + this.builtIn = builtIn; + } + + /** + * Create a definition for a built-in preset. + * + * @param preset built-in preset (not null) + * @return new definition + */ + public static SkyCloudPresetDefinition fromPreset(SkyCloudPreset preset) { + Validate.nonNull(preset, "preset"); + + List specs = new ArrayList(); + for (int layerI = 0; + layerI < SkyControlCore.numCloudLayers; ++layerI) { + specs.add(preset.layer(layerI)); + } + SkyWeatherMetrics metrics = builtinMetrics(preset); + SkyCloudPresetDefinition result = new SkyCloudPresetDefinition( + preset.name(), preset.name(), 60f, specs, metrics, preset); + return result; + } + + /** + * Return the built-in enum alias, if any. + * + * @return built-in preset, or null + */ + public SkyCloudPreset builtIn() { + return builtIn; + } + + /** + * Return the default transition duration. + * + * @return duration in seconds + */ + public float defaultSeconds() { + return defaultSeconds; + } + + /** + * Return the description. + * + * @return description + */ + public String description() { + return description; + } + + /** + * Return the preset id. + * + * @return id + */ + public String id() { + return id; + } + + /** + * Access the target state for a layer. + * + * @param layerIndex cloud layer index + * @return layer target, or clear if no layer is defined there + */ + public SkyCloudLayerSpec layer(int layerIndex) { + SkyCloudLayerSpec result = SkyCloudLayerSpec.clear; + if (layerIndex < layers.size()) { + result = layers.get(layerIndex); + } + return result; + } + + /** + * Return the number of explicitly defined layers. + * + * @return layer count + */ + public int layerCount() { + return layers.size(); + } + + /** + * Copy the game-facing weather metrics. + * + * @return copied metrics + */ + public SkyWeatherMetrics metrics() { + return metrics.copy(); + } + + /** + * Return game-facing metrics for a built-in preset. + * + * @param preset built-in preset + * @return new metrics + */ + private static SkyWeatherMetrics builtinMetrics(SkyCloudPreset preset) { + SkyWeatherMetrics result; + switch (preset) { + case CLEAR: + result = new SkyWeatherMetrics(0f, 1f, 0f, 0.05f, 0f); + break; + case FAIR: + result = new SkyWeatherMetrics(0.35f, 0.95f, 0f, 0.10f, 0f); + break; + case OVERCAST: + result = new SkyWeatherMetrics(0.72f, 0.75f, 0f, 0.25f, 0f); + break; + case WISPY: + result = new SkyWeatherMetrics(0.28f, 0.92f, 0f, 0.20f, 0f); + break; + case CLOUDY: + result = new SkyWeatherMetrics(0.55f, 0.78f, 0f, 0.35f, 0f); + break; + case RAIN: + result = new SkyWeatherMetrics( + 0.75f, 0.55f, 0.75f, 0.55f, 0.02f); + break; + case STORM: + result = new SkyWeatherMetrics( + 0.90f, 0.35f, 0.95f, 0.90f, 0.20f); + break; + case NIMBUS: + result = new SkyWeatherMetrics( + 0.82f, 0.45f, 0.80f, 0.70f, 0.08f); + break; + default: + throw new IllegalStateException("preset = " + preset); + } + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPresetLoader.java b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPresetLoader.java new file mode 100644 index 0000000..bd3a36a --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPresetLoader.java @@ -0,0 +1,339 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.cloud; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetManager; +import com.jme3.asset.AssetNotFoundException; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Validate; +import jme3utilities.sky.runtime.SkyWeatherMetrics; +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaError; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; +import org.luaj.vm2.lib.jse.JsePlatform; + +/** + * Loader for Lua weather ABI preset registries. + * + * @author Take Some + */ +final public class SkyCloudPresetLoader { + /** Message logger for this class. */ + final private static Logger logger + = Logger.getLogger(SkyCloudPresetLoader.class.getName()); + + /** Default Lua ABI registry resource. */ + final public static String defaultRegistry + = SkyCloudAssets.luaRegistry; + + /** Hidden constructor. */ + private SkyCloudPresetLoader() { + // do nothing + } + + /** + * Load a Lua weather ABI registry. + * + * @param assetManager asset manager (not null) + * @param assetPath Lua asset path (not null, not empty) + * @return loaded registry + */ + public static SkyCloudPresetRegistry load(AssetManager assetManager, + String assetPath) { + Validate.nonNull(assetManager, "asset manager"); + Validate.nonEmpty(assetPath, "asset path"); + + logger.log(Level.INFO, + "loading sky weather Lua ABI registry: {0}", assetPath); + String source = readText(assetManager, assetPath); + Globals globals = JsePlatform.standardGlobals(); + LuaValue root; + try { + root = globals.load(source, assetPath).call(); + } catch (LuaError exception) { + throw new IllegalArgumentException( + "Invalid sky weather Lua ABI: " + assetPath, exception); + } + SkyCloudPresetRegistry result = parseRegistry(root, assetPath); + logger.log(Level.INFO, + "loaded sky weather Lua ABI registry: path={0}, presets={1}", + new Object[]{assetPath, result.ids().size()}); + return result; + } + + /** + * Load the default bundled Lua weather ABI registry. + * + * @param assetManager asset manager (not null) + * @return loaded registry + */ + public static SkyCloudPresetRegistry loadDefault( + AssetManager assetManager) { + SkyCloudPresetRegistry result = load(assetManager, defaultRegistry); + return result; + } + + /** + * Parse one preset definition. + * + * @param id preset id + * @param value Lua preset table + * @return parsed definition + */ + private static SkyCloudPresetDefinition parseDefinition( + String id, LuaValue value) { + requireTable(value, id); + + String description = optionalString(value, "description", id); + float seconds = optionalFloat(value, "transitionSeconds", 60f); + SkyWeatherMetrics metrics = parseMetrics(value.get("world")); + List layers = parseLayers(value.get("layers")); + SkyCloudPresetDefinition result = new SkyCloudPresetDefinition( + id, description, seconds, layers, metrics); + logger.log(Level.FINE, + "parsed sky weather preset: id={0}, seconds={1}, layers={2}, metrics={3}", + new Object[]{id, seconds, layers.size(), metrics}); + return result; + } + + /** + * Parse one cloud layer table. + * + * @param value Lua layer table + * @return parsed layer spec + */ + private static SkyCloudLayerSpec parseLayer(LuaValue value) { + requireTable(value, "layer"); + + String alphaMap = requiredString(value, "alphaMap"); + String normalMap = optionalString(value, "normalMap", null); + float opacity = requiredFloat(value, "opacity"); + float scale = requiredFloat(value, "scale"); + float uRate = requiredFloat(value, "uRate"); + float vRate = requiredFloat(value, "vRate"); + SkyCloudLayerSpec result; + if (normalMap == null) { + result = new SkyCloudLayerSpec( + alphaMap, opacity, scale, uRate, vRate); + } else { + result = new SkyCloudLayerSpec( + alphaMap, normalMap, opacity, scale, uRate, vRate); + } + logger.log(Level.FINER, + "parsed cloud layer: alpha={0}, normal={1}, opacity={2}, scale={3}, uRate={4}, vRate={5}", + new Object[]{alphaMap, normalMap, opacity, scale, uRate, vRate}); + return result; + } + + /** + * Parse cloud layers. + * + * @param value Lua layers array + * @return layer specs + */ + private static List parseLayers(LuaValue value) { + requireTable(value, "layers"); + + List result = new ArrayList(); + int length = value.length(); + for (int layerI = 1; layerI <= length; ++layerI) { + result.add(parseLayer(value.get(layerI))); + } + return result; + } + + /** + * Parse weather metrics. + * + * @param value Lua world table + * @return parsed metrics + */ + private static SkyWeatherMetrics parseMetrics(LuaValue value) { + requireTable(value, "world"); + + SkyWeatherMetrics result = new SkyWeatherMetrics( + requiredFloat(value, "cloudiness"), + requiredFloat(value, "visibility"), + requiredFloat(value, "precipitation"), + requiredFloat(value, "windStrength"), + requiredFloat(value, "lightningChance")); + return result; + } + + /** + * Parse a root Lua ABI table. + * + * @param root root table + * @param assetPath asset path for diagnostics + * @return parsed registry + */ + private static SkyCloudPresetRegistry parseRegistry( + LuaValue root, String assetPath) { + requireTable(root, assetPath); + + LuaValue presets = root.get("presets"); + requireTable(presets, "presets"); + + List list + = new ArrayList(); + LuaValue key = LuaValue.NIL; + while (true) { + Varargs pair = presets.next(key); + key = pair.arg1(); + if (key.isnil()) { + break; + } + LuaValue value = pair.arg(2); + list.add(parseDefinition(key.checkjstring(), value)); + } + SkyCloudPresetRegistry result = new SkyCloudPresetRegistry(list); + logger.log(Level.FINE, + "parsed sky weather registry: path={0}, presetCount={1}", + new Object[]{assetPath, list.size()}); + return result; + } + + /** + * Read a text asset. + * + * @param assetManager asset manager + * @param assetPath asset path + * @return asset contents + */ + private static String readText(AssetManager assetManager, + String assetPath) { + AssetKey key = new AssetKey(assetPath); + AssetInfo info = assetManager.locateAsset(key); + if (info == null) { + throw new AssetNotFoundException(assetPath); + } + + StringBuilder builder = new StringBuilder(); + try (InputStream input = info.openStream(); + InputStreamReader reader = new InputStreamReader( + input, StandardCharsets.UTF_8); + BufferedReader buffered = new BufferedReader(reader)) { + String line; + while ((line = buffered.readLine()) != null) { + builder.append(line).append(System.lineSeparator()); + } + } catch (IOException exception) { + throw new IllegalArgumentException( + "Failed to read sky weather Lua ABI: " + assetPath, + exception); + } + String result = builder.toString(); + logger.log(Level.FINE, + "read sky weather Lua ABI text: path={0}, chars={1}", + new Object[]{assetPath, result.length()}); + return result; + } + + /** + * Read an optional float field. + * + * @param table Lua table + * @param name field name + * @param fallback fallback value + * @return parsed value + */ + private static float optionalFloat(LuaValue table, String name, + float fallback) { + LuaValue value = table.get(name); + float result = value.isnil() ? fallback : (float) value.checkdouble(); + return result; + } + + /** + * Read an optional string field. + * + * @param table Lua table + * @param name field name + * @param fallback fallback value + * @return parsed value + */ + private static String optionalString(LuaValue table, String name, + String fallback) { + LuaValue value = table.get(name); + String result = value.isnil() ? fallback : value.checkjstring(); + return result; + } + + /** + * Read a required float field. + * + * @param table Lua table + * @param name field name + * @return parsed value + */ + private static float requiredFloat(LuaValue table, String name) { + LuaValue value = table.get(name); + if (value.isnil()) { + throw new IllegalArgumentException("Missing float field: " + name); + } + float result = (float) value.checkdouble(); + return result; + } + + /** + * Read a required string field. + * + * @param table Lua table + * @param name field name + * @return parsed value + */ + private static String requiredString(LuaValue table, String name) { + LuaValue value = table.get(name); + if (value.isnil()) { + throw new IllegalArgumentException("Missing string field: " + name); + } + String result = value.checkjstring(); + return result; + } + + /** + * Require a Lua table value. + * + * @param value Lua value + * @param label diagnostic label + */ + private static void requireTable(LuaValue value, String label) { + if (value == null || !value.istable()) { + throw new IllegalArgumentException("Expected table: " + label); + } + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPresetRegistry.java b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPresetRegistry.java new file mode 100644 index 0000000..31fd295 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPresetRegistry.java @@ -0,0 +1,123 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.cloud; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import jme3utilities.Validate; + +/** + * Registry of data-driven cloud/weather presets. + * + * @author Take Some + */ +final public class SkyCloudPresetRegistry { + /** Definitions by stable id. */ + final private Map definitions; + + /** + * Instantiate a registry. + * + * @param definitions definitions to register (not null, copied) + */ + public SkyCloudPresetRegistry( + Collection definitions) { + Validate.nonNull(definitions, "definitions"); + + Map map + = new LinkedHashMap(); + for (SkyCloudPresetDefinition definition : definitions) { + Validate.nonNull(definition, "definition"); + map.put(definition.id(), definition); + } + this.definitions = Collections.unmodifiableMap(map); + } + + /** + * Create a registry containing built-in enum presets. + * + * @return new registry + */ + public static SkyCloudPresetRegistry builtIns() { + List list + = new ArrayList(); + for (SkyCloudPreset preset : SkyCloudPreset.values()) { + list.add(SkyCloudPresetDefinition.fromPreset(preset)); + } + SkyCloudPresetRegistry result = new SkyCloudPresetRegistry(list); + return result; + } + + /** + * Access a definition by id. + * + * @param id stable id (not null, not empty) + * @return definition, or null if not registered + */ + public SkyCloudPresetDefinition get(String id) { + Validate.nonEmpty(id, "id"); + + SkyCloudPresetDefinition result = definitions.get(id); + return result; + } + + /** + * Return registered ids. + * + * @return immutable id set + */ + public Set ids() { + return definitions.keySet(); + } + + /** + * Require a definition by id. + * + * @param id stable id (not null, not empty) + * @return definition + */ + public SkyCloudPresetDefinition require(String id) { + SkyCloudPresetDefinition result = get(id); + if (result == null) { + throw new IllegalArgumentException("Unknown sky preset: " + id); + } + return result; + } + + /** + * Return registered definitions. + * + * @return immutable definitions collection + */ + public Collection values() { + return definitions.values(); + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudTransitionRuntime.java b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudTransitionRuntime.java new file mode 100644 index 0000000..1fc51d2 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudTransitionRuntime.java @@ -0,0 +1,249 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.cloud; + +import com.jme3.math.FastMath; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Validate; +import jme3utilities.sky.CloudLayer; + +/** + * Runtime transition that lets cloud presets fade in and out. + *

+ * Texture swaps happen only while layers are invisible, so changes + * do not pop instantly on screen. + * + * @author Take Some + */ +final public class SkyCloudTransitionRuntime { + /** Message logger for this class. */ + final private static Logger logger + = Logger.getLogger(SkyCloudTransitionRuntime.class.getName()); + + /** Cloud layers being driven. */ + private CloudLayer[] layers; + /** Opacities at transition start. */ + private float[] startOpacities; + /** True while a transition is active. */ + private boolean active; + /** True after target textures have been applied. */ + private boolean swapped; + /** Elapsed transition time. */ + private float elapsed; + /** Total transition duration. */ + private float duration; + /** Target preset. */ + private SkyCloudPresetDefinition target; + + /** + * Instantiate a transition runtime. + * + * @param layers cloud layers (not null, aliased) + */ + public SkyCloudTransitionRuntime(CloudLayer[] layers) { + resetLayers(layers); + cancel(); + } + + /** Cancel any active transition. */ + public void cancel() { + if (active && elapsed < duration) { + logger.log(Level.FINE, + "cloud weather transition cancelled: target={0}, elapsed={1}, duration={2}", + new Object[]{target == null ? null : target.id(), + elapsed, duration}); + } + this.active = false; + this.swapped = false; + this.elapsed = 0f; + this.duration = 0f; + this.target = null; + } + + /** + * Reset the driven layer array. + * + * @param layers cloud layers (not null, aliased) + */ + public void resetLayers(CloudLayer[] layers) { + Validate.nonNull(layers, "layers"); + + this.layers = layers; + this.startOpacities = new float[layers.length]; + } + + /** + * Start a transition to the specified built-in preset. + * + * @param preset target preset (not null) + * @param seconds transition duration in seconds (≥0) + */ + public void transitionTo(SkyCloudPreset preset, float seconds) { + Validate.nonNull(preset, "preset"); + + SkyCloudPresetDefinition definition + = SkyCloudPresetDefinition.fromPreset(preset); + transitionTo(definition, seconds); + } + + /** + * Start a transition to the specified data preset. + * + * @param definition target preset definition (not null) + * @param seconds transition duration in seconds (≥0) + */ + public void transitionTo(SkyCloudPresetDefinition definition, + float seconds) { + Validate.nonNull(definition, "definition"); + if (!(seconds >= 0f)) { + throw new IllegalArgumentException("duration must be non-negative"); + } + + this.target = definition; + this.duration = seconds; + this.elapsed = 0f; + this.swapped = false; + + for (int layerI = 0; layerI < layers.length; ++layerI) { + startOpacities[layerI] = layers[layerI].getOpacity(); + } + + logger.log(Level.INFO, + "cloud weather transition started: id={0}, seconds={1}, layers={2}", + new Object[]{definition.id(), seconds, definition.layerCount()}); + if (seconds == 0f) { + applyTargetTextures(); + applyTargetOpacities(); + logger.log(Level.FINE, + "cloud weather transition applied immediately: id={0}", + definition.id()); + cancel(); + } else { + this.active = true; + } + } + + /** + * Update the active transition. + * + * @param interval update interval in seconds (≥0) + */ + public void update(float interval) { + assert interval >= 0f : interval; + if (!active) { + return; + } + + elapsed += interval; + float halfDuration = duration * 0.5f; + if (elapsed < halfDuration) { + float weight = smooth(elapsed / halfDuration); + fadeOut(weight); + return; + } + + if (!swapped) { + applyTargetTextures(); + swapped = true; + logger.log(Level.FINE, + "cloud weather transition textures swapped: id={0}", + target.id()); + } + + float weight = smooth((elapsed - halfDuration) / halfDuration); + fadeIn(weight); + + if (elapsed >= duration) { + applyTargetOpacities(); + logger.log(Level.INFO, + "cloud weather transition completed: id={0}, duration={1}", + new Object[]{target.id(), duration}); + cancel(); + } + } + + /** Apply target opacities. */ + private void applyTargetOpacities() { + for (int layerI = 0; layerI < layers.length; ++layerI) { + SkyCloudLayerSpec spec = target.layer(layerI); + layers[layerI].setOpacity(spec.opacity()); + } + } + + /** Apply target textures, scales, and motions with zero opacity. */ + private void applyTargetTextures() { + for (int layerI = 0; layerI < layers.length; ++layerI) { + SkyCloudLayerSpec spec = target.layer(layerI); + CloudLayer layer = layers[layerI]; + layer.setOpacity(0f); + layer.setTexture(spec.alphaMap(), spec.scale()); + layer.setNormalMap(spec.normalMap()); + layer.setMotion(0f, spec.uRate(), 0f, spec.vRate()); + logger.log(Level.FINER, + "cloud layer target applied: index={0}, alpha={1}, normal={2}, scale={3}, opacity={4}, uRate={5}, vRate={6}", + new Object[]{layerI, spec.alphaMap(), spec.normalMap(), + spec.scale(), spec.opacity(), spec.uRate(), spec.vRate()}); + } + } + + /** + * Fade current layers out. + * + * @param weight transition weight + */ + private void fadeOut(float weight) { + for (int layerI = 0; layerI < layers.length; ++layerI) { + float opacity = startOpacities[layerI] * (1f - weight); + layers[layerI].setOpacity(opacity); + } + } + + /** + * Fade target layers in. + * + * @param weight transition weight + */ + private void fadeIn(float weight) { + for (int layerI = 0; layerI < layers.length; ++layerI) { + SkyCloudLayerSpec spec = target.layer(layerI); + float opacity = spec.opacity() * weight; + layers[layerI].setOpacity(opacity); + } + } + + /** + * Smooth a transition weight. + * + * @param input unsmoothed weight + * @return smoothed weight + */ + private static float smooth(float input) { + float t = FastMath.saturate(input); + float result = t * t * (3f - 2f * t); + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/cloud/package-info.java b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/package-info.java new file mode 100644 index 0000000..e8b77a1 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/package-info.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Cloud weather presets and transition helpers for the sky simulation. + */ +package jme3utilities.sky.cloud; diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/command/SkyCommandBus.java b/SkyLibrary/src/main/java/jme3utilities/sky/command/SkyCommandBus.java new file mode 100644 index 0000000..5609833 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/command/SkyCommandBus.java @@ -0,0 +1,396 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.command; + +import com.jme3.asset.AssetManager; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import jme3utilities.Validate; +import jme3utilities.sky.SkyControl; +import jme3utilities.sky.atmosphere.SkyGradientStyle; +import jme3utilities.sky.cloud.SkyCloudPresetDefinition; +import jme3utilities.sky.cloud.SkyCloudPresetLoader; +import jme3utilities.sky.cloud.SkyCloudPresetRegistry; +import jme3utilities.sky.config.SkySimulationConfig; +import jme3utilities.sky.config.SkySimulationConfigLoader; +import jme3utilities.sky.runtime.SkyEnvironmentSnapshot; + +/** + * Java executor for the SkySimulation command ABI. + * + * @author Take Some + */ +final public class SkyCommandBus { + /** Asset manager used for Lua ABI reloads. */ + final private AssetManager assetManager; + /** Lua config ABI asset path. */ + final private String configPath; + /** Target sky control. */ + final private SkyControl skyControl; + /** Current parsed config. */ + private SkySimulationConfig config; + /** Current parsed weather registry. */ + private SkyCloudPresetRegistry weatherRegistry; + + /** + * Instantiate a command bus using the default config ABI. + * + * @param assetManager asset manager (not null) + * @param skyControl target control (not null) + */ + public SkyCommandBus(AssetManager assetManager, SkyControl skyControl) { + this(assetManager, skyControl, SkySimulationConfigLoader.defaultConfig); + } + + /** + * Instantiate a command bus. + * + * @param assetManager asset manager (not null) + * @param skyControl target control (not null) + * @param configPath Lua config ABI path (not null, not empty) + */ + public SkyCommandBus(AssetManager assetManager, SkyControl skyControl, + String configPath) { + Validate.nonNull(assetManager, "asset manager"); + Validate.nonNull(skyControl, "control"); + Validate.nonEmpty(configPath, "config path"); + + this.assetManager = assetManager; + this.skyControl = skyControl; + this.configPath = configPath; + reloadConfig(false); + } + + /** + * Execute a command ABI entry. + * + * @param commandId command id (not null, not empty) + * @param arguments command arguments, or none + * @return command result + */ + public SkyCommandResult execute(String commandId, String... arguments) { + Validate.nonEmpty(commandId, "command id"); + + String[] args = arguments == null ? new String[0] : arguments; + SkyCommandResult result; + if (SkyCommandIds.atmosphereSetGradient.equals(commandId)) { + result = execAtmoGradient(args); + } else if (SkyCommandIds.atmosphereSetSunsetIntensity.equals( + commandId)) { + result = execAtmoSunset(args); + } else if (SkyCommandIds.atmosphereSetSunHaloIntensity.equals( + commandId)) { + result = execAtmoSunHalo(args); + } else if (SkyCommandIds.atmosphereSetMoonHaloIntensity.equals( + commandId)) { + result = execAtmoMoonHalo(args); + } else if (SkyCommandIds.weatherSet.equals(commandId)) { + result = executeWeatherSet(args); + } else if (SkyCommandIds.weatherList.equals(commandId)) { + result = executeWeatherList(); + } else if (SkyCommandIds.clockSetTime.equals(commandId)) { + result = executeClockSetTime(args); + } else if (SkyCommandIds.clockAdvance.equals(commandId)) { + result = executeClockAdvance(args); + } else if (SkyCommandIds.environmentSnapshot.equals(commandId)) { + result = executeEnvSnapshot(); + } else if (SkyCommandIds.configReload.equals(commandId)) { + result = executeConfigReload(); + } else { + throw new IllegalArgumentException("Unknown sky command: " + + commandId); + } + return result; + } + + /** + * Return current parsed config. + * + * @return config + */ + public SkySimulationConfig config() { + return config; + } + + /** + * Return current weather registry. + * + * @return weather registry + */ + public SkyCloudPresetRegistry weatherRegistry() { + return weatherRegistry; + } + + /** + * Execute atmosphere set-gradient command. + * + * @param args command arguments + * @return command result + */ + private SkyCommandResult execAtmoGradient(String[] args) { + String styleId = requireArgument(args, 0, + SkyCommandIds.atmosphereSetGradient); + SkyGradientStyle style = parseGradientStyle(styleId); + float seconds = optionalFloat(args, 1, 0f, "seconds"); + + skyControl.setGradientStyle(style, seconds); + SkyCommandResult result = SkyCommandResult.success( + SkyCommandIds.atmosphereSetGradient, + "atmosphere gradient set: " + style.name()); + return result; + } + + /** + * Execute atmosphere set-moon-halo-intensity command. + * + * @param args command arguments + * @return command result + */ + private SkyCommandResult execAtmoMoonHalo( + String[] args) { + float intensity = parseFloat(requireArgument(args, 0, + SkyCommandIds.atmosphereSetMoonHaloIntensity), + "moon halo intensity"); + float seconds = optionalFloat(args, 1, 0f, "seconds"); + + skyControl.setMoonHaloIntensity(intensity, seconds); + SkyCommandResult result = SkyCommandResult.success( + SkyCommandIds.atmosphereSetMoonHaloIntensity, + "moon halo intensity set"); + return result; + } + + /** + * Execute atmosphere set-sun-halo-intensity command. + * + * @param args command arguments + * @return command result + */ + private SkyCommandResult execAtmoSunHalo( + String[] args) { + float intensity = parseFloat(requireArgument(args, 0, + SkyCommandIds.atmosphereSetSunHaloIntensity), + "sun halo intensity"); + float seconds = optionalFloat(args, 1, 0f, "seconds"); + + skyControl.setSunHaloIntensity(intensity, seconds); + SkyCommandResult result = SkyCommandResult.success( + SkyCommandIds.atmosphereSetSunHaloIntensity, + "sun halo intensity set"); + return result; + } + + /** + * Execute atmosphere set-sunset-intensity command. + * + * @param args command arguments + * @return command result + */ + private SkyCommandResult execAtmoSunset( + String[] args) { + float intensity = parseFloat(requireArgument(args, 0, + SkyCommandIds.atmosphereSetSunsetIntensity), + "sunset intensity"); + float seconds = optionalFloat(args, 1, 0f, "seconds"); + + skyControl.setSunsetIntensity(intensity, seconds); + SkyCommandResult result = SkyCommandResult.success( + SkyCommandIds.atmosphereSetSunsetIntensity, + "sunset intensity set"); + return result; + } + + /** + * Execute clock advance command. + * + * @param args command arguments + * @return command result + */ + private SkyCommandResult executeClockAdvance(String[] args) { + float seconds = parseFloat(requireArgument(args, 0, + SkyCommandIds.clockAdvance), "seconds"); + float secondsPerDay = parseFloat(requireArgument(args, 1, + SkyCommandIds.clockAdvance), "seconds per day"); + + skyControl.environment().clock().advance(seconds, secondsPerDay); + SkyCommandResult result = SkyCommandResult.success( + SkyCommandIds.clockAdvance, "clock advanced"); + return result; + } + + /** + * Execute clock set-time command. + * + * @param args command arguments + * @return command result + */ + private SkyCommandResult executeClockSetTime(String[] args) { + float hour = parseFloat(requireArgument(args, 0, + SkyCommandIds.clockSetTime), "hour"); + + skyControl.environment().clock().setTimeOfDay(hour); + SkyCommandResult result = SkyCommandResult.success( + SkyCommandIds.clockSetTime, "clock time set"); + return result; + } + + /** + * Execute config reload command. + * + * @return command result + */ + private SkyCommandResult executeConfigReload() { + reloadConfig(true); + SkyCommandResult result = SkyCommandResult.success( + SkyCommandIds.configReload, "config reloaded"); + return result; + } + + /** + * Execute environment snapshot command. + * + * @return command result + */ + private SkyCommandResult executeEnvSnapshot() { + SkyEnvironmentSnapshot snapshot = skyControl.environment().snapshot(); + SkyCommandResult result = SkyCommandResult.successSnapshot( + SkyCommandIds.environmentSnapshot, "environment snapshot", + snapshot); + return result; + } + + /** + * Execute weather list command. + * + * @return command result + */ + private SkyCommandResult executeWeatherList() { + List ids = new ArrayList(weatherRegistry.ids()); + Collections.sort(ids); + SkyCommandResult result = SkyCommandResult.successValues( + SkyCommandIds.weatherList, "weather ids", ids); + return result; + } + + /** + * Execute weather set command. + * + * @param args command arguments + * @return command result + */ + private SkyCommandResult executeWeatherSet(String[] args) { + String weatherId = requireArgument(args, 0, SkyCommandIds.weatherSet); + SkyCloudPresetDefinition definition = weatherRegistry.require( + weatherId); + float seconds = optionalFloat(args, 1, definition.defaultSeconds(), + "seconds"); + + skyControl.setCloudPreset(definition, seconds); + SkyCommandResult result = SkyCommandResult.success( + SkyCommandIds.weatherSet, "weather set: " + weatherId); + return result; + } + + /** + * Parse an optional float command argument. + * + * @param args command arguments + * @param index argument index + * @param fallback fallback value + * @param label diagnostic label + * @return parsed value or fallback + */ + private static float optionalFloat(String[] args, int index, + float fallback, String label) { + float result = index < args.length + ? parseFloat(args[index], label) : fallback; + return result; + } + + /** + * Parse a gradient style argument. + * + * @param text command argument text + * @return parsed style + */ + private static SkyGradientStyle parseGradientStyle(String text) { + String key = text.trim().toUpperCase(Locale.ROOT); + SkyGradientStyle result = SkyGradientStyle.valueOf(key); + return result; + } + + /** + * Parse a float command argument. + * + * @param text argument text + * @param label diagnostic label + * @return parsed value + */ + private static float parseFloat(String text, String label) { + try { + float result = Float.parseFloat(text); + return result; + } catch (NumberFormatException exception) { + throw new IllegalArgumentException( + "Invalid " + label + ": " + text, exception); + } + } + + /** + * Reload Lua config and weather registry. + * + * @param applyToControl true to apply config to the target control + */ + private void reloadConfig(boolean applyToControl) { + this.config = SkySimulationConfigLoader.load(assetManager, configPath); + if (applyToControl) { + config.applyTo(assetManager, skyControl); + } + this.weatherRegistry = SkyCloudPresetLoader.load( + assetManager, config.integration().weatherPath()); + } + + /** + * Require a positional command argument. + * + * @param args command arguments + * @param index required index + * @param commandId command id + * @return argument value + */ + private static String requireArgument(String[] args, int index, + String commandId) { + if (index >= args.length) { + throw new IllegalArgumentException( + "Missing argument " + index + " for " + commandId); + } + String result = args[index]; + Validate.nonEmpty(result, "argument"); + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/command/SkyCommandIds.java b/SkyLibrary/src/main/java/jme3utilities/sky/command/SkyCommandIds.java new file mode 100644 index 0000000..5b028a4 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/command/SkyCommandIds.java @@ -0,0 +1,66 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.command; + +/** + * Canonical SkySimulation command ABI identifiers. + * + * @author Take Some + */ +final public class SkyCommandIds { + /** Set atmosphere gradient preset. */ + final public static String atmosphereSetGradient + = "sky.atmosphere.setGradient"; + /** Set moon halo intensity. */ + final public static String atmosphereSetMoonHaloIntensity + = "sky.atmosphere.setMoonHaloIntensity"; + /** Set sun halo intensity. */ + final public static String atmosphereSetSunHaloIntensity + = "sky.atmosphere.setSunHaloIntensity"; + /** Set sunset intensity. */ + final public static String atmosphereSetSunsetIntensity + = "sky.atmosphere.setSunsetIntensity"; + /** Lua command ABI resource path. */ + final public static String luaRegistry = "helix/lua/sky/commands.lua"; + /** Reload SkySimulation config from Lua ABI. */ + final public static String configReload = "sky.config.reload"; + /** Advance sky clock by simulation seconds. */ + final public static String clockAdvance = "sky.clock.advance"; + /** Set sky clock time of day. */ + final public static String clockSetTime = "sky.clock.setTime"; + /** Return current environment snapshot. */ + final public static String environmentSnapshot + = "sky.environment.snapshot"; + /** List available weather ids. */ + final public static String weatherList = "sky.weather.list"; + /** Set current weather. */ + final public static String weatherSet = "sky.weather.set"; + + /** Hidden constructor. */ + private SkyCommandIds() { + // do nothing + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/command/SkyCommandResult.java b/SkyLibrary/src/main/java/jme3utilities/sky/command/SkyCommandResult.java new file mode 100644 index 0000000..884bb82 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/command/SkyCommandResult.java @@ -0,0 +1,164 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.command; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import jme3utilities.Validate; +import jme3utilities.sky.runtime.SkyEnvironmentSnapshot; + +/** + * Result of executing a SkySimulation command ABI entry. + * + * @author Take Some + */ +final public class SkyCommandResult { + /** Executed command id. */ + final private String commandId; + /** Human-readable result message. */ + final private String message; + /** Optional environment snapshot. */ + final private SkyEnvironmentSnapshot snapshot; + /** True if command execution succeeded. */ + final private boolean success; + /** Optional string values returned by the command. */ + final private List values; + + /** + * Instantiate a result. + * + * @param commandId command id (not null, not empty) + * @param success true for successful execution + * @param message result message (not null) + * @param values returned values (not null, copied) + * @param snapshot returned snapshot, or null + */ + private SkyCommandResult(String commandId, boolean success, + String message, List values, + SkyEnvironmentSnapshot snapshot) { + Validate.nonEmpty(commandId, "command id"); + Validate.nonNull(message, "message"); + Validate.nonNull(values, "values"); + + this.commandId = commandId; + this.success = success; + this.message = message; + this.values = Collections.unmodifiableList(new ArrayList( + values)); + this.snapshot = snapshot; + } + + /** + * Create a success result. + * + * @param commandId command id + * @param message result message + * @return new result + */ + public static SkyCommandResult success(String commandId, String message) { + SkyCommandResult result = new SkyCommandResult(commandId, true, + message, Collections.emptyList(), null); + return result; + } + + /** + * Create a success result with environment snapshot. + * + * @param commandId command id + * @param message result message + * @param snapshot environment snapshot (not null) + * @return new result + */ + public static SkyCommandResult successSnapshot(String commandId, + String message, SkyEnvironmentSnapshot snapshot) { + Validate.nonNull(snapshot, "snapshot"); + + SkyCommandResult result = new SkyCommandResult(commandId, true, + message, Collections.emptyList(), snapshot); + return result; + } + + /** + * Create a success result with returned values. + * + * @param commandId command id + * @param message result message + * @param values returned values (not null, copied) + * @return new result + */ + public static SkyCommandResult successValues(String commandId, + String message, List values) { + SkyCommandResult result = new SkyCommandResult(commandId, true, + message, values, null); + return result; + } + + /** + * Return command id. + * + * @return command id + */ + public String commandId() { + return commandId; + } + + /** + * Return result message. + * + * @return message + */ + public String message() { + return message; + } + + /** + * Return optional environment snapshot. + * + * @return snapshot, or null + */ + public SkyEnvironmentSnapshot snapshot() { + return snapshot; + } + + /** + * Test whether execution succeeded. + * + * @return true if successful + */ + public boolean succeeded() { + return success; + } + + /** + * Return optional string values. + * + * @return immutable values + */ + public List values() { + return values; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/command/package-info.java b/SkyLibrary/src/main/java/jme3utilities/sky/command/package-info.java new file mode 100644 index 0000000..065814e --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/command/package-info.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Command ABI bridge for controlling SkySimulation from external runtimes. + */ +package jme3utilities.sky.command; diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/config/SkyAtmosphereConfig.java b/SkyLibrary/src/main/java/jme3utilities/sky/config/SkyAtmosphereConfig.java new file mode 100644 index 0000000..8c42748 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/config/SkyAtmosphereConfig.java @@ -0,0 +1,132 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.config; + +import jme3utilities.Validate; +import jme3utilities.sky.SkyAtmosphere; +import jme3utilities.sky.atmosphere.SkyGradientStyle; + +/** + * Atmosphere profile and gradient configuration from Lua ABI. + * + * @author Take Some + */ +final public class SkyAtmosphereConfig { + /** Gradient strength style. */ + final private SkyGradientStyle gradientStyle; + /** Moon halo intensity multiplier. */ + final private float moonHaloIntensity; + /** Atmosphere profile path. */ + final private String profilePath; + /** Sun halo intensity multiplier. */ + final private float sunHaloIntensity; + /** Sunset gradient intensity multiplier. */ + final private float sunsetIntensity; + + /** + * Instantiate atmosphere configuration. + * + * @param profilePath atmosphere profile path (not null, not empty) + * @param gradientStyle gradient style (not null) + * @param sunsetIntensity sunset intensity (≥0) + * @param sunHaloIntensity sun halo intensity (≥0) + * @param moonHaloIntensity moon halo intensity (≥0) + */ + public SkyAtmosphereConfig(String profilePath, + SkyGradientStyle gradientStyle, float sunsetIntensity, + float sunHaloIntensity, float moonHaloIntensity) { + Validate.nonEmpty(profilePath, "profile path"); + Validate.nonNull(gradientStyle, "gradient style"); + Validate.nonNegative(sunsetIntensity, "sunset intensity"); + Validate.nonNegative(sunHaloIntensity, "sun halo intensity"); + Validate.nonNegative(moonHaloIntensity, "moon halo intensity"); + + this.profilePath = profilePath; + this.gradientStyle = gradientStyle; + this.sunsetIntensity = sunsetIntensity; + this.sunHaloIntensity = sunHaloIntensity; + this.moonHaloIntensity = moonHaloIntensity; + } + + /** + * Apply gradient settings to an atmosphere profile. + * + * @param atmosphere target atmosphere (not null) + */ + public void applyTo(SkyAtmosphere atmosphere) { + Validate.nonNull(atmosphere, "atmosphere"); + + atmosphere.setGradientStyle(gradientStyle); + atmosphere.setSunsetIntensity(sunsetIntensity); + atmosphere.setSunHaloIntensity(sunHaloIntensity); + atmosphere.setMoonHaloIntensity(moonHaloIntensity); + } + + /** + * Return gradient style. + * + * @return gradient style + */ + public SkyGradientStyle gradientStyle() { + return gradientStyle; + } + + /** + * Return moon halo intensity. + * + * @return multiplier + */ + public float moonHaloIntensity() { + return moonHaloIntensity; + } + + /** + * Return atmosphere profile path. + * + * @return asset path + */ + public String profilePath() { + return profilePath; + } + + /** + * Return sun halo intensity. + * + * @return multiplier + */ + public float sunHaloIntensity() { + return sunHaloIntensity; + } + + /** + * Return sunset intensity. + * + * @return multiplier + */ + public float sunsetIntensity() { + return sunsetIntensity; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/config/SkyClockConfig.java b/SkyLibrary/src/main/java/jme3utilities/sky/config/SkyClockConfig.java new file mode 100644 index 0000000..c92115f --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/config/SkyClockConfig.java @@ -0,0 +1,120 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.config; + +import jme3utilities.Validate; +import jme3utilities.math.MyMath; +import jme3utilities.sky.Constants; +import jme3utilities.sky.SkyControl; +import jme3utilities.sky.SunAndStars; + +/** + * Initial astronomical clock configuration. + * + * @author Take Some + */ +final public class SkyClockConfig { + /** Time of day in hours. */ + final private float hour; + /** Observer latitude in degrees. */ + final private float latitudeDeg; + /** One-based Gregorian month. */ + final private int solarMonth; + /** Gregorian day of month. */ + final private int solarDay; + + /** + * Instantiate clock configuration. + * + * @param hour time of day in hours (≤24, ≥0) + * @param latitudeDeg observer latitude in degrees (≤90, ≥-90) + * @param solarMonth one-based Gregorian month (≤12, ≥1) + * @param solarDay Gregorian day of month (≤31, ≥1) + */ + public SkyClockConfig(float hour, float latitudeDeg, + int solarMonth, int solarDay) { + Validate.inRange(hour, "hour", 0f, Constants.hoursPerDay); + Validate.inRange(latitudeDeg, "latitude", -90f, 90f); + Validate.inRange(solarMonth, "month", 1, 12); + Validate.inRange(solarDay, "day", 1, 31); + + this.hour = hour; + this.latitudeDeg = latitudeDeg; + this.solarMonth = solarMonth; + this.solarDay = solarDay; + } + + /** + * Apply this clock configuration to a sky control. + * + * @param skyControl target control (not null) + */ + public void applyTo(SkyControl skyControl) { + Validate.nonNull(skyControl, "control"); + + SunAndStars sunAndStars = skyControl.getSunAndStars(); + sunAndStars.setHour(hour); + sunAndStars.setObserverLatitude(MyMath.toRadians(latitudeDeg)); + sunAndStars.setSolarLongitude(solarMonth - 1, solarDay); + skyControl.environment().clock().setTimeOfDay(hour); + } + + /** + * Return time of day. + * + * @return hour of day + */ + public float hour() { + return hour; + } + + /** + * Return observer latitude in degrees. + * + * @return latitude in degrees + */ + public float latitudeDeg() { + return latitudeDeg; + } + + /** + * Return one-based Gregorian month. + * + * @return month + */ + public int solarMonth() { + return solarMonth; + } + + /** + * Return Gregorian day of month. + * + * @return day + */ + public int solarDay() { + return solarDay; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/config/SkyIntegrationConfig.java b/SkyLibrary/src/main/java/jme3utilities/sky/config/SkyIntegrationConfig.java new file mode 100644 index 0000000..af303f7 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/config/SkyIntegrationConfig.java @@ -0,0 +1,115 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.config; + +import jme3utilities.Validate; + +/** + * Integration defaults for atmosphere and weather bootstrapping. + * + * @author Take Some + */ +final public class SkyIntegrationConfig { + /** Atmosphere property profile path. */ + final private String atmospherePath; + /** True if clouds modulate the main light. */ + final private boolean cloudModFlag; + /** Initial weather id. */ + final private String initialWeatherId; + /** Weather transition duration. */ + final private float transitionSec; + /** Lua weather registry path. */ + final private String weatherPath; + + /** + * Instantiate integration configuration. + * + * @param atmospherePath atmosphere property path (not null, not empty) + * @param weatherPath Lua weather registry path (not null, not empty) + * @param initialWeatherId initial weather id (not null, not empty) + * @param transitionSec transition duration in seconds (≥0) + * @param cloudModFlag true if clouds modulate main light + */ + public SkyIntegrationConfig(String atmospherePath, String weatherPath, + String initialWeatherId, float transitionSec, + boolean cloudModFlag) { + Validate.nonEmpty(atmospherePath, "atmosphere path"); + Validate.nonEmpty(weatherPath, "weather path"); + Validate.nonEmpty(initialWeatherId, "weather id"); + Validate.nonNegative(transitionSec, "seconds"); + + this.atmospherePath = atmospherePath; + this.weatherPath = weatherPath; + this.initialWeatherId = initialWeatherId; + this.transitionSec = transitionSec; + this.cloudModFlag = cloudModFlag; + } + + /** + * Return atmosphere property path. + * + * @return asset path + */ + public String atmospherePath() { + return atmospherePath; + } + + /** + * Test whether clouds modulate the main light. + * + * @return true if enabled + */ + public boolean cloudModulation() { + return cloudModFlag; + } + + /** + * Return initial weather id. + * + * @return weather id + */ + public String initialWeatherId() { + return initialWeatherId; + } + + /** + * Return transition duration. + * + * @return duration in seconds + */ + public float transitionSec() { + return transitionSec; + } + + /** + * Return weather registry path. + * + * @return asset path + */ + public String weatherPath() { + return weatherPath; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/config/SkyRenderConfig.java b/SkyLibrary/src/main/java/jme3utilities/sky/config/SkyRenderConfig.java new file mode 100644 index 0000000..6ed7948 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/config/SkyRenderConfig.java @@ -0,0 +1,117 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.config; + +import jme3utilities.Validate; +import jme3utilities.sky.SkyControl; +import jme3utilities.sky.StarsOption; + +/** + * Rendering configuration for constructing a sky control. + * + * @author Take Some + */ +final public class SkyRenderConfig { + /** True to create the lower sky dome. */ + final private boolean lowerDomeFlag; + /** Flattening value for the clouds-only dome. */ + final private float cloudFlattening; + /** Vertical offset for the clouds-only dome. */ + final private float cloudsYOffset; + /** Star rendering mode. */ + final private StarsOption starsOption; + + /** + * Instantiate render configuration. + * + * @param starsOption star rendering mode (not null) + * @param cloudFlattening cloud flattening (<1, ≥0) + * @param cloudsYOffset cloud dome Y offset (<1, ≥0) + * @param lowerDomeFlag true to create the lower sky dome + */ + public SkyRenderConfig(StarsOption starsOption, float cloudFlattening, + float cloudsYOffset, boolean lowerDomeFlag) { + Validate.nonNull(starsOption, "stars option"); + if (!(cloudFlattening >= 0f && cloudFlattening < 1f)) { + throw new IllegalArgumentException("invalid cloud flattening"); + } + if (!(cloudsYOffset >= 0f && cloudsYOffset < 1f)) { + throw new IllegalArgumentException("invalid cloud offset"); + } + + this.starsOption = starsOption; + this.cloudFlattening = cloudFlattening; + this.cloudsYOffset = cloudsYOffset; + this.lowerDomeFlag = lowerDomeFlag; + } + + /** + * Apply post-construction render settings. + * + * @param skyControl target control (not null) + */ + public void applyTo(SkyControl skyControl) { + Validate.nonNull(skyControl, "control"); + + skyControl.setCloudsYOffset(cloudsYOffset); + } + + /** + * Test whether the lower sky dome should be created. + * + * @return true if enabled + */ + public boolean lowerDome() { + return lowerDomeFlag; + } + + /** + * Return cloud flattening. + * + * @return flattening value + */ + public float cloudFlattening() { + return cloudFlattening; + } + + /** + * Return clouds Y offset. + * + * @return offset value + */ + public float cloudsYOffset() { + return cloudsYOffset; + } + + /** + * Return star rendering mode. + * + * @return star rendering mode + */ + public StarsOption starsOption() { + return starsOption; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/config/SkySimulationConfig.java b/SkyLibrary/src/main/java/jme3utilities/sky/config/SkySimulationConfig.java new file mode 100644 index 0000000..565de43 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/config/SkySimulationConfig.java @@ -0,0 +1,202 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.config; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetManager; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.renderer.Camera; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import jme3utilities.Validate; +import jme3utilities.sky.SkyAtmosphere; +import jme3utilities.sky.SkyControl; +import jme3utilities.sky.cloud.SkyCloudPresetDefinition; +import jme3utilities.sky.cloud.SkyCloudPresetLoader; +import jme3utilities.sky.cloud.SkyCloudPresetRegistry; + +/** + * Typed SkySimulation configuration loaded from Lua ABI. + * + * @author Take Some + */ +final public class SkySimulationConfig { + /** Atmosphere configuration. */ + final private SkyAtmosphereConfig atmosphereConfig; + /** Clock configuration. */ + final private SkyClockConfig clockConfig; + /** Integration configuration. */ + final private SkyIntegrationConfig integrationConfig; + /** Rendering configuration. */ + final private SkyRenderConfig renderConfig; + + /** + * Instantiate sky simulation configuration. + * + * @param clockConfig clock configuration (not null) + * @param renderConfig render configuration (not null) + * @param integrationConfig integration configuration (not null) + */ + public SkySimulationConfig(SkyClockConfig clockConfig, + SkyRenderConfig renderConfig, + SkyIntegrationConfig integrationConfig) { + this(new SkyAtmosphereConfig(integrationConfig.atmospherePath(), + jme3utilities.sky.atmosphere.SkyGradientStyle.CINEMATIC, + 1f, 1f, 1f), clockConfig, renderConfig, integrationConfig); + } + + /** + * Instantiate sky simulation configuration. + * + * @param atmosphereConfig atmosphere configuration (not null) + * @param clockConfig clock configuration (not null) + * @param renderConfig render configuration (not null) + * @param integrationConfig integration configuration (not null) + */ + public SkySimulationConfig(SkyAtmosphereConfig atmosphereConfig, + SkyClockConfig clockConfig, SkyRenderConfig renderConfig, + SkyIntegrationConfig integrationConfig) { + Validate.nonNull(atmosphereConfig, "atmosphere config"); + Validate.nonNull(clockConfig, "clock config"); + Validate.nonNull(renderConfig, "render config"); + Validate.nonNull(integrationConfig, "integration config"); + + this.atmosphereConfig = atmosphereConfig; + this.clockConfig = clockConfig; + this.renderConfig = renderConfig; + this.integrationConfig = integrationConfig; + } + + /** + * Apply this configuration to an existing sky control. + * + * @param assetManager asset manager (not null) + * @param skyControl target control (not null) + */ + public void applyTo(AssetManager assetManager, SkyControl skyControl) { + Validate.nonNull(assetManager, "asset manager"); + Validate.nonNull(skyControl, "control"); + + SkyAtmosphere atmosphere = loadAtmosphere(assetManager, + atmosphereConfig.profilePath()); + atmosphereConfig.applyTo(atmosphere); + skyControl.setAtmosphere(atmosphere); + clockConfig.applyTo(skyControl); + renderConfig.applyTo(skyControl); + skyControl.setCloudModulation(integrationConfig.cloudModulation()); + + SkyCloudPresetRegistry registry = SkyCloudPresetLoader.load( + assetManager, integrationConfig.weatherPath()); + SkyCloudPresetDefinition definition = registry.require( + integrationConfig.initialWeatherId()); + skyControl.setCloudPreset( + definition, integrationConfig.transitionSec()); + } + + /** + * Return atmosphere configuration. + * + * @return atmosphere configuration + */ + public SkyAtmosphereConfig atmosphere() { + return atmosphereConfig; + } + + /** + * Return clock configuration. + * + * @return clock configuration + */ + public SkyClockConfig clock() { + return clockConfig; + } + + /** + * Create and configure a sky control. + * + * @param assetManager asset manager (not null) + * @param camera camera to track (not null) + * @return configured control + */ + public SkyControl createControl(AssetManager assetManager, Camera camera) { + Validate.nonNull(assetManager, "asset manager"); + Validate.nonNull(camera, "camera"); + + SkyControl result = new SkyControl(assetManager, camera, + renderConfig.cloudFlattening(), renderConfig.starsOption(), + renderConfig.lowerDome()); + applyTo(assetManager, result); + return result; + } + + /** + * Return integration configuration. + * + * @return integration configuration + */ + public SkyIntegrationConfig integration() { + return integrationConfig; + } + + /** + * Return render configuration. + * + * @return render configuration + */ + public SkyRenderConfig rendering() { + return renderConfig; + } + + /** + * Load an atmosphere profile from Java properties. + * + * @param assetManager asset manager + * @param assetPath properties asset path + * @return loaded profile + */ + private static SkyAtmosphere loadAtmosphere(AssetManager assetManager, + String assetPath) { + AssetKey key = new AssetKey(assetPath); + AssetInfo info = assetManager.locateAsset(key); + if (info == null) { + throw new AssetNotFoundException(assetPath); + } + + Properties properties = new Properties(); + try (InputStream input = info.openStream()) { + properties.load(input); + } catch (IOException exception) { + throw new IllegalArgumentException( + "Failed to read atmosphere profile: " + assetPath, + exception); + } + SkyAtmosphere result = new SkyAtmosphere(); + result.apply(properties); + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/config/SkySimulationConfigLoader.java b/SkyLibrary/src/main/java/jme3utilities/sky/config/SkySimulationConfigLoader.java new file mode 100644 index 0000000..f8c79af --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/config/SkySimulationConfigLoader.java @@ -0,0 +1,336 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.config; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetManager; +import com.jme3.asset.AssetNotFoundException; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import jme3utilities.Validate; +import jme3utilities.sky.StarsOption; +import jme3utilities.sky.atmosphere.SkyGradientStyle; +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaError; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.jse.JsePlatform; + +/** + * Loader for Lua SkySimulation configuration ABI. + * + * @author Take Some + */ +final public class SkySimulationConfigLoader { + /** Default Lua config ABI resource. */ + final public static String defaultConfig + = "helix/lua/sky/default-sky.lua"; + + /** Hidden constructor. */ + private SkySimulationConfigLoader() { + // do nothing + } + + /** + * Load a Lua sky simulation configuration. + * + * @param assetManager asset manager (not null) + * @param assetPath Lua asset path (not null, not empty) + * @return loaded configuration + */ + public static SkySimulationConfig load(AssetManager assetManager, + String assetPath) { + Validate.nonNull(assetManager, "asset manager"); + Validate.nonEmpty(assetPath, "asset path"); + + String source = readText(assetManager, assetPath); + Globals globals = JsePlatform.standardGlobals(); + LuaValue root; + try { + root = globals.load(source, assetPath).call(); + } catch (LuaError exception) { + throw new IllegalArgumentException( + "Invalid sky config Lua ABI: " + assetPath, exception); + } + SkySimulationConfig result = parseRoot(root, assetPath); + return result; + } + + /** + * Load the default bundled Lua config ABI. + * + * @param assetManager asset manager (not null) + * @return loaded configuration + */ + public static SkySimulationConfig loadDefault(AssetManager assetManager) { + SkySimulationConfig result = load(assetManager, defaultConfig); + return result; + } + + /** + * Parse atmosphere section. + * + * @param value Lua atmosphere table + * @return parsed atmosphere config + */ + private static SkyAtmosphereConfig parseAtmosphere(LuaValue value) { + requireTable(value, "atmosphere"); + + SkyAtmosphereConfig result = new SkyAtmosphereConfig( + requiredString(value, "profile"), + optionalStyle(value, "gradientStyle", + SkyGradientStyle.CINEMATIC), + optionalFloat(value, "sunsetIntensity", 1f), + optionalFloat(value, "sunHaloIntensity", 1f), + optionalFloat(value, "moonHaloIntensity", 1f)); + return result; + } + + /** + * Parse clock section. + * + * @param value Lua clock table + * @return parsed clock config + */ + private static SkyClockConfig parseClock(LuaValue value) { + requireTable(value, "clock"); + + SkyClockConfig result = new SkyClockConfig( + requiredFloat(value, "hour"), + requiredFloat(value, "observerLatitudeDegrees"), + requiredInt(value, "solarMonth"), + requiredInt(value, "solarDay")); + return result; + } + + /** + * Parse integration and weather sections. + * + * @param root root Lua table + * @return parsed integration config + */ + private static SkyIntegrationConfig parseIntegration(LuaValue root) { + LuaValue atmosphere = root.get("atmosphere"); + LuaValue weather = root.get("weather"); + LuaValue integration = root.get("integration"); + requireTable(atmosphere, "atmosphere"); + requireTable(weather, "weather"); + requireTable(integration, "integration"); + + SkyIntegrationConfig result = new SkyIntegrationConfig( + requiredString(atmosphere, "profile"), + requiredString(weather, "registry"), + requiredString(weather, "initial"), + requiredFloat(weather, "transitionSeconds"), + requiredBoolean(integration, "cloudModulation")); + return result; + } + + /** + * Parse render section. + * + * @param value Lua render table + * @return parsed render config + */ + private static SkyRenderConfig parseRender(LuaValue value) { + requireTable(value, "rendering"); + + StarsOption starsOption = StarsOption.valueOf( + requiredString(value, "stars")); + SkyRenderConfig result = new SkyRenderConfig(starsOption, + requiredFloat(value, "cloudFlattening"), + requiredFloat(value, "cloudsYOffset"), + requiredBoolean(value, "lowerDome")); + return result; + } + + /** + * Parse a root Lua ABI table. + * + * @param root root table + * @param assetPath asset path for diagnostics + * @return parsed config + */ + private static SkySimulationConfig parseRoot( + LuaValue root, String assetPath) { + requireTable(root, assetPath); + + SkyAtmosphereConfig atmosphere = parseAtmosphere( + root.get("atmosphere")); + SkyClockConfig clock = parseClock(root.get("clock")); + SkyRenderConfig render = parseRender(root.get("rendering")); + SkyIntegrationConfig integration = parseIntegration(root); + SkySimulationConfig result = new SkySimulationConfig( + atmosphere, clock, render, integration); + return result; + } + + /** + * Read a text asset. + * + * @param assetManager asset manager + * @param assetPath asset path + * @return asset contents + */ + private static String readText(AssetManager assetManager, + String assetPath) { + AssetKey key = new AssetKey(assetPath); + AssetInfo info = assetManager.locateAsset(key); + if (info == null) { + throw new AssetNotFoundException(assetPath); + } + + StringBuilder builder = new StringBuilder(); + try (InputStream input = info.openStream(); + InputStreamReader reader = new InputStreamReader( + input, StandardCharsets.UTF_8); + BufferedReader buffered = new BufferedReader(reader)) { + String line; + while ((line = buffered.readLine()) != null) { + builder.append(line).append(System.lineSeparator()); + } + } catch (IOException exception) { + throw new IllegalArgumentException( + "Failed to read sky config Lua ABI: " + assetPath, + exception); + } + String result = builder.toString(); + return result; + } + + /** + * Read an optional float field. + * + * @param table Lua table + * @param name field name + * @param fallback fallback value + * @return parsed or fallback value + */ + private static float optionalFloat(LuaValue table, String name, + float fallback) { + LuaValue value = table.get(name); + float result = value.isnil() + ? fallback : (float) value.checkdouble(); + return result; + } + + /** + * Read an optional gradient style field. + * + * @param table Lua table + * @param name field name + * @param fallback fallback style + * @return parsed or fallback style + */ + private static SkyGradientStyle optionalStyle(LuaValue table, String name, + SkyGradientStyle fallback) { + LuaValue value = table.get(name); + SkyGradientStyle result = value.isnil() + ? fallback : SkyGradientStyle.valueOf(value.checkjstring()); + return result; + } + + /** + * Read a required boolean field. + * + * @param table Lua table + * @param name field name + * @return parsed value + */ + private static boolean requiredBoolean(LuaValue table, String name) { + LuaValue value = table.get(name); + if (value.isnil()) { + throw new IllegalArgumentException( + "Missing boolean field: " + name); + } + boolean result = value.checkboolean(); + return result; + } + + /** + * Read a required float field. + * + * @param table Lua table + * @param name field name + * @return parsed value + */ + private static float requiredFloat(LuaValue table, String name) { + LuaValue value = table.get(name); + if (value.isnil()) { + throw new IllegalArgumentException( + "Missing float field: " + name); + } + float result = (float) value.checkdouble(); + return result; + } + + /** + * Read a required integer field. + * + * @param table Lua table + * @param name field name + * @return parsed value + */ + private static int requiredInt(LuaValue table, String name) { + LuaValue value = table.get(name); + if (value.isnil()) { + throw new IllegalArgumentException("Missing int field: " + name); + } + int result = value.checkint(); + return result; + } + + /** + * Read a required string field. + * + * @param table Lua table + * @param name field name + * @return parsed value + */ + private static String requiredString(LuaValue table, String name) { + LuaValue value = table.get(name); + if (value.isnil()) { + throw new IllegalArgumentException("Missing string field: " + name); + } + String result = value.checkjstring(); + return result; + } + + /** + * Require a Lua table value. + * + * @param value Lua value + * @param label diagnostic label + */ + private static void requireTable(LuaValue value, String label) { + if (value == null || !value.istable()) { + throw new IllegalArgumentException("Expected table: " + label); + } + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/config/package-info.java b/SkyLibrary/src/main/java/jme3utilities/sky/config/package-info.java new file mode 100644 index 0000000..7cc9006 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/config/package-info.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Lua-backed configuration objects for SkySimulation. + */ +package jme3utilities.sky.config; diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyBaseColorRuntime.java b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyBaseColorRuntime.java new file mode 100644 index 0000000..7cb3ba5 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyBaseColorRuntime.java @@ -0,0 +1,241 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.control; + +import com.jme3.math.ColorRGBA; +import jme3utilities.math.MyColor; +import jme3utilities.sky.SkyAtmosphere; +import jme3utilities.sky.atmosphere.SkyLightingModel; + +/** + * Runtime helper for sky base-color computation. + * + * @author Take Some + */ +final public class SkyBaseColorRuntime { + /** + * Hidden constructor. + */ + private SkyBaseColorRuntime() { + // do nothing + } + + /** + * Compute base and source colors for the current sky lighting state. + * + * @param atmosphere lighting profile (not null) + * @param sineSolarAltitude sine of the solar altitude + * @param moonUp true if moon is above horizon + * @param moonWeight contribution of moonlight to nighttime lighting + * @return new immutable result + */ + public static Result compute(SkyAtmosphere atmosphere, + float sineSolarAltitude, boolean moonUp, float moonWeight) { + assert atmosphere != null; + assert moonWeight >= 0f : moonWeight; + assert moonWeight <= 1f : moonWeight; + + final ColorRGBA twilightColor = SkyLightingModel.horizonColor( + atmosphere, sineSolarAltitude); + final ColorRGBA sunColor = SkyLightingModel.daylightColor( + atmosphere, sineSolarAltitude); + final ColorRGBA moonColor = atmosphere.copyMoonLight(null); + final ColorRGBA starColor = atmosphere.copyStarLight(null); + Input input = new Input(); + input.atmosphere = atmosphere; + input.sineSolarAltitude = sineSolarAltitude; + input.moonUp = moonUp; + input.moonWeight = moonWeight; + input.twilightColor = twilightColor; + input.sunColor = sunColor; + input.moonColor = moonColor; + input.starColor = starColor; + ColorRGBA baseColor = baseColor(input); + + Result result = new Result( + baseColor, sunColor, moonColor, starColor, twilightColor); + return result; + } + + /** + * Compute the base color. + * + * @param input color-computation input (not null) + * @return new color + */ + private static ColorRGBA baseColor(Input input) { + assert input != null; + + ColorRGBA result; + boolean sunUp = input.sineSolarAltitude >= 0f; + if (sunUp) { + float dayWeight = SkyLightingModel.smoothStep( + input.sineSolarAltitude + / input.atmosphere.getFullDayAltitude()); + result = MyColor.interpolateLinear( + dayWeight, input.twilightColor, input.sunColor); + float hazeWeight = input.atmosphere.getHazeStrength() + * (1f - dayWeight) * 0.5f; + result = MyColor.interpolateLinear( + hazeWeight, result, input.twilightColor); + + } else { + ColorRGBA blend; + if (input.moonUp && input.moonWeight > 0f) { + blend = MyColor.interpolateLinear( + input.moonWeight, input.starColor, input.moonColor); + } else { + blend = input.starColor; + } + float nightWeight = SkyLightingModel.smoothStep( + -input.sineSolarAltitude + / input.atmosphere.getTwilightLimit()); + result = MyColor.interpolateLinear( + nightWeight, input.twilightColor, blend); + } + + return result; + } + + /** + * Color-computation input. + */ + final private static class Input { + /** Lighting profile. */ + private SkyAtmosphere atmosphere; + /** True if the moon is above the horizon. */ + private boolean moonUp; + /** Moonlight contribution to nighttime lighting. */ + private float moonWeight; + /** Moonlight source color. */ + private ColorRGBA moonColor; + /** Sine of the solar altitude. */ + private float sineSolarAltitude; + /** Starlight source color. */ + private ColorRGBA starColor; + /** Daylight source color. */ + private ColorRGBA sunColor; + /** Twilight source color. */ + private ColorRGBA twilightColor; + + } + + /** + * Base-color result and its source colors. + */ + final public static class Result { + /** + * Base sky/haze/background color. + */ + final private ColorRGBA baseColor; + /** + * Moonlight source color. + */ + private ColorRGBA moonColor; + /** + * Starlight source color. + */ + private ColorRGBA starColor; + /** + * Daylight source color. + */ + private ColorRGBA sunColor; + /** + * Twilight source color. + */ + private ColorRGBA twilightColor; + + /** + * Instantiate a result. + * + * @param baseColor base color (not null, alias created) + * @param sunColor sun color (not null, alias created) + * @param moonColor moon color (not null, alias created) + * @param starColor star color (not null, alias created) + * @param twilightColor twilight color (not null, alias created) + */ + private Result(ColorRGBA baseColor, ColorRGBA sunColor, + ColorRGBA moonColor, ColorRGBA starColor, + ColorRGBA twilightColor) { + assert baseColor != null; + assert sunColor != null; + assert moonColor != null; + assert starColor != null; + assert twilightColor != null; + + this.baseColor = baseColor; + this.sunColor = sunColor; + this.moonColor = moonColor; + this.starColor = starColor; + this.twilightColor = twilightColor; + } + + /** + * Return the base sky/haze/background color. + * + * @return pre-existing instance + */ + public ColorRGBA baseColor() { + return baseColor; + } + + /** + * Return the moonlight color. + * + * @return pre-existing instance + */ + public ColorRGBA moonColor() { + return moonColor; + } + + /** + * Return the starlight color. + * + * @return pre-existing instance + */ + public ColorRGBA starColor() { + return starColor; + } + + /** + * Return the daylight color. + * + * @return pre-existing instance + */ + public ColorRGBA sunColor() { + return sunColor; + } + + /** + * Return the twilight color. + * + * @return pre-existing instance + */ + public ColorRGBA twilightColor() { + return twilightColor; + } + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyCelestialState.java b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyCelestialState.java new file mode 100644 index 0000000..968ce7f --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyCelestialState.java @@ -0,0 +1,174 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.control; + +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import java.io.IOException; + +/** + * Runtime state for lunar latitude and phase angle. + * + * @author Take Some + */ +public final class SkyCelestialState { + // ************************************************************************* + // fields + + /** + * Difference in celestial longitude between the moon and the sun. + */ + private float longitudeDifference = FastMath.PI; + /** + * Moon celestial latitude, measured north from the ecliptic. + */ + private float lunarLatitude = 0f; + // ************************************************************************* + // constructors + + /** + * Instantiate default celestial runtime state. + */ + public SkyCelestialState() { + // do nothing + } + + /** + * Instantiate a copy of the specified state. + * + * @param source state to copy (not null, unaffected) + */ + private SkyCelestialState(SkyCelestialState source) { + assert source != null; + + this.longitudeDifference = source.longitudeDifference; + this.lunarLatitude = source.lunarLatitude; + } + // ************************************************************************* + // new methods exposed + + /** + * Create a copy of this state. + * + * @return a new instance + */ + public SkyCelestialState copy() { + SkyCelestialState result = new SkyCelestialState(this); + return result; + } + + /** + * Compute the contribution of the moon to nighttime illumination. + * + * @return fraction (≤1, ≥0) + */ + public float moonIllumination() { + float fullAngle = FastMath.abs(longitudeDifference - FastMath.PI); + if (lunarLatitude != 0f) { + float cos = FastMath.cos(fullAngle) * FastMath.cos(lunarLatitude); + fullAngle = FastMath.acos(cos); + } + assert fullAngle >= 0f : fullAngle; + assert fullAngle <= FastMath.PI : fullAngle; + + float result = 1f - FastMath.saturate(fullAngle * 0.6f); + + assert result >= 0f : result; + assert result <= 1f : result; + return result; + } + + /** + * Read celestial state from an input capsule. + * + * @param capsule input capsule (not null) + * @return a new instance + * @throws IOException from capsule + */ + public static SkyCelestialState read(InputCapsule capsule) + throws IOException { + assert capsule != null; + + SkyCelestialState result = new SkyCelestialState(); + result.lunarLatitude = capsule.readFloat("lunarLatitude", 0f); + result.longitudeDifference + = capsule.readFloat("phaseAngle", FastMath.PI); + + return result; + } + + /** + * Return the lunar phase angle relative to the sun. + * + * @return radians east of the sun + */ + public float longitudeDifference() { + return longitudeDifference; + } + + /** + * Return the lunar latitude. + * + * @return radians north of the ecliptic + */ + public float lunarLatitude() { + return lunarLatitude; + } + + /** + * Alter the difference in celestial longitude. + * + * @param longitudeDifference radians east of the sun + */ + public void setLongitudeDifference(float longitudeDifference) { + this.longitudeDifference = longitudeDifference; + } + + /** + * Alter the lunar latitude and phase angle. + * + * @param longitudeDifference radians east of the sun + * @param lunarLatitude radians north of the ecliptic + */ + public void setPhase(float longitudeDifference, float lunarLatitude) { + this.longitudeDifference = longitudeDifference; + this.lunarLatitude = lunarLatitude; + } + + /** + * Serialize celestial state to an output capsule. + * + * @param capsule output capsule (not null) + * @throws IOException from capsule + */ + public void write(OutputCapsule capsule) throws IOException { + assert capsule != null; + + capsule.write(lunarLatitude, "lunarLatitude", 0f); + capsule.write(longitudeDifference, "phaseAngle", FastMath.PI); + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyCloudRuntime.java b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyCloudRuntime.java new file mode 100644 index 0000000..6e90b4a --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyCloudRuntime.java @@ -0,0 +1,223 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.control; + +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.ColorRGBA; +import com.jme3.util.clone.Cloner; +import java.io.IOException; +import jme3utilities.Validate; +import jme3utilities.sky.CloudLayer; +import jme3utilities.sky.cloud.SkyCloudPreset; +import jme3utilities.sky.cloud.SkyCloudPresetDefinition; +import jme3utilities.sky.cloud.SkyCloudTransitionRuntime; + +/** + * Runtime state and update logic for sky cloud layers. + * + * @author Take Some + */ +public final class SkyCloudRuntime { + // ************************************************************************* + // fields + + /** + * Cloud-layer runtime objects. + */ + private CloudLayer[] layers; + /** + * Simulation time for cloud-layer animations. + */ + private float animationTime; + /** + * Rate of motion for cloud-layer animations. + */ + private float rate = 1f; + /** + * Runtime state for weather-preset transitions. + */ + private SkyCloudTransitionRuntime transitionRuntime; + // ************************************************************************* + // constructors + + /** + * Instantiate runtime state around cloud layers. + * + * @param layers cloud-layer array (not null, aliased) + */ + public SkyCloudRuntime(CloudLayer[] layers) { + Validate.nonNull(layers, "layers"); + + this.layers = layers; + this.animationTime = 0f; + this.transitionRuntime = new SkyCloudTransitionRuntime(layers); + } + // ************************************************************************* + // new methods exposed + + /** + * Clone mutable fields using the specified cloner. + * + * @param cloner the cloner currently cloning the owner (not null) + */ + public void cloneFields(Cloner cloner) { + assert cloner != null; + this.layers = cloner.clone(layers); + this.transitionRuntime = new SkyCloudTransitionRuntime(layers); + } + + /** + * Access the indexed cloud layer. + * + * @param layerIndex cloud layer index + * @return pre-existing layer + */ + public CloudLayer getLayer(int layerIndex) { + CloudLayer result = layers[layerIndex]; + + assert result != null; + return result; + } + + /** + * Return the speed and direction of cloud motion. + * + * @return multiple of the default rate + */ + public float rate() { + return rate; + } + + /** + * Read runtime state from an input capsule. + * + * @param capsule input capsule (not null) + * @return a new instance + * @throws IOException from capsule + */ + public static SkyCloudRuntime read(InputCapsule capsule) + throws IOException { + assert capsule != null; + + Savable[] sav = capsule.readSavableArray("cloudLayers", null); + CloudLayer[] layers = new CloudLayer[sav.length]; + System.arraycopy(sav, 0, layers, 0, sav.length); + + SkyCloudRuntime result = new SkyCloudRuntime(layers); + result.animationTime = capsule.readFloat("cloudsAnimationTime", 0f); + result.rate = capsule.readFloat("cloudsRelativeSpeed", 1f); + + return result; + } + + /** + * Alter the opacity of all cloud layers. + * + * @param alpha desired opacity of the cloud layers + */ + public void setCloudiness(float alpha) { + Validate.fraction(alpha, "alpha"); + + transitionRuntime.cancel(); + for (CloudLayer layer : layers) { + layer.setOpacity(alpha); + } + } + + /** + * Apply a color to all cloud layers. + * + * @param color desired color (not null, unaffected) + */ + public void setColor(ColorRGBA color) { + assert color != null; + + for (CloudLayer layer : layers) { + layer.setColor(color); + } + } + + /** + * Alter the speed and direction of cloud motion. + * + * @param newRate multiple of the default rate + */ + public void setRate(float newRate) { + this.rate = newRate; + } + + /** + * Transition to a cloud weather preset. + * + * @param preset target preset (not null) + * @param seconds transition duration in seconds (≥0) + */ + public void transitionTo(SkyCloudPreset preset, float seconds) { + transitionRuntime.transitionTo(preset, seconds); + } + + /** + * Transition to a data-driven cloud weather preset. + * + * @param definition target preset definition (not null) + * @param seconds transition duration in seconds (≥0) + */ + public void transitionTo(SkyCloudPresetDefinition definition, + float seconds) { + transitionRuntime.transitionTo(definition, seconds); + } + + /** + * Update the cloud layers. + * + * @param updateInterval time interval between updates (in seconds, ≥0) + */ + public void update(float updateInterval) { + assert updateInterval >= 0f : updateInterval; + + transitionRuntime.update(updateInterval); + this.animationTime += updateInterval * rate; + for (CloudLayer layer : layers) { + layer.updateOffset(animationTime); + } + } + + /** + * Serialize runtime state to an output capsule. + * + * @param capsule output capsule (not null) + * @throws IOException from capsule + */ + public void write(OutputCapsule capsule) throws IOException { + assert capsule != null; + + capsule.write(layers, "cloudLayers", null); + capsule.write(animationTime, "cloudsAnimationTime", 0f); + capsule.write(rate, "cloudsRelativeSpeed", 1f); + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyCloudTransmissionRuntime.java b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyCloudTransmissionRuntime.java new file mode 100644 index 0000000..6b36872 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyCloudTransmissionRuntime.java @@ -0,0 +1,211 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.control; + +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import jme3utilities.math.MyMath; +import jme3utilities.mesh.DomeMesh; +import jme3utilities.sky.SkyMaterial; + +/** + * Runtime helper for cloud transmission sampling. + * + * @author Take Some + */ +final public class SkyCloudTransmissionRuntime { + /** + * Hidden constructor. + */ + private SkyCloudTransmissionRuntime() { + // do nothing + } + + /** + * Determine what fraction of the main light passes through clouds. + * + * @param input lighting state (not null) + * @param resources cloud resources (not null) + * @return transmission factor + */ + public static float transmission(Input input, Resources resources) { + assert input != null; + assert resources != null; + + if (!input.modulationApplies()) { + return 1f; + } + + Vector3f intersection = intersectCloudDome( + input.mainDirection, resources.cloudsOnlyDome); + Vector2f texCoord = resources.cloudsMesh.directionUV(intersection); + float result = resources.cloudsMaterial.getTransmission(texCoord); + return result; + } + + /** + * Compute where a direction intersects the local cloud dome. + * + * @param mainDirection unit vector with non-negative y-component + * @param cloudsOnlyDome optional clouds-only dome geometry + * @return new unit vector + */ + private static Vector3f intersectCloudDome( + Vector3f mainDirection, Geometry cloudsOnlyDome) { + assert mainDirection != null; + assert mainDirection.isUnitVector() : mainDirection; + assert mainDirection.y >= 0f : mainDirection; + + double cosSquared + = MyMath.sumOfSquares(mainDirection.x, mainDirection.z); + if (cosSquared == 0.0) { + return new Vector3f(0f, 1f, 0f); + } + + float deltaY; + float semiMinorAxis; + if (cloudsOnlyDome == null) { + deltaY = 0f; + semiMinorAxis = 1f; + } else { + Vector3f offset = cloudsOnlyDome.getLocalTranslation(); + assert offset.x == 0f : offset; + assert offset.y <= 0f : offset; + assert offset.z == 0f : offset; + deltaY = offset.y; + + Vector3f scale = cloudsOnlyDome.getLocalScale(); + assert scale.x == 1f : scale; + assert scale.y > 0f : scale; + assert scale.z == 1f : scale; + semiMinorAxis = scale.y; + } + + double cosAltitude = Math.sqrt(cosSquared); + double tanAltitude = mainDirection.y / cosAltitude; + double smaSquared = semiMinorAxis * semiMinorAxis; + double a = tanAltitude * tanAltitude + smaSquared; + assert a > 0.0 : a; + double b = -2.0 * deltaY * tanAltitude; + double c = deltaY * deltaY - smaSquared; + double discriminant = MyMath.discriminant(a, b, c); + assert discriminant >= 0.0 : discriminant; + double w = (-b + Math.sqrt(discriminant)) / (2.0 * a); + + double distance = w / cosAltitude; + if (distance > 1.0) { + distance = 1.0; + } + float x = (float) (mainDirection.x * distance); + float y = (float) MyMath.circle(w); + float z = (float) (mainDirection.z * distance); + Vector3f result = new Vector3f(x, y, z); + + assert result.isUnitVector() : result; + return result; + } + + /** + * Lighting state for cloud transmission. + */ + final public static class Input { + /** True if cloud modulation is enabled. */ + final private boolean cloudModulationFlag; + /** Main light direction. */ + final private Vector3f mainDirection; + /** True if moon is above the horizon. */ + final private boolean moonUp; + /** Moonlight contribution. */ + final private float moonWeight; + /** True if sun is above the horizon. */ + final private boolean sunUp; + + /** + * Instantiate an input. + * + * @param cloudModulationFlag true if cloud modulation is enabled + * @param sunUp true if the sun is above the horizon + * @param moonUp true if the moon is above the horizon + * @param moonWeight moonlight contribution (≤1, ≥0) + * @param mainDirection main light direction (not null, alias created) + */ + public Input(boolean cloudModulationFlag, boolean sunUp, + boolean moonUp, float moonWeight, Vector3f mainDirection) { + assert moonWeight >= 0f : moonWeight; + assert moonWeight <= 1f : moonWeight; + assert mainDirection != null; + assert mainDirection.isUnitVector() : mainDirection; + assert mainDirection.y >= 0f : mainDirection; + + this.cloudModulationFlag = cloudModulationFlag; + this.sunUp = sunUp; + this.moonUp = moonUp; + this.moonWeight = moonWeight; + this.mainDirection = mainDirection; + } + + /** + * Test whether cloud modulation applies. + * + * @return true if transmission should be sampled + */ + private boolean modulationApplies() { + boolean result = cloudModulationFlag + && (sunUp || moonUp && moonWeight > 0f); + return result; + } + } + + /** + * Cloud resources for transmission sampling. + */ + final public static class Resources { + /** Optional clouds-only dome. */ + final private Geometry cloudsOnlyDome; + /** Clouds material. */ + final private SkyMaterial cloudsMaterial; + /** Clouds mesh. */ + final private DomeMesh cloudsMesh; + + /** + * Instantiate resources. + * + * @param cloudsOnlyDome optional clouds-only dome + * @param cloudsMesh clouds mesh (not null, alias created) + * @param cloudsMaterial clouds material (not null, alias created) + */ + public Resources(Geometry cloudsOnlyDome, DomeMesh cloudsMesh, + SkyMaterial cloudsMaterial) { + assert cloudsMesh != null; + assert cloudsMaterial != null; + + this.cloudsOnlyDome = cloudsOnlyDome; + this.cloudsMesh = cloudsMesh; + this.cloudsMaterial = cloudsMaterial; + } + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyLightSelectionRuntime.java b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyLightSelectionRuntime.java new file mode 100644 index 0000000..206c742 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyLightSelectionRuntime.java @@ -0,0 +1,172 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.control; + +import com.jme3.math.Vector3f; + +/** + * Runtime helper for selecting the main sky light source. + * + * @author Take Some + */ +final public class SkyLightSelectionRuntime { + /** + * Hidden constructor. + */ + private SkyLightSelectionRuntime() { + // do nothing + } + + /** + * Select the main light direction and expose altitude flags. + * + * @param sunDirection world direction to the sun (not null, length=1) + * @param moonDirection world direction to the moon, or null if hidden + * @param moonWeight moonlight contribution (≤1, ≥0) + * @param fallbackDirection fallback direction (not null, length=1) + * @return new immutable result + */ + public static Result select(Vector3f sunDirection, Vector3f moonDirection, + float moonWeight, Vector3f fallbackDirection) { + assert sunDirection != null; + assert sunDirection.isUnitVector() : sunDirection; + assert moonWeight >= 0f : moonWeight; + assert moonWeight <= 1f : moonWeight; + assert fallbackDirection != null; + assert fallbackDirection.isUnitVector() : fallbackDirection; + assert fallbackDirection.y >= 0f : fallbackDirection; + if (moonDirection != null) { + assert moonDirection.isUnitVector() : moonDirection; + } + + float sineSolarAltitude = sunDirection.y; + float sineLunarAltitude; + if (moonDirection != null) { + sineLunarAltitude = moonDirection.y; + } else { + sineLunarAltitude = -1f; + } + + boolean moonUp = sineLunarAltitude >= 0f; + boolean sunUp = sineSolarAltitude >= 0f; + Vector3f mainDirection; + if (sunUp) { + mainDirection = sunDirection; + } else if (moonUp && moonWeight > 0f) { + assert moonDirection != null; + mainDirection = moonDirection; + } else { + mainDirection = fallbackDirection; + } + assert mainDirection.isUnitVector() : mainDirection; + assert mainDirection.y >= 0f : mainDirection; + + Result result = new Result(mainDirection, sineSolarAltitude, + sineLunarAltitude, sunUp, moonUp); + return result; + } + + /** + * Main-light selection result. + */ + final public static class Result { + /** Main light direction. */ + final private Vector3f mainDirection; + /** True if moon is above horizon. */ + final private boolean moonUp; + /** Sine of the lunar altitude. */ + final private float sineLunarAltitude; + /** Sine of the solar altitude. */ + final private float sineSolarAltitude; + /** True if sun is above horizon. */ + final private boolean sunUp; + + /** + * Instantiate a result. + * + * @param mainDirection main light direction (not null, alias created) + * @param sineSolarAltitude sine of the solar altitude + * @param sineLunarAltitude sine of the lunar altitude + * @param sunUp true if sun is above horizon + * @param moonUp true if moon is above horizon + */ + private Result(Vector3f mainDirection, float sineSolarAltitude, + float sineLunarAltitude, boolean sunUp, boolean moonUp) { + assert mainDirection != null; + + this.mainDirection = mainDirection; + this.sineSolarAltitude = sineSolarAltitude; + this.sineLunarAltitude = sineLunarAltitude; + this.sunUp = sunUp; + this.moonUp = moonUp; + } + + /** + * Return the selected main light direction. + * + * @return pre-existing vector + */ + public Vector3f mainDirection() { + return mainDirection; + } + + /** + * Test whether the moon is above the horizon. + * + * @return true if above the horizon + */ + public boolean moonUp() { + return moonUp; + } + + /** + * Return the sine of the lunar altitude. + * + * @return sine value + */ + public float sineLunarAltitude() { + return sineLunarAltitude; + } + + /** + * Return the sine of the solar altitude. + * + * @return sine value + */ + public float sineSolarAltitude() { + return sineSolarAltitude; + } + + /** + * Test whether the sun is above the horizon. + * + * @return true if above the horizon + */ + public boolean sunUp() { + return sunUp; + } + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyLightingOutputRuntime.java b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyLightingOutputRuntime.java new file mode 100644 index 0000000..8772c13 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyLightingOutputRuntime.java @@ -0,0 +1,292 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.control; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import jme3utilities.math.MyColor; +import jme3utilities.math.MyMath; +import jme3utilities.sky.SkyAtmosphere; + +/** + * Runtime helper for assembling final sky lighting output. + * + * @author Take Some + */ +final public class SkyLightingOutputRuntime { + /** + * Hidden constructor. + */ + private SkyLightingOutputRuntime() { + // do nothing + } + + /** + * Compute final directional, ambient, shadow, and bloom lighting values. + * + * @param input lighting output input (not null) + * @return new immutable result + */ + public static Result compute(Input input) { + assert input != null; + + ColorRGBA mainColor = computeMainColor(input); + ColorRGBA ambientColor = computeAmbientColor( + input.atmosphere, input.cloudsColor, mainColor); + float shadowIntensity = computeShadowIntensity( + input.atmosphere, mainColor, ambientColor); + float bloomIntensity = computeBloomIntensity( + input.atmosphere, input.sineSolarAltitude); + + Result result = new Result( + mainColor, ambientColor, shadowIntensity, bloomIntensity); + return result; + } + + /** + * Compute the ambient color. + * + * @param atmosphere lighting profile (not null) + * @param cloudsColor cloud color (not null, unaffected) + * @param mainColor main directional color (not null, unaffected) + * @return new color + */ + private static ColorRGBA computeAmbientColor(SkyAtmosphere atmosphere, + ColorRGBA cloudsColor, ColorRGBA mainColor) { + assert atmosphere != null; + assert cloudsColor != null; + assert mainColor != null; + + float slack = 1f - MyMath.max(mainColor.r, mainColor.g, mainColor.b); + assert slack >= 0f : slack; + ColorRGBA result = cloudsColor.mult( + slack * atmosphere.getAmbientScale()); + return result; + } + + /** + * Compute the recommended bloom intensity using the sun's altitude. + * + * @param atmosphere lighting profile (not null) + * @param sineSolarAltitude sine of the solar altitude + * @return bloom intensity + */ + private static float computeBloomIntensity(SkyAtmosphere atmosphere, + float sineSolarAltitude) { + assert atmosphere != null; + + float result = 6f * atmosphere.getBloomScale() * sineSolarAltitude; + result = FastMath.clamp( + result, 0f, atmosphere.getMaxBloomIntensity()); + return result; + } + + /** + * Compute the color and intensity of the main directional light. + * + * @param input lighting output input (not null) + * @return new color + */ + private static ColorRGBA computeMainColor(Input input) { + assert input != null; + + ColorRGBA result; + if (input.sunUp) { + float altitudeFactor = MyMath.cubeRoot( + FastMath.saturate(input.sineSolarAltitude)); + float sunFactor = input.transmit * altitudeFactor; + result = input.sunColor.mult(sunFactor); + + } else if (input.moonUp) { + float lunarAltitudeFactor = MyMath.cubeRoot( + FastMath.saturate(input.sineLunarAltitude)); + float moonFactor = input.transmit * input.moonWeight + * lunarAltitudeFactor; + result = MyColor.interpolateLinear( + moonFactor, input.starColor, input.moonColor); + + } else { + result = input.starColor.clone(); + } + + return result; + } + + /** + * Compute the recommended shadow intensity. + * + * @param atmosphere lighting profile (not null) + * @param mainColor main directional color (not null, unaffected) + * @param ambientColor ambient color (not null, unaffected) + * @return shadow intensity + */ + private static float computeShadowIntensity(SkyAtmosphere atmosphere, + ColorRGBA mainColor, ColorRGBA ambientColor) { + assert atmosphere != null; + assert mainColor != null; + assert ambientColor != null; + + float mainAmount = mainColor.r + mainColor.g + mainColor.b; + float ambientAmount = ambientColor.r + ambientColor.g + ambientColor.b; + float totalAmount = mainAmount + ambientAmount; + assert totalAmount > 0f : totalAmount; + float result = FastMath.saturate(mainAmount / totalAmount); + result = FastMath.saturate(result * atmosphere.getShadowContrast()); + return result; + } + + /** + * Lighting output input. + */ + final public static class Input { + /** Lighting profile. */ + final private SkyAtmosphere atmosphere; + /** Cloud color used for ambient-light estimation. */ + final private ColorRGBA cloudsColor; + /** Moonlight source color. */ + final private ColorRGBA moonColor; + /** True if the moon is above the horizon. */ + final private boolean moonUp; + /** Moonlight contribution to nighttime lighting. */ + final private float moonWeight; + /** Sine of the lunar altitude. */ + final private float sineLunarAltitude; + /** Sine of the solar altitude. */ + final private float sineSolarAltitude; + /** Starlight source color. */ + final private ColorRGBA starColor; + /** Daylight source color. */ + final private ColorRGBA sunColor; + /** True if the sun is above the horizon. */ + final private boolean sunUp; + /** Cloud transmission factor for the main light. */ + final private float transmit; + + /** + * Instantiate an input. + * + * @param atmosphere lighting profile (not null, alias created) + * @param lightSelection selected light state (not null, unaffected) + * @param baseResult base/source colors (not null, unaffected) + * @param cloudsColor cloud color (not null, alias created) + * @param moonWeight moonlight contribution (≤1, ≥0) + * @param transmit cloud transmission factor for the main light + */ + public Input(SkyAtmosphere atmosphere, + SkyLightSelectionRuntime.Result lightSelection, + SkyBaseColorRuntime.Result baseResult, ColorRGBA cloudsColor, + float moonWeight, float transmit) { + assert atmosphere != null; + assert lightSelection != null; + assert baseResult != null; + assert cloudsColor != null; + assert moonWeight >= 0f : moonWeight; + assert moonWeight <= 1f : moonWeight; + assert transmit >= 0f : transmit; + + this.atmosphere = atmosphere; + this.cloudsColor = cloudsColor; + this.sunColor = baseResult.sunColor(); + this.moonColor = baseResult.moonColor(); + this.starColor = baseResult.starColor(); + this.sunUp = lightSelection.sunUp(); + this.moonUp = lightSelection.moonUp(); + this.moonWeight = moonWeight; + this.sineSolarAltitude = lightSelection.sineSolarAltitude(); + this.sineLunarAltitude = lightSelection.sineLunarAltitude(); + this.transmit = transmit; + } + } + + /** + * Final lighting output result. + */ + final public static class Result { + /** Ambient light color. */ + final private ColorRGBA ambientColor; + /** Recommended bloom intensity. */ + final private float bloomIntensity; + /** Main directional light color. */ + final private ColorRGBA mainColor; + /** Recommended shadow intensity. */ + final private float shadowIntensity; + + /** + * Instantiate a result. + * + * @param mainColor main light color (not null, alias created) + * @param ambientColor ambient light color (not null, alias created) + * @param shadowIntensity recommended shadow intensity + * @param bloomIntensity recommended bloom intensity + */ + private Result(ColorRGBA mainColor, ColorRGBA ambientColor, + float shadowIntensity, float bloomIntensity) { + assert mainColor != null; + assert ambientColor != null; + + this.mainColor = mainColor; + this.ambientColor = ambientColor; + this.shadowIntensity = shadowIntensity; + this.bloomIntensity = bloomIntensity; + } + + /** + * Return the ambient light color. + * + * @return pre-existing instance + */ + public ColorRGBA ambientColor() { + return ambientColor; + } + + /** + * Return the recommended bloom intensity. + * + * @return bloom intensity + */ + public float bloomIntensity() { + return bloomIntensity; + } + + /** + * Return the main directional light color. + * + * @return pre-existing instance + */ + public ColorRGBA mainDirectionalColor() { + return mainColor; + } + + /** + * Return the recommended shadow intensity. + * + * @return shadow intensity + */ + public float shadowIntensity() { + return shadowIntensity; + } + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyMoonRuntime.java b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyMoonRuntime.java new file mode 100644 index 0000000..1b03958 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyMoonRuntime.java @@ -0,0 +1,251 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.control; + +import com.jme3.asset.AssetNotFoundException; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.texture.Texture; +import jme3utilities.math.MyMath; +import jme3utilities.mesh.DomeMesh; +import jme3utilities.sky.GlobeRenderer; +import jme3utilities.sky.LunarPhase; +import jme3utilities.sky.SkyMaterial; +import jme3utilities.sky.SunAndStars; + +/** + * Runtime helper for moon direction, phase texture, and dome projection. + * + * @author Take Some + */ +public final class SkyMoonRuntime { + /** + * Hidden constructor. + */ + private SkyMoonRuntime() { + // do nothing + } + + /** + * Immutable arguments for one moon update. + */ + final public static class MoonUpdateState { + /** Off-screen moon renderer, or null. */ + final private GlobeRenderer renderer; + /** Moon object index in the sky material. */ + final private int objectIndex; + /** Difference in longitude between the moon and sun. */ + final private float longitudeDifference; + /** Lunar latitude north of the ecliptic. */ + final private float lunarLatitude; + /** Moon texture scale. */ + final private float moonScale; + + /** + * Instantiate update arguments. + * + * @param renderer off-screen moon renderer, or null + * @param objectIndex moon object index + * @param longitudeDifference radians east of the sun + * @param lunarLatitude radians north of the ecliptic + * @param moonScale moon texture scale + */ + public MoonUpdateState(GlobeRenderer renderer, int objectIndex, + float longitudeDifference, float lunarLatitude, + float moonScale) { + this.renderer = renderer; + this.objectIndex = objectIndex; + this.longitudeDifference = longitudeDifference; + this.lunarLatitude = lunarLatitude; + this.moonScale = moonScale; + } + } + + /** + * Apply a custom moon texture if one has been specified. + * + * @param material top-dome material (not null) + * @param objectIndex moon object index + * @param assetPath custom texture asset path, or null + * @param colorMap custom texture object, or null + * @return true if a custom texture was applied, otherwise false + */ + public static boolean applyTexture(SkyMaterial material, int objectIndex, + String assetPath, Texture colorMap) { + assert material != null; + + if (colorMap != null) { + material.addObject(objectIndex, colorMap); + return true; + } else if (assetPath != null) { + material.addObject(objectIndex, assetPath); + return true; + } + + return false; + } + + /** + * Apply the moon texture for a phase preset. + * + * @param material top-dome material (not null) + * @param objectIndex moon object index + * @param preset phase preset (not null) + */ + public static void applyPresetTexture(SkyMaterial material, int objectIndex, + LunarPhase preset) { + assert material != null; + assert preset != null; + + String assetPath = preset.imagePath(""); + try { + material.addObject(objectIndex, assetPath); + } catch (AssetNotFoundException exception) { + assetPath = preset.imagePath("-nonviral"); + material.addObject(objectIndex, assetPath); + } + } + + /** + * Calculate the direction to the center of the moon. + * + * @param sunAndStars orientation model (not null) + * @param longitudeDifference radians east of the sun + * @param lunarLatitude radians north of the ecliptic + * @param storeResult storage for the result, or null + * @return a unit vector in world coordinates + */ + public static Vector3f direction(SunAndStars sunAndStars, + float longitudeDifference, float lunarLatitude, + Vector3f storeResult) { + assert sunAndStars != null; + + float solarLongitude = sunAndStars.getSolarLongitude(); + float celestialLongitude = solarLongitude + longitudeDifference; + celestialLongitude = MyMath.modulo(celestialLongitude, FastMath.TWO_PI); + Vector3f result = sunAndStars.convertToWorld( + lunarLatitude, celestialLongitude, storeResult); + + return result; + } + + /** + * Compute the clockwise rotation of the moon texture relative to the sky. + * + * @param sunAndStars orientation model (not null) + * @param topMesh top dome mesh (not null) + * @param lunarLatitude radians north of the ecliptic + * @param longitude moon celestial longitude + * @param uvCenter texture coordinates of the moon center (not null) + * @return new unit vector + */ + public static Vector2f rotation(SunAndStars sunAndStars, DomeMesh topMesh, + float lunarLatitude, float longitude, Vector2f uvCenter) { + assert sunAndStars != null; + assert topMesh != null; + assert uvCenter != null; + + float latitude = lunarLatitude + 0.01f; + if (latitude <= FastMath.HALF_PI) { + Vector3f north + = sunAndStars.convertToWorld(latitude, longitude, null); + Vector2f uvNorth = topMesh.directionUV(north); + if (uvNorth != null) { + Vector2f offset = uvNorth.subtract(uvCenter); + assert offset.length() > 0f : offset; + Vector2f result = offset.normalize(); + return result; + } + } + + latitude = lunarLatitude - 0.01f; + assert latitude >= -FastMath.HALF_PI : lunarLatitude; + Vector3f south = sunAndStars.convertToWorld(latitude, longitude, null); + Vector2f uvSouth = topMesh.directionUV(south); + if (uvSouth != null) { + Vector2f offset = uvCenter.subtract(uvSouth); + assert offset.length() > 0f : offset; + Vector2f result = offset.normalize(); + return result; + } + + assert false : south; + return null; + } + + /** + * Update the moon's position and size. + * + * @param material top-dome material (not null) + * @param topMesh top dome mesh (not null) + * @param sunAndStars orientation model (not null) + * @param phase current phase, or null to hide the moon + * @param state update arguments (not null) + * @return world direction to the moon, or null if hidden + */ + public static Vector3f updateMoon(SkyMaterial material, DomeMesh topMesh, + SunAndStars sunAndStars, LunarPhase phase, + MoonUpdateState state) { + assert material != null; + assert topMesh != null; + assert sunAndStars != null; + assert state != null; + + if (phase == null) { + material.hideObject(state.objectIndex); + return null; + } + + if (phase == LunarPhase.CUSTOM) { + assert state.renderer != null; + float intensity = 2f + FastMath.abs( + state.longitudeDifference - FastMath.PI); + state.renderer.setLightIntensity(intensity); + state.renderer.setPhase( + state.longitudeDifference, state.lunarLatitude); + } + + float solarLongitude = sunAndStars.getSolarLongitude(); + float celestialLongitude + = solarLongitude + state.longitudeDifference; + celestialLongitude = MyMath.modulo(celestialLongitude, FastMath.TWO_PI); + Vector3f worldDirection = sunAndStars.convertToWorld( + state.lunarLatitude, celestialLongitude, null); + Vector2f uvCenter = topMesh.directionUV(worldDirection); + + if (uvCenter != null) { + Vector2f rotation = rotation(sunAndStars, topMesh, + state.lunarLatitude, celestialLongitude, uvCenter); + material.setObjectTransform( + state.objectIndex, uvCenter, state.moonScale, rotation); + } else { + material.hideObject(state.objectIndex); + } + + return worldDirection; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyObjectLightingRuntime.java b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyObjectLightingRuntime.java new file mode 100644 index 0000000..802b26a --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/control/SkyObjectLightingRuntime.java @@ -0,0 +1,130 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.control; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import jme3utilities.sky.SkyAtmosphere; +import jme3utilities.sky.SkyMaterial; +import jme3utilities.sky.atmosphere.SkyLightingModel; + +/** + * Runtime helper for sun/moon material colors. + * + * @author Take Some + */ +public final class SkyObjectLightingRuntime { + // ************************************************************************* + // constants and loggers + + /** + * Sky-material object index for the sun. + */ + final private static int sunObjectIndex = 0; + /** + * Sky-material object index for the moon. + */ + final private static int moonObjectIndex = 1; + /** + * Maximum material alpha. + */ + final private static float maxAlpha = 1f; + + /** + * Hidden constructor. + */ + private SkyObjectLightingRuntime() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Update the sun and moon material colors using their altitudes. + * + * @param material top-dome material (not null) + * @param atmosphere lighting profile (not null) + * @param sineSolarAltitude sine of the solar altitude (≤1, ≥-1) + * @param sineLunarAltitude sine of the lunar altitude (≤1, ≥-1) + */ + public static void updateObjects(SkyMaterial material, + SkyAtmosphere atmosphere, float sineSolarAltitude, + float sineLunarAltitude) { + assert material != null; + assert atmosphere != null; + assert sineSolarAltitude <= 1f : sineSolarAltitude; + assert sineSolarAltitude >= -1f : sineSolarAltitude; + assert sineLunarAltitude <= 1f : sineLunarAltitude; + assert sineLunarAltitude >= -1f : sineLunarAltitude; + + updateSun(material, atmosphere, sineSolarAltitude); + updateMoon(material, atmosphere, sineLunarAltitude); + } + + /** + * Update the moon material color. + * + * @param material top-dome material (not null) + * @param atmosphere lighting profile (not null) + * @param sineLunarAltitude sine of the lunar altitude + */ + private static void updateMoon(SkyMaterial material, + SkyAtmosphere atmosphere, float sineLunarAltitude) { + float moonVisibility = FastMath.saturate( + 2f * sineLunarAltitude + 0.6f); + ColorRGBA moonColor = SkyLightingModel.lunarColor( + atmosphere, sineLunarAltitude); + ColorRGBA moonGlow = SkyLightingModel.moonGlowColor( + atmosphere, sineLunarAltitude); + moonColor.multLocal(moonVisibility); + moonGlow.multLocal(moonVisibility); + moonColor.a = maxAlpha; + moonGlow.a = maxAlpha * moonVisibility; + material.setObjectColor(moonObjectIndex, moonColor); + material.setObjectGlow(moonObjectIndex, moonGlow); + } + + /** + * Update the sun material color and glow. + * + * @param material top-dome material (not null) + * @param atmosphere lighting profile (not null) + * @param sineSolarAltitude sine of the solar altitude + */ + private static void updateSun(SkyMaterial material, + SkyAtmosphere atmosphere, float sineSolarAltitude) { + float sunVisibility = FastMath.saturate( + 1f + sineSolarAltitude / atmosphere.getTwilightLimit()); + ColorRGBA sunColor = SkyLightingModel.daylightColor( + atmosphere, sineSolarAltitude); + ColorRGBA sunGlow = SkyLightingModel.sunGlowColor( + atmosphere, sineSolarAltitude); + sunColor.a = maxAlpha * sunVisibility; + sunGlow.a = maxAlpha * sunVisibility; + material.setObjectColor(sunObjectIndex, sunColor); + material.setObjectGlow(sunObjectIndex, sunGlow); + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/control/package-info.java b/SkyLibrary/src/main/java/jme3utilities/sky/control/package-info.java new file mode 100644 index 0000000..3293473 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/control/package-info.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Sky-control runtime state and update helpers. + */ +package jme3utilities.sky.control; diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyCloudMaterialSlots.java b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyCloudMaterialSlots.java new file mode 100644 index 0000000..3012f25 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyCloudMaterialSlots.java @@ -0,0 +1,360 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.material; + +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.Vector2f; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.image.ImageRaster; +import java.io.IOException; +import jme3utilities.math.MyMath; +import jme3utilities.sky.Constants; + +/** + * Serializable cloud-layer state and raster cache for sky materials. + * + * @author Take Some + */ +public final class SkyCloudMaterialSlots { + // ************************************************************************* + // constants and loggers + + /** + * Maximum value for opacity/transmission. + */ + final private static float maxAlpha = 1f; + // ************************************************************************* + // fields + + /** + * Maximum opacity of each cloud layer. + */ + final private float[] alphas; + /** + * Image of each cloud layer, retained for serialization. + */ + final private Image[] images; + /** + * Cached rasterization of each cloud layer. + */ + final private ImageRaster[] rasters; + /** + * Scale factors of cloud layers. + */ + final private float[] scales; + /** + * UV offset of each cloud layer. + */ + final private Vector2f[] offsets; + // ************************************************************************* + // constructors + + /** + * Instantiate empty cloud-layer slots. + * + * @param numLayers number of supported cloud layers (≥0) + */ + public SkyCloudMaterialSlots(int numLayers) { + this.alphas = new float[numLayers]; + this.images = new Image[numLayers]; + this.offsets = new Vector2f[numLayers]; + this.rasters = new ImageRaster[numLayers]; + this.scales = new float[numLayers]; + } + + /** + * Instantiate cloud-layer slots from serialized state. + * + * @param alphas serialized opacity array (not null, aliased) + * @param images serialized images array (not null, aliased) + * @param offsets serialized offset array (not null, aliased) + * @param scales serialized scale array (not null, aliased) + */ + private SkyCloudMaterialSlots(float[] alphas, Image[] images, + Vector2f[] offsets, float[] scales) { + assert alphas != null; + assert images != null; + assert offsets != null; + assert scales != null; + + this.alphas = alphas; + this.images = images; + this.offsets = offsets; + this.rasters = new ImageRaster[images.length]; + this.scales = scales; + rebuildRasters(); + } + // ************************************************************************* + // new methods exposed + + /** + * Add or replace a cloud layer texture. + * + * @param layerIndex cloud layer index + * @param alphaMap alpha-map texture (not null, unaffected) + * @return true if this was the first texture for the layer + */ + public boolean addAlphaMap(int layerIndex, Texture alphaMap) { + assert alphaMap != null; + + boolean result = !isAdded(layerIndex); + Image image = alphaMap.getImage(); + this.images[layerIndex] = image; + this.rasters[layerIndex] = createRaster(image); + if (result) { + this.offsets[layerIndex] = new Vector2f(); + } + + return result; + } + + /** + * Access the recorded alpha for a cloud layer. + * + * @param layerIndex cloud layer index + * @return opacity alpha + */ + public float alpha(int layerIndex) { + float result = alphas[layerIndex]; + return result; + } + + /** + * Count supported cloud layers. + * + * @return number of slots (≥0) + */ + public int count() { + int result = images.length; + return result; + } + + /** + * Copy the recorded offset for a cloud layer. + * + * @param layerIndex cloud layer index + * @return a new vector + */ + public Vector2f copyOffset(int layerIndex) { + Vector2f result = offsets[layerIndex].clone(); + return result; + } + + /** + * Test whether a cloud layer has a texture. + * + * @param layerIndex cloud layer index + * @return true if added, otherwise false + */ + public boolean isAdded(int layerIndex) { + boolean result = images[layerIndex] != null; + return result; + } + + /** + * Read cloud-layer slots from an input capsule. + * + * @param capsule input capsule (not null) + * @return a new instance + * @throws IOException from capsule + */ + public static SkyCloudMaterialSlots read(InputCapsule capsule) + throws IOException { + assert capsule != null; + + float[] alphas = capsule.readFloatArray("cloudAlphas", null); + + Savable[] sav = capsule.readSavableArray("cloudImages", null); + Image[] images = new Image[sav.length]; + System.arraycopy(sav, 0, images, 0, sav.length); + + sav = capsule.readSavableArray("cloudOffsets", null); + Vector2f[] offsets = new Vector2f[sav.length]; + System.arraycopy(sav, 0, offsets, 0, sav.length); + + float[] scales = capsule.readFloatArray("cloudScales", null); + + SkyCloudMaterialSlots result + = new SkyCloudMaterialSlots(alphas, images, offsets, scales); + return result; + } + + /** + * Verify that a cloud layer has been added. + * + * @param layerIndex cloud layer index + * @throws IllegalStateException if the layer has not been added + */ + public void requireAdded(int layerIndex) { + if (!isAdded(layerIndex)) { + throw new IllegalStateException("layer not yet added"); + } + } + + /** + * Record the alpha for a cloud layer. + * + * @param layerIndex cloud layer index + * @param alpha desired alpha + */ + public void setAlpha(int layerIndex, float alpha) { + this.alphas[layerIndex] = alpha; + } + + /** + * Record the offset for a cloud layer. + * + * @param layerIndex cloud layer index + * @param newU first component of the new offset + * @param newV second component of the new offset + * @return the normalized offset vector + */ + public Vector2f setOffset(int layerIndex, float newU, float newV) { + float uOffset = MyMath.modulo(newU, 1f); + float vOffset = MyMath.modulo(newV, 1f); + Vector2f result = new Vector2f(uOffset, vOffset); + offsets[layerIndex].set(result); + + return result; + } + + /** + * Record the scale for a cloud layer. + * + * @param layerIndex cloud layer index + * @param scale desired scale + */ + public void setScale(int layerIndex, float scale) { + this.scales[layerIndex] = scale; + } + + /** + * Estimate how much light is transmitted through the cloud layers. + * + * @param skyCoordinates texture coordinates (not null, unaffected) + * @return fraction of light transmitted (≤1, ≥0) + */ + public float transmission(Vector2f skyCoordinates) { + assert skyCoordinates != null; + + float result = 1f; + for (int layerIndex = 0; layerIndex < count(); ++layerIndex) { + if (isAdded(layerIndex)) { + float transparency = transparency(layerIndex, skyCoordinates); + result *= transparency; + } + } + + assert result >= 0f : result; + assert result <= maxAlpha : result; + return result; + } + + /** + * Serialize cloud-layer slots to an output capsule. + * + * @param capsule output capsule (not null) + * @throws IOException from capsule + */ + public void write(OutputCapsule capsule) throws IOException { + assert capsule != null; + + capsule.write(alphas, "cloudAlphas", null); + capsule.write(images, "cloudImages", null); + capsule.write(offsets, "cloudOffsets", null); + capsule.write(scales, "cloudScales", null); + } + // ************************************************************************* + // private methods + + /** + * Create a raster cache for an image, if supported. + * + * @param image source image (not null, unaffected) + * @return new raster, or null if the image format is unsupported + */ + private static ImageRaster createRaster(Image image) { + assert image != null; + + ImageRaster result; + try { + result = ImageRaster.create(image); + } catch (UnsupportedOperationException exception) { + result = null; + } + + return result; + } + + /** + * Rebuild raster cache from serialized images. + */ + private void rebuildRasters() { + for (int layerIndex = 0; layerIndex < count(); ++layerIndex) { + Image image = images[layerIndex]; + if (image == null) { + this.rasters[layerIndex] = null; + } else { + this.rasters[layerIndex] = createRaster(image); + } + } + } + + /** + * Estimate how much light is transmitted through an indexed cloud layer at + * the specified texture coordinates. + * + * @param layerIndex cloud layer index + * @param skyCoordinates texture coordinates (not null, unaffected) + * @return fraction of light transmitted (≤1, ≥0) + */ + private float transparency(int layerIndex, Vector2f skyCoordinates) { + assert layerIndex >= 0 : layerIndex; + assert layerIndex < count() : layerIndex; + assert skyCoordinates != null; + assert isAdded(layerIndex) : layerIndex; + + ImageRaster raster = rasters[layerIndex]; + if (raster == null) { + return maxAlpha; + } + + Vector2f coord = skyCoordinates.mult(scales[layerIndex]); + coord.addLocal(offsets[layerIndex]); + coord.x = MyMath.modulo(coord.x, Constants.uvMax); + coord.y = MyMath.modulo(coord.y, Constants.uvMax); + float opacity = SkyTextureSampler.sampleRed(raster, coord); + opacity *= alphas[layerIndex]; + float result = maxAlpha - opacity; + + assert result >= 0f : result; + assert result <= maxAlpha : result; + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyDdsTextureLoader.java b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyDdsTextureLoader.java new file mode 100644 index 0000000..693a50a --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyDdsTextureLoader.java @@ -0,0 +1,300 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.material; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoadException; +import com.jme3.asset.AssetManager; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.asset.TextureKey; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.texture.image.ColorSpace; +import com.jme3.util.BufferUtils; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Minimal DDS texture loader for unsupported cloud maps. + * + * @author Take Some + */ +public final class SkyDdsTextureLoader { + /** Message logger for this class. */ + final private static Logger logger + = Logger.getLogger("jme3utilities.sky.material.Sky" + + "D" + "dsTextureLoader"); + + /** DDS fixed header size in bytes. */ + final private static int headerSize = 128; + + /** Hidden constructor. */ + private SkyDdsTextureLoader() { + // do nothing + } + + /** + * Load a DDS texture using a small built-in parser. + * + * @param assetManager asset manager (not null) + * @param assetPath texture asset path (not null) + * @return new texture + */ + public static Texture loadTexture( + AssetManager assetManager, String assetPath) { + assert assetManager != null; + assert assetPath != null; + + TextureKey key = new TextureKey(assetPath, false); + AssetInfo info = assetManager.locateAsset(key); + if (info == null) { + throw new AssetNotFoundException(assetPath); + } + + try (InputStream stream = info.openStream()) { + Image image = readImage(stream); + Texture2D result = new Texture2D(image); + result.setKey(key); + return result; + } catch (IOException exception) { + throw new AssetLoadException( + "Failed to load DDS asset " + assetPath, exception); + } + } + + /** + * Read a DDS image from a stream. + * + * @param stream input stream (not null) + * @return new image + * @throws IOException if reading fails or format is unsupported + */ + private static Image readImage(InputStream stream) throws IOException { + byte[] data = readAll(stream); + if (data.length < headerSize || data[0] != 'D' || data[1] != 'D' + || data[2] != 'S' || data[3] != ' ') { + throw new IOException("not a DDS file"); + } + + int height = littleInt(data, 12); + int width = littleInt(data, 16); + int depth = littleInt(data, 24); + int mipLevels = littleInt(data, 28); + String fourCc = fourCc(data, 84); + if (depth <= 0) { + depth = 1; + } + if (mipLevels <= 0) { + mipLevels = 1; + } + + FormatInfo format = format(fourCc); + MipData mipData = copyMipData( + data, width, height, mipLevels, format.blockBytes); + logger.log(Level.INFO, + "loaded compressed sky texture: width={0}, height={1}, mips={2}, code={3}, format={4}, blockBytes={5}", + new Object[]{width, height, mipData.sizes.length, fourCc, + format.imageFormat, format.blockBytes}); + Image result = new Image(format.imageFormat, width, height, + mipData.buffer, mipData.sizes, ColorSpace.Linear); + return result; + } + + /** + * Copy available mip levels into one direct buffer. + * + * @param data DDS file bytes (not null) + * @param width base width (>0) + * @param height base height (>0) + * @param mipLevels declared mip level count (>0) + * @param blockBytes bytes in one 4x4 compressed block + * @return new mip data + */ + private static MipData copyMipData(byte[] data, int width, int height, + int mipLevels, int blockBytes) { + int[] sizes = new int[mipLevels]; + int offset = headerSize; + int usedLevels = 0; + for (int level = 0; level < mipLevels; ++level) { + int levelWidth = Math.max(1, width >> level); + int levelHeight = Math.max(1, height >> level); + int blockWidth = Math.max(1, (levelWidth + 3) / 4); + int blockHeight = Math.max(1, (levelHeight + 3) / 4); + int size = blockWidth * blockHeight * blockBytes; + if (offset + size > data.length) { + break; + } + sizes[level] = size; + offset += size; + usedLevels = level + 1; + } + if (usedLevels == 0) { + throw new AssetLoadException("DDS contains no mip data"); + } + + int totalBytes = offset - headerSize; + ByteBuffer buffer = BufferUtils.createByteBuffer(totalBytes); + buffer.put(data, headerSize, totalBytes); + buffer.flip(); + int[] actualSizes = Arrays.copyOf(sizes, usedLevels); + MipData result = new MipData(buffer, actualSizes); + return result; + } + + /** + * Look up image format information for a FourCC. + * + * @param fourCc FourCC text (not null) + * @return format information + * @throws IOException if the FourCC is unsupported + */ + private static FormatInfo format(String fourCc) throws IOException { + FormatInfo result; + switch (fourCc) { + case "DXT1": + result = new FormatInfo(Image.Format.DXT1, 8); + break; + case "DXT3": + result = new FormatInfo(Image.Format.DXT3, 16); + break; + case "DXT5": + result = new FormatInfo(Image.Format.DXT5, 16); + break; + case "ATI1": + case "BC4U": + result = new FormatInfo(Image.Format.RGTC1, 8); + break; + case "BC4S": + result = new FormatInfo(Image.Format.SIGNED_RGTC1, 8); + break; + case "ATI2": + case "BC5U": + result = new FormatInfo(Image.Format.RGTC2, 16); + break; + case "BC5S": + result = new FormatInfo(Image.Format.SIGNED_RGTC2, 16); + break; + default: + throw new IOException("unsupported FourCC: " + fourCc); + } + + return result; + } + + + /** + * Read all bytes from a stream. + * + * @param stream input stream (not null) + * @return byte array + * @throws IOException if reading fails + */ + private static byte[] readAll(InputStream stream) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buffer = new byte[16 * 1024]; + int count; + while ((count = stream.read(buffer)) != -1) { + output.write(buffer, 0, count); + } + byte[] result = output.toByteArray(); + return result; + } + + /** + * Read a little-endian int. + * + * @param data source bytes (not null) + * @param offset byte offset + * @return integer value + */ + private static int littleInt(byte[] data, int offset) { + int result = data[offset] & 0xFF; + result |= (data[offset + 1] & 0xFF) << 8; + result |= (data[offset + 2] & 0xFF) << 16; + result |= (data[offset + 3] & 0xFF) << 24; + return result; + } + + /** + * Read a FourCC. + * + * @param data source bytes (not null) + * @param offset byte offset + * @return FourCC string + */ + private static String fourCc(byte[] data, int offset) { + char[] chars = new char[4]; + for (int i = 0; i < 4; ++i) { + chars[i] = (char) (data[offset + i] & 0xFF); + } + String result = new String(chars); + return result; + } + + /** Format metadata. */ + private static class FormatInfo { + /** Block size in bytes. */ + final private int blockBytes; + /** jME image format. */ + final private Image.Format imageFormat; + + /** + * Instantiate metadata. + * + * @param imageFormat jME image format + * @param blockBytes block size in bytes + */ + FormatInfo(Image.Format imageFormat, int blockBytes) { + this.imageFormat = imageFormat; + this.blockBytes = blockBytes; + } + } + + /** Mip data. */ + private static class MipData { + /** Packed mip buffer. */ + final private ByteBuffer buffer; + /** Mip sizes. */ + final private int[] sizes; + + /** + * Instantiate mip data. + * + * @param buffer packed mip buffer + * @param sizes mip sizes + */ + MipData(ByteBuffer buffer, int[] sizes) { + this.buffer = buffer; + this.sizes = sizes; + } + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyMaterialFactory.java b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyMaterialFactory.java new file mode 100644 index 0000000..ae3eded --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyMaterialFactory.java @@ -0,0 +1,125 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.material; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.MyAsset; +import jme3utilities.sky.SkyControlCore; +import jme3utilities.sky.SkyMaterial; +import jme3utilities.sky.StarsOption; + +/** + * Factory for sky materials and cloud-layer runtime objects. + * + * @author Take Some + */ +public final class SkyMaterialFactory { + /** Message logger for this class. */ + final private static Logger logger + = Logger.getLogger(SkyMaterialFactory.class.getName()); + + /** + * Hidden constructor. + */ + private SkyMaterialFactory() { + // do nothing + } + + /** + * Create the optional bottom-dome material. + * + * @param assetManager asset manager (not null) + * @param enabled true to create a material, otherwise false + * @return material, or null when disabled + */ + public static Material createBottom(AssetManager assetManager, + boolean enabled) { + Material result = enabled + ? MyAsset.createUnshadedMaterial(assetManager) : null; + logger.log(Level.FINE, + "bottom sky material requested: enabled={0}, created={1}", + new Object[]{enabled, result != null}); + return result; + } + + /** + * Create and initialize the cloud material. + * + * @param assetManager asset manager (not null) + * @param separateDome true for a separate cloud dome + * @param topMaterial initialized top material (not null) + * @return initialized cloud material (not null) + */ + public static SkyMaterial createClouds(AssetManager assetManager, + boolean separateDome, SkyMaterial topMaterial) { + SkyMaterial result; + if (separateDome) { + int numObjects = 0; + result = new SkyMaterial( + assetManager, numObjects, SkyControlCore.numCloudLayers); + result.initialize(); + result.getAdditionalRenderState().setDepthWrite(false); + result.setClearColor(ColorRGBA.BlackNoAlpha); + } else { + result = topMaterial; + } + + logger.log(Level.INFO, + "cloud sky material created: separateDome={0}, layers={1}", + new Object[]{separateDome, SkyControlCore.numCloudLayers}); + return result; + } + + /** + * Create and initialize the top sky material. + * + * @param assetManager asset manager (not null) + * @param separateCloudDome true when clouds use a separate dome + * @param starsOption star rendering option (not null) + * @return initialized top material (not null) + */ + public static SkyMaterial createTop(AssetManager assetManager, + boolean separateCloudDome, StarsOption starsOption) { + int topObjects = 2; + int topClouds = separateCloudDome ? 0 : SkyControlCore.numCloudLayers; + SkyMaterial result = new SkyMaterial(assetManager, topObjects, + topClouds); + result.initialize(); + result.addHaze(); + if (starsOption == StarsOption.TopDome) { + result.addStars(); + } + + logger.log(Level.INFO, + "top sky material created: objects={0}, cloudLayers={1}, stars={2}", + new Object[]{topObjects, topClouds, starsOption}); + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyMaterialParamNames.java b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyMaterialParamNames.java new file mode 100644 index 0000000..10fa86d --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyMaterialParamNames.java @@ -0,0 +1,202 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.material; + +import java.util.Locale; + +/** + * Shader parameter-name factory for sky materials. + * + * @author Take Some + */ +public final class SkyMaterialParamNames { + /** + * Hidden constructor. + */ + private SkyMaterialParamNames() { + // do nothing + } + + /** + * Build the alpha-map parameter name for a cloud layer. + * + * @param layerIndex cloud layer index + * @return material parameter name + */ + public static String cloudAlphaMap(int layerIndex) { + String result = cloud(layerIndex, "AlphaMap"); + return result; + } + + /** + * Build the color parameter name for a cloud layer. + * + * @param layerIndex cloud layer index + * @return material parameter name + */ + public static String cloudColor(int layerIndex) { + String result = cloud(layerIndex, "Color"); + return result; + } + + /** + * Build the glow parameter name for a cloud layer. + * + * @param layerIndex cloud layer index + * @return material parameter name + */ + public static String cloudGlow(int layerIndex) { + String result = cloud(layerIndex, "Glow"); + return result; + } + + /** + * Build the normal-map parameter name for a cloud layer. + * + * @param layerIndex cloud layer index + * @return material parameter name + */ + public static String cloudNormalMap(int layerIndex) { + String result = cloud(layerIndex, "NormalMap"); + return result; + } + + /** + * Build the offset parameter name for a cloud layer. + * + * @param layerIndex cloud layer index + * @return material parameter name + */ + public static String cloudOffset(int layerIndex) { + String result = cloud(layerIndex, "Offset"); + return result; + } + + /** + * Build the scale parameter name for a cloud layer. + * + * @param layerIndex cloud layer index + * @return material parameter name + */ + public static String cloudScale(int layerIndex) { + String result = cloud(layerIndex, "Scale"); + return result; + } + + /** + * Build the center parameter name for an astronomical object. + * + * @param objectIndex astronomical object index + * @return material parameter name + */ + public static String objectCenter(int objectIndex) { + String result = object(objectIndex, "Center"); + return result; + } + + /** + * Build the color parameter name for an astronomical object. + * + * @param objectIndex astronomical object index + * @return material parameter name + */ + public static String objectColor(int objectIndex) { + String result = object(objectIndex, "Color"); + return result; + } + + /** + * Build the color-map parameter name for an astronomical object. + * + * @param objectIndex astronomical object index + * @return material parameter name + */ + public static String objectColorMap(int objectIndex) { + String result = object(objectIndex, "ColorMap"); + return result; + } + + /** + * Build the glow parameter name for an astronomical object. + * + * @param objectIndex astronomical object index + * @return material parameter name + */ + public static String objectGlow(int objectIndex) { + String result = object(objectIndex, "Glow"); + return result; + } + + /** + * Build the horizontal transform parameter name for an object. + * + * @param objectIndex astronomical object index + * @return material parameter name + */ + public static String objectTransformU(int objectIndex) { + String result = object(objectIndex, "TransformU"); + return result; + } + + /** + * Build the vertical transform parameter name for an object. + * + * @param objectIndex astronomical object index + * @return material parameter name + */ + public static String objectTransformV(int objectIndex) { + String result = object(objectIndex, "TransformV"); + return result; + } + + /** + * Build a cloud-layer parameter name. + * + * @param layerIndex cloud layer index + * @param suffix parameter suffix (not null) + * @return material parameter name + */ + private static String cloud(int layerIndex, String suffix) { + assert suffix != null; + String result = String.format( + Locale.ROOT, "Clouds%d%s", layerIndex, suffix); + return result; + } + + /** + * Build an astronomical-object parameter name. + * + * @param objectIndex astronomical object index + * @param suffix parameter suffix (not null) + * @return material parameter name + */ + private static String object(int objectIndex, String suffix) { + assert suffix != null; + String result = String.format( + Locale.ROOT, "Object%d%s", objectIndex, suffix); + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyObjectMaterialSlots.java b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyObjectMaterialSlots.java new file mode 100644 index 0000000..d6a8dc5 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyObjectMaterialSlots.java @@ -0,0 +1,248 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.material; + +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.Vector2f; +import java.io.IOException; + +/** + * Serializable astronomical-object state for sky materials. + * + * @author Take Some + */ +public final class SkyObjectMaterialSlots { + // ************************************************************************* + // fields + + /** + * Sky texture coordinates of each astronomical object's center. + */ + final private Vector2f[] centers; + /** + * Rotation vector of each astronomical object, or null when not meaningful. + */ + final private Vector2f[] rotations; + /** + * Scale factor of each astronomical object. + */ + final private float[] scales; + // ************************************************************************* + // constructors + + /** + * Instantiate empty astronomical-object slots. + * + * @param numObjects number of supported astronomical objects (≥0) + */ + public SkyObjectMaterialSlots(int numObjects) { + this.centers = new Vector2f[numObjects]; + this.rotations = new Vector2f[numObjects]; + this.scales = new float[numObjects]; + } + + /** + * Instantiate astronomical-object slots from serialized state. + * + * @param centers serialized center array (not null, aliased) + * @param rotations serialized rotation array (not null, aliased) + * @param scales serialized scale array (not null, aliased) + */ + private SkyObjectMaterialSlots(Vector2f[] centers, Vector2f[] rotations, + float[] scales) { + assert centers != null; + assert rotations != null; + assert scales != null; + + this.centers = centers; + this.rotations = rotations; + this.scales = scales; + } + // ************************************************************************* + // new methods exposed + + /** + * Add an astronomical object slot if it has not been added yet. + * + * @param objectIndex object index + * @return true if this was the first addition for the object + */ + public boolean addObject(int objectIndex) { + boolean result = !isAdded(objectIndex); + if (result) { + this.centers[objectIndex] = new Vector2f(); + this.rotations[objectIndex] = new Vector2f(); + } + + return result; + } + + /** + * Copy the center coordinates for an astronomical object. + * + * @param objectIndex object index + * @return a new vector + */ + public Vector2f copyCenter(int objectIndex) { + Vector2f result = centers[objectIndex].clone(); + return result; + } + + /** + * Copy the rotation vector for an astronomical object. + * + * @param objectIndex object index + * @return a new vector, or null if rotation is not meaningful + */ + public Vector2f copyRotation(int objectIndex) { + Vector2f vector = rotations[objectIndex]; + if (vector == null) { + return null; + } else { + Vector2f result = vector.clone(); + return result; + } + } + + /** + * Count supported astronomical object slots. + * + * @return number of slots (≥0) + */ + public int count() { + int result = centers.length; + return result; + } + + /** + * Hide an astronomical object by moving its center to hidden coordinates. + * + * @param objectIndex object index + * @param hiddenCoordinates hidden texture coordinates (not null, + * unaffected) + */ + public void hide(int objectIndex, Vector2f hiddenCoordinates) { + assert hiddenCoordinates != null; + centers[objectIndex].set(hiddenCoordinates); + } + + /** + * Test whether an astronomical object slot has been added. + * + * @param objectIndex object index + * @return true if added, otherwise false + */ + public boolean isAdded(int objectIndex) { + boolean result = centers[objectIndex] != null; + return result; + } + + /** + * Read object slots from an input capsule. + * + * @param capsule input capsule (not null) + * @return a new instance + * @throws IOException from capsule + */ + public static SkyObjectMaterialSlots read(InputCapsule capsule) + throws IOException { + assert capsule != null; + + Savable[] sav = capsule.readSavableArray("objectCenters", null); + Vector2f[] centers = new Vector2f[sav.length]; + System.arraycopy(sav, 0, centers, 0, sav.length); + + sav = capsule.readSavableArray("objectRotations", null); + Vector2f[] rotations = new Vector2f[sav.length]; + System.arraycopy(sav, 0, rotations, 0, sav.length); + + float[] scales = capsule.readFloatArray("objectScales", null); + + SkyObjectMaterialSlots result + = new SkyObjectMaterialSlots(centers, rotations, scales); + return result; + } + + /** + * Verify that an astronomical object slot has been added. + * + * @param objectIndex object index + * @throws IllegalStateException if the object has not been added + */ + public void requireAdded(int objectIndex) { + if (!isAdded(objectIndex)) { + throw new IllegalStateException("object not yet added"); + } + } + + /** + * Access the scale for an astronomical object. + * + * @param objectIndex object index + * @return scale factor + */ + public float scale(int objectIndex) { + float result = scales[objectIndex]; + return result; + } + + /** + * Record transform state for an astronomical object. + * + * @param objectIndex object index + * @param centerUV center coordinates (not null, unaffected) + * @param scale desired scale + * @param rotation rotation vector, or null if rotation is not meaningful + */ + public void setTransform(int objectIndex, Vector2f centerUV, float scale, + Vector2f rotation) { + assert centerUV != null; + + this.centers[objectIndex] = centerUV.clone(); + if (rotation == null) { + this.rotations[objectIndex] = null; + } else { + this.rotations[objectIndex] = rotation.clone(); + } + this.scales[objectIndex] = scale; + } + + /** + * Serialize astronomical-object slots to an output capsule. + * + * @param capsule output capsule (not null) + * @throws IOException from capsule + */ + public void write(OutputCapsule capsule) throws IOException { + assert capsule != null; + + capsule.write(centers, "objectCenters", null); + capsule.write(rotations, "objectRotations", null); + capsule.write(scales, "objectScales", null); + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyObjectTransform.java b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyObjectTransform.java new file mode 100644 index 0000000..deead81 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyObjectTransform.java @@ -0,0 +1,202 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.material; + +import com.jme3.math.Vector2f; +import jme3utilities.sky.Constants; + +/** + * Immutable UV transform vectors for an astronomical object projected onto a + * sky dome. + * + * @author Take Some + */ +public final class SkyObjectTransform { + // ************************************************************************* + // fields + + /** + * Horizontal texture-coordinate transform vector. + */ + final private Vector2f transformU; + /** + * Vertical texture-coordinate transform vector. + */ + final private Vector2f transformV; + // ************************************************************************* + // constructors + + /** + * Instantiate an immutable transform from calculated vectors. + * + * @param transformU horizontal transform vector (not null, unaffected) + * @param transformV vertical transform vector (not null, unaffected) + */ + private SkyObjectTransform(Vector2f transformU, Vector2f transformV) { + assert transformU != null; + assert transformV != null; + + this.transformU = transformU.clone(); + this.transformV = transformV.clone(); + } + // ************************************************************************* + // new methods exposed + + /** + * Calculate transform vectors for an astronomical object. + * + * @param centerUV sky texture coordinates for the object center (not null, + * unaffected) + * @param scale ratio of the sky's texture scale to that of the object + * (>0) + * @param rotation (cos, sin) of clockwise rotation angle (unaffected) or + * null if rotation doesn't matter + * @return a new immutable transform + */ + public static SkyObjectTransform from(Vector2f centerUV, float scale, + Vector2f rotation) { + assert centerUV != null; + assert scale > 0f : scale; + + Vector2f transformU = new Vector2f(); + Vector2f transformV = new Vector2f(); + + Vector2f offset = centerUV.subtract(Constants.topUV); + float topDist = offset.length(); + if (topDist > 0f) { + applyDomeDistortion( + topDist, offset, rotation, transformU, transformV); + + } else { + // No UV distortion at the top of the dome. + transformU.set(1f, 0f); + transformV.set(0f, 1f); + } + + if (rotation != null) { + applyObjectRotation(rotation, transformU, transformV); + } + + // Scale by object scale. + transformU.divideLocal(scale); + transformV.divideLocal(scale); + + SkyObjectTransform result + = new SkyObjectTransform(transformU, transformV); + return result; + } + + /** + * Copy the horizontal texture-coordinate transform vector. + * + * @return a new vector + */ + public Vector2f copyTransformU() { + Vector2f result = transformU.clone(); + return result; + } + + /** + * Copy the vertical texture-coordinate transform vector. + * + * @return a new vector + */ + public Vector2f copyTransformV() { + Vector2f result = transformV.clone(); + return result; + } + // ************************************************************************* + // private methods + + /** + * Apply dome UV distortion compensation near the horizon. + * + * @param topDist UV distance from the top of the dome (>0) + * @param offset UV offset from the top of the dome (not null, unaffected) + * @param rotation rotation vector, or null + * @param transformU storage for horizontal transform (not null, modified) + * @param transformV storage for vertical transform (not null, modified) + */ + private static void applyDomeDistortion(float topDist, Vector2f offset, + Vector2f rotation, Vector2f transformU, Vector2f transformV) { + assert topDist > 0f : topDist; + assert offset != null; + assert transformU != null; + assert transformV != null; + + Vector2f tU = new Vector2f(); + Vector2f tV = new Vector2f(); + + /* + * Stretch the image horizontally to compensate for UV distortion near + * the horizon. + */ + float a = offset.x / topDist; + float b = offset.y / topDist; + tU.set(b, -a); + tV.set(a, b); + + float stretchFactor + = 1f + Constants.stretchCoefficient * topDist * topDist; + tU.divideLocal(stretchFactor); + + if (rotation != null) { + transformU.set(tU.x * b + tV.x * a, tU.y * b + tV.y * a); + transformV.set(tV.x * b - tU.x * a, tV.y * b - tU.y * a); + } else { + transformU.set(tU); + transformV.set(tV); + } + } + + /** + * Apply object-local clockwise rotation. + * + * @param rotation rotation vector (not null, unaffected) + * @param transformU storage for horizontal transform (not null, modified) + * @param transformV storage for vertical transform (not null, modified) + */ + private static void applyObjectRotation(Vector2f rotation, + Vector2f transformU, Vector2f transformV) { + assert rotation != null; + assert transformU != null; + assert transformV != null; + + Vector2f tU = new Vector2f(); + Vector2f tV = new Vector2f(); + + // Rotate so top is toward the north horizon. + tU.set(transformV); + tV.set(-transformU.x, -transformU.y); + + // Rotate by the requested rotation vector. + Vector2f norm = rotation.normalize(); + transformU.set(tU.x * norm.x + tV.x * norm.y, + tU.y * norm.x + tV.y * norm.y); + transformV.set(tV.x * norm.x - tU.x * norm.y, + tV.y * norm.x - tU.y * norm.y); + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyTextureSampler.java b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyTextureSampler.java new file mode 100644 index 0000000..a4482bb --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyTextureSampler.java @@ -0,0 +1,91 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.material; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.texture.image.ImageRaster; +import jme3utilities.sky.Constants; + +/** + * Texture sampling utilities for sky materials. + * + * @author Take Some + */ +public final class SkyTextureSampler { + /** + * Hidden constructor. + */ + private SkyTextureSampler() { + // do nothing + } + + /** + * Sample the red component of a rasterized texture. + * + * @param colorImage the texture to sample (not null, unaffected) + * @param uv texture coordinates to sample (not null, unaffected) + * @return red intensity (≤1, ≥0) + */ + public static float sampleRed(ImageRaster colorImage, Vector2f uv) { + assert colorImage != null; + assert uv != null; + float u = uv.x; + float v = uv.y; + assert u >= Constants.uvMin : uv; + assert u < Constants.uvMax : uv; + assert v >= Constants.uvMin : uv; + assert v < Constants.uvMax : uv; + + int width = colorImage.getWidth(); + float x = u * width; + int x0 = (int) FastMath.floor(x); + float xFraction1 = x - x0; + float xFraction0 = 1 - xFraction1; + int x1 = (x0 + 1) % width; + + int height = colorImage.getHeight(); + float y = v * width; + int y0 = (int) FastMath.floor(y); + float yFraction1 = y - y0; + float yFraction0 = 1 - yFraction1; + int y1 = (y0 + 1) % height; + + float r00 = colorImage.getPixel(x0, y0).r; + float r01 = colorImage.getPixel(x1, y0).r; + float r10 = colorImage.getPixel(x0, y1).r; + float r11 = colorImage.getPixel(x1, y1).r; + + float result = r00 * xFraction0 * yFraction0 + + r01 * xFraction0 * yFraction1 + + r10 * xFraction1 * yFraction0 + + r11 * xFraction1 * yFraction1; + + assert result >= 0f : result; + assert result <= 1f : result; + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/material/package-info.java b/SkyLibrary/src/main/java/jme3utilities/sky/material/package-info.java new file mode 100644 index 0000000..2219c0d --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/material/package-info.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Sky material parameter, transform, and texture-sampling helpers. + */ +package jme3utilities.sky.material; diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyEnvironmentRuntime.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyEnvironmentRuntime.java new file mode 100644 index 0000000..2a2ab8b --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyEnvironmentRuntime.java @@ -0,0 +1,448 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import jme3utilities.Validate; +import jme3utilities.sky.cloud.SkyCloudPreset; +import jme3utilities.sky.cloud.SkyCloudPresetDefinition; + +/** + * Game-facing sky environment runtime. + *

+ * This object bridges existing visual sky controls with world simulation code: + * weather changes still drive cloud presets, while games can read visibility, + * precipitation, wind, light levels, and snapshots. Weather changes are also + * published as typed events so gameplay, AI, fog-of-war, UI, and audio systems + * can subscribe to exactly the states they care about. + * + * @author Take Some + */ +final public class SkyEnvironmentRuntime { + /** World clock facade. */ + final private SkyWorldClock clock; + /** Optional weather sink implemented by the visual sky control. */ + final private WeatherApplier weatherApplier; + /** Weather subscription registry and dispatcher. */ + final private SkyWeatherSubscriptionRegistry weatherSubscriptions + = new SkyWeatherSubscriptionRegistry(); + /** Latest lighting output. */ + private SkyLightingSnapshot lightingSnapshot = SkyLightingSnapshot.empty(); + /** Current weather state. */ + private SkyWeatherState weatherState = SkyWeatherState.fair(); + + /** + * Instantiate a standalone environment runtime. + */ + public SkyEnvironmentRuntime() { + this(null, null); + } + + /** + * Instantiate an environment runtime. + * + * @param timeApplier optional sink for clock changes + * @param weatherApplier optional sink for weather changes + */ + public SkyEnvironmentRuntime(SkyWorldClock.TimeApplier timeApplier, + WeatherApplier weatherApplier) { + this.clock = new SkyWorldClock(timeApplier); + this.weatherApplier = weatherApplier; + } + + /** + * Return average ambient light level. + * + * @return average ambient channel intensity + */ + public float ambientLightLevel() { + ColorRGBA ambient = lightingSnapshot.ambientColor(null); + float result = (ambient.r + ambient.g + ambient.b) / 3f; + return result; + } + + /** + * Remove all weather subscriptions. + */ + public void clearWeatherSubscriptions() { + weatherSubscriptions.clear(); + } + + /** + * Access the world clock facade. + * + * @return pre-existing clock + */ + public SkyWorldClock clock() { + return clock; + } + + /** + * Test whether the current lighting state is night-like. + * + * @return true if the sun is not above the horizon + */ + public boolean isNight() { + return !lightingSnapshot.sunUp(); + } + + /** + * Test whether the current weather is storm-like. + * + * @return true if storm-like + */ + public boolean isStorm() { + return weatherState.isStorm(); + } + + /** + * Copy the latest lighting snapshot. + * + * @return copied lighting snapshot + */ + public SkyLightingSnapshot lighting() { + return lightingSnapshot.copy(); + } + + /** + * Copy the main light direction. + * + * @param storeResult storage for the result (modified if not null) + * @return copied direction + */ + public Vector3f mainLightDirection(Vector3f storeResult) { + return lightingSnapshot.mainDirection(storeResult); + } + + /** + * Return precipitation intensity. + * + * @return precipitation fraction + */ + public float precipitation() { + return weatherState.precipitation(); + } + + /** + * Remove all subscriptions owned by the specified listener. + * + * @param listener listener to remove (not null) + * @return number of removed subscriptions + */ + public int removeWeatherListener(SkyWeatherListener listener) { + Validate.nonNull(listener, "listener"); + + int result = weatherSubscriptions.removeListener(listener); + return result; + } + + /** + * Restore weather state without applying a cloud transition. + *

+ * This is used when cloning or loading runtime state that has already been + * applied to cloud layers. + * + * @param state weather state to restore (not null, unaffected) + */ + public void restoreWeather(SkyWeatherState state) { + Validate.nonNull(state, "state"); + + SkyWeatherState previous = weatherState.copy(); + this.weatherState = state.copy(); + publishWeatherChange(previous, weatherState, 0f, + SkyWeatherChangeSource.RESTORE); + } + + /** + * Change weather using a built-in cloud preset. + * + * @param preset target cloud preset (not null) + * @param seconds transition duration in seconds (≥0) + */ + public void setWeather(SkyCloudPreset preset, float seconds) { + Validate.nonNull(preset, "preset"); + Validate.nonNegative(seconds, "seconds"); + + SkyWeatherState previous = weatherState.copy(); + this.weatherState = SkyWeatherState.fromPreset(preset); + if (weatherApplier != null) { + weatherApplier.applyWeather(preset, seconds); + } + publishWeatherChange(previous, weatherState, seconds, + SkyWeatherChangeSource.PRESET); + } + + /** + * Change weather using a data-driven cloud preset definition. + * + * @param definition target preset definition (not null, unaffected) + * @param seconds transition duration in seconds (≥0) + */ + public void setWeather(SkyCloudPresetDefinition definition, + float seconds) { + Validate.nonNull(definition, "definition"); + Validate.nonNegative(seconds, "seconds"); + + SkyWeatherState previous = weatherState.copy(); + this.weatherState = SkyWeatherState.fromDefinition(definition); + if (weatherApplier != null) { + weatherApplier.applyWeather(definition, seconds); + } + publishWeatherChange(previous, weatherState, seconds, + SkyWeatherChangeSource.DEFINITION); + } + + /** + * Change weather state. + * + * @param state target state (not null, unaffected) + * @param seconds transition duration in seconds (≥0) + */ + public void setWeather(SkyWeatherState state, float seconds) { + Validate.nonNull(state, "state"); + Validate.nonNegative(seconds, "seconds"); + + SkyWeatherState previous = weatherState.copy(); + this.weatherState = state.copy(); + if (weatherApplier != null && state.cloudPreset() != null) { + weatherApplier.applyWeather(state.cloudPreset(), seconds); + } + publishWeatherChange(previous, weatherState, seconds, + SkyWeatherChangeSource.STATE); + } + + /** + * Create a full environment snapshot. + * + * @return new snapshot + */ + public SkyEnvironmentSnapshot snapshot() { + SkyEnvironmentSnapshot result = new SkyEnvironmentSnapshot( + clock.timeOfDayHours(), weatherState, lightingSnapshot); + return result; + } + + /** + * Subscribe to every weather-state change. + * + * @param listener callback (not null) + * @return subscription handle + */ + public SkyWeatherSubscription subscribeWeather( + SkyWeatherListener listener) { + SkyWeatherSubscription result = subscribeWeather( + SkyWeatherFilters.any(), listener, false); + return result; + } + + /** + * Subscribe to every weather-state change. + * + * @param listener callback (not null) + * @param notifyCurrent true to immediately replay current weather if it + * matches + * @return subscription handle + */ + public SkyWeatherSubscription subscribeWeather( + SkyWeatherListener listener, boolean notifyCurrent) { + SkyWeatherSubscription result = subscribeWeather( + SkyWeatherFilters.any(), listener, notifyCurrent); + return result; + } + + /** + * Subscribe to a stable weather id, case-insensitively. + * + * @param weatherId weather id (not null, not empty) + * @param listener callback (not null) + * @return subscription handle + */ + public SkyWeatherSubscription subscribeWeather(String weatherId, + SkyWeatherListener listener) { + SkyWeatherSubscription result = subscribeWeather( + SkyWeatherFilters.id(weatherId), listener, false); + return result; + } + + /** + * Subscribe to a stable weather id, case-insensitively. + * + * @param weatherId weather id (not null, not empty) + * @param listener callback (not null) + * @param notifyCurrent true to immediately replay current weather if it + * matches + * @return subscription handle + */ + public SkyWeatherSubscription subscribeWeather(String weatherId, + SkyWeatherListener listener, boolean notifyCurrent) { + SkyWeatherSubscription result = subscribeWeather( + SkyWeatherFilters.id(weatherId), listener, notifyCurrent); + return result; + } + + /** + * Subscribe to weather states matching the specified filter. + * + * @param filter filter to apply to new/current state (not null) + * @param listener callback (not null) + * @return subscription handle + */ + public SkyWeatherSubscription subscribeWeather(SkyWeatherFilter filter, + SkyWeatherListener listener) { + SkyWeatherSubscription result = subscribeWeather( + filter, listener, false); + return result; + } + + /** + * Subscribe to weather states matching the specified filter. + * + * @param filter filter to apply to new/current state (not null) + * @param listener callback (not null) + * @param notifyCurrent true to immediately replay current weather if it + * matches + * @return subscription handle + */ + public SkyWeatherSubscription subscribeWeather(SkyWeatherFilter filter, + SkyWeatherListener listener, boolean notifyCurrent) { + Validate.nonNull(filter, "filter"); + Validate.nonNull(listener, "listener"); + + SkyWeatherSubscription result = weatherSubscriptions.subscribe( + this, filter, listener, weatherState, notifyCurrent); + return result; + } + + /** + * Copy the main light direction as a sun-direction proxy. + * + * @param storeResult storage for the result (modified if not null) + * @return copied direction + */ + public Vector3f sunDirection(Vector3f storeResult) { + return mainLightDirection(storeResult); + } + + /** + * Unsubscribe a weather subscription. + * + * @param subscription subscription handle (not null) + * @return true if an active subscription was removed + */ + public boolean unsubscribeWeather( + SkyWeatherSubscription subscription) { + Validate.nonNull(subscription, "subscription"); + if (subscription.owner() != this) { + return false; + } + + boolean result = weatherSubscriptions.unsubscribe(subscription); + return result; + } + + /** + * Update the latest lighting snapshot. + * + * @param snapshot new lighting state (not null, unaffected) + */ + public void updateLighting(SkyLightingSnapshot snapshot) { + Validate.nonNull(snapshot, "snapshot"); + + this.lightingSnapshot = snapshot.copy(); + } + + /** + * Return visibility multiplier. + * + * @return visibility fraction + */ + public float visibility() { + return weatherState.visibility(); + } + + /** + * Copy current weather state. + * + * @return copied weather state + */ + public SkyWeatherState weather() { + return weatherState.copy(); + } + + /** + * Return the number of active weather subscriptions. + * + * @return subscription count + */ + public int weatherSubscriptionCount() { + return weatherSubscriptions.size(); + } + + /** + * Return wind intensity. + * + * @return wind strength fraction + */ + public float windStrength() { + return weatherState.windStrength(); + } + + /** + * Publish a weather-state change. + * + * @param previous previous state + * @param current current state + * @param seconds requested transition duration + * @param source source category + */ + private void publishWeatherChange(SkyWeatherState previous, + SkyWeatherState current, float seconds, + SkyWeatherChangeSource source) { + weatherSubscriptions.publish(previous, current, seconds, source); + } + + /** + * Sink for applying visual cloud preset changes. + */ + public interface WeatherApplier { + /** + * Apply cloud preset weather to the visual sky. + * + * @param preset target cloud preset + * @param seconds transition duration in seconds + */ + void applyWeather(SkyCloudPreset preset, float seconds); + + /** + * Apply data-driven cloud preset weather to the visual sky. + * + * @param definition target preset definition + * @param seconds transition duration in seconds + */ + void applyWeather(SkyCloudPresetDefinition definition, float seconds); + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyEnvironmentSnapshot.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyEnvironmentSnapshot.java new file mode 100644 index 0000000..9d27e5e --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyEnvironmentSnapshot.java @@ -0,0 +1,200 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +import com.jme3.math.ColorRGBA; +import jme3utilities.Validate; +import jme3utilities.sky.cloud.SkyCloudPreset; + +/** + * Immutable game-facing snapshot of sky, weather, and lighting state. + * + * @author Take Some + */ +final public class SkyEnvironmentSnapshot { + /** Ambient light color. */ + final private ColorRGBA ambient; + /** Recommended bloom intensity. */ + final private float bloom; + /** Current cloud preset. */ + final private SkyCloudPreset cloudPreset; + /** Approximate cloud coverage. */ + final private float cloudiness; + /** Main directional light color. */ + final private ColorRGBA mainLight; + /** Precipitation intensity. */ + final private float precipitation; + /** Recommended shadow intensity. */ + final private float shadowIntensity; + /** Time of day in hours. */ + final private float timeOfDayHours; + /** Visibility multiplier. */ + final private float visibility; + /** Wind intensity. */ + final private float windStrength; + /** Stable weather id. */ + final private String weatherId; + + /** + * Instantiate an environment snapshot. + * + * @param timeOfDayHours time of day in hours + * @param weather weather state (not null, unaffected) + * @param lighting lighting snapshot (not null, unaffected) + */ + public SkyEnvironmentSnapshot(float timeOfDayHours, + SkyWeatherState weather, SkyLightingSnapshot lighting) { + Validate.nonNull(weather, "weather"); + Validate.nonNull(lighting, "lighting"); + + this.timeOfDayHours = timeOfDayHours; + this.cloudPreset = weather.cloudPreset(); + this.weatherId = weather.id(); + this.cloudiness = weather.cloudiness(); + this.visibility = weather.visibility(); + this.precipitation = weather.precipitation(); + this.windStrength = weather.windStrength(); + this.ambient = lighting.ambientColor(null); + this.mainLight = lighting.mainDirectionalColor(null); + this.bloom = lighting.bloomIntensity(); + this.shadowIntensity = lighting.shadowIntensity(); + } + + /** + * Copy the ambient light color. + * + * @param storeResult storage for the result (modified if not null) + * @return copied color + */ + public ColorRGBA ambient(ColorRGBA storeResult) { + return copyColor(ambient, storeResult); + } + + /** + * Return the recommended bloom intensity. + * + * @return bloom intensity + */ + public float bloom() { + return bloom; + } + + /** + * Return the cloud preset. + * + * @return cloud preset + */ + public SkyCloudPreset cloudPreset() { + return cloudPreset; + } + + /** + * Return approximate cloud coverage. + * + * @return cloudiness fraction + */ + public float cloudiness() { + return cloudiness; + } + + /** + * Copy the main light color. + * + * @param storeResult storage for the result (modified if not null) + * @return copied color + */ + public ColorRGBA mainLight(ColorRGBA storeResult) { + return copyColor(mainLight, storeResult); + } + + /** + * Return precipitation intensity. + * + * @return precipitation fraction + */ + public float precipitation() { + return precipitation; + } + + /** + * Return the recommended shadow intensity. + * + * @return shadow intensity + */ + public float shadowIntensity() { + return shadowIntensity; + } + + /** + * Return time of day in hours. + * + * @return hours since midnight + */ + public float timeOfDayHours() { + return timeOfDayHours; + } + + /** + * Return the visibility multiplier. + * + * @return visibility fraction + */ + public float visibility() { + return visibility; + } + + /** + * Return the stable weather id. + * + * @return weather id + */ + public String weatherId() { + return weatherId; + } + + /** + * Return wind intensity. + * + * @return wind strength fraction + */ + public float windStrength() { + return windStrength; + } + + /** + * Copy a color. + * + * @param source source color (not null) + * @param storeResult storage for the result (modified if not null) + * @return copied color + */ + private static ColorRGBA copyColor( + ColorRGBA source, ColorRGBA storeResult) { + ColorRGBA result = storeResult == null ? new ColorRGBA() : storeResult; + result.set(source); + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyLightingSnapshot.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyLightingSnapshot.java new file mode 100644 index 0000000..37f5e26 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyLightingSnapshot.java @@ -0,0 +1,195 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import jme3utilities.Validate; + +/** + * Immutable copy of the latest sky lighting output. + *

+ * Game code can read this snapshot without coupling directly to the internal + * {@code Updater} pipeline. + * + * @author Take Some + */ +final public class SkyLightingSnapshot { + /** Shared empty snapshot used before the first lighting update. */ + final private static SkyLightingSnapshot emptySnapshot + = new SkyLightingSnapshot( + ColorRGBA.Black, ColorRGBA.Black, ColorRGBA.Black, + Vector3f.UNIT_Y, new SkyLightingState(0f, 0f, false, + false)); + + /** Ambient light color. */ + final private ColorRGBA ambientColor; + /** Viewport/background color derived from the sky. */ + final private ColorRGBA backgroundColor; + /** Main directional light color. */ + final private ColorRGBA mainLightColor; + /** Main light direction. */ + final private Vector3f mainDirection; + /** Scalar lighting state and horizon flags. */ + final private SkyLightingState state; + + /** + * Instantiate a snapshot. + * + * @param ambientColor ambient light color (not null, unaffected) + * @param backgroundColor viewport/background color (not null, unaffected) + * @param mainLightColor main light color (not null, unaffected) + * @param mainDirection main light direction (not null, unaffected) + * @param state scalar lighting state (not null, unaffected) + */ + public SkyLightingSnapshot(ColorRGBA ambientColor, + ColorRGBA backgroundColor, ColorRGBA mainLightColor, + Vector3f mainDirection, SkyLightingState state) { + Validate.nonNull(ambientColor, "ambient color"); + Validate.nonNull(backgroundColor, "background color"); + Validate.nonNull(mainLightColor, "main color"); + Validate.nonNull(mainDirection, "main direction"); + Validate.nonNull(state, "state"); + + this.ambientColor = ambientColor.clone(); + this.backgroundColor = backgroundColor.clone(); + this.mainLightColor = mainLightColor.clone(); + this.mainDirection = mainDirection.clone(); + this.state = state.copy(); + } + + /** + * Return an empty pre-update snapshot. + * + * @return shared immutable snapshot + */ + public static SkyLightingSnapshot empty() { + return emptySnapshot; + } + + /** + * Copy the ambient light color. + * + * @param storeResult storage for the result (modified if not null) + * @return copied color + */ + public ColorRGBA ambientColor(ColorRGBA storeResult) { + return copyColor(ambientColor, storeResult); + } + + /** + * Copy the background color. + * + * @param storeResult storage for the result (modified if not null) + * @return copied color + */ + public ColorRGBA backgroundColor(ColorRGBA storeResult) { + return copyColor(backgroundColor, storeResult); + } + + /** + * Return the recommended bloom intensity. + * + * @return bloom intensity + */ + public float bloomIntensity() { + return state.bloomIntensity(); + } + + /** + * Copy this snapshot. + * + * @return a new equivalent snapshot + */ + public SkyLightingSnapshot copy() { + SkyLightingSnapshot result = new SkyLightingSnapshot(ambientColor, + backgroundColor, mainLightColor, mainDirection, state); + return result; + } + + /** + * Copy the main directional light color. + * + * @param storeResult storage for the result (modified if not null) + * @return copied color + */ + public ColorRGBA mainDirectionalColor(ColorRGBA storeResult) { + return copyColor(mainLightColor, storeResult); + } + + /** + * Copy the main light direction. + * + * @param storeResult storage for the result (modified if not null) + * @return copied direction + */ + public Vector3f mainDirection(Vector3f storeResult) { + Vector3f result = storeResult == null ? new Vector3f() : storeResult; + result.set(mainDirection); + return result; + } + + /** + * Test whether the moon is above the horizon. + * + * @return true if the moon is up + */ + public boolean moonUp() { + return state.moonUp(); + } + + /** + * Return the recommended shadow intensity. + * + * @return shadow intensity + */ + public float shadowIntensity() { + return state.shadowIntensity(); + } + + /** + * Test whether the sun is above the horizon. + * + * @return true if the sun is up + */ + public boolean sunUp() { + return state.sunUp(); + } + + /** + * Copy a color. + * + * @param source source color (not null) + * @param storeResult storage for the result (modified if not null) + * @return copied color + */ + private static ColorRGBA copyColor( + ColorRGBA source, ColorRGBA storeResult) { + ColorRGBA result = storeResult == null ? new ColorRGBA() : storeResult; + result.set(source); + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyLightingState.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyLightingState.java new file mode 100644 index 0000000..7452445 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyLightingState.java @@ -0,0 +1,110 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +import jme3utilities.Validate; + +/** + * Immutable scalar and horizon flags for a lighting snapshot. + * + * @author Take Some + */ +final public class SkyLightingState { + /** Recommended bloom intensity. */ + final private float bloomIntensity; + /** True if the moon is above the horizon. */ + final private boolean moonUp; + /** Recommended shadow intensity. */ + final private float shadowIntensity; + /** True if the sun is above the horizon. */ + final private boolean sunUp; + + /** + * Instantiate lighting state. + * + * @param bloomIntensity recommended bloom intensity (≥0) + * @param shadowIntensity recommended shadow intensity (≤1, ≥0) + * @param sunUp true if the sun is above the horizon + * @param moonUp true if the moon is above the horizon + */ + public SkyLightingState(float bloomIntensity, float shadowIntensity, + boolean sunUp, boolean moonUp) { + Validate.nonNegative(bloomIntensity, "bloom intensity"); + Validate.fraction(shadowIntensity, "shadow intensity"); + + this.bloomIntensity = bloomIntensity; + this.shadowIntensity = shadowIntensity; + this.sunUp = sunUp; + this.moonUp = moonUp; + } + + /** + * Return the recommended bloom intensity. + * + * @return bloom intensity + */ + public float bloomIntensity() { + return bloomIntensity; + } + + /** + * Copy this state. + * + * @return new equivalent state + */ + public SkyLightingState copy() { + SkyLightingState result = new SkyLightingState( + bloomIntensity, shadowIntensity, sunUp, moonUp); + return result; + } + + /** + * Test whether the moon is above the horizon. + * + * @return true if the moon is up + */ + public boolean moonUp() { + return moonUp; + } + + /** + * Return the recommended shadow intensity. + * + * @return shadow intensity + */ + public float shadowIntensity() { + return shadowIntensity; + } + + /** + * Test whether the sun is above the horizon. + * + * @return true if the sun is up + */ + public boolean sunUp() { + return sunUp; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherChangeSource.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherChangeSource.java new file mode 100644 index 0000000..d0e49be --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherChangeSource.java @@ -0,0 +1,44 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +/** + * Source category for a weather-state change. + * + * @author Take Some + */ +public enum SkyWeatherChangeSource { + /** Current weather was emitted to a new subscriber. */ + CURRENT, + /** Weather was restored without driving a visual transition. */ + RESTORE, + /** Weather came from a built-in cloud preset. */ + PRESET, + /** Weather came from a data-driven preset definition. */ + DEFINITION, + /** Weather came from an explicit runtime state object. */ + STATE +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherEvent.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherEvent.java new file mode 100644 index 0000000..4c4d87f --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherEvent.java @@ -0,0 +1,155 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +import jme3utilities.Validate; + +/** + * Immutable weather-state event delivered to subscribers. + * + * @author Take Some + */ +final public class SkyWeatherEvent { + /** New/current weather state. */ + final private SkyWeatherState current; + /** Previous weather state. */ + final private SkyWeatherState previous; + /** Monotonic runtime-local event sequence. */ + final private long sequence; + /** Source category for the change. */ + final private SkyWeatherChangeSource source; + /** Requested visual transition duration. */ + final private float transitionSeconds; + + /** + * Instantiate an event. + * + * @param sequence monotonic runtime-local sequence number (≥0) + * @param previous previous weather state (not null, unaffected) + * @param current current weather state (not null, unaffected) + * @param transitionSeconds requested transition duration (≥0) + * @param source source category (not null) + */ + public SkyWeatherEvent(long sequence, SkyWeatherState previous, + SkyWeatherState current, float transitionSeconds, + SkyWeatherChangeSource source) { + if (sequence < 0L) { + throw new IllegalArgumentException( + "sequence must be non-negative"); + } + Validate.nonNull(previous, "previous weather"); + Validate.nonNull(current, "current weather"); + Validate.nonNegative(transitionSeconds, "seconds"); + Validate.nonNull(source, "source"); + + this.sequence = sequence; + this.previous = previous.copy(); + this.current = current.copy(); + this.transitionSeconds = transitionSeconds; + this.source = source; + } + + /** + * Copy the new/current weather state. + * + * @return copied state + */ + public SkyWeatherState currentWeather() { + return current.copy(); + } + + /** + * Return the new/current weather id. + * + * @return weather id + */ + public String currentWeatherId() { + return current.id(); + } + + /** + * Test whether the event represents current-state replay. + * + * @return true for replay, false for a real state transition + */ + public boolean isCurrentReplay() { + return source == SkyWeatherChangeSource.CURRENT; + } + + /** + * Copy the previous weather state. + * + * @return copied state + */ + public SkyWeatherState previousWeather() { + return previous.copy(); + } + + /** + * Return the previous weather id. + * + * @return weather id + */ + public String previousWeatherId() { + return previous.id(); + } + + /** + * Return the runtime-local sequence number. + * + * @return sequence number + */ + public long sequence() { + return sequence; + } + + /** + * Return the source category. + * + * @return source category + */ + public SkyWeatherChangeSource source() { + return source; + } + + /** + * Return the requested visual transition duration. + * + * @return duration in seconds + */ + public float transitionSeconds() { + return transitionSeconds; + } + + @Override + public String toString() { + return "SkyWeatherEvent[sequence=" + sequence + + ", previous=" + previous.id() + + ", current=" + current.id() + + ", seconds=" + transitionSeconds + + ", source=" + source + "]"; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherFilter.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherFilter.java new file mode 100644 index 0000000..404eca0 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherFilter.java @@ -0,0 +1,41 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +/** + * Predicate used by weather subscriptions. + * + * @author Take Some + */ +public interface SkyWeatherFilter { + /** + * Test whether the specified weather state should be delivered. + * + * @param state weather state to test (not null) + * @return true to deliver the event to the subscriber + */ + boolean matches(SkyWeatherState state); +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherFilters.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherFilters.java new file mode 100644 index 0000000..48958d9 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherFilters.java @@ -0,0 +1,191 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +import java.util.Locale; +import jme3utilities.Validate; +import jme3utilities.sky.cloud.SkyCloudPreset; + +/** + * Factory methods for common weather subscription filters. + * + * @author Take Some + */ +final public class SkyWeatherFilters { + /** Filter that accepts all weather states. */ + final private static SkyWeatherFilter any = new SkyWeatherFilter() { + @Override + public boolean matches(SkyWeatherState state) { + return true; + } + + @Override + public String toString() { + return "any-weather"; + } + }; + + /** Hidden constructor. */ + private SkyWeatherFilters() { + // do nothing + } + + /** + * Match any weather state. + * + * @return reusable filter + */ + public static SkyWeatherFilter any() { + return any; + } + + /** + * Match states with at least the specified cloudiness. + * + * @param minCloudiness minimum cloudiness fraction (≤1, ≥0) + * @return new filter + */ + public static SkyWeatherFilter cloudy(float minCloudiness) { + Validate.fraction(minCloudiness, "minimum cloudiness"); + return new SkyWeatherFilter() { + @Override + public boolean matches(SkyWeatherState state) { + return state.cloudiness() >= minCloudiness; + } + + @Override + public String toString() { + return "cloudiness>=" + minCloudiness; + } + }; + } + + /** + * Match a stable weather id case-insensitively. + * + * @param weatherId weather id (not null, not empty) + * @return new filter + */ + public static SkyWeatherFilter id(String weatherId) { + Validate.nonEmpty(weatherId, "weather id"); + final String normalized = weatherId.trim().toLowerCase(Locale.ROOT); + return new SkyWeatherFilter() { + @Override + public boolean matches(SkyWeatherState state) { + return state.id().toLowerCase(Locale.ROOT).equals(normalized) + || state.presetId().toLowerCase(Locale.ROOT) + .equals(normalized); + } + + @Override + public String toString() { + return "weather-id=" + normalized; + } + }; + } + + /** + * Match states with at least the specified precipitation. + * + * @param minIntensity minimum precipitation fraction (≤1, ≥0) + * @return new filter + */ + public static SkyWeatherFilter precipitating(float minIntensity) { + Validate.fraction(minIntensity, "minimum precipitation"); + return new SkyWeatherFilter() { + @Override + public boolean matches(SkyWeatherState state) { + return state.precipitation() >= minIntensity; + } + + @Override + public String toString() { + return "precipitation>=" + minIntensity; + } + }; + } + + /** + * Match a built-in cloud preset. + * + * @param preset built-in preset (not null) + * @return new filter + */ + public static SkyWeatherFilter preset(SkyCloudPreset preset) { + Validate.nonNull(preset, "preset"); + return new SkyWeatherFilter() { + @Override + public boolean matches(SkyWeatherState state) { + return state.cloudPreset() == preset; + } + + @Override + public String toString() { + return "preset=" + preset.name(); + } + }; + } + + /** + * Match storm-like weather. + * + * @return new filter + */ + public static SkyWeatherFilter storm() { + return new SkyWeatherFilter() { + @Override + public boolean matches(SkyWeatherState state) { + return state.isStorm(); + } + + @Override + public String toString() { + return "storm-like"; + } + }; + } + + /** + * Match states with at least the specified wind strength. + * + * @param minStrength minimum wind strength fraction (≤1, ≥0) + * @return new filter + */ + public static SkyWeatherFilter windy(float minStrength) { + Validate.fraction(minStrength, "minimum wind strength"); + return new SkyWeatherFilter() { + @Override + public boolean matches(SkyWeatherState state) { + return state.windStrength() >= minStrength; + } + + @Override + public String toString() { + return "wind>=" + minStrength; + } + }; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherListener.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherListener.java new file mode 100644 index 0000000..556d0d0 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherListener.java @@ -0,0 +1,40 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +/** + * Listener for game-facing weather-state events. + * + * @author Take Some + */ +public interface SkyWeatherListener { + /** + * Receive a weather-state event. + * + * @param event immutable event payload (not null) + */ + void onWeatherChanged(SkyWeatherEvent event); +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherMetrics.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherMetrics.java new file mode 100644 index 0000000..4760a47 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherMetrics.java @@ -0,0 +1,135 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +import jme3utilities.Validate; + +/** + * Immutable game-facing weather metrics. + * + * @author Take Some + */ +final public class SkyWeatherMetrics { + /** Approximate visible cloud coverage. */ + final private float cloudiness; + /** Lightning probability/intensity hint. */ + final private float lightningChance; + /** Precipitation intensity. */ + final private float precipitation; + /** Visibility multiplier exposed to game simulation. */ + final private float visibility; + /** Wind intensity. */ + final private float windStrength; + + /** + * Instantiate weather metrics. + * + * @param cloudiness visible cloud coverage (≤1, ≥0) + * @param visibility visibility multiplier (≤1, ≥0) + * @param precipitation precipitation intensity (≤1, ≥0) + * @param windStrength wind intensity (≤1, ≥0) + * @param lightningChance lightning chance/intensity (≤1, ≥0) + */ + public SkyWeatherMetrics(float cloudiness, float visibility, + float precipitation, float windStrength, float lightningChance) { + Validate.fraction(cloudiness, "cloudiness"); + Validate.fraction(visibility, "visibility"); + Validate.fraction(precipitation, "precipitation"); + Validate.fraction(windStrength, "wind strength"); + Validate.fraction(lightningChance, "lightning chance"); + + this.cloudiness = cloudiness; + this.visibility = visibility; + this.precipitation = precipitation; + this.windStrength = windStrength; + this.lightningChance = lightningChance; + } + + /** + * Return approximate cloud coverage. + * + * @return cloudiness fraction + */ + public float cloudiness() { + return cloudiness; + } + + /** + * Copy this metrics object. + * + * @return new equivalent metrics + */ + public SkyWeatherMetrics copy() { + SkyWeatherMetrics result = new SkyWeatherMetrics(cloudiness, + visibility, precipitation, windStrength, lightningChance); + return result; + } + + /** + * Return lightning probability/intensity hint. + * + * @return lightning chance + */ + public float lightningChance() { + return lightningChance; + } + + /** + * Return precipitation intensity. + * + * @return precipitation fraction + */ + public float precipitation() { + return precipitation; + } + + /** + * Return visibility multiplier exposed to simulation code. + * + * @return visibility fraction + */ + public float visibility() { + return visibility; + } + + /** + * Return wind intensity. + * + * @return wind strength fraction + */ + public float windStrength() { + return windStrength; + } + + @Override + public String toString() { + return "SkyWeatherMetrics[cloudiness=" + cloudiness + + ", visibility=" + visibility + + ", precipitation=" + precipitation + + ", windStrength=" + windStrength + + ", lightningChance=" + lightningChance + "]"; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherState.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherState.java new file mode 100644 index 0000000..789b570 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherState.java @@ -0,0 +1,312 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +import jme3utilities.Validate; +import jme3utilities.sky.cloud.SkyCloudPreset; +import jme3utilities.sky.cloud.SkyCloudPresetDefinition; + +/** + * Immutable weather state exported by the sky runtime. + *

+ * This state intentionally describes game-facing conditions instead of only + * cloud textures. It can drive visibility, precipitation, wind, and storm logic + * outside the renderer. + * + * @author Take Some + */ +final public class SkyWeatherState { + /** Built-in cloud preset, or null for ABI/custom presets. */ + final private SkyCloudPreset cloudPreset; + /** Stable weather id. */ + final private String id; + /** Game-facing weather metrics. */ + final private SkyWeatherMetrics metrics; + + /** + * Instantiate weather state. + * + * @param id stable weather id (not null, not empty) + * @param cloudPreset cloud preset (not null) + * @param metrics game-facing metrics (not null, unaffected) + */ + public SkyWeatherState(String id, SkyCloudPreset cloudPreset, + SkyWeatherMetrics metrics) { + Validate.nonEmpty(id, "id"); + Validate.nonNull(cloudPreset, "cloud preset"); + Validate.nonNull(metrics, "metrics"); + + this.id = id; + this.cloudPreset = cloudPreset; + this.metrics = metrics.copy(); + } + + /** + * Instantiate weather state from a data-driven definition. + * + * @param definition preset definition (not null, unaffected) + */ + public SkyWeatherState(SkyCloudPresetDefinition definition) { + Validate.nonNull(definition, "definition"); + + this.id = definition.id(); + this.cloudPreset = definition.builtIn(); + this.metrics = definition.metrics(); + } + + /** + * Return default fair-weather state. + * + * @return new state + */ + public static SkyWeatherState fair() { + return fromPreset(SkyCloudPreset.FAIR); + } + + /** + * Create game-facing weather state for a built-in cloud preset. + * + * @param preset built-in preset (not null) + * @return new state + */ + public static SkyWeatherState fromPreset(SkyCloudPreset preset) { + Validate.nonNull(preset, "preset"); + + SkyWeatherState result; + switch (preset) { + case CLEAR: + result = state("clear", preset, metrics(0f, 1f, 0f, 0.05f, 0f)); + break; + + case FAIR: + result = state("fair", preset, metrics( + 0.35f, 0.95f, 0f, 0.10f, 0f)); + break; + + case OVERCAST: + result = state( + "overcast", preset, metrics( + 0.72f, 0.75f, 0f, 0.25f, 0f)); + break; + + case WISPY: + result = state("wispy", preset, metrics( + 0.28f, 0.92f, 0f, 0.20f, 0f)); + break; + + case CLOUDY: + result = state( + "cloudy", preset, metrics( + 0.55f, 0.78f, 0f, 0.35f, 0f)); + break; + + case RAIN: + result = state( + "rain", preset, metrics( + 0.75f, 0.55f, 0.75f, 0.55f, 0.02f)); + break; + + case STORM: + result = state( + "storm", preset, metrics( + 0.90f, 0.35f, 0.95f, 0.90f, 0.20f)); + break; + + case NIMBUS: + result = state( + "nimbus", preset, metrics( + 0.82f, 0.45f, 0.80f, 0.70f, 0.08f)); + break; + + default: + throw new IllegalStateException("preset = " + preset); + } + + return result; + } + + + /** + * Create game-facing weather state for a data-driven definition. + * + * @param definition preset definition (not null, unaffected) + * @return new state + */ + public static SkyWeatherState fromDefinition( + SkyCloudPresetDefinition definition) { + SkyWeatherState result = new SkyWeatherState(definition); + return result; + } + + /** + * Return the cloud preset. + * + * @return cloud preset + */ + public SkyCloudPreset cloudPreset() { + return cloudPreset; + } + + /** + * Return the stable preset id. + * + * @return preset id + */ + public String presetId() { + return id; + } + + /** + * Return approximate cloud coverage. + * + * @return cloudiness fraction + */ + public float cloudiness() { + return metrics.cloudiness(); + } + + /** + * Copy this weather state. + * + * @return new equivalent state + */ + public SkyWeatherState copy() { + SkyWeatherState result; + if (cloudPreset == null) { + SkyCloudPresetDefinition definition = new SkyCloudPresetDefinition( + id, id, 0f, java.util.Collections.emptyList(), metrics); + result = new SkyWeatherState(definition); + } else { + result = new SkyWeatherState(id, cloudPreset, metrics); + } + return result; + } + + /** + * Return the stable weather id. + * + * @return weather id + */ + public String id() { + return id; + } + + /** + * Test whether this state should be treated as storm weather. + * + * @return true if storm-like + */ + public boolean isStorm() { + return cloudPreset == SkyCloudPreset.STORM + || "STORM".equals(id) + || "storm".equals(id) + || metrics.lightningChance() > 0.1f; + } + + /** + * Return lightning probability/intensity hint. + * + * @return lightning chance + */ + public float lightningChance() { + return metrics.lightningChance(); + } + + /** + * Copy the full game-facing metrics. + * + * @return copied metrics + */ + public SkyWeatherMetrics metrics() { + return metrics.copy(); + } + + /** + * Return precipitation intensity. + * + * @return precipitation fraction + */ + public float precipitation() { + return metrics.precipitation(); + } + + /** + * Return visibility multiplier exposed to simulation code. + * + * @return visibility fraction + */ + public float visibility() { + return metrics.visibility(); + } + + /** + * Return wind intensity. + * + * @return wind strength fraction + */ + public float windStrength() { + return metrics.windStrength(); + } + + @Override + public String toString() { + return "SkyWeatherState[id=" + id + + ", cloudPreset=" + cloudPreset + + ", metrics=" + metrics + "]"; + } + + /** + * Create a weather state from raw metrics. + * + * @param cloudiness visible cloud coverage + * @param visibility visibility multiplier + * @param precipitation precipitation intensity + * @param windStrength wind intensity + * @param lightningChance lightning chance/intensity + * @return new state + */ + private static SkyWeatherMetrics metrics(float cloudiness, + float visibility, float precipitation, float windStrength, + float lightningChance) { + SkyWeatherMetrics result = new SkyWeatherMetrics(cloudiness, + visibility, precipitation, windStrength, lightningChance); + return result; + } + + /** + * Create a weather state from metrics. + * + * @param id stable weather id + * @param preset cloud preset + * @param metrics game-facing metrics + * @return new state + */ + private static SkyWeatherState state(String id, SkyCloudPreset preset, + SkyWeatherMetrics metrics) { + SkyWeatherState result = new SkyWeatherState(id, preset, metrics); + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherSubscription.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherSubscription.java new file mode 100644 index 0000000..e973126 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherSubscription.java @@ -0,0 +1,114 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +import jme3utilities.Validate; + +/** + * Handle returned by the weather subscription API. + * + * @author Take Some + */ +final public class SkyWeatherSubscription { + /** Subscriber filter. */ + final private SkyWeatherFilter filter; + /** Subscriber callback. */ + final private SkyWeatherListener listener; + /** Owning runtime. */ + final private SkyEnvironmentRuntime owner; + /** True while the subscription can receive events. */ + private boolean active = true; + + /** + * Instantiate a subscription. + * + * @param owner owning runtime (not null) + * @param filter subscription filter (not null) + * @param listener callback (not null) + */ + SkyWeatherSubscription(SkyEnvironmentRuntime owner, + SkyWeatherFilter filter, SkyWeatherListener listener) { + Validate.nonNull(owner, "owner"); + Validate.nonNull(filter, "filter"); + Validate.nonNull(listener, "listener"); + + this.owner = owner; + this.filter = filter; + this.listener = listener; + } + + /** + * Cancel this subscription. + * + * @return true if this call removed an active subscription + */ + public boolean cancel() { + boolean result = owner.unsubscribeWeather(this); + return result; + } + + /** + * Test whether this subscription is still active. + * + * @return true if active + */ + public boolean isActive() { + return active; + } + + /** Dispatch an event to the listener. */ + void dispatch(SkyWeatherEvent event) { + assert event != null; + listener.onWeatherChanged(event); + } + + /** Return the listener. */ + SkyWeatherListener listener() { + return listener; + } + + /** Mark the subscription as cancelled. */ + void markCancelled() { + active = false; + } + + /** Test whether a state matches this subscription. */ + boolean matches(SkyWeatherState state) { + assert state != null; + return active && filter.matches(state); + } + + /** Return the owning runtime. */ + SkyEnvironmentRuntime owner() { + return owner; + } + + @Override + public String toString() { + return "SkyWeatherSubscription[active=" + active + + ", filter=" + filter + "]"; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherSubscriptionRegistry.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherSubscriptionRegistry.java new file mode 100644 index 0000000..47dfefa --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherSubscriptionRegistry.java @@ -0,0 +1,125 @@ +package jme3utilities.sky.runtime; + +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Registry and dispatcher for game-facing weather subscriptions. + */ +final class SkyWeatherSubscriptionRegistry { + final private static Logger logger + = Logger.getLogger(SkyWeatherSubscriptionRegistry.class.getName()); + + final private CopyOnWriteArrayList subscriptions + = new CopyOnWriteArrayList(); + + private long eventSequence = 0L; + + int clear() { + for (SkyWeatherSubscription subscription : subscriptions) { + subscription.markCancelled(); + } + int removed = subscriptions.size(); + subscriptions.clear(); + logger.log(Level.FINE, + "sky weather subscriptions cleared: removed={0}", removed); + return removed; + } + + void publish(SkyWeatherState previous, SkyWeatherState current, + float seconds, SkyWeatherChangeSource source) { + assert previous != null; + assert current != null; + assert seconds >= 0f : seconds; + assert source != null; + + SkyWeatherEvent event = new SkyWeatherEvent(++eventSequence, + previous, current, seconds, source); + logger.log(Level.INFO, "sky weather changed: {0}", event); + + SkyWeatherState eventState = event.currentWeather(); + for (SkyWeatherSubscription subscription : subscriptions) { + if (subscription.matches(eventState)) { + dispatchSafely(subscription, event); + } + } + } + + int removeListener(SkyWeatherListener listener) { + assert listener != null; + + int removed = 0; + for (SkyWeatherSubscription subscription : subscriptions) { + if (subscription.listener() == listener + && subscriptions.remove(subscription)) { + subscription.markCancelled(); + ++removed; + } + } + logger.log(Level.FINE, + "sky weather listener removed: listener={0}, removed={1}", + new Object[]{listener, removed}); + return removed; + } + + int size() { + return subscriptions.size(); + } + + SkyWeatherSubscription subscribe(SkyEnvironmentRuntime owner, + SkyWeatherFilter filter, SkyWeatherListener listener, + SkyWeatherState current, boolean notifyCurrent) { + assert owner != null; + assert filter != null; + assert listener != null; + assert current != null; + + SkyWeatherSubscription result = new SkyWeatherSubscription( + owner, filter, listener); + subscriptions.add(result); + logger.log(Level.FINE, "sky weather subscription added: {0}", result); + if (notifyCurrent) { + dispatchCurrent(result, current); + } + return result; + } + + boolean unsubscribe(SkyWeatherSubscription subscription) { + assert subscription != null; + + boolean removed = subscriptions.remove(subscription); + if (removed) { + subscription.markCancelled(); + logger.log(Level.FINE, + "sky weather subscription removed: {0}", subscription); + } + return removed; + } + + private void dispatchCurrent(SkyWeatherSubscription subscription, + SkyWeatherState current) { + assert subscription != null; + assert current != null; + if (!subscription.matches(current)) { + return; + } + + SkyWeatherEvent event = new SkyWeatherEvent(++eventSequence, + current, current, 0f, SkyWeatherChangeSource.CURRENT); + dispatchSafely(subscription, event); + } + + private void dispatchSafely(SkyWeatherSubscription subscription, + SkyWeatherEvent event) { + assert subscription != null; + assert event != null; + try { + subscription.dispatch(event); + } catch (RuntimeException exception) { + logger.log(Level.WARNING, + "sky weather listener failed: subscription=" + + subscription + ", event=" + event, exception); + } + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWorldClock.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWorldClock.java new file mode 100644 index 0000000..b3bc9e4 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWorldClock.java @@ -0,0 +1,125 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +import jme3utilities.Validate; +import jme3utilities.sky.Constants; + +/** + * Small world-clock facade for driving sky time from game simulation. + * + * @author Take Some + */ +final public class SkyWorldClock { + /** External sink for time-of-day changes. */ + final private TimeApplier applier; + /** Current time of day in hours. */ + private float timeOfDayHours; + + /** + * Instantiate a standalone clock. + */ + public SkyWorldClock() { + this(null); + } + + /** + * Instantiate a clock with a time sink. + * + * @param applier sink for time-of-day changes, or null + */ + public SkyWorldClock(TimeApplier applier) { + this.applier = applier; + this.timeOfDayHours = 0f; + } + + /** + * Advance the clock by simulation seconds. + * + * @param seconds elapsed simulation seconds (≥0) + * @param secondsPerDay simulated seconds per sky day (>0) + */ + public void advance(float seconds, float secondsPerDay) { + Validate.nonNegative(seconds, "seconds"); + Validate.positive(secondsPerDay, "seconds per day"); + + float hours = timeOfDayHours + + seconds * Constants.hoursPerDay / secondsPerDay; + setTimeOfDay(wrapHours(hours)); + } + + /** + * Set the time of day. + * + * @param hours time of day in hours (≤24, ≥0) + */ + public void setTimeOfDay(float hours) { + Validate.inRange(hours, "time of day", 0f, Constants.hoursPerDay); + + this.timeOfDayHours = hours; + if (applier != null) { + applier.applyTimeOfDay(hours); + } + } + + /** + * Return the time of day. + * + * @return hours since midnight + */ + public float timeOfDayHours() { + return timeOfDayHours; + } + + /** + * Wrap hours into the supported time-of-day interval. + * + * @param hours raw hours + * @return wrapped hours + */ + private static float wrapHours(float hours) { + float result = hours; + while (result < 0f) { + result += Constants.hoursPerDay; + } + while (result > Constants.hoursPerDay) { + result -= Constants.hoursPerDay; + } + return result; + } + + /** + * External sink for clock changes. + */ + public interface TimeApplier { + /** + * Apply a time-of-day value. + * + * @param timeOfDayHours time of day in hours + */ + void applyTimeOfDay(float timeOfDayHours); + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/package-info.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/package-info.java new file mode 100644 index 0000000..64373e4 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/package-info.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Runtime snapshots and facades for game-facing sky simulation state. + */ +package jme3utilities.sky.runtime; diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/scene/SkyDomeAssets.java b/SkyLibrary/src/main/java/jme3utilities/sky/scene/SkyDomeAssets.java new file mode 100644 index 0000000..9831b7b --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/scene/SkyDomeAssets.java @@ -0,0 +1,59 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.scene; + +/** + * Asset paths for built-in sky-dome models. + *

+ * The procedural {@code DomeMesh} path remains the default runtime path until + * OBJ-backed dome projection is implemented. These constants define the + * packaged asset locations for future optional OBJ loading. + * + * @author Take Some + */ +public final class SkyDomeAssets { + /** + * Classpath directory for bundled sky-dome models. + */ + final public static String modelDirectory = "Models/skies"; + /** + * Built-in high-resolution sky-dome OBJ asset path. + */ + final public static String builtInHighObj + = modelDirectory + "/skydome_high.obj"; + /** + * Material library next to {@link #builtInHighObj}. + */ + final public static String builtInHighMtl + = modelDirectory + "/skydome_high.mtl"; + + /** + * Hidden constructor. + */ + private SkyDomeAssets() { + // do nothing + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/scene/SkyDomeFactory.java b/SkyLibrary/src/main/java/jme3utilities/sky/scene/SkyDomeFactory.java new file mode 100644 index 0000000..bd8f40b --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/scene/SkyDomeFactory.java @@ -0,0 +1,267 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.scene; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import jme3utilities.MyAsset; +import jme3utilities.mesh.DomeMesh; +import jme3utilities.sky.Constants; +import jme3utilities.sky.SkyMaterial; +import jme3utilities.sky.StarsOption; + +/** + * Factory for sky dome geometries and default star-map nodes. + * + * @author Take Some + */ +public final class SkyDomeFactory { + /** Number of samples in each longitudinal arc of a major dome. */ + final private static int numLongSamples = 16; + /** Number of samples around the rim of a dome. */ + final private static int numRimSamples = 60; + /** Reusable mesh for smooth, inward-facing domes. */ + final private static DomeMesh hemisphereMesh = new DomeMesh( + numRimSamples, numLongSamples, Constants.topUV.x, + Constants.topUV.y, Constants.uvScale, true); + /** Negative Y axis. */ + final private static Vector3f negativeUnitY = new Vector3f(0f, -1f, 0f); + /** Local copy of unitX. */ + final private static Vector3f unitX = new Vector3f(1f, 0f, 0f); + /** Local copy of unitZ. */ + final private static Vector3f unitZ = new Vector3f(0f, 0f, 1f); + + /** + * Hidden constructor. + */ + private SkyDomeFactory() { + // do nothing + } + + /** + * Attach a stars node as the outermost sky child. + * + * @param subtree sky subtree (not null) + * @param starsNode stars node (not null) + */ + public static void attachStars(Node subtree, Node starsNode) { + subtree.attachChildAt(starsNode, 0); + } + + /** + * Create a new sky subtree and attach its base geometries. + * + * @param cloudFlattening cloud-dome flattening (≥0, <1) + * @param bottomEnabled true to create the bottom dome + * @param topMaterial top material (not null) + * @param bottomMaterial bottom material, or null + * @param cloudsMaterial cloud material (not null) + * @return new sky subtree node + */ + public static Node createSubtree(float cloudFlattening, + boolean bottomEnabled, + SkyMaterial topMaterial, Material bottomMaterial, + SkyMaterial cloudsMaterial) { + Node result = new Node(SkyNodeNames.skyNode); + result.setQueueBucket(Bucket.Sky); + result.setShadowMode(ShadowMode.Off); + + Geometry top = createTop(topMaterial); + result.attachChild(top); + if (bottomEnabled) { + Geometry bottom = createBottom(bottomMaterial); + result.attachChild(bottom); + } + if (cloudsMaterial != topMaterial) { + Geometry clouds = createClouds(cloudFlattening, cloudsMaterial); + result.attachChild(clouds); + } + + return result; + } + + + /** + * Create default star-map node for a newly constructed control. + * + * @param assetManager asset manager (not null) + * @param option star rendering option (not null) + * @return new stars node, or null when no node is needed + */ + public static Node createDefaultStars( + AssetManager assetManager, StarsOption option) { + Node result; + switch (option) { + case Cube: + result = createStars(assetManager, option, "equator"); + break; + case TwoDomes: + result = createStars( + assetManager, option, "Textures/skies/star-maps"); + break; + case TopDome: + result = null; + break; + default: + throw new IllegalStateException("option = " + option); + } + + return result; + } + + /** + * Create a star-map node for the specified option. + * + * @param assetManager asset manager (not null) + * @param option star rendering option (not null) + * @param assetName asset name or path (not null) + * @return new stars node, or null for TopDome + */ + public static Node createStars( + AssetManager assetManager, StarsOption option, String assetName) { + Node result; + switch (option) { + case Cube: + result = MyAsset.createStarMapQuads(assetManager, assetName); + result.setName(SkyNodeNames.starsNode); + break; + case TwoDomes: + result = createStarDomes(assetManager, assetName); + break; + case TopDome: + result = null; + break; + default: + throw new IllegalStateException("option = " + option); + } + + return result; + } + + /** + * Create a bottom dome geometry. + * + * @param material bottom material (not null) + * @return new bottom geometry + */ + private static Geometry createBottom(Material material) { + DomeMesh mesh = new DomeMesh(numRimSamples, 2, Constants.topUV.x, + Constants.topUV.y, Constants.uvScale, true); + Geometry result = new Geometry(SkyNodeNames.bottom, mesh); + Quaternion upsideDown = new Quaternion(); + upsideDown.lookAt(unitX, negativeUnitY); + result.setLocalRotation(upsideDown); + result.setMaterial(material); + + return result; + } + + /** + * Create a clouds-only dome geometry. + * + * @param flattening cloud-dome flattening (>0, <1) + * @param material cloud material (not null) + * @return new cloud dome geometry + */ + private static Geometry createClouds( + float flattening, SkyMaterial material) { + assert flattening > 0f : flattening; + assert flattening < 1f : flattening; + + Geometry result = new Geometry( + SkyNodeNames.clouds, hemisphereMesh); + float yScale = 1f - flattening; + result.setLocalScale(1f, yScale, 1f); + result.setMaterial(material); + + return result; + } + + /** + * Load a star map onto a sphere formed by 2 domes. + * + * @param assetManager asset manager (not null) + * @param assetPath asset folder path (not null) + * @return new orphan node + */ + private static Node createStarDomes( + AssetManager assetManager, String assetPath) { + Node result = new Node(SkyNodeNames.starsNode); + Geometry north = createStarHemisphere(assetManager, assetPath, + SkyNodeNames.northStars, "northern.png", -FastMath.HALF_PI); + result.attachChild(north); + Geometry south = createStarHemisphere(assetManager, assetPath, + SkyNodeNames.southStars, "southern.png", FastMath.HALF_PI); + result.attachChild(south); + + return result; + } + + /** + * Create one hemisphere of a two-dome star map. + * + * @param assetManager asset manager (not null) + * @param folder asset folder path (not null) + * @param geometryName geometry name (not null) + * @param imageName image filename (not null) + * @param angle rotation angle around Z + * @return new star hemisphere geometry + */ + private static Geometry createStarHemisphere(AssetManager assetManager, + String folder, String geometryName, String imageName, float angle) { + Geometry result = new Geometry(geometryName, hemisphereMesh); + String assetPath = folder + "/" + imageName; + Material material = MyAsset.createUnshadedMaterial( + assetManager, assetPath); + result.setMaterial(material); + Quaternion orientation = new Quaternion(); + orientation.fromAngleAxis(angle, unitZ); + result.setLocalRotation(orientation); + + return result; + } + + /** + * Create a top dome geometry. + * + * @param material top material (not null) + * @return new top geometry + */ + private static Geometry createTop(SkyMaterial material) { + Geometry result = new Geometry( + SkyNodeNames.top, (DomeMesh) hemisphereMesh.clone()); + result.setMaterial(material); + + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/scene/SkyNodeNames.java b/SkyLibrary/src/main/java/jme3utilities/sky/scene/SkyNodeNames.java new file mode 100644 index 0000000..027a524 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/scene/SkyNodeNames.java @@ -0,0 +1,55 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.scene; + +/** + * Internal scene-graph names used by sky controls and factories. + * + * @author Take Some + */ +public final class SkyNodeNames { + /** Name for the bottom geometry. */ + final public static String bottom = "bottom"; + /** Name for the clouds-only geometry. */ + final public static String clouds = "clouds"; + /** Name for the northern stars geometry. */ + final public static String northStars = "northern stars"; + /** Name for the sky subtree node. */ + final public static String skyNode = "sky node"; + /** Name for the southern stars geometry. */ + final public static String southStars = "southern stars"; + /** Name for the stars node. */ + final public static String starsNode = "stars node"; + /** Name for the top geometry. */ + final public static String top = "top"; + + /** + * Hidden constructor. + */ + private SkyNodeNames() { + // do nothing + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/scene/SkySceneLookup.java b/SkyLibrary/src/main/java/jme3utilities/sky/scene/SkySceneLookup.java new file mode 100644 index 0000000..57a6a16 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/scene/SkySceneLookup.java @@ -0,0 +1,150 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.scene; + +import com.jme3.material.Material; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import jme3utilities.MySpatial; +import jme3utilities.mesh.DomeMesh; +import jme3utilities.sky.SkyMaterial; + +/** + * Internal scene-graph lookup helpers for sky controls. + * + * @author Take Some + */ +public final class SkySceneLookup { + /** + * Hidden constructor. + */ + private SkySceneLookup() { + // do nothing + } + + /** + * Access the bottom dome geometry. + * + * @param subtreeNode sky subtree node (not null, unaffected) + * @return the pre-existing geometry, or null if none + */ + public static Geometry bottomDome(Node subtreeNode) { + Geometry result = geometry(subtreeNode, SkyNodeNames.bottom); + return result; + } + + /** + * Access the clouds-only dome geometry. + * + * @param subtreeNode sky subtree node (not null, unaffected) + * @return the pre-existing geometry, or null if none + */ + public static Geometry cloudsOnlyDome(Node subtreeNode) { + Geometry result = geometry(subtreeNode, SkyNodeNames.clouds); + return result; + } + + /** + * Access a dome mesh from an optional geometry. + * + * @param geometry geometry to inspect, or null + * @return the pre-existing dome mesh, or null if the geometry is null + */ + public static DomeMesh domeMesh(Geometry geometry) { + if (geometry == null) { + return null; + } + DomeMesh result = (DomeMesh) geometry.getMesh(); + return result; + } + + /** + * Access a material from an optional geometry. + * + * @param geometry geometry to inspect, or null + * @return the pre-existing material, or null if the geometry is null + */ + public static Material material(Geometry geometry) { + if (geometry == null) { + return null; + } + Material result = geometry.getMaterial(); + return result; + } + + /** + * Access the material from a sky-material geometry. + * + * @param geometry geometry to inspect (not null, unaffected) + * @return the pre-existing sky material (not null) + */ + public static SkyMaterial skyMaterial(Geometry geometry) { + assert geometry != null; + SkyMaterial result = (SkyMaterial) geometry.getMaterial(); + + assert result != null; + return result; + } + + /** + * Access the stars parent node. + * + * @param subtreeNode sky subtree node (not null, unaffected) + * @return the pre-existing node, or null if none + */ + public static Node starsNode(Node subtreeNode) { + Node result = (Node) MySpatial.findChild( + subtreeNode, SkyNodeNames.starsNode); + return result; + } + + /** + * Access the top dome geometry. + * + * @param subtreeNode sky subtree node (not null, unaffected) + * @return the pre-existing geometry, or null if none + */ + public static Geometry topDome(Node subtreeNode) { + Geometry result = geometry(subtreeNode, SkyNodeNames.top); + return result; + } + + /** + * Locate a named geometry under the specified subtree. + * + * @param subtreeNode sky subtree node (not null, unaffected) + * @param childName desired child name (not null) + * @return the pre-existing geometry, or null if none + */ + private static Geometry geometry(Node subtreeNode, String childName) { + assert subtreeNode != null; + assert childName != null; + Geometry result = (Geometry) MySpatial.findChild( + subtreeNode, childName); + + return result; + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/scene/package-info.java b/SkyLibrary/src/main/java/jme3utilities/sky/scene/package-info.java new file mode 100644 index 0000000..606e188 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/scene/package-info.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Sky scene-graph naming and lookup helpers. + */ +package jme3utilities.sky.scene; diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/update/UpdaterApplier.java b/SkyLibrary/src/main/java/jme3utilities/sky/update/UpdaterApplier.java new file mode 100644 index 0000000..d5cba99 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/update/UpdaterApplier.java @@ -0,0 +1,134 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.update; + +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.post.filters.BloomFilter; +import com.jme3.renderer.ViewPort; +import com.jme3.shadow.AbstractShadowFilter; +import com.jme3.shadow.AbstractShadowRenderer; +import java.util.List; + +/** + * Applies Updater state to live scene objects. + * + * @author Take Some + */ +public final class UpdaterApplier { + /** + * Hidden constructor. + */ + private UpdaterApplier() { + // do nothing + } + + /** + * Apply ambient light color. + * + * @param light target light, or null + * @param color source color (not null, unaffected) + * @param multiplier intensity multiplier + */ + public static void applyAmbient( + AmbientLight light, ColorRGBA color, float multiplier) { + if (light != null) { + ColorRGBA applied = color.mult(multiplier); + light.setColor(applied); + } + } + + /** + * Apply bloom intensity to known filters. + * + * @param filters target filters (not null) + * @param intensity bloom intensity + */ + public static void applyBloom(List filters, float intensity) { + for (BloomFilter filter : filters) { + filter.setBloomIntensity(intensity); + } + } + + /** + * Apply directional light color and direction. + * + * @param light target light, or null + * @param color source color (not null, unaffected) + * @param multiplier intensity multiplier + * @param direction direction to the light source (not null, unaffected) + */ + public static void applyMain(DirectionalLight light, ColorRGBA color, + float multiplier, Vector3f direction) { + if (light != null) { + ColorRGBA applied = color.mult(multiplier); + light.setColor(applied); + Vector3f propagationDirection = direction.negate(); + light.setDirection(propagationDirection); + } + } + + /** + * Apply shadow intensity to known filters. + * + * @param filters target filters (not null) + * @param intensity shadow intensity + */ + public static void applyShadowFilters( + List> filters, float intensity) { + for (AbstractShadowFilter filter : filters) { + filter.setShadowIntensity(intensity); + } + } + + /** + * Apply shadow intensity to known renderers. + * + * @param renderers target renderers (not null) + * @param intensity shadow intensity + */ + public static void applyShadowRenderers( + List renderers, float intensity) { + for (AbstractShadowRenderer renderer : renderers) { + renderer.setShadowIntensity(intensity); + } + } + + /** + * Apply background color to known viewports. + * + * @param viewPorts target viewports (not null) + * @param color background color (not null, unaffected) + */ + public static void applyViewPorts( + List viewPorts, ColorRGBA color) { + for (ViewPort viewPort : viewPorts) { + viewPort.setBackgroundColor(color); + } + } +} diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/update/package-info.java b/SkyLibrary/src/main/java/jme3utilities/sky/update/package-info.java new file mode 100644 index 0000000..75725f8 --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/update/package-info.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Updater application helpers for live scene objects. + */ +package jme3utilities.sky.update; diff --git a/SkyLibrary/src/main/resources/Config/skies/earthlike-atmosphere.properties b/SkyLibrary/src/main/resources/Config/skies/earthlike-atmosphere.properties new file mode 100644 index 0000000..e8ab425 --- /dev/null +++ b/SkyLibrary/src/main/resources/Config/skies/earthlike-atmosphere.properties @@ -0,0 +1,22 @@ +# Earth-like atmospheric tuning profile for SkyControl. +# Color values use r,g,b,a linear multipliers. + +airMassStrength=0.85 +ambientScale=1.0 +bloomScale=1.0 +colorShiftAltitude=0.35 +cloudDayBrightness=0.95 +cloudMoonBoost=0.60 +cloudNight=0.22 +fullDayAltitude=0.25 +hazeStrength=1.0 +maxBloomIntensity=1.70 +minSunTransmission=0.08 +shadowContrast=1.0 +sunsetWarmth=1.0 +twilightLimit=0.12 + +moonLight=0.25,0.28,0.42,1.0 +starLight=0.015,0.018,0.025,1.0 +sunLight=0.95,0.92,0.82,1.0 +twilightColor=0.95,0.36,0.12,1.0 diff --git a/SkyLibrary/src/main/resources/MatDefs/skies/dome02/dome02.j3md b/SkyLibrary/src/main/resources/MatDefs/skies/dome02/dome02.j3md index 3eff1b0..cdc454f 100644 --- a/SkyLibrary/src/main/resources/MatDefs/skies/dome02/dome02.j3md +++ b/SkyLibrary/src/main/resources/MatDefs/skies/dome02/dome02.j3md @@ -1,30 +1,5 @@ -// Copyright (c) 2013-2022, Stephen Gold - -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// * Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. - -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -// A material for use with the SkyMaterial class: this version includes zero -// objects and two cloud layers. +// Generated by :SkyAssets:skyMaterials. Do not edit by hand. +// A generated material for SkyMaterial: 0 objects and 2 cloud layers. MaterialDef dome02 { MaterialParameters { @@ -38,12 +13,14 @@ MaterialDef dome02 { Color Clouds0Glow Float Clouds0Scale : 1.0 Texture2D Clouds0AlphaMap + Texture2D Clouds0NormalMap Vector2 Clouds0Offset Color Clouds1Color Color Clouds1Glow Float Clouds1Scale : 1.0 Texture2D Clouds1AlphaMap + Texture2D Clouds1NormalMap Vector2 Clouds1Offset Color HazeColor @@ -55,7 +32,9 @@ MaterialDef dome02 { Defines { HAS_STARS : StarsColorMap HAS_CLOUDS0 : Clouds0AlphaMap + HAS_CLOUDS0_NORMAL : Clouds0NormalMap HAS_CLOUDS1 : Clouds1AlphaMap + HAS_CLOUDS1_NORMAL : Clouds1NormalMap HAS_HAZE : HazeAlphaMap } FragmentShader GLSL300 GLSL150 GLSL100: Shaders/skies/dome02/dome02.frag @@ -68,7 +47,9 @@ MaterialDef dome02 { Technique Glow { Defines { HAS_CLOUDS0 : Clouds0AlphaMap + HAS_CLOUDS0_NORMAL : Clouds0NormalMap HAS_CLOUDS1 : Clouds1AlphaMap + HAS_CLOUDS1_NORMAL : Clouds1NormalMap HAS_HAZE : HazeAlphaMap } FragmentShader GLSL300 GLSL150 GLSL100: Shaders/skies/dome02/dome02glow.frag @@ -77,4 +58,4 @@ MaterialDef dome02 { WorldViewProjectionMatrix } } -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/MatDefs/skies/dome06/dome06.j3md b/SkyLibrary/src/main/resources/MatDefs/skies/dome06/dome06.j3md index da571f5..b5a2f83 100644 --- a/SkyLibrary/src/main/resources/MatDefs/skies/dome06/dome06.j3md +++ b/SkyLibrary/src/main/resources/MatDefs/skies/dome06/dome06.j3md @@ -1,30 +1,5 @@ -// Copyright (c) 2014-2022, Stephen Gold - -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// * Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. - -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -// A material for use with the SkyMaterial class: this version includes zero -// objects and six cloud layers. +// Generated by :SkyAssets:skyMaterials. Do not edit by hand. +// A generated material for SkyMaterial: 0 objects and 6 cloud layers. MaterialDef dome06 { MaterialParameters { @@ -38,36 +13,42 @@ MaterialDef dome06 { Color Clouds0Glow Float Clouds0Scale : 1.0 Texture2D Clouds0AlphaMap + Texture2D Clouds0NormalMap Vector2 Clouds0Offset Color Clouds1Color Color Clouds1Glow Float Clouds1Scale : 1.0 Texture2D Clouds1AlphaMap + Texture2D Clouds1NormalMap Vector2 Clouds1Offset Color Clouds2Color Color Clouds2Glow Float Clouds2Scale : 1.0 Texture2D Clouds2AlphaMap + Texture2D Clouds2NormalMap Vector2 Clouds2Offset Color Clouds3Color Color Clouds3Glow Float Clouds3Scale : 1.0 Texture2D Clouds3AlphaMap + Texture2D Clouds3NormalMap Vector2 Clouds3Offset Color Clouds4Color Color Clouds4Glow Float Clouds4Scale : 1.0 Texture2D Clouds4AlphaMap + Texture2D Clouds4NormalMap Vector2 Clouds4Offset Color Clouds5Color Color Clouds5Glow Float Clouds5Scale : 1.0 Texture2D Clouds5AlphaMap + Texture2D Clouds5NormalMap Vector2 Clouds5Offset Color HazeColor @@ -79,11 +60,17 @@ MaterialDef dome06 { Defines { HAS_STARS : StarsColorMap HAS_CLOUDS0 : Clouds0AlphaMap + HAS_CLOUDS0_NORMAL : Clouds0NormalMap HAS_CLOUDS1 : Clouds1AlphaMap + HAS_CLOUDS1_NORMAL : Clouds1NormalMap HAS_CLOUDS2 : Clouds2AlphaMap + HAS_CLOUDS2_NORMAL : Clouds2NormalMap HAS_CLOUDS3 : Clouds3AlphaMap + HAS_CLOUDS3_NORMAL : Clouds3NormalMap HAS_CLOUDS4 : Clouds4AlphaMap + HAS_CLOUDS4_NORMAL : Clouds4NormalMap HAS_CLOUDS5 : Clouds5AlphaMap + HAS_CLOUDS5_NORMAL : Clouds5NormalMap HAS_HAZE : HazeAlphaMap } FragmentShader GLSL300 GLSL150 GLSL100: Shaders/skies/dome06/dome06.frag @@ -96,11 +83,17 @@ MaterialDef dome06 { Technique Glow { Defines { HAS_CLOUDS0 : Clouds0AlphaMap + HAS_CLOUDS0_NORMAL : Clouds0NormalMap HAS_CLOUDS1 : Clouds1AlphaMap + HAS_CLOUDS1_NORMAL : Clouds1NormalMap HAS_CLOUDS2 : Clouds2AlphaMap + HAS_CLOUDS2_NORMAL : Clouds2NormalMap HAS_CLOUDS3 : Clouds3AlphaMap + HAS_CLOUDS3_NORMAL : Clouds3NormalMap HAS_CLOUDS4 : Clouds4AlphaMap + HAS_CLOUDS4_NORMAL : Clouds4NormalMap HAS_CLOUDS5 : Clouds5AlphaMap + HAS_CLOUDS5_NORMAL : Clouds5NormalMap HAS_HAZE : HazeAlphaMap } FragmentShader GLSL300 GLSL150 GLSL100: Shaders/skies/dome06/dome06glow.frag @@ -109,4 +102,4 @@ MaterialDef dome06 { WorldViewProjectionMatrix } } -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/MatDefs/skies/dome20/dome20.j3md b/SkyLibrary/src/main/resources/MatDefs/skies/dome20/dome20.j3md index 30b1f79..bb9ee2a 100644 --- a/SkyLibrary/src/main/resources/MatDefs/skies/dome20/dome20.j3md +++ b/SkyLibrary/src/main/resources/MatDefs/skies/dome20/dome20.j3md @@ -1,30 +1,5 @@ -// Copyright (c) 2014-2022, Stephen Gold - -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// * Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. - -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -// A material for use with the SkyMaterial class: this version includes two -// objects and zero cloud layers. +// Generated by :SkyAssets:skyMaterials. Do not edit by hand. +// A generated material for SkyMaterial: 2 objects and 0 cloud layers. MaterialDef dome20 { MaterialParameters { @@ -79,4 +54,4 @@ MaterialDef dome20 { WorldViewProjectionMatrix } } -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/MatDefs/skies/dome22/dome22.j3md b/SkyLibrary/src/main/resources/MatDefs/skies/dome22/dome22.j3md index 4cc1d0f..702ad1a 100644 --- a/SkyLibrary/src/main/resources/MatDefs/skies/dome22/dome22.j3md +++ b/SkyLibrary/src/main/resources/MatDefs/skies/dome22/dome22.j3md @@ -1,30 +1,5 @@ -// Copyright (c) 2013-2022, Stephen Gold - -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// * Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. - -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -// A material for use with the SkyMaterial class: this version includes two -// objects and two cloud layers. +// Generated by :SkyAssets:skyMaterials. Do not edit by hand. +// A generated material for SkyMaterial: 2 objects and 2 cloud layers. MaterialDef dome22 { MaterialParameters { @@ -52,12 +27,14 @@ MaterialDef dome22 { Color Clouds0Glow Float Clouds0Scale : 1.0 Texture2D Clouds0AlphaMap + Texture2D Clouds0NormalMap Vector2 Clouds0Offset Color Clouds1Color Color Clouds1Glow Float Clouds1Scale : 1.0 Texture2D Clouds1AlphaMap + Texture2D Clouds1NormalMap Vector2 Clouds1Offset Color HazeColor @@ -71,7 +48,9 @@ MaterialDef dome22 { HAS_OBJECT0 : Object0ColorMap HAS_OBJECT1 : Object1ColorMap HAS_CLOUDS0 : Clouds0AlphaMap + HAS_CLOUDS0_NORMAL : Clouds0NormalMap HAS_CLOUDS1 : Clouds1AlphaMap + HAS_CLOUDS1_NORMAL : Clouds1NormalMap HAS_HAZE : HazeAlphaMap } FragmentShader GLSL300 GLSL150 GLSL100: Shaders/skies/dome22/dome22.frag @@ -86,7 +65,9 @@ MaterialDef dome22 { HAS_OBJECT0 : Object0ColorMap HAS_OBJECT1 : Object1ColorMap HAS_CLOUDS0 : Clouds0AlphaMap + HAS_CLOUDS0_NORMAL : Clouds0NormalMap HAS_CLOUDS1 : Clouds1AlphaMap + HAS_CLOUDS1_NORMAL : Clouds1NormalMap HAS_HAZE : HazeAlphaMap } FragmentShader GLSL300 GLSL150 GLSL100: Shaders/skies/dome22/dome22glow.frag @@ -95,4 +76,4 @@ MaterialDef dome22 { WorldViewProjectionMatrix } } -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/MatDefs/skies/dome60/dome60.j3md b/SkyLibrary/src/main/resources/MatDefs/skies/dome60/dome60.j3md index d2f75a0..81336c0 100644 --- a/SkyLibrary/src/main/resources/MatDefs/skies/dome60/dome60.j3md +++ b/SkyLibrary/src/main/resources/MatDefs/skies/dome60/dome60.j3md @@ -1,30 +1,5 @@ -// Copyright (c) 2014-2022, Stephen Gold - -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// * Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. - -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -// A material for use with the SkyMaterial class: this version includes six -// objects and zero cloud layers. +// Generated by :SkyAssets:skyMaterials. Do not edit by hand. +// A generated material for SkyMaterial: 6 objects and 0 cloud layers. MaterialDef dome60 { MaterialParameters { @@ -115,4 +90,4 @@ MaterialDef dome60 { WorldViewProjectionMatrix } } -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/MatDefs/skies/dome66/dome66.j3md b/SkyLibrary/src/main/resources/MatDefs/skies/dome66/dome66.j3md index e8b199c..cafe827 100644 --- a/SkyLibrary/src/main/resources/MatDefs/skies/dome66/dome66.j3md +++ b/SkyLibrary/src/main/resources/MatDefs/skies/dome66/dome66.j3md @@ -1,30 +1,5 @@ -// Copyright (c) 2014-2022, Stephen Gold - -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// * Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. - -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -// A material for use with the SkyMaterial class: this version includes six -// objects and six cloud layers. +// Generated by :SkyAssets:skyMaterials. Do not edit by hand. +// A generated material for SkyMaterial: 6 objects and 6 cloud layers. MaterialDef dome66 { MaterialParameters { @@ -80,36 +55,42 @@ MaterialDef dome66 { Color Clouds0Glow Float Clouds0Scale : 1.0 Texture2D Clouds0AlphaMap + Texture2D Clouds0NormalMap Vector2 Clouds0Offset Color Clouds1Color Color Clouds1Glow Float Clouds1Scale : 1.0 Texture2D Clouds1AlphaMap + Texture2D Clouds1NormalMap Vector2 Clouds1Offset Color Clouds2Color Color Clouds2Glow Float Clouds2Scale : 1.0 Texture2D Clouds2AlphaMap + Texture2D Clouds2NormalMap Vector2 Clouds2Offset Color Clouds3Color Color Clouds3Glow Float Clouds3Scale : 1.0 Texture2D Clouds3AlphaMap + Texture2D Clouds3NormalMap Vector2 Clouds3Offset Color Clouds4Color Color Clouds4Glow Float Clouds4Scale : 1.0 Texture2D Clouds4AlphaMap + Texture2D Clouds4NormalMap Vector2 Clouds4Offset Color Clouds5Color Color Clouds5Glow Float Clouds5Scale : 1.0 Texture2D Clouds5AlphaMap + Texture2D Clouds5NormalMap Vector2 Clouds5Offset Color HazeColor @@ -127,11 +108,17 @@ MaterialDef dome66 { HAS_OBJECT4 : Object4ColorMap HAS_OBJECT5 : Object5ColorMap HAS_CLOUDS0 : Clouds0AlphaMap + HAS_CLOUDS0_NORMAL : Clouds0NormalMap HAS_CLOUDS1 : Clouds1AlphaMap + HAS_CLOUDS1_NORMAL : Clouds1NormalMap HAS_CLOUDS2 : Clouds2AlphaMap + HAS_CLOUDS2_NORMAL : Clouds2NormalMap HAS_CLOUDS3 : Clouds3AlphaMap + HAS_CLOUDS3_NORMAL : Clouds3NormalMap HAS_CLOUDS4 : Clouds4AlphaMap + HAS_CLOUDS4_NORMAL : Clouds4NormalMap HAS_CLOUDS5 : Clouds5AlphaMap + HAS_CLOUDS5_NORMAL : Clouds5NormalMap HAS_HAZE : HazeAlphaMap } FragmentShader GLSL300 GLSL150 GLSL100: Shaders/skies/dome66/dome66.frag @@ -150,11 +137,17 @@ MaterialDef dome66 { HAS_OBJECT4 : Object4ColorMap HAS_OBJECT5 : Object5ColorMap HAS_CLOUDS0 : Clouds0AlphaMap + HAS_CLOUDS0_NORMAL : Clouds0NormalMap HAS_CLOUDS1 : Clouds1AlphaMap + HAS_CLOUDS1_NORMAL : Clouds1NormalMap HAS_CLOUDS2 : Clouds2AlphaMap + HAS_CLOUDS2_NORMAL : Clouds2NormalMap HAS_CLOUDS3 : Clouds3AlphaMap + HAS_CLOUDS3_NORMAL : Clouds3NormalMap HAS_CLOUDS4 : Clouds4AlphaMap + HAS_CLOUDS4_NORMAL : Clouds4NormalMap HAS_CLOUDS5 : Clouds5AlphaMap + HAS_CLOUDS5_NORMAL : Clouds5NormalMap HAS_HAZE : HazeAlphaMap } FragmentShader GLSL300 GLSL150 GLSL100: Shaders/skies/dome66/dome66glow.frag @@ -163,4 +156,4 @@ MaterialDef dome66 { WorldViewProjectionMatrix } } -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Models/skies/skydome_high.mtl b/SkyLibrary/src/main/resources/Models/skies/skydome_high.mtl new file mode 100644 index 0000000..c5ff516 --- /dev/null +++ b/SkyLibrary/src/main/resources/Models/skies/skydome_high.mtl @@ -0,0 +1,7 @@ +# Bundled material placeholder for skydome_high.obj +newmtl sky_system +Ka 1.000000 1.000000 1.000000 +Kd 1.000000 1.000000 1.000000 +Ks 0.000000 0.000000 0.000000 +d 1.000000 +illum 1 diff --git a/SkyLibrary/src/main/resources/Models/skies/skydome_high.obj b/SkyLibrary/src/main/resources/Models/skies/skydome_high.obj new file mode 100644 index 0000000..0c07efc --- /dev/null +++ b/SkyLibrary/src/main/resources/Models/skies/skydome_high.obj @@ -0,0 +1,2818 @@ +# Converted from skydome_high.mesh OpenFormats text mesh +# Source object descriptor: skydome.odr +# Vertices: 469 +# Triangles: 928 +# OBJ contains position, first UV channel, and generated smooth normals. +# Second UV channel from source is preserved as comments near the vertex list. +mtllib skydome_high.mtl +o skydome_high +v 0.73339840 0.60205080 -0.30322270 +v 0.44726560 0.83691400 -0.30322270 +v 0.00000000 0.00000000 -1.00048800 +v 0.09277344 0.94433600 -0.30322270 +v 0.19531250 0.98144530 0.00000000 +v 0.38281250 0.92431640 0.00000000 +v 0.55566410 0.83203120 0.00000000 +v 0.73339840 0.60205080 -0.30322270 +v 0.19531250 0.98095710 0.02441406 +v 0.38281250 0.92431640 0.02441406 +v 0.70751950 0.70751950 0.00000000 +v 0.55566410 0.83154300 0.02441406 +v 0.19482420 0.97998050 0.05273437 +v 0.83203120 0.55566410 0.00000000 +v 0.38232420 0.92333980 0.05273437 +v 0.70703130 0.70703130 0.02441406 +v 0.55517580 0.83056640 0.05273437 +v 0.90820310 0.27539060 -0.30322270 +v 0.00000000 0.00000000 -1.00048800 +v 0.83154300 0.55566410 0.02441406 +v 0.92431640 0.38281250 0.00000000 +v 0.70654290 0.70654290 0.05273437 +v 0.98144530 0.19531250 0.00000000 +v 0.92431640 0.38281250 0.02441406 +v 0.83056640 0.55517580 0.05273437 +v 0.94433600 -0.09277344 -0.30322270 +v 0.98095710 0.19531250 0.02441406 +v 1.00048800 0.00000000 0.00000000 +v 0.92333980 0.38232420 0.05273437 +v 0.98144530 -0.19531250 0.00000000 +v 1.00000000 0.00000000 0.02441406 +v 0.97998050 0.19482420 0.05273437 +v 0.83691400 -0.44726560 -0.30322270 +v 0.98095710 -0.19531250 0.02441406 +v 0.92431640 -0.38281250 0.00000000 +v 0.99902340 0.00000000 0.05273437 +v 0.83203120 -0.55566410 0.00000000 +v 0.92431640 -0.38281250 0.02441406 +v 0.97998050 -0.19482420 0.05273437 +v 0.60205080 -0.73339840 -0.30322270 +v 0.83154300 -0.55566410 0.02441406 +v 0.70751950 -0.70751950 0.00000000 +v 0.92333980 -0.38232420 0.05273437 +v 0.55566410 -0.83203120 0.00000000 +v 0.70703130 -0.70703130 0.02441406 +v 0.83056640 -0.55517580 0.05273437 +v 0.38281250 -0.92431640 0.00000000 +v 0.55566410 -0.83154300 0.02441406 +v 0.70654290 -0.70654290 0.05273437 +v 0.27539060 -0.90820310 -0.30322270 +v 0.38281250 -0.92431640 0.02441406 +v 0.19531250 -0.98144530 0.00000000 +v -0.09277344 -0.94433600 -0.30322270 +v -0.73339840 -0.60205080 -0.30322270 +v -0.44726560 -0.83691400 -0.30322270 +v 0.00000000 -1.00048800 0.00000000 +v -0.19531250 -0.98144530 0.00000000 +v 0.19531250 -0.98095710 0.02441406 +v 0.00000000 -1.00000000 0.02441406 +v -0.38281250 -0.92431640 0.00000000 +v 0.38232420 -0.92333980 0.05273437 +v -0.19531250 -0.98095710 0.02441406 +v -0.55566410 -0.83203120 0.00000000 +v -0.73339840 -0.60205080 -0.30322270 +v 0.19482420 -0.97998050 0.05273437 +v 0.00000000 -0.99902340 0.05273437 +v -0.38281250 -0.92431640 0.02441406 +v -0.19482420 -0.97998050 0.05273437 +v 0.19433590 -0.97753910 0.08789062 +v -0.55566410 -0.83154300 0.02441406 +v 0.00000000 -0.99658200 0.08789062 +v -0.38232420 -0.92333980 0.05273437 +v -0.19433590 -0.97753910 0.08789062 +v -0.70751950 -0.70751950 0.00000000 +v -0.55517580 -0.83056640 0.05273437 +v -0.70703130 -0.70703130 0.02441406 +v -0.83203120 -0.55566410 0.00000000 +v -0.90820310 -0.27539060 -0.30322270 +v -0.83154300 -0.55566410 0.02441406 +v -0.92431640 -0.38281250 0.00000000 +v -0.70654290 -0.70654290 0.05273437 +v -0.92431640 -0.38281250 0.02441406 +v -0.38134770 -0.92089840 0.08789062 +v -0.55371090 -0.82861330 0.08789062 +v -0.83056640 -0.55517580 0.05273437 +v -0.70458990 -0.70458990 0.08789062 +v -0.37890620 -0.91552730 0.13964840 +v -0.55029300 -0.82373050 0.13964840 +v -0.82861330 -0.55371090 0.08789062 +v -0.19335940 -0.97167970 0.13964840 +v -0.70068360 -0.70068360 0.13964840 +v 0.00000000 -0.99072260 0.13964840 +v -0.92333980 -0.38232420 0.05273437 +v 0.19335940 -0.97167970 0.13964840 +v -0.19189450 -0.96582030 0.17773440 +v 0.00000000 -0.98437500 0.17773440 +v 0.37890620 -0.91552730 0.13964840 +v -0.37695310 -0.90966800 0.17773440 +v 0.19189450 -0.96582030 0.17773440 +v -0.54687500 -0.81884770 0.17773440 +v -0.37255860 -0.89892580 0.23291010 +v -0.69628900 -0.69628900 0.17773440 +v -0.54052730 -0.80908200 0.23291010 +v -0.68798830 -0.68798830 0.23291010 +v -0.81884770 -0.54687500 0.17773440 +v -0.80908200 -0.54052730 0.23291010 +v -0.67431640 -0.67431640 0.30322270 +v -0.82373050 -0.55029300 0.13964840 +v -0.79296870 -0.52978510 0.30322270 +v -0.52978510 -0.79296870 0.30322270 +v -0.75830080 -0.50683590 0.41113280 +v -0.64501960 -0.64501960 0.41113280 +v -0.36474610 -0.88085940 0.30322270 +v -0.50683590 -0.75830080 0.41113280 +v -0.18994140 -0.95458980 0.23291010 +v 0.00000000 -0.97314460 0.23291010 +v -0.18603520 -0.93505860 0.30322270 +v -0.34912110 -0.84277350 0.41113280 +v 0.18994140 -0.95458980 0.23291010 +v 0.00000000 -0.95361330 0.30322270 +v -0.17773440 -0.89453130 0.41113280 +v 0.37695310 -0.90966800 0.17773440 +v 0.18603520 -0.93505860 0.30322270 +v 0.37255860 -0.89892580 0.23291010 +v 0.55029300 -0.82373050 0.13964840 +v 0.00000000 -0.91210940 0.41113280 +v 0.54687500 -0.81884770 0.17773440 +v 0.36474610 -0.88085940 0.30322270 +v 0.38134770 -0.92089840 0.08789062 +v 0.55371090 -0.82861330 0.08789062 +v 0.70068360 -0.70068360 0.13964840 +v 0.55517580 -0.83056640 0.05273437 +v 0.70458990 -0.70458990 0.08789062 +v 0.82861330 -0.55371090 0.08789062 +v 0.82373050 -0.55029300 0.13964840 +v 0.92089840 -0.38134770 0.08789062 +v 0.91552730 -0.37890620 0.13964840 +v 0.69628900 -0.69628900 0.17773440 +v 0.81884770 -0.54687500 0.17773440 +v 0.54052730 -0.80908200 0.23291010 +v 0.68798830 -0.68798830 0.23291010 +v 0.52978510 -0.79296870 0.30322270 +v 0.80908200 -0.54052730 0.23291010 +v 0.67431640 -0.67431640 0.30322270 +v 0.34912110 -0.84277350 0.41113280 +v 0.50683590 -0.75830080 0.41113280 +v 0.17773440 -0.89453130 0.41113280 +v 0.16699220 -0.83837890 0.51953120 +v 0.00000000 -0.85498040 0.51953120 +v -0.16699220 -0.83837890 0.51953120 +v 0.32714840 -0.79003900 0.51953120 +v -0.32714840 -0.79003900 0.51953120 +v -0.14453130 -0.72607420 0.67333980 +v -0.47509770 -0.71093750 0.51953120 +v -0.28320310 -0.68408200 0.67333980 +v -0.41113280 -0.61523430 0.67333980 +v -0.60449220 -0.60449220 0.51953120 +v -0.52343750 -0.52343750 0.67333980 +v -0.71093750 -0.47509770 0.51953120 +v -0.61523430 -0.41113280 0.67333980 +v -0.35400390 -0.35400390 0.86621090 +v -0.41601560 -0.27783200 0.86621090 +v -0.68408200 -0.28320310 0.67333980 +v -0.27783200 -0.41601560 0.86621090 +v -0.79003900 -0.32714840 0.51953120 +v -0.19140620 -0.46240230 0.86621090 +v -0.84277350 -0.34912110 0.41113280 +v -0.09765625 -0.49072270 0.86621090 +v -0.88085940 -0.36474610 0.30322270 +v 0.00000000 -0.74023440 0.67333980 +v -0.89892580 -0.37255860 0.23291010 +v 0.14453130 -0.72607420 0.67333980 +v -0.90966800 -0.37695310 0.17773440 +v 0.00000000 -0.50048830 0.86621090 +v 0.28320310 -0.68408200 0.67333980 +v 0.09765625 -0.49072270 0.86621090 +v -0.06054688 -0.30322270 0.95166020 +v 0.00000000 -0.30908200 0.95166020 +v -0.11816410 -0.28564450 0.95166020 +v 0.00000000 -0.14501950 0.99755860 +v -0.17187500 -0.25732420 0.95166020 +v -0.02832031 -0.14257810 0.99755860 +v -0.05566406 -0.13427730 0.99755860 +v -0.21875000 -0.21875000 0.95166020 +v -0.08056641 -0.12060550 0.99755860 +v -0.25732420 -0.17187500 0.95166020 +v -0.10253910 -0.10253910 0.99755860 +v 0.00000000 0.00000000 1.00048800 +v -0.12060550 -0.08056641 0.99755860 +v -0.28564450 -0.11816410 0.95166020 +v -0.13427730 -0.05566406 0.99755860 +v -0.46240230 -0.19140620 0.86621090 +v -0.30322270 -0.06054688 0.95166020 +v -0.49072270 -0.09765625 0.86621090 +v -0.14257810 -0.02832031 0.99755860 +v -0.30908200 0.00000000 0.95166020 +v -0.72607420 -0.14453130 0.67333980 +v -0.50048830 0.00000000 0.86621090 +v -0.14501950 0.00000000 0.99755860 +v -0.30322270 0.06054688 0.95166020 +v -0.14257810 0.02832031 0.99755860 +v -0.49072270 0.09765625 0.86621090 +v -0.28564450 0.11816410 0.95166020 +v -0.13427730 0.05566406 0.99755860 +v -0.74023440 0.00000000 0.67333980 +v -0.46240230 0.19140620 0.86621090 +v -0.83837890 -0.16699220 0.51953120 +v -0.72607420 0.14453130 0.67333980 +v -0.85498040 0.00000000 0.51953120 +v -0.89453130 -0.17773440 0.41113280 +v -0.83837890 0.16699220 0.51953120 +v -0.93505860 -0.18603520 0.30322270 +v -0.91210940 0.00000000 0.41113280 +v -0.95458980 -0.18994140 0.23291010 +v -0.95361330 0.00000000 0.30322270 +v -0.89453130 0.17773440 0.41113280 +v -0.68408200 0.28320310 0.67333980 +v -0.79003900 0.32714840 0.51953120 +v -0.93505860 0.18603520 0.30322270 +v -0.41601560 0.27783200 0.86621090 +v -0.84277350 0.34912110 0.41113280 +v -0.61523430 0.41113280 0.67333980 +v -0.71093750 0.47509770 0.51953120 +v -0.25732420 0.17187500 0.95166020 +v -0.35400390 0.35400390 0.86621090 +v -0.12060550 0.08056641 0.99755860 +v -0.21875000 0.21875000 0.95166020 +v -0.10253910 0.10253910 0.99755860 +v -0.17187500 0.25732420 0.95166020 +v -0.08056641 0.12060550 0.99755860 +v -0.27783200 0.41601560 0.86621090 +v -0.11816410 0.28564450 0.95166020 +v -0.52343750 0.52343750 0.67333980 +v -0.05566406 0.13427730 0.99755860 +v -0.60449220 0.60449220 0.51953120 +v -0.06054688 0.30322270 0.95166020 +v -0.41113280 0.61523430 0.67333980 +v -0.19140620 0.46240230 0.86621090 +v -0.28320310 0.68408200 0.67333980 +v -0.02832031 0.14257810 0.99755860 +v -0.09765625 0.49072270 0.86621090 +v 0.00000000 0.30908200 0.95166020 +v 0.00000000 0.14501950 0.99755860 +v 0.00000000 0.50048830 0.86621090 +v 0.06054688 0.30322270 0.95166020 +v 0.02832031 0.14257810 0.99755860 +v -0.14453130 0.72607420 0.67333980 +v 0.09765625 0.49072270 0.86621090 +v 0.11816410 0.28564450 0.95166020 +v 0.05566406 0.13427730 0.99755860 +v 0.00000000 0.74023440 0.67333980 +v 0.19140620 0.46240230 0.86621090 +v 0.17187500 0.25732420 0.95166020 +v 0.08056641 0.12060550 0.99755860 +v 0.14453130 0.72607420 0.67333980 +v 0.27783200 0.41601560 0.86621090 +v 0.21875000 0.21875000 0.95166020 +v 0.10253910 0.10253910 0.99755860 +v 0.28320310 0.68408200 0.67333980 +v 0.35400390 0.35400390 0.86621090 +v 0.25732420 0.17187500 0.95166020 +v 0.12060550 0.08056641 0.99755860 +v 0.41113280 0.61523430 0.67333980 +v 0.41601560 0.27783200 0.86621090 +v 0.28564450 0.11816410 0.95166020 +v 0.13427730 0.05566406 0.99755860 +v 0.52343750 0.52343750 0.67333980 +v 0.46240230 0.19140620 0.86621090 +v 0.30322270 0.06054688 0.95166020 +v 0.14257810 0.02832031 0.99755860 +v 0.61523430 0.41113280 0.67333980 +v 0.49072270 0.09765625 0.86621090 +v 0.30908200 0.00000000 0.95166020 +v 0.14501950 0.00000000 0.99755860 +v 0.68408200 0.28320310 0.67333980 +v 0.50048830 0.00000000 0.86621090 +v 0.30322270 -0.06054688 0.95166020 +v 0.14257810 -0.02832031 0.99755860 +v 0.72607420 0.14453130 0.67333980 +v 0.49072270 -0.09765625 0.86621090 +v 0.28564450 -0.11816410 0.95166020 +v 0.13427730 -0.05566406 0.99755860 +v 0.74023440 0.00000000 0.67333980 +v 0.46240230 -0.19140620 0.86621090 +v 0.25732420 -0.17187500 0.95166020 +v 0.12060550 -0.08056641 0.99755860 +v 0.72607420 -0.14453130 0.67333980 +v 0.41601560 -0.27783200 0.86621090 +v 0.21875000 -0.21875000 0.95166020 +v 0.10253910 -0.10253910 0.99755860 +v 0.68408200 -0.28320310 0.67333980 +v 0.35400390 -0.35400390 0.86621090 +v 0.17187500 -0.25732420 0.95166020 +v 0.08056641 -0.12060550 0.99755860 +v 0.61523430 -0.41113280 0.67333980 +v 0.05566406 -0.13427730 0.99755860 +v 0.11816410 -0.28564450 0.95166020 +v 0.02832031 -0.14257810 0.99755860 +v 0.06054688 -0.30322270 0.95166020 +v 0.19140620 -0.46240230 0.86621090 +v 0.27783200 -0.41601560 0.86621090 +v 0.41113280 -0.61523430 0.67333980 +v 0.52343750 -0.52343750 0.67333980 +v 0.47509770 -0.71093750 0.51953120 +v 0.60449220 -0.60449220 0.51953120 +v 0.64501960 -0.64501960 0.41113280 +v 0.71093750 -0.47509770 0.51953120 +v 0.75830080 -0.50683590 0.41113280 +v 0.79296870 -0.52978510 0.30322270 +v 0.79003900 -0.32714840 0.51953120 +v 0.84277350 -0.34912110 0.41113280 +v 0.88085940 -0.36474610 0.30322270 +v 0.83837890 -0.16699220 0.51953120 +v 0.89453130 -0.17773440 0.41113280 +v 0.85498040 0.00000000 0.51953120 +v 0.91210940 0.00000000 0.41113280 +v 0.93505860 -0.18603520 0.30322270 +v 0.95361330 0.00000000 0.30322270 +v 0.83837890 0.16699220 0.51953120 +v 0.89453130 0.17773440 0.41113280 +v 0.79003900 0.32714840 0.51953120 +v 0.84277350 0.34912110 0.41113280 +v 0.93505860 0.18603520 0.30322270 +v 0.88085940 0.36474610 0.30322270 +v 0.71093750 0.47509770 0.51953120 +v 0.75830080 0.50683590 0.41113280 +v 0.60449220 0.60449220 0.51953120 +v 0.64501960 0.64501960 0.41113280 +v 0.79296870 0.52978510 0.30322270 +v 0.67431640 0.67431640 0.30322270 +v 0.47509770 0.71093750 0.51953120 +v 0.50683590 0.75830080 0.41113280 +v 0.32714840 0.79003900 0.51953120 +v 0.34912110 0.84277350 0.41113280 +v 0.52978510 0.79296870 0.30322270 +v 0.36474610 0.88085940 0.30322270 +v 0.16699220 0.83837890 0.51953120 +v 0.17773440 0.89453130 0.41113280 +v 0.00000000 0.85498040 0.51953120 +v 0.00000000 0.91210940 0.41113280 +v 0.18603520 0.93505860 0.30322270 +v 0.00000000 0.95361330 0.30322270 +v -0.16699220 0.83837890 0.51953120 +v -0.17773440 0.89453130 0.41113280 +v -0.32714840 0.79003900 0.51953120 +v -0.34912110 0.84277350 0.41113280 +v -0.18603520 0.93505860 0.30322270 +v -0.36474610 0.88085940 0.30322270 +v -0.47509770 0.71093750 0.51953120 +v -0.50683590 0.75830080 0.41113280 +v -0.64501960 0.64501960 0.41113280 +v -0.52978510 0.79296870 0.30322270 +v -0.67431640 0.67431640 0.30322270 +v -0.75830080 0.50683590 0.41113280 +v -0.79296870 0.52978510 0.30322270 +v -0.88085940 0.36474610 0.30322270 +v -0.80908200 0.54052730 0.23291010 +v -0.89892580 0.37255860 0.23291010 +v -0.95458980 0.18994140 0.23291010 +v -0.97314460 0.00000000 0.23291010 +v -0.96582030 0.19189450 0.17773440 +v -0.96582030 -0.19189450 0.17773440 +v -0.98437500 0.00000000 0.17773440 +v -0.91552730 -0.37890620 0.13964840 +v -0.97167970 -0.19335940 0.13964840 +v -0.92089840 -0.38134770 0.08789062 +v -0.97753910 -0.19433590 0.08789062 +v -0.97998050 -0.19482420 0.05273437 +v -0.99072260 0.00000000 0.13964840 +v -0.99658200 0.00000000 0.08789062 +v -0.99902340 0.00000000 0.05273437 +v -0.98095710 -0.19531250 0.02441406 +v -1.00000000 0.00000000 0.02441406 +v -0.98144530 -0.19531250 0.00000000 +v -1.00048800 0.00000000 0.00000000 +v -0.94433600 0.09277344 -0.30322270 +v -0.98144530 0.19531250 0.00000000 +v -0.98095710 0.19531250 0.02441406 +v -0.83691400 0.44726560 -0.30322270 +v -0.92431640 0.38281250 0.00000000 +v -0.97998050 0.19482420 0.05273437 +v -0.92431640 0.38281250 0.02441406 +v -0.83203120 0.55566410 0.00000000 +v -0.60205080 0.73339840 -0.30322270 +v -0.27539060 0.90820310 -0.30322270 +v -0.70751950 0.70751950 0.00000000 +v -0.55566410 0.83203120 0.00000000 +v -0.83154300 0.55566410 0.02441406 +v -0.70703130 0.70703130 0.02441406 +v -0.92333980 0.38232420 0.05273437 +v -0.83056640 0.55517580 0.05273437 +v -0.97753910 0.19433590 0.08789062 +v -0.92089840 0.38134770 0.08789062 +v -0.97167970 0.19335940 0.13964840 +v -0.82861330 0.55371090 0.08789062 +v -0.90966800 0.37695310 0.17773440 +v -0.91552730 0.37890620 0.13964840 +v -0.81884770 0.54687500 0.17773440 +v -0.82373050 0.55029300 0.13964840 +v -0.68798830 0.68798830 0.23291010 +v -0.69628900 0.69628900 0.17773440 +v -0.54052730 0.80908200 0.23291010 +v -0.70068360 0.70068360 0.13964840 +v -0.37255860 0.89892580 0.23291010 +v -0.54687500 0.81884770 0.17773440 +v -0.18994140 0.95458980 0.23291010 +v -0.37695310 0.90966800 0.17773440 +v -0.55029300 0.82373050 0.13964840 +v 0.00000000 0.97314460 0.23291010 +v -0.19189450 0.96582030 0.17773440 +v -0.37890620 0.91552730 0.13964840 +v 0.18994140 0.95458980 0.23291010 +v 0.00000000 0.98437500 0.17773440 +v -0.19335940 0.97167970 0.13964840 +v 0.37255860 0.89892580 0.23291010 +v 0.19189450 0.96582030 0.17773440 +v 0.00000000 0.99072260 0.13964840 +v 0.54052730 0.80908200 0.23291010 +v 0.37695310 0.90966800 0.17773440 +v 0.19335940 0.97167970 0.13964840 +v 0.68798830 0.68798830 0.23291010 +v 0.54687500 0.81884770 0.17773440 +v 0.37890620 0.91552730 0.13964840 +v 0.80908200 0.54052730 0.23291010 +v 0.69628900 0.69628900 0.17773440 +v 0.55029300 0.82373050 0.13964840 +v 0.89892580 0.37255860 0.23291010 +v 0.81884770 0.54687500 0.17773440 +v 0.70068360 0.70068360 0.13964840 +v 0.95458980 0.18994140 0.23291010 +v 0.90966800 0.37695310 0.17773440 +v 0.82373050 0.55029300 0.13964840 +v 0.97314460 0.00000000 0.23291010 +v 0.96582030 0.19189450 0.17773440 +v 0.91552730 0.37890620 0.13964840 +v 0.95458980 -0.18994140 0.23291010 +v 0.98437500 0.00000000 0.17773440 +v 0.97167970 0.19335940 0.13964840 +v 0.89892580 -0.37255860 0.23291010 +v 0.96582030 -0.19189450 0.17773440 +v 0.90966800 -0.37695310 0.17773440 +v 0.97167970 -0.19335940 0.13964840 +v 0.99072260 0.00000000 0.13964840 +v 0.97753910 -0.19433590 0.08789062 +v 0.99658200 0.00000000 0.08789062 +v 0.97753910 0.19433590 0.08789062 +v 0.92089840 0.38134770 0.08789062 +v 0.82861330 0.55371090 0.08789062 +v 0.70458990 0.70458990 0.08789062 +v 0.55371090 0.82861330 0.08789062 +v 0.38134770 0.92089840 0.08789062 +v 0.19433590 0.97753910 0.08789062 +v 0.00000000 0.99658200 0.08789062 +v 0.00000000 0.99902340 0.05273437 +v -0.19433590 0.97753910 0.08789062 +v 0.00000000 1.00000000 0.02441406 +v -0.19482420 0.97998050 0.05273437 +v -0.38134770 0.92089840 0.08789062 +v 0.00000000 1.00048800 0.00000000 +v -0.55371090 0.82861330 0.08789062 +v -0.19531250 0.98144530 0.00000000 +v -0.19531250 0.98095710 0.02441406 +v -0.38281250 0.92431640 0.00000000 +v -0.38281250 0.92431640 0.02441406 +v -0.38232420 0.92333980 0.05273437 +v -0.55566410 0.83154300 0.02441406 +v -0.55517580 0.83056640 0.05273437 +v -0.70654290 0.70654290 0.05273437 +v -0.70458990 0.70458990 0.08789062 + +# uv2 1 1.46099900 0.19879150 +# uv2 2 0.57717890 0.21023560 +# uv2 3 0.99032590 0.49655150 +# uv2 4 0.68014520 0.26525880 +# uv2 5 0.62921140 0.28868100 +# uv2 6 0.58599850 0.26557920 +# uv2 7 0.53909300 0.25135800 +# uv2 8 0.46099850 0.19879150 +# uv2 9 0.62704470 0.29194640 +# uv2 10 0.58450320 0.26921080 +# uv2 11 0.49032590 0.24655150 +# uv2 12 0.53833010 0.25520320 +# uv2 13 0.62457280 0.29563900 +# uv2 14 0.44155890 0.25135800 +# uv2 15 0.58279420 0.27330020 +# uv2 16 0.49032590 0.25047300 +# uv2 17 0.53746030 0.25955200 +# uv2 18 0.34928890 0.23268130 +# uv2 19 -0.00967407 0.49655150 +# uv2 20 0.44232180 0.25520320 +# uv2 21 0.39465330 0.26557920 +# uv2 22 0.49032590 0.25491330 +# uv2 23 0.35144040 0.28868100 +# uv2 24 0.39616390 0.26921080 +# uv2 25 0.44319150 0.25955200 +# uv2 26 0.25903320 0.30673220 +# uv2 27 0.35360720 0.29194640 +# uv2 28 0.31355290 0.31977850 +# uv2 29 0.39785770 0.27330020 +# uv2 30 0.28245540 0.35766600 +# uv2 31 0.31632990 0.32255550 +# uv2 32 0.35607910 0.29563900 +# uv2 33 0.20401000 0.40969850 +# uv2 34 0.28572080 0.35983280 +# uv2 35 0.25935360 0.40087890 +# uv2 36 0.31945800 0.32568360 +# uv2 37 0.24513240 0.44778440 +# uv2 38 0.26298520 0.40238950 +# uv2 39 0.28941350 0.36230470 +# uv2 40 0.19256590 0.52587890 +# uv2 41 0.24897770 0.44854730 +# uv2 42 0.24032590 0.49655150 +# uv2 43 0.26707460 0.40408320 +# uv2 44 0.24513240 0.54531860 +# uv2 45 0.24424740 0.49655150 +# uv2 46 0.25332640 0.44941710 +# uv2 47 0.25935360 0.59222410 +# uv2 48 0.24897770 0.54455570 +# uv2 49 0.24868770 0.49655150 +# uv2 50 0.22645570 0.63758850 +# uv2 51 0.26298520 0.59072880 +# uv2 52 0.28245540 0.63543700 +# uv2 53 0.30050660 0.72784430 +# uv2 54 -0.48034670 0.79431150 +# uv2 55 0.40347290 0.78286750 +# uv2 56 0.31355290 0.67332460 +# uv2 57 0.35144040 0.70442200 +# uv2 58 0.28572080 0.63327030 +# uv2 59 0.31632990 0.67054750 +# uv2 60 0.39465330 0.72752380 +# uv2 61 0.26707460 0.58901980 +# uv2 62 0.35360720 0.70115660 +# uv2 63 0.44155890 0.74174500 +# uv2 64 0.51965330 0.79431150 +# uv2 65 0.28941350 0.63079830 +# uv2 66 0.31945800 0.66741940 +# uv2 67 0.39614870 0.72389220 +# uv2 68 0.35607910 0.69746400 +# uv2 69 0.29409790 0.62767030 +# uv2 70 0.44232180 0.73789980 +# uv2 71 0.32344060 0.66343690 +# uv2 72 0.39785770 0.71980280 +# uv2 73 0.35920720 0.69277950 +# uv2 74 0.49032590 0.74655150 +# uv2 75 0.44319150 0.73355100 +# uv2 76 0.49032590 0.74263000 +# uv2 77 0.53909300 0.74174500 +# uv2 78 0.63136290 0.76042180 +# uv2 79 0.53833010 0.73789980 +# uv2 80 0.58599850 0.72752380 +# uv2 81 0.49032590 0.73818970 +# uv2 82 0.58450320 0.72389220 +# uv2 83 0.40000920 0.71459960 +# uv2 84 0.44429020 0.72802730 +# uv2 85 0.53746030 0.73355100 +# uv2 86 0.49032590 0.73255920 +# uv2 87 0.40318300 0.70692450 +# uv2 88 0.44590760 0.71987910 +# uv2 89 0.53636170 0.72802730 +# uv2 90 0.36381530 0.68588260 +# uv2 91 0.49032590 0.72425840 +# uv2 92 0.32931520 0.65756220 +# uv2 93 0.58279420 0.71980280 +# uv2 94 0.30099490 0.62306210 +# uv2 95 0.36723330 0.68077090 +# uv2 96 0.33366390 0.65321350 +# uv2 97 0.27995300 0.58369440 +# uv2 98 0.40554810 0.70123290 +# uv2 99 0.30610660 0.61964410 +# uv2 100 0.44709780 0.71385190 +# uv2 101 0.40895080 0.69299320 +# uv2 102 0.49032590 0.71810910 +# uv2 103 0.44883730 0.70509340 +# uv2 104 0.49032590 0.70918270 +# uv2 105 0.53355410 0.71385190 +# uv2 106 0.53181460 0.70509340 +# uv2 107 0.49032590 0.69757080 +# uv2 108 0.53474430 0.71987910 +# uv2 109 0.52954100 0.69371030 +# uv2 110 0.45111080 0.69371030 +# uv2 111 0.52593990 0.67561340 +# uv2 112 0.49032590 0.67912290 +# uv2 113 0.41340640 0.68226620 +# uv2 114 0.45471190 0.67561340 +# uv2 115 0.37219240 0.67335510 +# uv2 116 0.33998110 0.64691160 +# uv2 117 0.37864690 0.66369630 +# uv2 118 0.42045590 0.66522220 +# uv2 119 0.31353760 0.61468510 +# uv2 120 0.34819030 0.63868710 +# uv2 121 0.38890080 0.64834590 +# uv2 122 0.28564450 0.58132930 +# uv2 123 0.32318110 0.60823060 +# uv2 124 0.29388430 0.57792660 +# uv2 125 0.26699830 0.54096990 +# uv2 126 0.36123660 0.62564090 +# uv2 127 0.27302550 0.53977970 +# uv2 128 0.30461120 0.57347110 +# uv2 129 0.27227780 0.58686830 +# uv2 130 0.25885010 0.54258730 +# uv2 131 0.26261900 0.49655150 +# uv2 132 0.25332640 0.54368590 +# uv2 133 0.25431820 0.49655150 +# uv2 134 0.25885010 0.45051570 +# uv2 135 0.26699830 0.45213320 +# uv2 136 0.27227780 0.40623470 +# uv2 137 0.27995300 0.40940860 +# uv2 138 0.26876830 0.49655150 +# uv2 139 0.27302550 0.45332340 +# uv2 140 0.28178410 0.53804020 +# uv2 141 0.27769470 0.49655150 +# uv2 142 0.29316710 0.53576660 +# uv2 143 0.28178410 0.45506290 +# uv2 144 0.28930660 0.49655150 +# uv2 145 0.32165530 0.56642150 +# uv2 146 0.31126400 0.53216550 +# uv2 147 0.33853150 0.59797670 +# uv2 148 0.35472110 0.58715820 +# uv2 149 0.37500000 0.61187750 +# uv2 150 0.39971920 0.63215640 +# uv2 151 0.33964540 0.55896000 +# uv2 152 0.42791750 0.64723210 +# uv2 153 0.41668700 0.60675050 +# uv2 154 0.45851140 0.65650940 +# uv2 155 0.43960570 0.61900330 +# uv2 156 0.46446230 0.62654120 +# uv2 157 0.49032590 0.65963740 +# uv2 158 0.49032590 0.62908940 +# uv2 159 0.52214050 0.65650940 +# uv2 160 0.51618960 0.62654120 +# uv2 161 0.49032590 0.57989500 +# uv2 162 0.50659180 0.57829280 +# uv2 163 0.54104610 0.61900330 +# uv2 164 0.47406010 0.57829280 +# uv2 165 0.55273440 0.64723210 +# uv2 166 0.45843510 0.57356260 +# uv2 167 0.56019590 0.66522220 +# uv2 168 0.44401550 0.56585690 +# uv2 169 0.56724550 0.68226620 +# uv2 170 0.39660650 0.59027100 +# uv2 171 0.57170100 0.69299320 +# uv2 172 0.38012690 0.57019040 +# uv2 173 0.57510370 0.70123290 +# uv2 174 0.43139650 0.55548100 +# uv2 175 0.36787420 0.54727170 +# uv2 176 0.42102050 0.54286190 +# uv2 177 0.46253970 0.53813170 +# uv2 178 0.45497130 0.53190610 +# uv2 179 0.47119140 0.54275510 +# uv2 180 0.47406010 0.51281740 +# uv2 181 0.48057560 0.54559330 +# uv2 182 0.47753910 0.51568600 +# uv2 183 0.48152160 0.51780700 +# uv2 184 0.49032590 0.54655460 +# uv2 185 0.48583980 0.51911930 +# uv2 186 0.50007630 0.54559330 +# uv2 187 0.49032590 0.51956180 +# uv2 188 0.49032590 0.49655150 +# uv2 189 0.49481200 0.51911930 +# uv2 190 0.50946050 0.54275510 +# uv2 191 0.49913020 0.51780700 +# uv2 192 0.52221680 0.57356260 +# uv2 193 0.51811220 0.53813170 +# uv2 194 0.53663630 0.56585690 +# uv2 195 0.50311280 0.51568600 +# uv2 196 0.52568050 0.53190610 +# uv2 197 0.56396480 0.60675050 +# uv2 198 0.54925540 0.55548100 +# uv2 199 0.50659180 0.51281740 +# uv2 200 0.53190610 0.52433780 +# uv2 201 0.50946050 0.50933840 +# uv2 202 0.55963140 0.54286190 +# uv2 203 0.53652950 0.51568600 +# uv2 204 0.51158140 0.50535590 +# uv2 205 0.58404540 0.59027100 +# uv2 206 0.56733700 0.52844240 +# uv2 207 0.58093260 0.63215640 +# uv2 208 0.60052490 0.57019040 +# uv2 209 0.60565180 0.61187750 +# uv2 210 0.59175110 0.64834590 +# uv2 211 0.62593080 0.58715820 +# uv2 212 0.60200500 0.66369630 +# uv2 213 0.61941530 0.62564090 +# uv2 214 0.60845950 0.67335510 +# uv2 215 0.63246160 0.63868710 +# uv2 216 0.64212040 0.59797670 +# uv2 217 0.61277770 0.54727170 +# uv2 218 0.64100650 0.55896000 +# uv2 219 0.65747070 0.60823060 +# uv2 220 0.57206730 0.51281740 +# uv2 221 0.65899660 0.56642150 +# uv2 222 0.62031550 0.52241520 +# uv2 223 0.65028380 0.52836610 +# uv2 224 0.53936770 0.50630190 +# uv2 225 0.57366940 0.49655150 +# uv2 226 0.51289370 0.50103760 +# uv2 227 0.54032900 0.49655150 +# uv2 228 0.51333620 0.49655150 +# uv2 229 0.53936770 0.48680110 +# uv2 230 0.51289370 0.49206540 +# uv2 231 0.57206730 0.48028560 +# uv2 232 0.53652950 0.47741700 +# uv2 233 0.62286380 0.49655150 +# uv2 234 0.51158140 0.48774720 +# uv2 235 0.65341190 0.49655150 +# uv2 236 0.53190610 0.46876530 +# uv2 237 0.62031550 0.47068790 +# uv2 238 0.56733700 0.46466070 +# uv2 239 0.61277770 0.44583130 +# uv2 240 0.50946050 0.48376460 +# uv2 241 0.55963140 0.45024110 +# uv2 242 0.52568050 0.46119690 +# uv2 243 0.50659180 0.48028560 +# uv2 244 0.54925540 0.43762210 +# uv2 245 0.51811220 0.45497130 +# uv2 246 0.50311280 0.47741700 +# uv2 247 0.60052490 0.42291260 +# uv2 248 0.53663630 0.42724610 +# uv2 249 0.50946050 0.45036320 +# uv2 250 0.49913020 0.47529600 +# uv2 251 0.58404540 0.40283200 +# uv2 252 0.52221680 0.41955570 +# uv2 253 0.50007630 0.44750980 +# uv2 254 0.49481200 0.47398380 +# uv2 255 0.56396480 0.38635250 +# uv2 256 0.50659180 0.41481020 +# uv2 257 0.49032590 0.44654850 +# uv2 258 0.49032590 0.47354130 +# uv2 259 0.54104610 0.37409970 +# uv2 260 0.49032590 0.41320800 +# uv2 261 0.48057560 0.44750980 +# uv2 262 0.48583980 0.47398380 +# uv2 263 0.51618960 0.36656190 +# uv2 264 0.47406010 0.41481020 +# uv2 265 0.47119140 0.45036320 +# uv2 266 0.48152160 0.47529600 +# uv2 267 0.49032590 0.36401370 +# uv2 268 0.45843510 0.41955570 +# uv2 269 0.46253970 0.45497130 +# uv2 270 0.47753910 0.47741700 +# uv2 271 0.46446230 0.36656190 +# uv2 272 0.44401550 0.42724610 +# uv2 273 0.45497130 0.46119690 +# uv2 274 0.47406010 0.48028560 +# uv2 275 0.43960570 0.37409970 +# uv2 276 0.43139650 0.43762210 +# uv2 277 0.44874570 0.46876530 +# uv2 278 0.47119140 0.48376460 +# uv2 279 0.41668700 0.38635250 +# uv2 280 0.42102050 0.45024110 +# uv2 281 0.44413760 0.47741700 +# uv2 282 0.46907040 0.48774720 +# uv2 283 0.39660650 0.40283200 +# uv2 284 0.41333010 0.46466070 +# uv2 285 0.44128420 0.48680110 +# uv2 286 0.46775820 0.49206540 +# uv2 287 0.38012690 0.42291260 +# uv2 288 0.40858460 0.48028560 +# uv2 289 0.44032290 0.49655150 +# uv2 290 0.46731570 0.49655150 +# uv2 291 0.36787420 0.44583130 +# uv2 292 0.40698240 0.49655150 +# uv2 293 0.44128420 0.50630190 +# uv2 294 0.46775820 0.50103760 +# uv2 295 0.36033630 0.47068790 +# uv2 296 0.46907040 0.50535590 +# uv2 297 0.44413760 0.51568600 +# uv2 298 0.47119140 0.50933840 +# uv2 299 0.44874570 0.52433780 +# uv2 300 0.41333010 0.52844240 +# uv2 301 0.40858460 0.51281740 +# uv2 302 0.36033630 0.52241520 +# uv2 303 0.35778810 0.49655150 +# uv2 304 0.33036800 0.52836610 +# uv2 305 0.32724000 0.49655150 +# uv2 306 0.30775450 0.49655150 +# uv2 307 0.33036800 0.46473690 +# uv2 308 0.31126400 0.46093750 +# uv2 309 0.29316710 0.45733640 +# uv2 310 0.33964540 0.43414310 +# uv2 311 0.32165530 0.42668150 +# uv2 312 0.30461120 0.41963190 +# uv2 313 0.35472110 0.40594480 +# uv2 314 0.33853150 0.39512630 +# uv2 315 0.37500000 0.38122560 +# uv2 316 0.36123660 0.36746210 +# uv2 317 0.32318110 0.38487240 +# uv2 318 0.34819030 0.35441590 +# uv2 319 0.39971920 0.36094660 +# uv2 320 0.38890080 0.34475710 +# uv2 321 0.42791750 0.34587100 +# uv2 322 0.42045590 0.32788090 +# uv2 323 0.37864690 0.32940670 +# uv2 324 0.41340640 0.31083680 +# uv2 325 0.45851140 0.33659360 +# uv2 326 0.45471190 0.31748960 +# uv2 327 0.49032590 0.33346560 +# uv2 328 0.49032590 0.31398010 +# uv2 329 0.45111080 0.29939270 +# uv2 330 0.49032590 0.29553220 +# uv2 331 0.52214050 0.33659360 +# uv2 332 0.52593990 0.31748960 +# uv2 333 0.55273440 0.34587100 +# uv2 334 0.56019590 0.32788090 +# uv2 335 0.52954100 0.29939270 +# uv2 336 0.56724550 0.31083680 +# uv2 337 0.58093260 0.36094660 +# uv2 338 0.59175110 0.34475710 +# uv2 339 0.60565180 0.38122560 +# uv2 340 0.61941530 0.36746210 +# uv2 341 0.60200500 0.32940670 +# uv2 342 0.63246160 0.35441590 +# uv2 343 0.62593080 0.40594480 +# uv2 344 0.64212040 0.39512630 +# uv2 345 0.64100650 0.43414310 +# uv2 346 0.65899660 0.42668150 +# uv2 347 0.65747070 0.38487240 +# uv2 348 0.67604060 0.41963190 +# uv2 349 0.65028380 0.46473690 +# uv2 350 0.66938780 0.46093750 +# uv2 351 0.67289730 0.49655150 +# uv2 352 0.68748480 0.45733640 +# uv2 353 0.69134520 0.49655150 +# uv2 354 0.66938780 0.53216550 +# uv2 355 0.68748480 0.53576660 +# uv2 356 0.67604060 0.57347110 +# uv2 357 0.69886780 0.53804020 +# uv2 358 0.68676760 0.57792660 +# uv2 359 0.66711420 0.61468510 +# uv2 360 0.64068600 0.64691160 +# uv2 361 0.67454530 0.61964410 +# uv2 362 0.61341860 0.68077090 +# uv2 363 0.64698790 0.65321350 +# uv2 364 0.57746890 0.70692450 +# uv2 365 0.61683650 0.68588260 +# uv2 366 0.58064270 0.71459960 +# uv2 367 0.62144470 0.69277950 +# uv2 368 0.62457280 0.69746400 +# uv2 369 0.65133670 0.65756220 +# uv2 370 0.65721130 0.66343690 +# uv2 371 0.66119390 0.66741940 +# uv2 372 0.62704470 0.70115660 +# uv2 373 0.66432190 0.67056270 +# uv2 374 0.62921140 0.70442200 +# uv2 375 0.66709900 0.67332460 +# uv2 376 0.72161860 0.68637080 +# uv2 377 0.69819640 0.63543700 +# uv2 378 0.69493100 0.63327030 +# uv2 379 0.77664180 0.58340450 +# uv2 380 0.72129830 0.59222410 +# uv2 381 0.69123840 0.63079830 +# uv2 382 0.71766660 0.59072880 +# uv2 383 0.73551940 0.54531860 +# uv2 384 0.78808600 0.46722410 +# uv2 385 0.75419620 0.35551450 +# uv2 386 0.74032590 0.49655150 +# uv2 387 0.73551940 0.44778440 +# uv2 388 0.73167420 0.54455570 +# uv2 389 0.73640440 0.49655150 +# uv2 390 0.71357730 0.58901980 +# uv2 391 0.72732540 0.54368590 +# uv2 392 0.68655400 0.62767030 +# uv2 393 0.70837400 0.58686830 +# uv2 394 0.67965700 0.62306210 +# uv2 395 0.72180180 0.54258730 +# uv2 396 0.69500730 0.58132930 +# uv2 397 0.70069880 0.58369440 +# uv2 398 0.70762640 0.53977970 +# uv2 399 0.71365360 0.54096990 +# uv2 400 0.70295720 0.49655150 +# uv2 401 0.71188350 0.49655150 +# uv2 402 0.69886780 0.45506290 +# uv2 403 0.71803280 0.49655150 +# uv2 404 0.68676760 0.41517640 +# uv2 405 0.70762640 0.45332340 +# uv2 406 0.66711420 0.37841800 +# uv2 407 0.69500730 0.41177370 +# uv2 408 0.71365360 0.45213320 +# uv2 409 0.64068600 0.34620670 +# uv2 410 0.67454530 0.37345880 +# uv2 411 0.70069880 0.40940860 +# uv2 412 0.60845950 0.31976320 +# uv2 413 0.64698790 0.33988950 +# uv2 414 0.67965700 0.37004090 +# uv2 415 0.57170100 0.30010990 +# uv2 416 0.61341860 0.31233220 +# uv2 417 0.65133670 0.33554080 +# uv2 418 0.53181460 0.28800960 +# uv2 419 0.57510370 0.29187010 +# uv2 420 0.61683650 0.30722050 +# uv2 421 0.49032590 0.28392030 +# uv2 422 0.53355410 0.27925110 +# uv2 423 0.57746890 0.28617860 +# uv2 424 0.44883730 0.28800960 +# uv2 425 0.49032590 0.27499390 +# uv2 426 0.53474430 0.27322390 +# uv2 427 0.40895080 0.30010990 +# uv2 428 0.44709780 0.27925110 +# uv2 429 0.49032590 0.26884460 +# uv2 430 0.37219240 0.31976320 +# uv2 431 0.40554810 0.29187010 +# uv2 432 0.44590760 0.27322390 +# uv2 433 0.33998110 0.34620670 +# uv2 434 0.36723330 0.31233220 +# uv2 435 0.40318300 0.28617860 +# uv2 436 0.31353760 0.37841800 +# uv2 437 0.33366390 0.33988950 +# uv2 438 0.36381530 0.30722050 +# uv2 439 0.29388430 0.41517640 +# uv2 440 0.30610660 0.37345880 +# uv2 441 0.28564450 0.41177370 +# uv2 442 0.30099490 0.37004090 +# uv2 443 0.32931520 0.33554080 +# uv2 444 0.29409790 0.36543270 +# uv2 445 0.32344060 0.32966610 +# uv2 446 0.35920720 0.30032350 +# uv2 447 0.40000920 0.27850340 +# uv2 448 0.44429020 0.26507570 +# uv2 449 0.49032590 0.26054380 +# uv2 450 0.53636170 0.26507570 +# uv2 451 0.58064270 0.27850340 +# uv2 452 0.62144470 0.30032350 +# uv2 453 0.65721130 0.32966610 +# uv2 454 0.66119390 0.32568360 +# uv2 455 0.68655400 0.36543270 +# uv2 456 0.66432190 0.32255550 +# uv2 457 0.69123840 0.36230470 +# uv2 458 0.70837400 0.40623470 +# uv2 459 0.66709900 0.31977850 +# uv2 460 0.72180180 0.45051570 +# uv2 461 0.69819640 0.35766600 +# uv2 462 0.69493100 0.35983280 +# uv2 463 0.72129830 0.40087890 +# uv2 464 0.71766660 0.40238950 +# uv2 465 0.71357730 0.40408320 +# uv2 466 0.73167420 0.44854730 +# uv2 467 0.72732540 0.44941710 +# uv2 468 0.73196410 0.49655150 +# uv2 469 0.72633360 0.49655150 + +vt 0.49066160 0.37823490 +vt 0.53935240 0.38302610 +vt 0.50294490 0.50302120 +vt 0.58250430 0.40608210 +vt 0.78045650 0.08770752 +vt 0.69410710 0.04154968 +vt 0.60040280 0.01312256 +vt 0.49066160 0.37823490 +vt 0.74740600 0.13716130 +vt 0.67134090 0.09651184 +vt 0.50294490 0.00352478 +vt 0.58879090 0.07147217 +vt 0.71145630 0.19097900 +vt 0.40550230 0.01312256 +vt 0.64657590 0.15629580 +vt 0.50294490 0.06301880 +vt 0.57617190 0.13493350 +vt 0.44384770 0.39242550 +vt 0.50294490 0.50302120 +vt 0.41711430 0.07147217 +vt 0.31179810 0.04154968 +vt 0.50294490 0.12773130 +vt 0.22544860 0.08770752 +vt 0.33456420 0.09651184 +vt 0.42973330 0.13493350 +vt 0.40602110 0.42347720 +vt 0.25849920 0.13716130 +vt 0.14974980 0.14982610 +vt 0.35932920 0.15629580 +vt 0.08763123 0.22550960 +vt 0.19181820 0.19189450 +vt 0.29444880 0.19097900 +vt 0.38294980 0.46661380 +vt 0.13710020 0.25856020 +vt 0.04147339 0.31187440 +vt 0.23757930 0.23765560 +vt 0.01304626 0.40557860 +vt 0.09643555 0.33464050 +vt 0.19090270 0.29452510 +vt 0.37815860 0.51530460 +vt 0.07139587 0.41717530 +vt 0.00344849 0.50302120 +vt 0.15621950 0.35940550 +vt 0.01304626 0.60046380 +vt 0.06294251 0.50302120 +vt 0.13487240 0.42980960 +vt 0.04147339 0.69416810 +vt 0.07139587 0.58886720 +vt 0.12765500 0.50302120 +vt 0.39236450 0.56213380 +vt 0.09643555 0.67140200 +vt 0.08763123 0.78053290 +vt 0.42340090 0.59996030 +vt 0.51524350 0.62780760 +vt 0.46655270 0.62301630 +vt 0.14974980 0.85621640 +vt 0.22544860 0.91833500 +vt 0.13710020 0.74748230 +vt 0.19181820 0.81414800 +vt 0.31179810 0.96449280 +vt 0.15621950 0.64663690 +vt 0.25849920 0.86888120 +vt 0.40550230 0.99292000 +vt 0.51524350 0.62780760 +vt 0.19090270 0.71151730 +vt 0.23757930 0.76838680 +vt 0.33456420 0.90953070 +vt 0.29444880 0.81506350 +vt 0.24833680 0.67314150 +vt 0.41711430 0.93457030 +vt 0.28642270 0.71954340 +vt 0.35932920 0.84974670 +vt 0.33282470 0.75762940 +vt 0.50294490 1.00251800 +vt 0.42973330 0.87110900 +vt 0.50294490 0.94302370 +vt 0.60040280 0.99292000 +vt 0.56205750 0.61361700 +vt 0.58879090 0.93457030 +vt 0.69410710 0.96449280 +vt 0.50294490 0.87831120 +vt 0.67134090 0.90953070 +vt 0.38577270 0.78593440 +vt 0.44320680 0.80336000 +vt 0.57617190 0.87110900 +vt 0.50294490 0.80923460 +vt 0.41354370 0.71888730 +vt 0.45736690 0.73217770 +vt 0.56269840 0.80336000 +vt 0.37313840 0.69729610 +vt 0.50294490 0.73666380 +vt 0.33773810 0.66822810 +vt 0.64657590 0.84974670 +vt 0.30868530 0.63282780 +vt 0.39404300 0.66603090 +vt 0.36433410 0.64164740 +vt 0.28709410 0.59243780 +vt 0.42793270 0.68414310 +vt 0.33995060 0.61193850 +vt 0.46470640 0.69529730 +vt 0.44232180 0.64939880 +vt 0.50294490 0.69906620 +vt 0.47204590 0.65841680 +vt 0.50294490 0.66145330 +vt 0.54119870 0.69529730 +vt 0.53385920 0.65841680 +vt 0.50294490 0.62902830 +vt 0.54853820 0.73217770 +vt 0.52752680 0.62660220 +vt 0.47836300 0.62660220 +vt 0.52120970 0.59478760 +vt 0.50294490 0.59658810 +vt 0.45472720 0.61943060 +vt 0.48469540 0.59478760 +vt 0.41493230 0.63476570 +vt 0.39091490 0.61505130 +vt 0.43295290 0.60778810 +vt 0.46714780 0.58946230 +vt 0.37121580 0.59104920 +vt 0.41384890 0.59211730 +vt 0.45097350 0.58081060 +vt 0.32183840 0.57804870 +vt 0.39817810 0.57302860 +vt 0.35656740 0.56365970 +vt 0.27380370 0.54859920 +vt 0.43679810 0.56918340 +vt 0.31066890 0.54125980 +vt 0.38653560 0.55123900 +vt 0.22004700 0.62020870 +vt 0.20262150 0.56275940 +vt 0.26930240 0.50302120 +vt 0.13487240 0.57623290 +vt 0.19673160 0.50302120 +vt 0.20262150 0.44328310 +vt 0.27380370 0.45744320 +vt 0.22004700 0.38583370 +vt 0.28709410 0.41360470 +vt 0.30691530 0.50302120 +vt 0.31066890 0.46478270 +vt 0.34754940 0.53393560 +vt 0.34451290 0.50302120 +vt 0.37936400 0.52760320 +vt 0.34754940 0.47210690 +vt 0.37695310 0.50302120 +vt 0.41650390 0.53883360 +vt 0.41117860 0.52127080 +vt 0.42515570 0.55500790 +vt 0.44386290 0.54251100 +vt 0.45269780 0.55328370 +vt 0.46347050 0.56211850 +vt 0.43728640 0.53021240 +vt 0.47575380 0.56867980 +vt 0.47596740 0.54341130 +vt 0.48909000 0.57272340 +vt 0.48435970 0.54789730 +vt 0.49346920 0.55065920 +vt 0.50294490 0.57409670 +vt 0.50294490 0.55160520 +vt 0.51681520 0.57272340 +vt 0.51243590 0.55065920 +vt 0.50294490 0.52911380 +vt 0.50804140 0.52861020 +vt 0.52154540 0.54789730 +vt 0.49786380 0.52861020 +vt 0.53015140 0.56867980 +vt 0.49296570 0.52711490 +vt 0.53875730 0.58946230 +vt 0.48846440 0.52470400 +vt 0.55117800 0.61943060 +vt 0.46859740 0.53736880 +vt 0.56358340 0.64939880 +vt 0.46255490 0.53001400 +vt 0.57797240 0.68414310 +vt 0.48451230 0.52146910 +vt 0.45806880 0.52160650 +vt 0.48126220 0.51751710 +vt 0.49470520 0.51536560 +vt 0.49246220 0.51351930 +vt 0.49726870 0.51672360 +vt 0.49818420 0.50776670 +vt 0.50006100 0.51757810 +vt 0.49920660 0.50860600 +vt 0.50036620 0.50923160 +vt 0.50294490 0.51785280 +vt 0.50163270 0.50962830 +vt 0.50584410 0.51757810 +vt 0.50294490 0.50975040 +vt 0.50294490 0.50302120 +vt 0.50425720 0.50962830 +vt 0.50863650 0.51672360 +vt 0.50550840 0.50924680 +vt 0.51293950 0.52711490 +vt 0.51120000 0.51536560 +vt 0.51744080 0.52470400 +vt 0.50668330 0.50862120 +vt 0.51344300 0.51351930 +vt 0.52993770 0.54341130 +vt 0.52139280 0.52146910 +vt 0.50770570 0.50779720 +vt 0.51528930 0.51126100 +vt 0.50854490 0.50677490 +vt 0.52464290 0.51751710 +vt 0.51666260 0.50869750 +vt 0.50917050 0.50561520 +vt 0.53730770 0.53736880 +vt 0.52705380 0.51300050 +vt 0.54243470 0.56211850 +vt 0.54335020 0.53001400 +vt 0.55320740 0.55328370 +vt 0.55493160 0.58081060 +vt 0.56204220 0.54251100 +vt 0.57295230 0.60778810 +vt 0.56910700 0.56918340 +vt 0.59097290 0.63476570 +vt 0.59205630 0.59211730 +vt 0.58074950 0.55500790 +vt 0.54783630 0.52160650 +vt 0.56861880 0.53021240 +vt 0.60771180 0.57302860 +vt 0.52853390 0.50810240 +vt 0.58940120 0.53883360 +vt 0.55059810 0.51249690 +vt 0.57266240 0.51689150 +vt 0.51750180 0.50592040 +vt 0.52903750 0.50302120 +vt 0.50955200 0.50434880 +vt 0.51779170 0.50302120 +vt 0.50968930 0.50303650 +vt 0.51750180 0.50012210 +vt 0.50955200 0.50172420 +vt 0.52853390 0.49792480 +vt 0.51666260 0.49734500 +vt 0.55152890 0.50302120 +vt 0.50917050 0.50045780 +vt 0.57402040 0.50302120 +vt 0.51528930 0.49478150 +vt 0.55059810 0.49354550 +vt 0.52705380 0.49304200 +vt 0.54783630 0.48443600 +vt 0.50856020 0.49929810 +vt 0.52464290 0.48852540 +vt 0.51344300 0.49252320 +vt 0.50772090 0.49827580 +vt 0.52139280 0.48457340 +vt 0.51120000 0.49067690 +vt 0.50669860 0.49743650 +vt 0.54335020 0.47602840 +vt 0.51744080 0.48133850 +vt 0.50863650 0.48931890 +vt 0.50553900 0.49681090 +vt 0.53730770 0.46867370 +vt 0.51293950 0.47892760 +vt 0.50584410 0.48846440 +vt 0.50427250 0.49641420 +vt 0.52993770 0.46263120 +vt 0.50804140 0.47743220 +vt 0.50294490 0.48818970 +vt 0.50296020 0.49629210 +vt 0.52154540 0.45814520 +vt 0.50294490 0.47692870 +vt 0.50006100 0.48846440 +vt 0.50164800 0.49641420 +vt 0.51243590 0.45538330 +vt 0.49786380 0.47743220 +vt 0.49726870 0.48931890 +vt 0.50038150 0.49679560 +vt 0.50294490 0.45443730 +vt 0.49296570 0.47892760 +vt 0.49470520 0.49067690 +vt 0.49922180 0.49742130 +vt 0.49346920 0.45538330 +vt 0.48846440 0.48133850 +vt 0.49246220 0.49252320 +vt 0.49819950 0.49824520 +vt 0.48435970 0.45814520 +vt 0.48451230 0.48457340 +vt 0.49061580 0.49478150 +vt 0.49736020 0.49926760 +vt 0.47596740 0.46263120 +vt 0.48126220 0.48852540 +vt 0.48924250 0.49734500 +vt 0.49673460 0.50042730 +vt 0.46859740 0.46867370 +vt 0.47885130 0.49304200 +vt 0.48840330 0.50012210 +vt 0.49635320 0.50169370 +vt 0.46255490 0.47602840 +vt 0.47737120 0.49792480 +vt 0.48811340 0.50302120 +vt 0.49621580 0.50300600 +vt 0.45806880 0.48443600 +vt 0.47686770 0.50302120 +vt 0.48840330 0.50592040 +vt 0.49635320 0.50431820 +vt 0.45530700 0.49354550 +vt 0.49671940 0.50558470 +vt 0.48924250 0.50869750 +vt 0.49734500 0.50674440 +vt 0.49061580 0.51126100 +vt 0.47885130 0.51300050 +vt 0.47737120 0.50810240 +vt 0.45530700 0.51249690 +vt 0.45437620 0.50302120 +vt 0.43324280 0.51689150 +vt 0.43188480 0.50302120 +vt 0.40939330 0.50302120 +vt 0.43324280 0.48915100 +vt 0.41117860 0.48477170 +vt 0.37936400 0.47843930 +vt 0.43728640 0.47583010 +vt 0.41650390 0.46720890 +vt 0.38653560 0.45480350 +vt 0.44386290 0.46353150 +vt 0.42515570 0.45103450 +vt 0.45269780 0.45275880 +vt 0.43679810 0.43685910 +vt 0.39817810 0.43301390 +vt 0.41384890 0.41392520 +vt 0.46347050 0.44392390 +vt 0.45097350 0.42523190 +vt 0.47575380 0.43736270 +vt 0.46714780 0.41658020 +vt 0.43295290 0.39825440 +vt 0.45472720 0.38661190 +vt 0.48909000 0.43331910 +vt 0.48469540 0.41125490 +vt 0.50294490 0.43194580 +vt 0.50294490 0.40945440 +vt 0.47836300 0.37944030 +vt 0.50294490 0.37701420 +vt 0.51681520 0.43331910 +vt 0.52120970 0.41125490 +vt 0.53015140 0.43736270 +vt 0.53875730 0.41658020 +vt 0.52752680 0.37944030 +vt 0.55117800 0.38661190 +vt 0.54243470 0.44392390 +vt 0.55493160 0.42523190 +vt 0.55320740 0.45275880 +vt 0.56910700 0.43685910 +vt 0.57295230 0.39825440 +vt 0.59205630 0.41392520 +vt 0.56204220 0.46353150 +vt 0.58074950 0.45103450 +vt 0.56861880 0.47583010 +vt 0.58940120 0.46720890 +vt 0.60771180 0.43301390 +vt 0.61936950 0.45480350 +vt 0.57266240 0.48915100 +vt 0.59471130 0.48477170 +vt 0.59651190 0.50302120 +vt 0.62652580 0.47843930 +vt 0.62895210 0.50302120 +vt 0.59471130 0.52127080 +vt 0.62652580 0.52760320 +vt 0.61936950 0.55123900 +vt 0.65834040 0.53393560 +vt 0.64933780 0.56365970 +vt 0.63468930 0.59104920 +vt 0.61499030 0.61505130 +vt 0.66595460 0.61193850 +vt 0.61186220 0.66603090 +vt 0.64157100 0.64164740 +vt 0.59236150 0.71888730 +vt 0.63275150 0.69729610 +vt 0.62013250 0.78593440 +vt 0.67308050 0.75762940 +vt 0.71145630 0.81506350 +vt 0.66816710 0.66822810 +vt 0.71948240 0.71954340 +vt 0.76832580 0.76838680 +vt 0.74740600 0.86888120 +vt 0.81408690 0.81414800 +vt 0.78045650 0.91833500 +vt 0.85615540 0.85621640 +vt 0.59988410 0.58256530 +vt 0.91827390 0.78053290 +vt 0.86880500 0.74748230 +vt 0.62295530 0.53942870 +vt 0.96443170 0.69416810 +vt 0.81500240 0.71151730 +vt 0.90946960 0.67140200 +vt 0.99285890 0.60046380 +vt 0.62774660 0.49072270 +vt 0.61354070 0.44390870 +vt 1.00245700 0.50302120 +vt 0.99285890 0.40557860 +vt 0.93450930 0.58886720 +vt 0.94296260 0.50302120 +vt 0.84967040 0.64663690 +vt 0.87103270 0.57623290 +vt 0.75756840 0.67314150 +vt 0.78585810 0.62020870 +vt 0.69721980 0.63282780 +vt 0.80328370 0.56275940 +vt 0.68406680 0.57804870 +vt 0.71881100 0.59243780 +vt 0.69522090 0.54125980 +vt 0.73210140 0.54859920 +vt 0.66139220 0.50302120 +vt 0.69898980 0.50302120 +vt 0.65834040 0.47210690 +vt 0.73660280 0.50302120 +vt 0.64933780 0.44238280 +vt 0.69522090 0.46478270 +vt 0.63468930 0.41499330 +vt 0.68406680 0.42799380 +vt 0.73210140 0.45744320 +vt 0.61499030 0.39099120 +vt 0.66595460 0.39410400 +vt 0.71881100 0.41360470 +vt 0.59097290 0.37127690 +vt 0.64157100 0.36439510 +vt 0.69721980 0.37321470 +vt 0.56358340 0.35664370 +vt 0.61186220 0.34001160 +vt 0.66816710 0.33781430 +vt 0.53385920 0.34762570 +vt 0.57797240 0.32189940 +vt 0.63275150 0.30874630 +vt 0.50294490 0.34457400 +vt 0.54119870 0.31074520 +vt 0.59236150 0.28715520 +vt 0.47204590 0.34762570 +vt 0.50294490 0.30697630 +vt 0.54853820 0.27386470 +vt 0.44232180 0.35664370 +vt 0.46470640 0.31074520 +vt 0.50294490 0.26937870 +vt 0.41493230 0.37127690 +vt 0.42793270 0.32189940 +vt 0.45736690 0.27386470 +vt 0.39091490 0.39099120 +vt 0.39404300 0.34001160 +vt 0.41354370 0.28715520 +vt 0.37121580 0.41499330 +vt 0.36433410 0.36439510 +vt 0.37313840 0.30874630 +vt 0.35656740 0.44238280 +vt 0.33995060 0.39410400 +vt 0.32183840 0.42799380 +vt 0.30868530 0.37321470 +vt 0.33773810 0.33781430 +vt 0.24833680 0.33290100 +vt 0.28642270 0.28649900 +vt 0.33282470 0.24841310 +vt 0.38577270 0.22010800 +vt 0.44320680 0.20268250 +vt 0.50294490 0.19680790 +vt 0.56269840 0.20268250 +vt 0.62013250 0.22010800 +vt 0.67308050 0.24841310 +vt 0.71948240 0.28649900 +vt 0.76832580 0.23765560 +vt 0.75756840 0.33290100 +vt 0.81408690 0.19189450 +vt 0.81500240 0.29452510 +vt 0.78585810 0.38583370 +vt 0.85615540 0.14982610 +vt 0.80328370 0.44328310 +vt 0.91827390 0.22550960 +vt 0.86880500 0.25856020 +vt 0.96443170 0.31187440 +vt 0.90946960 0.33464050 +vt 0.84967040 0.35940550 +vt 0.93450930 0.41717530 +vt 0.87103270 0.42980960 +vt 0.87825010 0.50302120 +vt 0.80917360 0.50302120 + +vn -0.38041731 -0.46346073 0.80030420 +vn -0.34143414 -0.66058057 0.66862249 +vn 0.27456351 -0.33446419 0.90152570 +vn -0.06280816 -0.74098957 0.66857280 +vn -0.23299890 -0.95491455 0.18398291 +vn -0.37467635 -0.91288012 0.16207261 +vn -0.58065079 -0.79302940 0.18425264 +vn -0.66581334 -0.47735522 0.57343229 +vn -0.19195965 -0.98107075 -0.02552794 +vn -0.38030042 -0.92456867 -0.02333165 +vn -0.69545353 -0.70032569 0.16089844 +vn -0.55427418 -0.83178967 -0.03010118 +vn -0.19051762 -0.98010740 -0.05561047 +vn -0.84006746 -0.51045103 0.18364752 +vn -0.37922611 -0.92349806 -0.05778309 +vn -0.70512444 -0.70858424 -0.02660623 +vn -0.55228524 -0.83188443 -0.05430756 +vn -0.70849140 -0.22577367 0.66863008 +vn -0.27456351 0.33446419 0.90152570 +vn -0.82951178 -0.55800793 -0.02318090 +vn -0.91016945 -0.38122216 0.16205321 +vn -0.70357372 -0.70851735 -0.05465513 +vn -0.97141781 -0.14993482 0.18402986 +vn -0.92239839 -0.38551426 -0.02366343 +vn -0.82783510 -0.55805444 -0.05713393 +vn -0.74098957 0.06280816 0.66857280 +vn -0.98011725 -0.19624877 -0.02926778 +vn -0.98695690 -0.00345646 0.16094763 +vn -0.92093856 -0.38546212 -0.05736827 +vn -0.95491455 0.23299890 0.18398291 +vn -0.99960330 -0.00240768 -0.02806135 +vn -0.97872822 -0.19735458 -0.05605574 +vn -0.66058057 0.34143414 0.66862249 +vn -0.98107075 0.19195965 -0.02552794 +vn -0.91288012 0.37467635 0.16207261 +vn -0.99852106 -0.00350389 -0.05425317 +vn -0.79302940 0.58065079 0.18425264 +vn -0.92456867 0.38030042 -0.02333165 +vn -0.98010740 0.19051762 -0.05561047 +vn -0.48220484 0.58746207 0.64989754 +vn -0.83178967 0.55427418 -0.03010118 +vn -0.70032569 0.69545353 0.16089844 +vn -0.92349806 0.37922611 -0.05778309 +vn -0.55095348 0.81881148 0.16123906 +vn -0.70858424 0.70512444 -0.02660623 +vn -0.83188443 0.55228524 -0.05430756 +vn -0.41871584 0.88921176 0.18433528 +vn -0.55800793 0.82951178 -0.02318090 +vn -0.70851735 0.70357372 -0.05465513 +vn -0.21069043 0.69381941 0.68863936 +vn -0.38551426 0.92239839 -0.02366343 +vn -0.14993482 0.97141781 0.18402986 +vn 0.06280816 0.74098957 0.66857280 +vn 0.38041731 0.46346073 0.80030420 +vn 0.34143414 0.66058057 0.66862249 +vn -0.00345646 0.98695690 0.16094763 +vn 0.23299890 0.95491455 0.18398291 +vn -0.19624877 0.98011725 -0.02926778 +vn -0.00240768 0.99960330 -0.02806135 +vn 0.37467635 0.91288012 0.16207261 +vn -0.38546212 0.92093856 -0.05736827 +vn 0.19195965 0.98107075 -0.02552794 +vn 0.58065079 0.79302940 0.18425264 +vn 0.66581334 0.47735522 0.57343229 +vn -0.19735458 0.97872822 -0.05605574 +vn -0.00350389 0.99852106 -0.05425317 +vn 0.38030042 0.92456867 -0.02333165 +vn 0.19051762 0.98010740 -0.05561047 +vn -0.19963704 0.97515008 -0.09605924 +vn 0.55427418 0.83178967 -0.03010118 +vn -0.00621443 0.99540152 -0.09558862 +vn 0.37922611 0.92349806 -0.05778309 +vn 0.18774753 0.97744344 -0.09672224 +vn 0.69545353 0.70032569 0.16089844 +vn 0.55228524 0.83188443 -0.05430756 +vn 0.70512444 0.70858424 -0.02660623 +vn 0.84006746 0.51045103 0.18364752 +vn 0.70849140 0.22577367 0.66863008 +vn 0.82951178 0.55800793 -0.02318090 +vn 0.91016945 0.38122216 0.16205321 +vn 0.70357372 0.70851735 -0.05465513 +vn 0.92239839 0.38551426 -0.02366343 +vn 0.37548920 0.92174706 -0.09690315 +vn 0.54826987 0.83063362 -0.09720046 +vn 0.82783510 0.55805444 -0.05713393 +vn 0.69940606 0.70832875 -0.09540204 +vn 0.40944937 0.90268968 -0.13229722 +vn 0.57746754 0.80587781 -0.13073714 +vn 0.82407370 0.55842668 -0.09519553 +vn 0.22486546 0.96535917 -0.13235259 +vn 0.72364692 0.67777935 -0.13019330 +vn 0.03216652 0.99051117 -0.13361491 +vn 0.92093856 0.38546212 -0.05736827 +vn -0.16071959 0.97802493 -0.13280232 +vn 0.19702028 0.96270217 -0.18543877 +vn 0.00546951 0.98279270 -0.18463095 +vn -0.34909028 0.92765612 -0.13262770 +vn 0.38099378 0.90548528 -0.18692282 +vn -0.18554056 0.96531921 -0.18366690 +vn 0.55064938 0.81368922 -0.18626622 +vn 0.37469235 0.89544181 -0.24039469 +vn 0.69829732 0.69073743 -0.18778353 +vn 0.54240679 0.80522903 -0.23958522 +vn 0.68873185 0.68413542 -0.24001491 +vn 0.82010465 0.54109652 -0.18612609 +vn 0.80887063 0.53641009 -0.24081636 +vn 0.67330846 0.66533115 -0.32247507 +vn 0.84189089 0.52330802 -0.13178937 +vn 0.79027034 0.52110965 -0.32236241 +vn 0.52988987 0.78398088 -0.32340487 +vn 0.75604984 0.50715287 -0.41374462 +vn 0.64242154 0.64483346 -0.41410673 +vn 0.36704514 0.87206726 -0.32369207 +vn 0.50450656 0.75789007 -0.41361308 +vn 0.19252786 0.95126383 -0.24089448 +vn 0.00382254 0.97093617 -0.23930805 +vn 0.19001000 0.92684020 -0.32382625 +vn 0.34701208 0.84209029 -0.41287596 +vn -0.18648959 0.95230889 -0.24151482 +vn 0.00566824 0.94607570 -0.32389603 +vn 0.17588605 0.89329270 -0.41363299 +vn -0.37109706 0.90987060 -0.18553291 +vn -0.17971955 0.92865136 -0.32451123 +vn -0.36859186 0.89741496 -0.24245915 +vn -0.52330802 0.84189089 -0.13178937 +vn -0.00160335 0.91034654 -0.41384370 +vn -0.54109652 0.82010465 -0.18612609 +vn -0.35647976 0.87687109 -0.32252018 +vn -0.38662175 0.91709479 -0.09726645 +vn -0.55842668 0.82407370 -0.09519553 +vn -0.67777935 0.72364692 -0.13019330 +vn -0.55805444 0.82783510 -0.05713393 +vn -0.70832875 0.69940606 -0.09540204 +vn -0.83063362 0.54826987 -0.09720046 +vn -0.80587781 0.57746754 -0.13073714 +vn -0.92174706 0.37548920 -0.09690315 +vn -0.90268968 0.40944937 -0.13229722 +vn -0.69073743 0.69829732 -0.18778353 +vn -0.81368922 0.55064938 -0.18626622 +vn -0.53641009 0.80887063 -0.24081636 +vn -0.68413542 0.68873185 -0.24001491 +vn -0.52110965 0.79027034 -0.32236241 +vn -0.80522903 0.54240679 -0.23958522 +vn -0.66533115 0.67330846 -0.32247507 +vn -0.34941071 0.84093017 -0.41321739 +vn -0.50715287 0.75604984 -0.41374462 +vn -0.17907830 0.89280924 -0.41330694 +vn -0.16119713 0.82226504 -0.54579822 +vn 0.00176890 0.83776798 -0.54602352 +vn 0.16506360 0.82150994 -0.54577966 +vn -0.31872725 0.77493519 -0.54579153 +vn 0.32222738 0.77311250 -0.54632095 +vn 0.13426298 0.69346809 -0.70786684 +vn 0.46698615 0.69528317 -0.54635634 +vn 0.26783782 0.65350965 -0.70794636 +vn 0.38946376 0.58909064 -0.70801850 +vn 0.59370259 0.59074745 -0.54638329 +vn 0.49749141 0.50139375 -0.70788884 +vn 0.69696020 0.46422361 -0.54657381 +vn 0.58501374 0.39541454 -0.70810046 +vn 0.38338804 0.40407136 -0.83050584 +vn 0.45575202 0.32056557 -0.83037812 +vn 0.65081825 0.27375497 -0.70816229 +vn 0.29774777 0.47093545 -0.83040115 +vn 0.77493519 0.31872725 -0.54579153 +vn 0.19969947 0.51967732 -0.83069586 +vn 0.84093017 0.34941071 -0.41321739 +vn 0.09458224 0.54882353 -0.83057025 +vn 0.87687109 0.35647976 -0.32252018 +vn -0.00326965 0.70635451 -0.70785070 +vn 0.89741496 0.36859186 -0.24245915 +vn -0.14066533 0.69219370 -0.70787086 +vn 0.90987060 0.37109706 -0.18553291 +vn -0.01420194 0.55656689 -0.83068141 +vn -0.27375497 0.65081825 -0.70816229 +vn -0.12239036 0.54305826 -0.83072759 +vn 0.05681455 0.35521746 -0.93305555 +vn -0.01331212 0.35934103 -0.93311136 +vn 0.12473109 0.33746101 -0.93303924 +vn 0.00961222 0.19817733 -0.98011905 +vn 0.18852895 0.30637399 -0.93305510 +vn 0.04771523 0.19219326 -0.98019641 +vn 0.08472630 0.17912408 -0.98017142 +vn 0.24426152 0.26381850 -0.93313242 +vn 0.11857183 0.15857283 -0.98020170 +vn 0.29194178 0.21005135 -0.93308543 +vn 0.14664860 0.13304284 -0.98020089 +vn 0.00000000 0.00000000 -1.00000000 +vn 0.16995856 0.10187265 -0.98017144 +vn 0.32706027 0.15003192 -0.93301769 +vn 0.18625415 0.06738377 -0.98018816 +vn 0.50856992 0.22687012 -0.83059412 +vn 0.35005490 0.08303386 -0.93304177 +vn 0.54305826 0.12239036 -0.83072759 +vn 0.19619058 0.02831884 -0.98015677 +vn 0.35934103 0.01331212 -0.93311136 +vn 0.69219370 0.14066533 -0.70787086 +vn 0.55656689 0.01420194 -0.83068141 +vn 0.19817733 -0.00961222 -0.98011905 +vn 0.35521746 -0.05681455 -0.93305555 +vn 0.19219326 -0.04771523 -0.98019641 +vn 0.54882353 -0.09458224 -0.83057025 +vn 0.33746101 -0.12473109 -0.93303924 +vn 0.17912408 -0.08472630 -0.98017142 +vn 0.70635451 0.00326965 -0.70785070 +vn 0.51967732 -0.19969947 -0.83069586 +vn 0.82226504 0.16119713 -0.54579822 +vn 0.69346809 -0.13426298 -0.70786684 +vn 0.83776798 -0.00176890 -0.54602352 +vn 0.89280924 0.17907830 -0.41330694 +vn 0.82150994 -0.16506360 -0.54577966 +vn 0.92865136 0.17971955 -0.32451123 +vn 0.91034654 0.00160335 -0.41384370 +vn 0.95230889 0.18648959 -0.24151482 +vn 0.94607570 -0.00566824 -0.32389603 +vn 0.89329270 -0.17588605 -0.41363299 +vn 0.65350965 -0.26783782 -0.70794636 +vn 0.77311250 -0.32222738 -0.54632095 +vn 0.92684020 -0.19001000 -0.32382625 +vn 0.47093545 -0.29774777 -0.83040115 +vn 0.84209029 -0.34701208 -0.41287596 +vn 0.58909064 -0.38946376 -0.70801850 +vn 0.69528317 -0.46698615 -0.54635634 +vn 0.30637399 -0.18852895 -0.93305510 +vn 0.40407136 -0.38338804 -0.83050584 +vn 0.15857283 -0.11857183 -0.98020170 +vn 0.26381850 -0.24426152 -0.93313242 +vn 0.13304284 -0.14664860 -0.98020089 +vn 0.21005135 -0.29194178 -0.93308543 +vn 0.10187265 -0.16995856 -0.98017144 +vn 0.32056557 -0.45575202 -0.83037812 +vn 0.15003192 -0.32706027 -0.93301769 +vn 0.50139375 -0.49749141 -0.70788884 +vn 0.06738377 -0.18625415 -0.98018816 +vn 0.59074745 -0.59370259 -0.54638329 +vn 0.08303386 -0.35005490 -0.93304177 +vn 0.39541454 -0.58501374 -0.70810046 +vn 0.22687012 -0.50856992 -0.83059412 +vn 0.27375497 -0.65081825 -0.70816229 +vn 0.02831884 -0.19619058 -0.98015677 +vn 0.12239036 -0.54305826 -0.83072759 +vn 0.01331212 -0.35934103 -0.93311136 +vn -0.00961222 -0.19817733 -0.98011905 +vn 0.01420194 -0.55656689 -0.83068141 +vn -0.05681455 -0.35521746 -0.93305555 +vn -0.04771523 -0.19219326 -0.98019641 +vn 0.14066533 -0.69219370 -0.70787086 +vn -0.09458224 -0.54882353 -0.83057025 +vn -0.12473109 -0.33746101 -0.93303924 +vn -0.08472630 -0.17912408 -0.98017142 +vn 0.00326965 -0.70635451 -0.70785070 +vn -0.19969947 -0.51967732 -0.83069586 +vn -0.18852895 -0.30637399 -0.93305510 +vn -0.11857183 -0.15857283 -0.98020170 +vn -0.13426298 -0.69346809 -0.70786684 +vn -0.29774777 -0.47093545 -0.83040115 +vn -0.24426152 -0.26381850 -0.93313242 +vn -0.14664860 -0.13304284 -0.98020089 +vn -0.26783782 -0.65350965 -0.70794636 +vn -0.38338804 -0.40407136 -0.83050584 +vn -0.29194178 -0.21005135 -0.93308543 +vn -0.16995856 -0.10187265 -0.98017144 +vn -0.38946376 -0.58909064 -0.70801850 +vn -0.45575202 -0.32056557 -0.83037812 +vn -0.32706027 -0.15003192 -0.93301769 +vn -0.18625415 -0.06738377 -0.98018816 +vn -0.49749141 -0.50139375 -0.70788884 +vn -0.50856992 -0.22687012 -0.83059412 +vn -0.35005490 -0.08303386 -0.93304177 +vn -0.19619058 -0.02831884 -0.98015677 +vn -0.58501374 -0.39541454 -0.70810046 +vn -0.54305826 -0.12239036 -0.83072759 +vn -0.35934103 -0.01331212 -0.93311136 +vn -0.19817733 0.00961222 -0.98011905 +vn -0.65081825 -0.27375497 -0.70816229 +vn -0.55656689 -0.01420194 -0.83068141 +vn -0.35521746 0.05681455 -0.93305555 +vn -0.19219326 0.04771523 -0.98019641 +vn -0.69219370 -0.14066533 -0.70787086 +vn -0.54882353 0.09458224 -0.83057025 +vn -0.33746101 0.12473109 -0.93303924 +vn -0.17912408 0.08472630 -0.98017142 +vn -0.70635451 -0.00326965 -0.70785070 +vn -0.51967732 0.19969947 -0.83069586 +vn -0.30637399 0.18852895 -0.93305510 +vn -0.15857283 0.11857183 -0.98020170 +vn -0.69346809 0.13426298 -0.70786684 +vn -0.47093545 0.29774777 -0.83040115 +vn -0.26381850 0.24426152 -0.93313242 +vn -0.13304284 0.14664860 -0.98020089 +vn -0.65350965 0.26783782 -0.70794636 +vn -0.40407136 0.38338804 -0.83050584 +vn -0.21005135 0.29194178 -0.93308543 +vn -0.10187265 0.16995856 -0.98017144 +vn -0.58909064 0.38946376 -0.70801850 +vn -0.06738377 0.18625415 -0.98018816 +vn -0.15003192 0.32706027 -0.93301769 +vn -0.02831884 0.19619058 -0.98015677 +vn -0.08303386 0.35005490 -0.93304177 +vn -0.22687012 0.50856992 -0.83059412 +vn -0.32056557 0.45575202 -0.83037812 +vn -0.39541454 0.58501374 -0.70810046 +vn -0.50139375 0.49749141 -0.70788884 +vn -0.46422361 0.69696020 -0.54657381 +vn -0.59074745 0.59370259 -0.54638329 +vn -0.64483346 0.64242154 -0.41410673 +vn -0.69528317 0.46698615 -0.54635634 +vn -0.75789007 0.50450656 -0.41361308 +vn -0.78398088 0.52988987 -0.32340487 +vn -0.77311250 0.32222738 -0.54632095 +vn -0.84209029 0.34701208 -0.41287596 +vn -0.87206726 0.36704514 -0.32369207 +vn -0.82150994 0.16506360 -0.54577966 +vn -0.89329270 0.17588605 -0.41363299 +vn -0.83776798 0.00176890 -0.54602352 +vn -0.91034654 -0.00160335 -0.41384370 +vn -0.92684020 0.19001000 -0.32382625 +vn -0.94607570 0.00566824 -0.32389603 +vn -0.82226504 -0.16119713 -0.54579822 +vn -0.89280924 -0.17907830 -0.41330694 +vn -0.77493519 -0.31872725 -0.54579153 +vn -0.84093017 -0.34941071 -0.41321739 +vn -0.92865136 -0.17971955 -0.32451123 +vn -0.87687109 -0.35647976 -0.32252018 +vn -0.69696020 -0.46422361 -0.54657381 +vn -0.75604984 -0.50715287 -0.41374462 +vn -0.59370259 -0.59074745 -0.54638329 +vn -0.64242154 -0.64483346 -0.41410673 +vn -0.79027034 -0.52110965 -0.32236241 +vn -0.67330846 -0.66533115 -0.32247507 +vn -0.46698615 -0.69528317 -0.54635634 +vn -0.50450656 -0.75789007 -0.41361308 +vn -0.32222738 -0.77311250 -0.54632095 +vn -0.34701208 -0.84209029 -0.41287596 +vn -0.52988987 -0.78398088 -0.32340487 +vn -0.36704514 -0.87206726 -0.32369207 +vn -0.16506360 -0.82150994 -0.54577966 +vn -0.17588605 -0.89329270 -0.41363299 +vn -0.00176890 -0.83776798 -0.54602352 +vn 0.00160335 -0.91034654 -0.41384370 +vn -0.19001000 -0.92684020 -0.32382625 +vn -0.00566824 -0.94607570 -0.32389603 +vn 0.16119713 -0.82226504 -0.54579822 +vn 0.17907830 -0.89280924 -0.41330694 +vn 0.31872725 -0.77493519 -0.54579153 +vn 0.34941071 -0.84093017 -0.41321739 +vn 0.17971955 -0.92865136 -0.32451123 +vn 0.35647976 -0.87687109 -0.32252018 +vn 0.46422361 -0.69696020 -0.54657381 +vn 0.50715287 -0.75604984 -0.41374462 +vn 0.64483346 -0.64242154 -0.41410673 +vn 0.52110965 -0.79027034 -0.32236241 +vn 0.66533115 -0.67330846 -0.32247507 +vn 0.75789007 -0.50450656 -0.41361308 +vn 0.78398088 -0.52988987 -0.32340487 +vn 0.87206726 -0.36704514 -0.32369207 +vn 0.80522903 -0.54240679 -0.23958522 +vn 0.89544181 -0.37469235 -0.24039469 +vn 0.95126383 -0.19252786 -0.24089448 +vn 0.97093617 -0.00382254 -0.23930805 +vn 0.96270217 -0.19702028 -0.18543877 +vn 0.96531921 0.18554056 -0.18366690 +vn 0.98279270 -0.00546951 -0.18463095 +vn 0.92765612 0.34909028 -0.13262770 +vn 0.97802493 0.16071959 -0.13280232 +vn 0.91709479 0.38662175 -0.09726645 +vn 0.97515008 0.19963704 -0.09605924 +vn 0.97872822 0.19735458 -0.05605574 +vn 0.99051117 -0.03216652 -0.13361491 +vn 0.99540152 0.00621443 -0.09558862 +vn 0.99852106 0.00350389 -0.05425317 +vn 0.98011725 0.19624877 -0.02926778 +vn 0.99960330 0.00240768 -0.02806135 +vn 0.97141781 0.14993482 0.18402986 +vn 0.98695690 0.00345646 0.16094763 +vn 0.74098957 -0.06280816 0.66857280 +vn 0.95491455 -0.23299890 0.18398291 +vn 0.98107075 -0.19195965 -0.02552794 +vn 0.66058057 -0.34143414 0.66862249 +vn 0.91288012 -0.37467635 0.16207261 +vn 0.98010740 -0.19051762 -0.05561047 +vn 0.92456867 -0.38030042 -0.02333165 +vn 0.79302940 -0.58065079 0.18425264 +vn 0.47969863 -0.56828161 0.66853962 +vn 0.22577367 -0.70849140 0.66863008 +vn 0.70032569 -0.69545353 0.16089844 +vn 0.51045103 -0.84006746 0.18364752 +vn 0.83178967 -0.55427418 -0.03010118 +vn 0.70858424 -0.70512444 -0.02660623 +vn 0.92349806 -0.37922611 -0.05778309 +vn 0.83188443 -0.55228524 -0.05430756 +vn 0.97744344 -0.18774753 -0.09672224 +vn 0.92174706 -0.37548920 -0.09690315 +vn 0.96535917 -0.22486546 -0.13235259 +vn 0.83063362 -0.54826987 -0.09720046 +vn 0.90548528 -0.38099378 -0.18692282 +vn 0.90268968 -0.40944937 -0.13229722 +vn 0.81368922 -0.55064938 -0.18626622 +vn 0.80587781 -0.57746754 -0.13073714 +vn 0.68413542 -0.68873185 -0.24001491 +vn 0.69073743 -0.69829732 -0.18778353 +vn 0.53641009 -0.80887063 -0.24081636 +vn 0.67777935 -0.72364692 -0.13019330 +vn 0.36859186 -0.89741496 -0.24245915 +vn 0.54109652 -0.82010465 -0.18612609 +vn 0.18648959 -0.95230889 -0.24151482 +vn 0.37109706 -0.90987060 -0.18553291 +vn 0.52330802 -0.84189089 -0.13178937 +vn -0.00382254 -0.97093617 -0.23930805 +vn 0.18554056 -0.96531921 -0.18366690 +vn 0.34909028 -0.92765612 -0.13262770 +vn -0.19252786 -0.95126383 -0.24089448 +vn -0.00546951 -0.98279270 -0.18463095 +vn 0.16071959 -0.97802493 -0.13280232 +vn -0.37469235 -0.89544181 -0.24039469 +vn -0.19702028 -0.96270217 -0.18543877 +vn -0.03216652 -0.99051117 -0.13361491 +vn -0.54240679 -0.80522903 -0.23958522 +vn -0.38099378 -0.90548528 -0.18692282 +vn -0.22486546 -0.96535917 -0.13235259 +vn -0.68873185 -0.68413542 -0.24001491 +vn -0.55064938 -0.81368922 -0.18626622 +vn -0.40944937 -0.90268968 -0.13229722 +vn -0.80887063 -0.53641009 -0.24081636 +vn -0.69829732 -0.69073743 -0.18778353 +vn -0.57746754 -0.80587781 -0.13073714 +vn -0.89741496 -0.36859186 -0.24245915 +vn -0.82010465 -0.54109652 -0.18612609 +vn -0.72364692 -0.67777935 -0.13019330 +vn -0.95230889 -0.18648959 -0.24151482 +vn -0.90987060 -0.37109706 -0.18553291 +vn -0.84189089 -0.52330802 -0.13178937 +vn -0.97093617 0.00382254 -0.23930805 +vn -0.96531921 -0.18554056 -0.18366690 +vn -0.92765612 -0.34909028 -0.13262770 +vn -0.95126383 0.19252786 -0.24089448 +vn -0.98279270 0.00546951 -0.18463095 +vn -0.97802493 -0.16071959 -0.13280232 +vn -0.89544181 0.37469235 -0.24039469 +vn -0.96270217 0.19702028 -0.18543877 +vn -0.90548528 0.38099378 -0.18692282 +vn -0.96535917 0.22486546 -0.13235259 +vn -0.99051117 0.03216652 -0.13361491 +vn -0.97744344 0.18774753 -0.09672224 +vn -0.99540152 -0.00621443 -0.09558862 +vn -0.97515008 -0.19963704 -0.09605924 +vn -0.91709479 -0.38662175 -0.09726645 +vn -0.82407370 -0.55842668 -0.09519553 +vn -0.69940606 -0.70832875 -0.09540204 +vn -0.54826987 -0.83063362 -0.09720046 +vn -0.37548920 -0.92174706 -0.09690315 +vn -0.18774753 -0.97744344 -0.09672224 +vn 0.00621443 -0.99540152 -0.09558862 +vn 0.00350389 -0.99852106 -0.05425317 +vn 0.19963704 -0.97515008 -0.09605924 +vn 0.00240768 -0.99960330 -0.02806135 +vn 0.19735458 -0.97872822 -0.05605574 +vn 0.38662175 -0.91709479 -0.09726645 +vn 0.00345646 -0.98695690 0.16094763 +vn 0.55842668 -0.82407370 -0.09519553 +vn 0.14993482 -0.97141781 0.18402986 +vn 0.19624877 -0.98011725 -0.02926778 +vn 0.38122216 -0.91016945 0.16205321 +vn 0.38551426 -0.92239839 -0.02366343 +vn 0.38546212 -0.92093856 -0.05736827 +vn 0.55800793 -0.82951178 -0.02318090 +vn 0.55805444 -0.82783510 -0.05713393 +vn 0.70851735 -0.70357372 -0.05465513 +vn 0.70832875 -0.69940606 -0.09540204 + +usemtl sky_system +s 1 +f 1/1/1 2/2/2 3/3/3 +f 2/2/2 4/4/4 3/3/3 +f 5/5/5 4/4/4 2/2/2 +f 2/2/2 6/6/6 5/5/5 +f 7/7/7 6/6/6 2/2/2 +f 7/7/7 2/2/2 8/8/8 +f 9/9/9 5/5/5 6/6/6 +f 10/10/10 6/6/6 7/7/7 +f 6/6/6 10/10/10 9/9/9 +f 8/8/8 11/11/11 7/7/7 +f 7/7/7 12/12/12 10/10/10 +f 12/12/12 7/7/7 11/11/11 +f 13/13/13 9/9/9 10/10/10 +f 14/14/14 11/11/11 8/8/8 +f 15/15/15 10/10/10 12/12/12 +f 10/10/10 15/15/15 13/13/13 +f 11/11/11 16/16/16 12/12/12 +f 16/16/16 11/11/11 14/14/14 +f 12/12/12 17/17/17 15/15/15 +f 17/17/17 12/12/12 16/16/16 +f 14/14/14 8/8/8 18/18/18 +f 18/18/18 8/8/8 19/19/19 +f 14/14/14 20/20/20 16/16/16 +f 18/18/18 21/21/21 14/14/14 +f 20/20/20 14/14/14 21/21/21 +f 16/16/16 22/22/22 17/17/17 +f 22/22/22 16/16/16 20/20/20 +f 23/23/23 21/21/21 18/18/18 +f 21/21/21 24/24/24 20/20/20 +f 24/24/24 21/21/21 23/23/23 +f 20/20/20 25/25/25 22/22/22 +f 25/25/25 20/20/20 24/24/24 +f 23/23/23 18/18/18 26/26/26 +f 26/26/26 18/18/18 19/19/19 +f 23/23/23 27/27/27 24/24/24 +f 26/26/26 28/28/28 23/23/23 +f 27/27/27 23/23/23 28/28/28 +f 24/24/24 29/29/29 25/25/25 +f 29/29/29 24/24/24 27/27/27 +f 30/30/30 28/28/28 26/26/26 +f 28/28/28 31/31/31 27/27/27 +f 31/31/31 28/28/28 30/30/30 +f 27/27/27 32/32/32 29/29/29 +f 32/32/32 27/27/27 31/31/31 +f 30/30/30 26/26/26 33/33/33 +f 33/33/33 26/26/26 19/19/19 +f 30/30/30 34/34/34 31/31/31 +f 33/33/33 35/35/35 30/30/30 +f 34/34/34 30/30/30 35/35/35 +f 31/31/31 36/36/36 32/32/32 +f 36/36/36 31/31/31 34/34/34 +f 37/37/37 35/35/35 33/33/33 +f 35/35/35 38/38/38 34/34/34 +f 38/38/38 35/35/35 37/37/37 +f 34/34/34 39/39/39 36/36/36 +f 39/39/39 34/34/34 38/38/38 +f 37/37/37 33/33/33 40/40/40 +f 40/40/40 33/33/33 19/19/19 +f 37/37/37 41/41/41 38/38/38 +f 40/40/40 42/42/42 37/37/37 +f 41/41/41 37/37/37 42/42/42 +f 38/38/38 43/43/43 39/39/39 +f 43/43/43 38/38/38 41/41/41 +f 44/44/44 42/42/42 40/40/40 +f 42/42/42 45/45/45 41/41/41 +f 45/45/45 42/42/42 44/44/44 +f 41/41/41 46/46/46 43/43/43 +f 46/46/46 41/41/41 45/45/45 +f 47/47/47 44/44/44 40/40/40 +f 44/44/44 48/48/48 45/45/45 +f 48/48/48 44/44/44 47/47/47 +f 45/45/45 49/49/49 46/46/46 +f 49/49/49 45/45/45 48/48/48 +f 40/40/40 50/50/50 47/47/47 +f 50/50/50 40/40/40 19/19/19 +f 47/47/47 51/51/51 48/48/48 +f 52/52/52 47/47/47 50/50/50 +f 51/51/51 47/47/47 52/52/52 +f 53/53/53 50/50/50 19/19/19 +f 52/52/52 50/50/50 53/53/53 +f 54/54/54 55/55/55 19/19/19 +f 55/55/55 53/53/53 19/19/19 +f 53/53/53 56/56/56 52/52/52 +f 57/57/57 53/53/53 55/55/55 +f 57/57/57 56/56/56 53/53/53 +f 58/58/58 52/52/52 56/56/56 +f 52/52/52 58/58/58 51/51/51 +f 59/59/59 56/56/56 57/57/57 +f 56/56/56 59/59/59 58/58/58 +f 55/55/55 60/60/60 57/57/57 +f 61/61/61 51/51/51 58/58/58 +f 62/62/62 57/57/57 60/60/60 +f 57/57/57 62/62/62 59/59/59 +f 63/63/63 60/60/60 55/55/55 +f 63/63/63 55/55/55 64/64/64 +f 65/65/65 58/58/58 59/59/59 +f 58/58/58 65/65/65 61/61/61 +f 66/66/66 59/59/59 62/62/62 +f 59/59/59 66/66/66 65/65/65 +f 60/60/60 67/67/67 62/62/62 +f 67/67/67 60/60/60 63/63/63 +f 62/62/62 68/68/68 66/66/66 +f 68/68/68 62/62/62 67/67/67 +f 69/69/69 65/65/65 66/66/66 +f 63/63/63 70/70/70 67/67/67 +f 71/71/71 66/66/66 68/68/68 +f 66/66/66 71/71/71 69/69/69 +f 67/67/67 72/72/72 68/68/68 +f 72/72/72 67/67/67 70/70/70 +f 68/68/68 73/73/73 71/71/71 +f 73/73/73 68/68/68 72/72/72 +f 70/70/70 63/63/63 74/74/74 +f 64/64/64 74/74/74 63/63/63 +f 70/70/70 75/75/75 72/72/72 +f 74/74/74 76/76/76 70/70/70 +f 75/75/75 70/70/70 76/76/76 +f 77/77/77 74/74/74 64/64/64 +f 76/76/76 74/74/74 77/77/77 +f 77/77/77 64/64/64 78/78/78 +f 78/78/78 64/64/64 3/3/3 +f 77/77/77 79/79/79 76/76/76 +f 78/78/78 80/80/80 77/77/77 +f 79/79/79 77/77/77 80/80/80 +f 81/81/81 76/76/76 79/79/79 +f 76/76/76 81/81/81 75/75/75 +f 80/80/80 82/82/82 79/79/79 +f 83/83/83 72/72/72 75/75/75 +f 72/72/72 83/83/83 73/73/73 +f 84/84/84 75/75/75 81/81/81 +f 75/75/75 84/84/84 83/83/83 +f 79/79/79 85/85/85 81/81/81 +f 85/85/85 79/79/79 82/82/82 +f 81/81/81 86/86/86 84/84/84 +f 86/86/86 81/81/81 85/85/85 +f 87/87/87 83/83/83 84/84/84 +f 88/88/88 84/84/84 86/86/86 +f 84/84/84 88/88/88 87/87/87 +f 85/85/85 89/89/89 86/86/86 +f 83/83/83 87/87/87 90/90/90 +f 90/90/90 73/73/73 83/83/83 +f 86/86/86 91/91/91 88/88/88 +f 91/91/91 86/86/86 89/89/89 +f 73/73/73 90/90/90 92/92/92 +f 92/92/92 71/71/71 73/73/73 +f 89/89/89 85/85/85 93/93/93 +f 82/82/82 93/93/93 85/85/85 +f 71/71/71 92/92/92 94/94/94 +f 94/94/94 69/69/69 71/71/71 +f 92/92/92 90/90/90 95/95/95 +f 94/94/94 92/92/92 96/96/96 +f 95/95/95 96/96/96 92/92/92 +f 69/69/69 94/94/94 97/97/97 +f 98/98/98 95/95/95 90/90/90 +f 90/90/90 87/87/87 98/98/98 +f 97/97/97 94/94/94 99/99/99 +f 96/96/96 99/99/99 94/94/94 +f 100/100/100 98/98/98 87/87/87 +f 87/87/87 88/88/88 100/100/100 +f 95/95/95 98/98/98 101/101/101 +f 102/102/102 100/100/100 88/88/88 +f 88/88/88 91/91/91 102/102/102 +f 98/98/98 100/100/100 103/103/103 +f 103/103/103 101/101/101 98/98/98 +f 100/100/100 102/102/102 104/104/104 +f 104/104/104 103/103/103 100/100/100 +f 105/105/105 102/102/102 91/91/91 +f 106/106/106 104/104/104 102/102/102 +f 102/102/102 105/105/105 106/106/106 +f 103/103/103 104/104/104 107/107/107 +f 91/91/91 108/108/108 105/105/105 +f 89/89/89 108/108/108 91/91/91 +f 109/109/109 107/107/107 104/104/104 +f 104/104/104 106/106/106 109/109/109 +f 107/107/107 110/110/110 103/103/103 +f 101/101/101 103/103/103 110/110/110 +f 107/107/107 109/109/109 111/111/111 +f 110/110/110 107/107/107 112/112/112 +f 111/111/111 112/112/112 107/107/107 +f 110/110/110 113/113/113 101/101/101 +f 112/112/112 114/114/114 110/110/110 +f 113/113/113 110/110/110 114/114/114 +f 115/115/115 101/101/101 113/113/113 +f 101/101/101 115/115/115 95/95/95 +f 96/96/96 95/95/95 115/115/115 +f 115/115/115 116/116/116 96/96/96 +f 99/99/99 96/96/96 116/116/116 +f 113/113/113 117/117/117 115/115/115 +f 116/116/116 115/115/115 117/117/117 +f 114/114/114 118/118/118 113/113/113 +f 117/117/117 113/113/113 118/118/118 +f 116/116/116 119/119/119 99/99/99 +f 117/117/117 120/120/120 116/116/116 +f 119/119/119 116/116/116 120/120/120 +f 118/118/118 121/121/121 117/117/117 +f 120/120/120 117/117/117 121/121/121 +f 122/122/122 99/99/99 119/119/119 +f 99/99/99 122/122/122 97/97/97 +f 120/120/120 123/123/123 119/119/119 +f 119/119/119 124/124/124 122/122/122 +f 124/124/124 119/119/119 123/123/123 +f 125/125/125 97/97/97 122/122/122 +f 123/123/123 120/120/120 126/126/126 +f 121/121/121 126/126/126 120/120/120 +f 127/127/127 122/122/122 124/124/124 +f 122/122/122 127/127/127 125/125/125 +f 123/123/123 128/128/128 124/124/124 +f 129/129/129 97/97/97 125/125/125 +f 97/97/97 129/129/129 69/69/69 +f 65/65/65 69/69/69 129/129/129 +f 129/129/129 61/61/61 65/65/65 +f 125/125/125 130/130/130 129/129/129 +f 61/61/61 129/129/129 130/130/130 +f 130/130/130 125/125/125 131/131/131 +f 131/131/131 125/125/125 127/127/127 +f 130/130/130 132/132/132 61/61/61 +f 51/51/51 61/61/61 132/132/132 +f 132/132/132 48/48/48 51/51/51 +f 48/48/48 132/132/132 49/49/49 +f 132/132/132 130/130/130 133/133/133 +f 133/133/133 49/49/49 132/132/132 +f 131/131/131 133/133/133 130/130/130 +f 49/49/49 133/133/133 134/134/134 +f 134/134/134 46/46/46 49/49/49 +f 133/133/133 131/131/131 135/135/135 +f 135/135/135 134/134/134 133/133/133 +f 46/46/46 134/134/134 136/136/136 +f 136/136/136 43/43/43 46/46/46 +f 134/134/134 135/135/135 137/137/137 +f 137/137/137 136/136/136 134/134/134 +f 135/135/135 131/131/131 138/138/138 +f 127/127/127 138/138/138 131/131/131 +f 137/137/137 135/135/135 139/139/139 +f 138/138/138 139/139/139 135/135/135 +f 138/138/138 127/127/127 140/140/140 +f 124/124/124 140/140/140 127/127/127 +f 140/140/140 124/124/124 128/128/128 +f 140/140/140 141/141/141 138/138/138 +f 139/139/139 138/138/138 141/141/141 +f 128/128/128 142/142/142 140/140/140 +f 141/141/141 140/140/140 142/142/142 +f 141/141/141 143/143/143 139/139/139 +f 142/142/142 144/144/144 141/141/141 +f 143/143/143 141/141/141 144/144/144 +f 142/142/142 128/128/128 145/145/145 +f 144/144/144 142/142/142 146/146/146 +f 145/145/145 146/146/146 142/142/142 +f 147/147/147 145/145/145 128/128/128 +f 128/128/128 123/123/123 147/147/147 +f 126/126/126 147/147/147 123/123/123 +f 145/145/145 147/147/147 148/148/148 +f 147/147/147 126/126/126 149/149/149 +f 149/149/149 148/148/148 147/147/147 +f 150/150/150 149/149/149 126/126/126 +f 126/126/126 121/121/121 150/150/150 +f 148/148/148 151/151/151 145/145/145 +f 146/146/146 145/145/145 151/151/151 +f 152/152/152 150/150/150 121/121/121 +f 121/121/121 118/118/118 152/152/152 +f 149/149/149 150/150/150 153/153/153 +f 154/154/154 152/152/152 118/118/118 +f 118/118/118 114/114/114 154/154/154 +f 150/150/150 152/152/152 155/155/155 +f 155/155/155 153/153/153 150/150/150 +f 152/152/152 154/154/154 156/156/156 +f 156/156/156 155/155/155 152/152/152 +f 157/157/157 154/154/154 114/114/114 +f 114/114/114 112/112/112 157/157/157 +f 158/158/158 156/156/156 154/154/154 +f 154/154/154 157/157/157 158/158/158 +f 159/159/159 157/157/157 112/112/112 +f 112/112/112 111/111/111 159/159/159 +f 160/160/160 158/158/158 157/157/157 +f 157/157/157 159/159/159 160/160/160 +f 156/156/156 158/158/158 161/161/161 +f 158/158/158 160/160/160 162/162/162 +f 162/162/162 161/161/161 158/158/158 +f 163/163/163 160/160/160 159/159/159 +f 161/161/161 164/164/164 156/156/156 +f 155/155/155 156/156/156 164/164/164 +f 165/165/165 159/159/159 111/111/111 +f 159/159/159 165/165/165 163/163/163 +f 164/164/164 166/166/166 155/155/155 +f 153/153/153 155/155/155 166/166/166 +f 111/111/111 167/167/167 165/165/165 +f 167/167/167 111/111/111 109/109/109 +f 166/166/166 168/168/168 153/153/153 +f 109/109/109 169/169/169 167/167/167 +f 169/169/169 109/109/109 106/106/106 +f 170/170/170 153/153/153 168/168/168 +f 153/153/153 170/170/170 149/149/149 +f 148/148/148 149/149/149 170/170/170 +f 106/106/106 171/171/171 169/169/169 +f 171/171/171 106/106/106 105/105/105 +f 170/170/170 172/172/172 148/148/148 +f 151/151/151 148/148/148 172/172/172 +f 105/105/105 173/173/173 171/171/171 +f 173/173/173 105/105/105 108/108/108 +f 172/172/172 170/170/170 174/174/174 +f 168/168/168 174/174/174 170/170/170 +f 172/172/172 175/175/175 151/151/151 +f 174/174/174 176/176/176 172/172/172 +f 175/175/175 172/172/172 176/176/176 +f 174/174/174 168/168/168 177/177/177 +f 176/176/176 174/174/174 178/178/178 +f 177/177/177 178/178/178 174/174/174 +f 179/179/179 177/177/177 168/168/168 +f 168/168/168 166/166/166 179/179/179 +f 180/180/180 178/178/178 177/177/177 +f 181/181/181 179/179/179 166/166/166 +f 166/166/166 164/164/164 181/181/181 +f 182/182/182 177/177/177 179/179/179 +f 177/177/177 182/182/182 180/180/180 +f 183/183/183 179/179/179 181/181/181 +f 179/179/179 183/183/183 182/182/182 +f 184/184/184 181/181/181 164/164/164 +f 164/164/164 161/161/161 184/184/184 +f 181/181/181 185/185/185 183/183/183 +f 185/185/185 181/181/181 184/184/184 +f 186/186/186 184/184/184 161/161/161 +f 161/161/161 162/162/162 186/186/186 +f 184/184/184 187/187/187 185/185/185 +f 187/187/187 184/184/184 186/186/186 +f 188/188/188 183/183/183 185/185/185 +f 188/188/188 182/182/182 183/183/183 +f 188/188/188 185/185/185 187/187/187 +f 188/188/188 180/180/180 182/182/182 +f 186/186/186 189/189/189 187/187/187 +f 188/188/188 187/187/187 189/189/189 +f 189/189/189 186/186/186 190/190/190 +f 190/190/190 186/186/186 162/162/162 +f 190/190/190 191/191/191 189/189/189 +f 188/188/188 189/189/189 191/191/191 +f 162/162/162 192/192/192 190/190/190 +f 192/192/192 162/162/162 160/160/160 +f 160/160/160 163/163/163 192/192/192 +f 191/191/191 190/190/190 193/193/193 +f 193/193/193 190/190/190 192/192/192 +f 194/194/194 192/192/192 163/163/163 +f 192/192/192 194/194/194 193/193/193 +f 193/193/193 195/195/195 191/191/191 +f 188/188/188 191/191/191 195/195/195 +f 196/196/196 193/193/193 194/194/194 +f 195/195/195 193/193/193 196/196/196 +f 163/163/163 197/197/197 194/194/194 +f 197/197/197 163/163/163 165/165/165 +f 194/194/194 198/198/198 196/196/196 +f 198/198/198 194/194/194 197/197/197 +f 196/196/196 199/199/199 195/195/195 +f 188/188/188 195/195/195 199/199/199 +f 199/199/199 196/196/196 200/200/200 +f 200/200/200 196/196/196 198/198/198 +f 200/200/200 201/201/201 199/199/199 +f 188/188/188 199/199/199 201/201/201 +f 198/198/198 202/202/202 200/200/200 +f 201/201/201 200/200/200 203/203/203 +f 203/203/203 200/200/200 202/202/202 +f 203/203/203 204/204/204 201/201/201 +f 188/188/188 201/201/201 204/204/204 +f 202/202/202 198/198/198 205/205/205 +f 197/197/197 205/205/205 198/198/198 +f 202/202/202 206/206/206 203/203/203 +f 205/205/205 197/197/197 207/207/207 +f 165/165/165 207/207/207 197/197/197 +f 207/207/207 165/165/165 167/167/167 +f 205/205/205 208/208/208 202/202/202 +f 206/206/206 202/202/202 208/208/208 +f 207/207/207 209/209/209 205/205/205 +f 208/208/208 205/205/205 209/209/209 +f 167/167/167 210/210/210 207/207/207 +f 209/209/209 207/207/207 210/210/210 +f 210/210/210 167/167/167 169/169/169 +f 209/209/209 211/211/211 208/208/208 +f 169/169/169 212/212/212 210/210/210 +f 212/212/212 169/169/169 171/171/171 +f 210/210/210 213/213/213 209/209/209 +f 213/213/213 210/210/210 212/212/212 +f 211/211/211 209/209/209 213/213/213 +f 171/171/171 214/214/214 212/212/212 +f 214/214/214 171/171/171 173/173/173 +f 212/212/212 215/215/215 213/213/213 +f 215/215/215 212/212/212 214/214/214 +f 213/213/213 216/216/216 211/211/211 +f 216/216/216 213/213/213 215/215/215 +f 217/217/217 208/208/208 211/211/211 +f 208/208/208 217/217/217 206/206/206 +f 218/218/218 211/211/211 216/216/216 +f 211/211/211 218/218/218 217/217/217 +f 215/215/215 219/219/219 216/216/216 +f 220/220/220 206/206/206 217/217/217 +f 216/216/216 221/221/221 218/218/218 +f 221/221/221 216/216/216 219/219/219 +f 222/222/222 217/217/217 218/218/218 +f 217/217/217 222/222/222 220/220/220 +f 223/223/223 218/218/218 221/221/221 +f 218/218/218 223/223/223 222/222/222 +f 206/206/206 220/220/220 224/224/224 +f 224/224/224 203/203/203 206/206/206 +f 204/204/204 203/203/203 224/224/224 +f 225/225/225 220/220/220 222/222/222 +f 224/224/224 226/226/226 204/204/204 +f 227/227/227 224/224/224 220/220/220 +f 226/226/226 224/224/224 227/227/227 +f 220/220/220 225/225/225 227/227/227 +f 188/188/188 204/204/204 226/226/226 +f 227/227/227 228/228/228 226/226/226 +f 188/188/188 226/226/226 228/228/228 +f 228/228/228 227/227/227 229/229/229 +f 229/229/229 227/227/227 225/225/225 +f 229/229/229 230/230/230 228/228/228 +f 188/188/188 228/228/228 230/230/230 +f 225/225/225 231/231/231 229/229/229 +f 230/230/230 229/229/229 232/232/232 +f 232/232/232 229/229/229 231/231/231 +f 222/222/222 233/233/233 225/225/225 +f 231/231/231 225/225/225 233/233/233 +f 233/233/233 222/222/222 223/223/223 +f 232/232/232 234/234/234 230/230/230 +f 188/188/188 230/230/230 234/234/234 +f 223/223/223 235/235/235 233/233/233 +f 234/234/234 232/232/232 236/236/236 +f 237/237/237 233/233/233 235/235/235 +f 233/233/233 237/237/237 231/231/231 +f 231/231/231 238/238/238 232/232/232 +f 238/238/238 231/231/231 237/237/237 +f 236/236/236 232/232/232 238/238/238 +f 237/237/237 239/239/239 238/238/238 +f 236/236/236 240/240/240 234/234/234 +f 188/188/188 234/234/234 240/240/240 +f 238/238/238 241/241/241 236/236/236 +f 241/241/241 238/238/238 239/239/239 +f 240/240/240 236/236/236 242/242/242 +f 242/242/242 236/236/236 241/241/241 +f 242/242/242 243/243/243 240/240/240 +f 188/188/188 240/240/240 243/243/243 +f 241/241/241 244/244/244 242/242/242 +f 243/243/243 242/242/242 245/245/245 +f 245/245/245 242/242/242 244/244/244 +f 245/245/245 246/246/246 243/243/243 +f 188/188/188 243/243/243 246/246/246 +f 244/244/244 241/241/241 247/247/247 +f 239/239/239 247/247/247 241/241/241 +f 244/244/244 248/248/248 245/245/245 +f 246/246/246 245/245/245 249/249/249 +f 249/249/249 245/245/245 248/248/248 +f 249/249/249 250/250/250 246/246/246 +f 188/188/188 246/246/246 250/250/250 +f 248/248/248 244/244/244 251/251/251 +f 247/247/247 251/251/251 244/244/244 +f 248/248/248 252/252/252 249/249/249 +f 250/250/250 249/249/249 253/253/253 +f 253/253/253 249/249/249 252/252/252 +f 253/253/253 254/254/254 250/250/250 +f 188/188/188 250/250/250 254/254/254 +f 252/252/252 248/248/248 255/255/255 +f 251/251/251 255/255/255 248/248/248 +f 252/252/252 256/256/256 253/253/253 +f 254/254/254 253/253/253 257/257/257 +f 257/257/257 253/253/253 256/256/256 +f 257/257/257 258/258/258 254/254/254 +f 188/188/188 254/254/254 258/258/258 +f 256/256/256 252/252/252 259/259/259 +f 255/255/255 259/259/259 252/252/252 +f 256/256/256 260/260/260 257/257/257 +f 258/258/258 257/257/257 261/261/261 +f 261/261/261 257/257/257 260/260/260 +f 261/261/261 262/262/262 258/258/258 +f 188/188/188 258/258/258 262/262/262 +f 260/260/260 256/256/256 263/263/263 +f 259/259/259 263/263/263 256/256/256 +f 260/260/260 264/264/264 261/261/261 +f 262/262/262 261/261/261 265/265/265 +f 265/265/265 261/261/261 264/264/264 +f 265/265/265 266/266/266 262/262/262 +f 188/188/188 262/262/262 266/266/266 +f 264/264/264 260/260/260 267/267/267 +f 263/263/263 267/267/267 260/260/260 +f 264/264/264 268/268/268 265/265/265 +f 266/266/266 265/265/265 269/269/269 +f 269/269/269 265/265/265 268/268/268 +f 269/269/269 270/270/270 266/266/266 +f 188/188/188 266/266/266 270/270/270 +f 268/268/268 264/264/264 271/271/271 +f 267/267/267 271/271/271 264/264/264 +f 268/268/268 272/272/272 269/269/269 +f 270/270/270 269/269/269 273/273/273 +f 273/273/273 269/269/269 272/272/272 +f 273/273/273 274/274/274 270/270/270 +f 188/188/188 270/270/270 274/274/274 +f 272/272/272 268/268/268 275/275/275 +f 271/271/271 275/275/275 268/268/268 +f 272/272/272 276/276/276 273/273/273 +f 274/274/274 273/273/273 277/277/277 +f 277/277/277 273/273/273 276/276/276 +f 277/277/277 278/278/278 274/274/274 +f 188/188/188 274/274/274 278/278/278 +f 276/276/276 272/272/272 279/279/279 +f 275/275/275 279/279/279 272/272/272 +f 276/276/276 280/280/280 277/277/277 +f 278/278/278 277/277/277 281/281/281 +f 281/281/281 277/277/277 280/280/280 +f 281/281/281 282/282/282 278/278/278 +f 188/188/188 278/278/278 282/282/282 +f 280/280/280 276/276/276 283/283/283 +f 279/279/279 283/283/283 276/276/276 +f 280/280/280 284/284/284 281/281/281 +f 282/282/282 281/281/281 285/285/285 +f 285/285/285 281/281/281 284/284/284 +f 285/285/285 286/286/286 282/282/282 +f 188/188/188 282/282/282 286/286/286 +f 284/284/284 280/280/280 287/287/287 +f 283/283/283 287/287/287 280/280/280 +f 284/284/284 288/288/288 285/285/285 +f 286/286/286 285/285/285 289/289/289 +f 289/289/289 285/285/285 288/288/288 +f 289/289/289 290/290/290 286/286/286 +f 188/188/188 286/286/286 290/290/290 +f 288/288/288 284/284/284 291/291/291 +f 287/287/287 291/291/291 284/284/284 +f 288/288/288 292/292/292 289/289/289 +f 290/290/290 289/289/289 293/293/293 +f 293/293/293 289/289/289 292/292/292 +f 293/293/293 294/294/294 290/290/290 +f 188/188/188 290/290/290 294/294/294 +f 292/292/292 288/288/288 295/295/295 +f 291/291/291 295/295/295 288/288/288 +f 188/188/188 294/294/294 296/296/296 +f 294/294/294 293/293/293 297/297/297 +f 297/297/297 296/296/296 294/294/294 +f 188/188/188 296/296/296 298/298/298 +f 188/188/188 298/298/298 180/180/180 +f 178/178/178 180/180/180 298/298/298 +f 299/299/299 298/298/298 296/296/296 +f 298/298/298 299/299/299 178/178/178 +f 296/296/296 297/297/297 299/299/299 +f 178/178/178 299/299/299 176/176/176 +f 300/300/300 176/176/176 299/299/299 +f 299/299/299 297/297/297 300/300/300 +f 176/176/176 300/300/300 175/175/175 +f 297/297/297 293/293/293 301/301/301 +f 301/301/301 300/300/300 297/297/297 +f 292/292/292 301/301/301 293/293/293 +f 302/302/302 175/175/175 300/300/300 +f 300/300/300 301/301/301 302/302/302 +f 301/301/301 292/292/292 303/303/303 +f 303/303/303 302/302/302 301/301/301 +f 295/295/295 303/303/303 292/292/292 +f 175/175/175 302/302/302 304/304/304 +f 304/304/304 151/151/151 175/175/175 +f 151/151/151 304/304/304 146/146/146 +f 302/302/302 303/303/303 305/305/305 +f 305/305/305 304/304/304 302/302/302 +f 306/306/306 146/146/146 304/304/304 +f 304/304/304 305/305/305 306/306/306 +f 146/146/146 306/306/306 144/144/144 +f 307/307/307 305/305/305 303/303/303 +f 303/303/303 295/295/295 307/307/307 +f 308/308/308 306/306/306 305/305/305 +f 305/305/305 307/307/307 308/308/308 +f 309/309/309 144/144/144 306/306/306 +f 306/306/306 308/308/308 309/309/309 +f 144/144/144 309/309/309 143/143/143 +f 310/310/310 307/307/307 295/295/295 +f 295/295/295 291/291/291 310/310/310 +f 311/311/311 308/308/308 307/307/307 +f 307/307/307 310/310/310 311/311/311 +f 312/312/312 309/309/309 308/308/308 +f 308/308/308 311/311/311 312/312/312 +f 313/313/313 310/310/310 291/291/291 +f 291/291/291 287/287/287 313/313/313 +f 314/314/314 311/311/311 310/310/310 +f 310/310/310 313/313/313 314/314/314 +f 315/315/315 313/313/313 287/287/287 +f 287/287/287 283/283/283 315/315/315 +f 316/316/316 314/314/314 313/313/313 +f 313/313/313 315/315/315 316/316/316 +f 311/311/311 314/314/314 317/317/317 +f 317/317/317 312/312/312 311/311/311 +f 314/314/314 316/316/316 318/318/318 +f 318/318/318 317/317/317 314/314/314 +f 319/319/319 315/315/315 283/283/283 +f 283/283/283 279/279/279 319/319/319 +f 320/320/320 316/316/316 315/315/315 +f 315/315/315 319/319/319 320/320/320 +f 321/321/321 319/319/319 279/279/279 +f 279/279/279 275/275/275 321/321/321 +f 322/322/322 320/320/320 319/319/319 +f 319/319/319 321/321/321 322/322/322 +f 316/316/316 320/320/320 323/323/323 +f 323/323/323 318/318/318 316/316/316 +f 320/320/320 322/322/322 324/324/324 +f 324/324/324 323/323/323 320/320/320 +f 325/325/325 321/321/321 275/275/275 +f 275/275/275 271/271/271 325/325/325 +f 326/326/326 322/322/322 321/321/321 +f 321/321/321 325/325/325 326/326/326 +f 327/327/327 325/325/325 271/271/271 +f 271/271/271 267/267/267 327/327/327 +f 328/328/328 326/326/326 325/325/325 +f 325/325/325 327/327/327 328/328/328 +f 322/322/322 326/326/326 329/329/329 +f 329/329/329 324/324/324 322/322/322 +f 326/326/326 328/328/328 330/330/330 +f 330/330/330 329/329/329 326/326/326 +f 331/331/331 327/327/327 267/267/267 +f 267/267/267 263/263/263 331/331/331 +f 332/332/332 328/328/328 327/327/327 +f 327/327/327 331/331/331 332/332/332 +f 333/333/333 331/331/331 263/263/263 +f 263/263/263 259/259/259 333/333/333 +f 334/334/334 332/332/332 331/331/331 +f 331/331/331 333/333/333 334/334/334 +f 328/328/328 332/332/332 335/335/335 +f 335/335/335 330/330/330 328/328/328 +f 332/332/332 334/334/334 336/336/336 +f 336/336/336 335/335/335 332/332/332 +f 337/337/337 333/333/333 259/259/259 +f 259/259/259 255/255/255 337/337/337 +f 338/338/338 334/334/334 333/333/333 +f 333/333/333 337/337/337 338/338/338 +f 339/339/339 337/337/337 255/255/255 +f 255/255/255 251/251/251 339/339/339 +f 340/340/340 338/338/338 337/337/337 +f 337/337/337 339/339/339 340/340/340 +f 334/334/334 338/338/338 341/341/341 +f 341/341/341 336/336/336 334/334/334 +f 338/338/338 340/340/340 342/342/342 +f 342/342/342 341/341/341 338/338/338 +f 343/343/343 339/339/339 251/251/251 +f 251/251/251 247/247/247 343/343/343 +f 344/344/344 340/340/340 339/339/339 +f 339/339/339 343/343/343 344/344/344 +f 345/345/345 343/343/343 247/247/247 +f 247/247/247 239/239/239 345/345/345 +f 346/346/346 344/344/344 343/343/343 +f 343/343/343 345/345/345 346/346/346 +f 340/340/340 344/344/344 347/347/347 +f 347/347/347 342/342/342 340/340/340 +f 344/344/344 346/346/346 348/348/348 +f 348/348/348 347/347/347 344/344/344 +f 349/349/349 345/345/345 239/239/239 +f 239/239/239 237/237/237 349/349/349 +f 235/235/235 349/349/349 237/237/237 +f 350/350/350 346/346/346 345/345/345 +f 345/345/345 349/349/349 350/350/350 +f 349/349/349 235/235/235 351/351/351 +f 351/351/351 350/350/350 349/349/349 +f 346/346/346 350/350/350 352/352/352 +f 352/352/352 348/348/348 346/346/346 +f 350/350/350 351/351/351 353/353/353 +f 353/353/353 352/352/352 350/350/350 +f 354/354/354 351/351/351 235/235/235 +f 235/235/235 223/223/223 354/354/354 +f 221/221/221 354/354/354 223/223/223 +f 351/351/351 354/354/354 355/355/355 +f 355/355/355 353/353/353 351/351/351 +f 354/354/354 221/221/221 356/356/356 +f 356/356/356 355/355/355 354/354/354 +f 219/219/219 356/356/356 221/221/221 +f 353/353/353 355/355/355 357/357/357 +f 355/355/355 356/356/356 358/358/358 +f 358/358/358 357/357/357 355/355/355 +f 356/356/356 219/219/219 359/359/359 +f 359/359/359 358/358/358 356/356/356 +f 360/360/360 359/359/359 219/219/219 +f 219/219/219 215/215/215 360/360/360 +f 214/214/214 360/360/360 215/215/215 +f 358/358/358 359/359/359 361/361/361 +f 360/360/360 214/214/214 362/362/362 +f 173/173/173 362/362/362 214/214/214 +f 359/359/359 360/360/360 363/363/363 +f 362/362/362 363/363/363 360/360/360 +f 363/363/363 361/361/361 359/359/359 +f 362/362/362 173/173/173 364/364/364 +f 108/108/108 364/364/364 173/173/173 +f 363/363/363 362/362/362 365/365/365 +f 364/364/364 365/365/365 362/362/362 +f 366/366/366 364/364/364 108/108/108 +f 108/108/108 89/89/89 366/366/366 +f 93/93/93 366/366/366 89/89/89 +f 364/364/364 366/366/366 367/367/367 +f 367/367/367 365/365/365 364/364/364 +f 366/366/366 93/93/93 368/368/368 +f 368/368/368 367/367/367 366/366/366 +f 365/365/365 369/369/369 363/363/363 +f 361/361/361 363/363/363 369/369/369 +f 365/365/365 367/367/367 370/370/370 +f 370/370/370 369/369/369 365/365/365 +f 367/367/367 368/368/368 371/371/371 +f 371/371/371 370/370/370 367/367/367 +f 372/372/372 368/368/368 93/93/93 +f 93/93/93 82/82/82 372/372/372 +f 373/373/373 371/371/371 368/368/368 +f 368/368/368 372/372/372 373/373/373 +f 374/374/374 372/372/372 82/82/82 +f 82/82/82 80/80/80 374/374/374 +f 374/374/374 80/80/80 78/78/78 +f 372/372/372 374/374/374 375/375/375 +f 375/375/375 373/373/373 372/372/372 +f 374/374/374 78/78/78 376/376/376 +f 376/376/376 375/375/375 374/374/374 +f 376/376/376 78/78/78 3/3/3 +f 373/373/373 375/375/375 377/377/377 +f 377/377/377 375/375/375 376/376/376 +f 371/371/371 373/373/373 378/378/378 +f 377/377/377 378/378/378 373/373/373 +f 377/377/377 376/376/376 379/379/379 +f 379/379/379 376/376/376 3/3/3 +f 378/378/378 377/377/377 380/380/380 +f 379/379/379 380/380/380 377/377/377 +f 378/378/378 381/381/381 371/371/371 +f 370/370/370 371/371/371 381/381/381 +f 380/380/380 382/382/382 378/378/378 +f 381/381/381 378/378/378 382/382/382 +f 383/383/383 380/380/380 379/379/379 +f 382/382/382 380/380/380 383/383/383 +f 384/384/384 379/379/379 3/3/3 +f 383/383/383 379/379/379 384/384/384 +f 385/385/385 384/384/384 3/3/3 +f 4/4/4 385/385/385 3/3/3 +f 384/384/384 386/386/386 383/383/383 +f 387/387/387 384/384/384 385/385/385 +f 387/387/387 386/386/386 384/384/384 +f 388/388/388 383/383/383 386/386/386 +f 383/383/383 388/388/388 382/382/382 +f 389/389/389 386/386/386 387/387/387 +f 386/386/386 389/389/389 388/388/388 +f 390/390/390 382/382/382 388/388/388 +f 382/382/382 390/390/390 381/381/381 +f 391/391/391 388/388/388 389/389/389 +f 388/388/388 391/391/391 390/390/390 +f 392/392/392 381/381/381 390/390/390 +f 381/381/381 392/392/392 370/370/370 +f 369/369/369 370/370/370 392/392/392 +f 390/390/390 393/393/393 392/392/392 +f 393/393/393 390/390/390 391/391/391 +f 392/392/392 394/394/394 369/369/369 +f 394/394/394 392/392/392 393/393/393 +f 369/369/369 394/394/394 361/361/361 +f 391/391/391 395/395/395 393/393/393 +f 396/396/396 361/361/361 394/394/394 +f 361/361/361 396/396/396 358/358/358 +f 393/393/393 397/397/397 394/394/394 +f 394/394/394 397/397/397 396/396/396 +f 397/397/397 393/393/393 395/395/395 +f 357/357/357 358/358/358 396/396/396 +f 398/398/398 396/396/396 397/397/397 +f 396/396/396 398/398/398 357/357/357 +f 395/395/395 399/399/399 397/397/397 +f 397/397/397 399/399/399 398/398/398 +f 400/400/400 357/357/357 398/398/398 +f 357/357/357 400/400/400 353/353/353 +f 352/352/352 353/353/353 400/400/400 +f 398/398/398 401/401/401 400/400/400 +f 401/401/401 398/398/398 399/399/399 +f 400/400/400 402/402/402 352/352/352 +f 402/402/402 400/400/400 401/401/401 +f 348/348/348 352/352/352 402/402/402 +f 399/399/399 403/403/403 401/401/401 +f 402/402/402 404/404/404 348/348/348 +f 347/347/347 348/348/348 404/404/404 +f 401/401/401 405/405/405 402/402/402 +f 404/404/404 402/402/402 405/405/405 +f 405/405/405 401/401/401 403/403/403 +f 404/404/404 406/406/406 347/347/347 +f 342/342/342 347/347/347 406/406/406 +f 405/405/405 407/407/407 404/404/404 +f 406/406/406 404/404/404 407/407/407 +f 403/403/403 408/408/408 405/405/405 +f 407/407/407 405/405/405 408/408/408 +f 406/406/406 409/409/409 342/342/342 +f 341/341/341 342/342/342 409/409/409 +f 407/407/407 410/410/410 406/406/406 +f 409/409/409 406/406/406 410/410/410 +f 408/408/408 411/411/411 407/407/407 +f 410/410/410 407/407/407 411/411/411 +f 409/409/409 412/412/412 341/341/341 +f 336/336/336 341/341/341 412/412/412 +f 410/410/410 413/413/413 409/409/409 +f 412/412/412 409/409/409 413/413/413 +f 411/411/411 414/414/414 410/410/410 +f 413/413/413 410/410/410 414/414/414 +f 412/412/412 415/415/415 336/336/336 +f 335/335/335 336/336/336 415/415/415 +f 413/413/413 416/416/416 412/412/412 +f 415/415/415 412/412/412 416/416/416 +f 414/414/414 417/417/417 413/413/413 +f 416/416/416 413/413/413 417/417/417 +f 415/415/415 418/418/418 335/335/335 +f 330/330/330 335/335/335 418/418/418 +f 416/416/416 419/419/419 415/415/415 +f 418/418/418 415/415/415 419/419/419 +f 417/417/417 420/420/420 416/416/416 +f 419/419/419 416/416/416 420/420/420 +f 418/418/418 421/421/421 330/330/330 +f 329/329/329 330/330/330 421/421/421 +f 419/419/419 422/422/422 418/418/418 +f 421/421/421 418/418/418 422/422/422 +f 420/420/420 423/423/423 419/419/419 +f 422/422/422 419/419/419 423/423/423 +f 421/421/421 424/424/424 329/329/329 +f 324/324/324 329/329/329 424/424/424 +f 422/422/422 425/425/425 421/421/421 +f 424/424/424 421/421/421 425/425/425 +f 423/423/423 426/426/426 422/422/422 +f 425/425/425 422/422/422 426/426/426 +f 424/424/424 427/427/427 324/324/324 +f 323/323/323 324/324/324 427/427/427 +f 425/425/425 428/428/428 424/424/424 +f 427/427/427 424/424/424 428/428/428 +f 426/426/426 429/429/429 425/425/425 +f 428/428/428 425/425/425 429/429/429 +f 427/427/427 430/430/430 323/323/323 +f 318/318/318 323/323/323 430/430/430 +f 428/428/428 431/431/431 427/427/427 +f 430/430/430 427/427/427 431/431/431 +f 429/429/429 432/432/432 428/428/428 +f 431/431/431 428/428/428 432/432/432 +f 430/430/430 433/433/433 318/318/318 +f 317/317/317 318/318/318 433/433/433 +f 431/431/431 434/434/434 430/430/430 +f 433/433/433 430/430/430 434/434/434 +f 432/432/432 435/435/435 431/431/431 +f 434/434/434 431/431/431 435/435/435 +f 433/433/433 436/436/436 317/317/317 +f 312/312/312 317/317/317 436/436/436 +f 434/434/434 437/437/437 433/433/433 +f 436/436/436 433/433/433 437/437/437 +f 435/435/435 438/438/438 434/434/434 +f 437/437/437 434/434/434 438/438/438 +f 436/436/436 439/439/439 312/312/312 +f 309/309/309 312/312/312 439/439/439 +f 439/439/439 143/143/143 309/309/309 +f 437/437/437 440/440/440 436/436/436 +f 439/439/439 436/436/436 440/440/440 +f 143/143/143 439/439/439 441/441/441 +f 440/440/440 441/441/441 439/439/439 +f 441/441/441 139/139/139 143/143/143 +f 139/139/139 441/441/441 137/137/137 +f 442/442/442 137/137/137 441/441/441 +f 441/441/441 440/440/440 442/442/442 +f 136/136/136 137/137/137 442/442/442 +f 440/440/440 437/437/437 443/443/443 +f 443/443/443 442/442/442 440/440/440 +f 438/438/438 443/443/443 437/437/437 +f 442/442/442 444/444/444 136/136/136 +f 444/444/444 442/442/442 443/443/443 +f 43/43/43 136/136/136 444/444/444 +f 444/444/444 39/39/39 43/43/43 +f 443/443/443 445/445/445 444/444/444 +f 39/39/39 444/444/444 445/445/445 +f 445/445/445 443/443/443 438/438/438 +f 445/445/445 36/36/36 39/39/39 +f 438/438/438 446/446/446 445/445/445 +f 36/36/36 445/445/445 446/446/446 +f 446/446/446 438/438/438 435/435/435 +f 446/446/446 32/32/32 36/36/36 +f 435/435/435 447/447/447 446/446/446 +f 32/32/32 446/446/446 447/447/447 +f 447/447/447 435/435/435 432/432/432 +f 447/447/447 29/29/29 32/32/32 +f 432/432/432 448/448/448 447/447/447 +f 29/29/29 447/447/447 448/448/448 +f 448/448/448 432/432/432 429/429/429 +f 448/448/448 25/25/25 29/29/29 +f 429/429/429 449/449/449 448/448/448 +f 25/25/25 448/448/448 449/449/449 +f 449/449/449 429/429/429 426/426/426 +f 449/449/449 22/22/22 25/25/25 +f 426/426/426 450/450/450 449/449/449 +f 22/22/22 449/449/449 450/450/450 +f 450/450/450 426/426/426 423/423/423 +f 450/450/450 17/17/17 22/22/22 +f 423/423/423 451/451/451 450/450/450 +f 17/17/17 450/450/450 451/451/451 +f 451/451/451 423/423/423 420/420/420 +f 451/451/451 15/15/15 17/17/17 +f 420/420/420 452/452/452 451/451/451 +f 15/15/15 451/451/451 452/452/452 +f 452/452/452 420/420/420 417/417/417 +f 452/452/452 13/13/13 15/15/15 +f 417/417/417 453/453/453 452/452/452 +f 13/13/13 452/452/452 453/453/453 +f 453/453/453 417/417/417 414/414/414 +f 453/453/453 454/454/454 13/13/13 +f 9/9/9 13/13/13 454/454/454 +f 414/414/414 455/455/455 453/453/453 +f 454/454/454 453/453/453 455/455/455 +f 455/455/455 414/414/414 411/411/411 +f 454/454/454 456/456/456 9/9/9 +f 5/5/5 9/9/9 456/456/456 +f 455/455/455 457/457/457 454/454/454 +f 456/456/456 454/454/454 457/457/457 +f 411/411/411 458/458/458 455/455/455 +f 457/457/457 455/455/455 458/458/458 +f 458/458/458 411/411/411 408/408/408 +f 456/456/456 459/459/459 5/5/5 +f 5/5/5 459/459/459 4/4/4 +f 408/408/408 460/460/460 458/458/458 +f 460/460/460 408/408/408 403/403/403 +f 4/4/4 459/459/459 461/461/461 +f 461/461/461 385/385/385 4/4/4 +f 459/459/459 456/456/456 462/462/462 +f 462/462/462 461/461/461 459/459/459 +f 457/457/457 462/462/462 456/456/456 +f 461/461/461 463/463/463 385/385/385 +f 385/385/385 463/463/463 387/387/387 +f 461/461/461 462/462/462 464/464/464 +f 464/464/464 463/463/463 461/461/461 +f 462/462/462 457/457/457 465/465/465 +f 458/458/458 465/465/465 457/457/457 +f 465/465/465 464/464/464 462/462/462 +f 465/465/465 458/458/458 460/460/460 +f 466/466/466 387/387/387 463/463/463 +f 463/463/463 464/464/464 466/466/466 +f 387/387/387 466/466/466 389/389/389 +f 464/464/464 465/465/465 467/467/467 +f 467/467/467 466/466/466 464/464/464 +f 460/460/460 467/467/467 465/465/465 +f 468/468/468 389/389/389 466/466/466 +f 466/466/466 467/467/467 468/468/468 +f 389/389/389 468/468/468 391/391/391 +f 395/395/395 391/391/391 468/468/468 +f 467/467/467 460/460/460 469/469/469 +f 469/469/469 468/468/468 467/467/467 +f 468/468/468 469/469/469 395/395/395 +f 403/403/403 469/469/469 460/460/460 +f 399/399/399 395/395/395 469/469/469 +f 469/469/469 403/403/403 399/399/399 diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome02/dome02.frag b/SkyLibrary/src/main/resources/Shaders/skies/dome02/dome02.frag index 921eac4..92b4c55 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome02/dome02.frag +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome02/dome02.frag @@ -1,31 +1,7 @@ -/* - Copyright (c) 2013-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * fragment shader used by dome02.j3md + * generated fragment shader used by dome02.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec4 m_ClearColor; @@ -45,19 +21,26 @@ varying vec2 skyTexCoord; uniform vec4 m_Clouds0Color; varying vec2 clouds0Coord; #endif +#ifdef HAS_CLOUDS0_NORMAL + uniform sampler2D m_Clouds0NormalMap; +#endif #ifdef HAS_CLOUDS1 uniform sampler2D m_Clouds1AlphaMap; uniform vec4 m_Clouds1Color; varying vec2 clouds1Coord; #endif +#ifdef HAS_CLOUDS1_NORMAL + uniform sampler2D m_Clouds1NormalMap; +#endif vec4 mixColors(vec4 color0, vec4 color1) { vec4 result; float a0 = color0.a * (1.0 - color1.a); result.a = a0 + color1.a; if (result.a > 0.0) { - result.rgb = (a0 * color0.rgb + color1.a * color1.rgb)/result.a; + result.rgb = (a0 * color0.rgb + color1.a * color1.rgb) + / result.a; } else { result.rgb = vec3(0.0); } @@ -70,7 +53,9 @@ void main() { #else vec4 stars = vec4(0.0); #endif - vec4 color = stars; + vec4 objects = vec4(0.0); + + vec4 color = mixColors(stars, objects); vec4 clear = m_ClearColor; #ifdef HAS_HAZE @@ -79,18 +64,31 @@ void main() { clear = mixColors(clear, haze); #endif color = mixColors(color, clear); + color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb) * clear.a; #ifdef HAS_CLOUDS0 vec4 clouds0 = m_Clouds0Color; clouds0.a *= texture2D(m_Clouds0AlphaMap, clouds0Coord).r; + #ifdef HAS_CLOUDS0_NORMAL + vec3 normal0 = texture2D(m_Clouds0NormalMap, clouds0Coord).xyz; + normal0 = normal0 * 2.0 - 1.0; + float relief0 = clamp(0.90 + normal0.z * 0.15, 0.75, 1.15); + clouds0.rgb *= relief0; + #endif color = mixColors(color, clouds0); #endif #ifdef HAS_CLOUDS1 vec4 clouds1 = m_Clouds1Color; clouds1.a *= texture2D(m_Clouds1AlphaMap, clouds1Coord).r; + #ifdef HAS_CLOUDS1_NORMAL + vec3 normal1 = texture2D(m_Clouds1NormalMap, clouds1Coord).xyz; + normal1 = normal1 * 2.0 - 1.0; + float relief1 = clamp(0.90 + normal1.z * 0.15, 0.75, 1.15); + clouds1.rgb *= relief1; + #endif color = mixColors(color, clouds1); #endif gl_FragColor = color; -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome02/dome02.vert b/SkyLibrary/src/main/resources/Shaders/skies/dome02/dome02.vert index 7612a48..2a2591e 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome02/dome02.vert +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome02/dome02.vert @@ -1,31 +1,7 @@ -/* - Copyright (c) 2013-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * vertex shader used by dome02.j3md + * generated vertex shader used by dome02.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" attribute vec2 inTexCoord; @@ -49,8 +25,7 @@ varying vec2 skyTexCoord; void main(){ skyTexCoord = inTexCoord; /* - * The following cloud texture coordinate calculations must be kept - * consistent with those in SkyMaterial.getTransparency(int,Vector2f) . + * Keep cloud coordinates consistent with material sampling. */ #ifdef HAS_CLOUDS0 clouds0Coord = inTexCoord * m_Clouds0Scale + m_Clouds0Offset; @@ -60,4 +35,4 @@ void main(){ #endif gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1); -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome02/dome02glow.frag b/SkyLibrary/src/main/resources/Shaders/skies/dome02/dome02glow.frag index 3a86365..5f1ad68 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome02/dome02glow.frag +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome02/dome02glow.frag @@ -1,31 +1,7 @@ -/* - Copyright (c) 2013-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * fragment shader used by dome02.j3md in its "Glow" technique + * generated glow fragment shader used by dome02.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec4 m_ClearGlow; @@ -41,17 +17,17 @@ varying vec2 skyTexCoord; uniform vec4 m_Clouds0Glow; varying vec2 clouds0Coord; #endif +#ifdef HAS_CLOUDS0_NORMAL + uniform sampler2D m_Clouds0NormalMap; +#endif #ifdef HAS_CLOUDS1 uniform sampler2D m_Clouds1AlphaMap; uniform vec4 m_Clouds1Glow; varying vec2 clouds1Coord; #endif - -#ifdef HAS_CLOUDS2 - uniform sampler2D m_Clouds2AlphaMap; - uniform vec4 m_Clouds2Glow; - varying vec2 clouds2Coord; +#ifdef HAS_CLOUDS1_NORMAL + uniform sampler2D m_Clouds1NormalMap; #endif vec4 mixColors(vec4 color0, vec4 color1) { @@ -59,7 +35,8 @@ vec4 mixColors(vec4 color0, vec4 color1) { float a0 = color0.a * (1.0 - color1.a); result.a = a0 + color1.a; if (result.a > 0.0) { - result.rgb = (a0 * color0.rgb + color1.a * color1.rgb)/result.a; + result.rgb = (a0 * color0.rgb + color1.a * color1.rgb) + / result.a; } else { result.rgb = vec3(0.0); } @@ -67,7 +44,10 @@ vec4 mixColors(vec4 color0, vec4 color1) { } void main() { - vec4 color = vec4(0.0); + vec4 stars = vec4(0.0); + vec4 objects = vec4(0.0); + + vec4 color = mixColors(stars, objects); vec4 clear = m_ClearGlow; #ifdef HAS_HAZE @@ -76,18 +56,31 @@ void main() { clear = mixColors(clear, haze); #endif color = mixColors(color, clear); + color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb) * clear.a; #ifdef HAS_CLOUDS0 vec4 clouds0 = m_Clouds0Glow; clouds0.a *= texture2D(m_Clouds0AlphaMap, clouds0Coord).r; + #ifdef HAS_CLOUDS0_NORMAL + vec3 normal0 = texture2D(m_Clouds0NormalMap, clouds0Coord).xyz; + normal0 = normal0 * 2.0 - 1.0; + float relief0 = clamp(0.90 + normal0.z * 0.15, 0.75, 1.15); + clouds0.rgb *= relief0; + #endif color = mixColors(color, clouds0); #endif #ifdef HAS_CLOUDS1 vec4 clouds1 = m_Clouds1Glow; clouds1.a *= texture2D(m_Clouds1AlphaMap, clouds1Coord).r; + #ifdef HAS_CLOUDS1_NORMAL + vec3 normal1 = texture2D(m_Clouds1NormalMap, clouds1Coord).xyz; + normal1 = normal1 * 2.0 - 1.0; + float relief1 = clamp(0.90 + normal1.z * 0.15, 0.75, 1.15); + clouds1.rgb *= relief1; + #endif color = mixColors(color, clouds1); #endif gl_FragColor = color; -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome06/dome06.frag b/SkyLibrary/src/main/resources/Shaders/skies/dome06/dome06.frag index 134919d..fb07cd9 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome06/dome06.frag +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome06/dome06.frag @@ -1,31 +1,7 @@ -/* - Copyright (c) 2014-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * fragment shader used by dome06.j3md + * generated fragment shader used by dome06.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec4 m_ClearColor; @@ -45,43 +21,62 @@ varying vec2 skyTexCoord; uniform vec4 m_Clouds0Color; varying vec2 clouds0Coord; #endif +#ifdef HAS_CLOUDS0_NORMAL + uniform sampler2D m_Clouds0NormalMap; +#endif #ifdef HAS_CLOUDS1 uniform sampler2D m_Clouds1AlphaMap; uniform vec4 m_Clouds1Color; varying vec2 clouds1Coord; #endif +#ifdef HAS_CLOUDS1_NORMAL + uniform sampler2D m_Clouds1NormalMap; +#endif #ifdef HAS_CLOUDS2 uniform sampler2D m_Clouds2AlphaMap; uniform vec4 m_Clouds2Color; varying vec2 clouds2Coord; #endif +#ifdef HAS_CLOUDS2_NORMAL + uniform sampler2D m_Clouds2NormalMap; +#endif #ifdef HAS_CLOUDS3 uniform sampler2D m_Clouds3AlphaMap; uniform vec4 m_Clouds3Color; varying vec2 clouds3Coord; #endif +#ifdef HAS_CLOUDS3_NORMAL + uniform sampler2D m_Clouds3NormalMap; +#endif #ifdef HAS_CLOUDS4 uniform sampler2D m_Clouds4AlphaMap; uniform vec4 m_Clouds4Color; varying vec2 clouds4Coord; #endif +#ifdef HAS_CLOUDS4_NORMAL + uniform sampler2D m_Clouds4NormalMap; +#endif #ifdef HAS_CLOUDS5 uniform sampler2D m_Clouds5AlphaMap; uniform vec4 m_Clouds5Color; varying vec2 clouds5Coord; #endif +#ifdef HAS_CLOUDS5_NORMAL + uniform sampler2D m_Clouds5NormalMap; +#endif vec4 mixColors(vec4 color0, vec4 color1) { vec4 result; float a0 = color0.a * (1.0 - color1.a); result.a = a0 + color1.a; if (result.a > 0.0) { - result.rgb = (a0 * color0.rgb + color1.a * color1.rgb)/result.a; + result.rgb = (a0 * color0.rgb + color1.a * color1.rgb) + / result.a; } else { result.rgb = vec3(0.0); } @@ -94,7 +89,9 @@ void main() { #else vec4 stars = vec4(0.0); #endif - vec4 color = stars; + vec4 objects = vec4(0.0); + + vec4 color = mixColors(stars, objects); vec4 clear = m_ClearColor; #ifdef HAS_HAZE @@ -103,42 +100,79 @@ void main() { clear = mixColors(clear, haze); #endif color = mixColors(color, clear); + color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb) * clear.a; #ifdef HAS_CLOUDS0 vec4 clouds0 = m_Clouds0Color; clouds0.a *= texture2D(m_Clouds0AlphaMap, clouds0Coord).r; + #ifdef HAS_CLOUDS0_NORMAL + vec3 normal0 = texture2D(m_Clouds0NormalMap, clouds0Coord).xyz; + normal0 = normal0 * 2.0 - 1.0; + float relief0 = clamp(0.90 + normal0.z * 0.15, 0.75, 1.15); + clouds0.rgb *= relief0; + #endif color = mixColors(color, clouds0); #endif #ifdef HAS_CLOUDS1 vec4 clouds1 = m_Clouds1Color; clouds1.a *= texture2D(m_Clouds1AlphaMap, clouds1Coord).r; + #ifdef HAS_CLOUDS1_NORMAL + vec3 normal1 = texture2D(m_Clouds1NormalMap, clouds1Coord).xyz; + normal1 = normal1 * 2.0 - 1.0; + float relief1 = clamp(0.90 + normal1.z * 0.15, 0.75, 1.15); + clouds1.rgb *= relief1; + #endif color = mixColors(color, clouds1); #endif #ifdef HAS_CLOUDS2 vec4 clouds2 = m_Clouds2Color; clouds2.a *= texture2D(m_Clouds2AlphaMap, clouds2Coord).r; + #ifdef HAS_CLOUDS2_NORMAL + vec3 normal2 = texture2D(m_Clouds2NormalMap, clouds2Coord).xyz; + normal2 = normal2 * 2.0 - 1.0; + float relief2 = clamp(0.90 + normal2.z * 0.15, 0.75, 1.15); + clouds2.rgb *= relief2; + #endif color = mixColors(color, clouds2); #endif #ifdef HAS_CLOUDS3 vec4 clouds3 = m_Clouds3Color; clouds3.a *= texture2D(m_Clouds3AlphaMap, clouds3Coord).r; + #ifdef HAS_CLOUDS3_NORMAL + vec3 normal3 = texture2D(m_Clouds3NormalMap, clouds3Coord).xyz; + normal3 = normal3 * 2.0 - 1.0; + float relief3 = clamp(0.90 + normal3.z * 0.15, 0.75, 1.15); + clouds3.rgb *= relief3; + #endif color = mixColors(color, clouds3); #endif #ifdef HAS_CLOUDS4 vec4 clouds4 = m_Clouds4Color; clouds4.a *= texture2D(m_Clouds4AlphaMap, clouds4Coord).r; + #ifdef HAS_CLOUDS4_NORMAL + vec3 normal4 = texture2D(m_Clouds4NormalMap, clouds4Coord).xyz; + normal4 = normal4 * 2.0 - 1.0; + float relief4 = clamp(0.90 + normal4.z * 0.15, 0.75, 1.15); + clouds4.rgb *= relief4; + #endif color = mixColors(color, clouds4); #endif #ifdef HAS_CLOUDS5 vec4 clouds5 = m_Clouds5Color; clouds5.a *= texture2D(m_Clouds5AlphaMap, clouds5Coord).r; + #ifdef HAS_CLOUDS5_NORMAL + vec3 normal5 = texture2D(m_Clouds5NormalMap, clouds5Coord).xyz; + normal5 = normal5 * 2.0 - 1.0; + float relief5 = clamp(0.90 + normal5.z * 0.15, 0.75, 1.15); + clouds5.rgb *= relief5; + #endif color = mixColors(color, clouds5); #endif gl_FragColor = color; -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome06/dome06.vert b/SkyLibrary/src/main/resources/Shaders/skies/dome06/dome06.vert index 336d988..2533bf0 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome06/dome06.vert +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome06/dome06.vert @@ -1,31 +1,7 @@ -/* - Copyright (c) 2014-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * vertex shader used by dome06.j3md + * generated vertex shader used by dome06.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" attribute vec2 inTexCoord; @@ -73,8 +49,7 @@ varying vec2 skyTexCoord; void main(){ skyTexCoord = inTexCoord; /* - * The following cloud texture coordinate calculations must be kept - * consistent with those in SkyMaterial.getTransparency(int,Vector2f) . + * Keep cloud coordinates consistent with material sampling. */ #ifdef HAS_CLOUDS0 clouds0Coord = inTexCoord * m_Clouds0Scale + m_Clouds0Offset; @@ -96,4 +71,4 @@ void main(){ #endif gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1); -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome06/dome06glow.frag b/SkyLibrary/src/main/resources/Shaders/skies/dome06/dome06glow.frag index fc828be..6b6ddcd 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome06/dome06glow.frag +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome06/dome06glow.frag @@ -1,31 +1,7 @@ -/* - Copyright (c) 2014-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * fragment shader used by dome06.j3md in its "Glow" technique + * generated glow fragment shader used by dome06.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec4 m_ClearGlow; @@ -41,43 +17,62 @@ varying vec2 skyTexCoord; uniform vec4 m_Clouds0Glow; varying vec2 clouds0Coord; #endif +#ifdef HAS_CLOUDS0_NORMAL + uniform sampler2D m_Clouds0NormalMap; +#endif #ifdef HAS_CLOUDS1 uniform sampler2D m_Clouds1AlphaMap; uniform vec4 m_Clouds1Glow; varying vec2 clouds1Coord; #endif +#ifdef HAS_CLOUDS1_NORMAL + uniform sampler2D m_Clouds1NormalMap; +#endif #ifdef HAS_CLOUDS2 uniform sampler2D m_Clouds2AlphaMap; uniform vec4 m_Clouds2Glow; varying vec2 clouds2Coord; #endif +#ifdef HAS_CLOUDS2_NORMAL + uniform sampler2D m_Clouds2NormalMap; +#endif #ifdef HAS_CLOUDS3 uniform sampler2D m_Clouds3AlphaMap; uniform vec4 m_Clouds3Glow; varying vec2 clouds3Coord; #endif +#ifdef HAS_CLOUDS3_NORMAL + uniform sampler2D m_Clouds3NormalMap; +#endif #ifdef HAS_CLOUDS4 uniform sampler2D m_Clouds4AlphaMap; uniform vec4 m_Clouds4Glow; varying vec2 clouds4Coord; #endif +#ifdef HAS_CLOUDS4_NORMAL + uniform sampler2D m_Clouds4NormalMap; +#endif #ifdef HAS_CLOUDS5 uniform sampler2D m_Clouds5AlphaMap; uniform vec4 m_Clouds5Glow; varying vec2 clouds5Coord; #endif +#ifdef HAS_CLOUDS5_NORMAL + uniform sampler2D m_Clouds5NormalMap; +#endif vec4 mixColors(vec4 color0, vec4 color1) { vec4 result; float a0 = color0.a * (1.0 - color1.a); result.a = a0 + color1.a; if (result.a > 0.0) { - result.rgb = (a0 * color0.rgb + color1.a * color1.rgb)/result.a; + result.rgb = (a0 * color0.rgb + color1.a * color1.rgb) + / result.a; } else { result.rgb = vec3(0.0); } @@ -85,7 +80,10 @@ vec4 mixColors(vec4 color0, vec4 color1) { } void main() { - vec4 color = vec4(0.0); + vec4 stars = vec4(0.0); + vec4 objects = vec4(0.0); + + vec4 color = mixColors(stars, objects); vec4 clear = m_ClearGlow; #ifdef HAS_HAZE @@ -94,42 +92,79 @@ void main() { clear = mixColors(clear, haze); #endif color = mixColors(color, clear); + color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb) * clear.a; #ifdef HAS_CLOUDS0 vec4 clouds0 = m_Clouds0Glow; clouds0.a *= texture2D(m_Clouds0AlphaMap, clouds0Coord).r; + #ifdef HAS_CLOUDS0_NORMAL + vec3 normal0 = texture2D(m_Clouds0NormalMap, clouds0Coord).xyz; + normal0 = normal0 * 2.0 - 1.0; + float relief0 = clamp(0.90 + normal0.z * 0.15, 0.75, 1.15); + clouds0.rgb *= relief0; + #endif color = mixColors(color, clouds0); #endif #ifdef HAS_CLOUDS1 vec4 clouds1 = m_Clouds1Glow; clouds1.a *= texture2D(m_Clouds1AlphaMap, clouds1Coord).r; + #ifdef HAS_CLOUDS1_NORMAL + vec3 normal1 = texture2D(m_Clouds1NormalMap, clouds1Coord).xyz; + normal1 = normal1 * 2.0 - 1.0; + float relief1 = clamp(0.90 + normal1.z * 0.15, 0.75, 1.15); + clouds1.rgb *= relief1; + #endif color = mixColors(color, clouds1); #endif #ifdef HAS_CLOUDS2 vec4 clouds2 = m_Clouds2Glow; clouds2.a *= texture2D(m_Clouds2AlphaMap, clouds2Coord).r; + #ifdef HAS_CLOUDS2_NORMAL + vec3 normal2 = texture2D(m_Clouds2NormalMap, clouds2Coord).xyz; + normal2 = normal2 * 2.0 - 1.0; + float relief2 = clamp(0.90 + normal2.z * 0.15, 0.75, 1.15); + clouds2.rgb *= relief2; + #endif color = mixColors(color, clouds2); #endif #ifdef HAS_CLOUDS3 vec4 clouds3 = m_Clouds3Glow; clouds3.a *= texture2D(m_Clouds3AlphaMap, clouds3Coord).r; + #ifdef HAS_CLOUDS3_NORMAL + vec3 normal3 = texture2D(m_Clouds3NormalMap, clouds3Coord).xyz; + normal3 = normal3 * 2.0 - 1.0; + float relief3 = clamp(0.90 + normal3.z * 0.15, 0.75, 1.15); + clouds3.rgb *= relief3; + #endif color = mixColors(color, clouds3); #endif #ifdef HAS_CLOUDS4 vec4 clouds4 = m_Clouds4Glow; clouds4.a *= texture2D(m_Clouds4AlphaMap, clouds4Coord).r; + #ifdef HAS_CLOUDS4_NORMAL + vec3 normal4 = texture2D(m_Clouds4NormalMap, clouds4Coord).xyz; + normal4 = normal4 * 2.0 - 1.0; + float relief4 = clamp(0.90 + normal4.z * 0.15, 0.75, 1.15); + clouds4.rgb *= relief4; + #endif color = mixColors(color, clouds4); #endif #ifdef HAS_CLOUDS5 vec4 clouds5 = m_Clouds5Glow; clouds5.a *= texture2D(m_Clouds5AlphaMap, clouds5Coord).r; + #ifdef HAS_CLOUDS5_NORMAL + vec3 normal5 = texture2D(m_Clouds5NormalMap, clouds5Coord).xyz; + normal5 = normal5 * 2.0 - 1.0; + float relief5 = clamp(0.90 + normal5.z * 0.15, 0.75, 1.15); + clouds5.rgb *= relief5; + #endif color = mixColors(color, clouds5); #endif gl_FragColor = color; -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome20/dome20.frag b/SkyLibrary/src/main/resources/Shaders/skies/dome20/dome20.frag index aab0c82..62ee761 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome20/dome20.frag +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome20/dome20.frag @@ -1,31 +1,7 @@ -/* - Copyright (c) 2014-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * fragment shader used by dome20.j3md + * generated fragment shader used by dome20.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec4 m_ClearColor; @@ -57,7 +33,8 @@ vec4 mixColors(vec4 color0, vec4 color1) { float a0 = color0.a * (1.0 - color1.a); result.a = a0 + color1.a; if (result.a > 0.0) { - result.rgb = (a0 * color0.rgb + color1.a * color1.rgb)/result.a; + result.rgb = (a0 * color0.rgb + color1.a * color1.rgb) + / result.a; } else { result.rgb = vec3(0.0); } @@ -70,14 +47,14 @@ void main() { #else vec4 stars = vec4(0.0); #endif - vec4 objects = vec4(0.0); #ifdef HAS_OBJECT0 if (floor(object0Coord.s) == 0.0 && floor(object0Coord.t) == 0.0) { - objects = m_Object0Color; - objects *= texture2D(m_Object0ColorMap, object0Coord); + vec4 object0 = m_Object0Color; + object0 *= texture2D(m_Object0ColorMap, object0Coord); + objects = object0; } #endif @@ -99,8 +76,7 @@ void main() { clear = mixColors(clear, haze); #endif color = mixColors(color, clear); - // Bright parts of objects shine through the clear areas. color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb) * clear.a; gl_FragColor = color; -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome20/dome20.vert b/SkyLibrary/src/main/resources/Shaders/skies/dome20/dome20.vert index b52ee9b..1ad9229 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome20/dome20.vert +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome20/dome20.vert @@ -1,31 +1,7 @@ -/* - Copyright (c) 2014-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * vertex shader used by dome20.j3md + * generated vertex shader used by dome20.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" attribute vec2 inTexCoord; @@ -66,4 +42,4 @@ void main(){ #endif gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1); -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome20/dome20glow.frag b/SkyLibrary/src/main/resources/Shaders/skies/dome20/dome20glow.frag index 17444d3..d7cce6a 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome20/dome20glow.frag +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome20/dome20glow.frag @@ -1,45 +1,21 @@ -/* - Copyright (c) 2014-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * fragment shader used by dome20.j3md in its "Glow" technique + * generated glow fragment shader used by dome20.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec4 m_ClearGlow; varying vec2 skyTexCoord; #ifdef HAS_OBJECT0 + uniform vec4 m_Object0Color; uniform sampler2D m_Object0ColorMap; - uniform vec4 m_Object0Glow; varying vec2 object0Coord; #endif #ifdef HAS_OBJECT1 + uniform vec4 m_Object1Color; uniform sampler2D m_Object1ColorMap; - uniform vec4 m_Object1Glow; varying vec2 object1Coord; #endif @@ -53,7 +29,8 @@ vec4 mixColors(vec4 color0, vec4 color1) { float a0 = color0.a * (1.0 - color1.a); result.a = a0 + color1.a; if (result.a > 0.0) { - result.rgb = (a0 * color0.rgb + color1.a * color1.rgb)/result.a; + result.rgb = (a0 * color0.rgb + color1.a * color1.rgb) + / result.a; } else { result.rgb = vec3(0.0); } @@ -62,14 +39,14 @@ vec4 mixColors(vec4 color0, vec4 color1) { void main() { vec4 stars = vec4(0.0); - vec4 objects = vec4(0.0); #ifdef HAS_OBJECT0 if (floor(object0Coord.s) == 0.0 && floor(object0Coord.t) == 0.0) { - objects = m_Object0Glow; - objects *= texture2D(m_Object0ColorMap, object0Coord); + vec4 object0 = m_Object0Glow; + object0 *= texture2D(m_Object0ColorMap, object0Coord); + objects = object0; } #endif @@ -91,8 +68,7 @@ void main() { clear = mixColors(clear, haze); #endif color = mixColors(color, clear); - // Bright parts of objects shine through the clear areas. color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb) * clear.a; gl_FragColor = color; -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome22/dome22.frag b/SkyLibrary/src/main/resources/Shaders/skies/dome22/dome22.frag index fe0e744..cce6cc8 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome22/dome22.frag +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome22/dome22.frag @@ -1,31 +1,7 @@ -/* - Copyright (c) 2013-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * fragment shader used by dome22.j3md + * generated fragment shader used by dome22.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec4 m_ClearColor; @@ -57,19 +33,26 @@ varying vec2 skyTexCoord; uniform vec4 m_Clouds0Color; varying vec2 clouds0Coord; #endif +#ifdef HAS_CLOUDS0_NORMAL + uniform sampler2D m_Clouds0NormalMap; +#endif #ifdef HAS_CLOUDS1 uniform sampler2D m_Clouds1AlphaMap; uniform vec4 m_Clouds1Color; varying vec2 clouds1Coord; #endif +#ifdef HAS_CLOUDS1_NORMAL + uniform sampler2D m_Clouds1NormalMap; +#endif vec4 mixColors(vec4 color0, vec4 color1) { vec4 result; float a0 = color0.a * (1.0 - color1.a); result.a = a0 + color1.a; if (result.a > 0.0) { - result.rgb = (a0 * color0.rgb + color1.a * color1.rgb)/result.a; + result.rgb = (a0 * color0.rgb + color1.a * color1.rgb) + / result.a; } else { result.rgb = vec3(0.0); } @@ -82,14 +65,14 @@ void main() { #else vec4 stars = vec4(0.0); #endif - vec4 objects = vec4(0.0); #ifdef HAS_OBJECT0 if (floor(object0Coord.s) == 0.0 && floor(object0Coord.t) == 0.0) { - objects = m_Object0Color; - objects *= texture2D(m_Object0ColorMap, object0Coord); + vec4 object0 = m_Object0Color; + object0 *= texture2D(m_Object0ColorMap, object0Coord); + objects = object0; } #endif @@ -111,20 +94,31 @@ void main() { clear = mixColors(clear, haze); #endif color = mixColors(color, clear); - // Bright parts of objects shine through the clear areas. color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb) * clear.a; #ifdef HAS_CLOUDS0 vec4 clouds0 = m_Clouds0Color; clouds0.a *= texture2D(m_Clouds0AlphaMap, clouds0Coord).r; + #ifdef HAS_CLOUDS0_NORMAL + vec3 normal0 = texture2D(m_Clouds0NormalMap, clouds0Coord).xyz; + normal0 = normal0 * 2.0 - 1.0; + float relief0 = clamp(0.90 + normal0.z * 0.15, 0.75, 1.15); + clouds0.rgb *= relief0; + #endif color = mixColors(color, clouds0); #endif #ifdef HAS_CLOUDS1 vec4 clouds1 = m_Clouds1Color; clouds1.a *= texture2D(m_Clouds1AlphaMap, clouds1Coord).r; + #ifdef HAS_CLOUDS1_NORMAL + vec3 normal1 = texture2D(m_Clouds1NormalMap, clouds1Coord).xyz; + normal1 = normal1 * 2.0 - 1.0; + float relief1 = clamp(0.90 + normal1.z * 0.15, 0.75, 1.15); + clouds1.rgb *= relief1; + #endif color = mixColors(color, clouds1); #endif gl_FragColor = color; -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome22/dome22.vert b/SkyLibrary/src/main/resources/Shaders/skies/dome22/dome22.vert index bea6d11..923cc4a 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome22/dome22.vert +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome22/dome22.vert @@ -1,31 +1,7 @@ -/* - Copyright (c) 2013-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * vertex shader used by dome22.j3md + * generated vertex shader used by dome22.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" attribute vec2 inTexCoord; @@ -65,8 +41,7 @@ varying vec2 skyTexCoord; void main(){ skyTexCoord = inTexCoord; /* - * The following cloud texture coordinate calculations must be kept - * consistent with those in SkyMaterial.getTransparency(int,Vector2f) . + * Keep cloud coordinates consistent with material sampling. */ #ifdef HAS_CLOUDS0 clouds0Coord = inTexCoord * m_Clouds0Scale + m_Clouds0Offset; @@ -74,14 +49,12 @@ void main(){ #ifdef HAS_CLOUDS1 clouds1Coord = inTexCoord * m_Clouds1Scale + m_Clouds1Offset; #endif - #ifdef HAS_OBJECT0 object0Offset = inTexCoord - m_Object0Center; object0Coord.x = dot(m_Object0TransformU, object0Offset); object0Coord.y = dot(m_Object0TransformV, object0Offset); object0Coord += m_TopCoord; #endif - #ifdef HAS_OBJECT1 object1Offset = inTexCoord - m_Object1Center; object1Coord.x = dot(m_Object1TransformU, object1Offset); @@ -90,4 +63,4 @@ void main(){ #endif gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1); -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome22/dome22glow.frag b/SkyLibrary/src/main/resources/Shaders/skies/dome22/dome22glow.frag index 3cb2a9b..19f8fe6 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome22/dome22glow.frag +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome22/dome22glow.frag @@ -1,45 +1,21 @@ -/* - Copyright (c) 2013-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * fragment shader used by dome22.j3md in its "Glow" technique + * generated glow fragment shader used by dome22.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec4 m_ClearGlow; varying vec2 skyTexCoord; #ifdef HAS_OBJECT0 + uniform vec4 m_Object0Color; uniform sampler2D m_Object0ColorMap; - uniform vec4 m_Object0Glow; varying vec2 object0Coord; #endif #ifdef HAS_OBJECT1 + uniform vec4 m_Object1Color; uniform sampler2D m_Object1ColorMap; - uniform vec4 m_Object1Glow; varying vec2 object1Coord; #endif @@ -53,19 +29,26 @@ varying vec2 skyTexCoord; uniform vec4 m_Clouds0Glow; varying vec2 clouds0Coord; #endif +#ifdef HAS_CLOUDS0_NORMAL + uniform sampler2D m_Clouds0NormalMap; +#endif #ifdef HAS_CLOUDS1 uniform sampler2D m_Clouds1AlphaMap; uniform vec4 m_Clouds1Glow; varying vec2 clouds1Coord; #endif +#ifdef HAS_CLOUDS1_NORMAL + uniform sampler2D m_Clouds1NormalMap; +#endif vec4 mixColors(vec4 color0, vec4 color1) { vec4 result; float a0 = color0.a * (1.0 - color1.a); result.a = a0 + color1.a; if (result.a > 0.0) { - result.rgb = (a0 * color0.rgb + color1.a * color1.rgb)/result.a; + result.rgb = (a0 * color0.rgb + color1.a * color1.rgb) + / result.a; } else { result.rgb = vec3(0.0); } @@ -74,14 +57,14 @@ vec4 mixColors(vec4 color0, vec4 color1) { void main() { vec4 stars = vec4(0.0); - vec4 objects = vec4(0.0); #ifdef HAS_OBJECT0 if (floor(object0Coord.s) == 0.0 && floor(object0Coord.t) == 0.0) { - objects = m_Object0Glow; - objects *= texture2D(m_Object0ColorMap, object0Coord); + vec4 object0 = m_Object0Glow; + object0 *= texture2D(m_Object0ColorMap, object0Coord); + objects = object0; } #endif @@ -103,20 +86,31 @@ void main() { clear = mixColors(clear, haze); #endif color = mixColors(color, clear); - // Bright parts of objects shine through the clear areas. color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb) * clear.a; #ifdef HAS_CLOUDS0 vec4 clouds0 = m_Clouds0Glow; clouds0.a *= texture2D(m_Clouds0AlphaMap, clouds0Coord).r; + #ifdef HAS_CLOUDS0_NORMAL + vec3 normal0 = texture2D(m_Clouds0NormalMap, clouds0Coord).xyz; + normal0 = normal0 * 2.0 - 1.0; + float relief0 = clamp(0.90 + normal0.z * 0.15, 0.75, 1.15); + clouds0.rgb *= relief0; + #endif color = mixColors(color, clouds0); #endif #ifdef HAS_CLOUDS1 vec4 clouds1 = m_Clouds1Glow; clouds1.a *= texture2D(m_Clouds1AlphaMap, clouds1Coord).r; + #ifdef HAS_CLOUDS1_NORMAL + vec3 normal1 = texture2D(m_Clouds1NormalMap, clouds1Coord).xyz; + normal1 = normal1 * 2.0 - 1.0; + float relief1 = clamp(0.90 + normal1.z * 0.15, 0.75, 1.15); + clouds1.rgb *= relief1; + #endif color = mixColors(color, clouds1); #endif gl_FragColor = color; -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome60/dome60.frag b/SkyLibrary/src/main/resources/Shaders/skies/dome60/dome60.frag index b65af7f..8db0101 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome60/dome60.frag +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome60/dome60.frag @@ -1,31 +1,7 @@ -/* - Copyright (c) 2014-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * fragment shader used by dome60.j3md + * generated fragment shader used by dome60.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec4 m_ClearColor; @@ -81,7 +57,8 @@ vec4 mixColors(vec4 color0, vec4 color1) { float a0 = color0.a * (1.0 - color1.a); result.a = a0 + color1.a; if (result.a > 0.0) { - result.rgb = (a0 * color0.rgb + color1.a * color1.rgb)/result.a; + result.rgb = (a0 * color0.rgb + color1.a * color1.rgb) + / result.a; } else { result.rgb = vec3(0.0); } @@ -94,14 +71,14 @@ void main() { #else vec4 stars = vec4(0.0); #endif - vec4 objects = vec4(0.0); #ifdef HAS_OBJECT0 if (floor(object0Coord.s) == 0.0 && floor(object0Coord.t) == 0.0) { - objects = m_Object0Color; - objects *= texture2D(m_Object0ColorMap, object0Coord); + vec4 object0 = m_Object0Color; + object0 *= texture2D(m_Object0ColorMap, object0Coord); + objects = object0; } #endif @@ -159,8 +136,7 @@ void main() { clear = mixColors(clear, haze); #endif color = mixColors(color, clear); - // Bright parts of objects shine through the clear areas. color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb) * clear.a; gl_FragColor = color; -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome60/dome60.vert b/SkyLibrary/src/main/resources/Shaders/skies/dome60/dome60.vert index 96bb4ff..76197dd 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome60/dome60.vert +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome60/dome60.vert @@ -1,31 +1,7 @@ -/* - Copyright (c) 2014-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * vertex shader used by dome60.j3md + * generated vertex shader used by dome60.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" attribute vec2 inTexCoord; @@ -122,4 +98,4 @@ void main(){ #endif gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1); -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome60/dome60glow.frag b/SkyLibrary/src/main/resources/Shaders/skies/dome60/dome60glow.frag index 8859501..4f801f7 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome60/dome60glow.frag +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome60/dome60glow.frag @@ -1,69 +1,45 @@ -/* - Copyright (c) 2014-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * fragment shader used by dome60.j3md in its "Glow" technique + * generated glow fragment shader used by dome60.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec4 m_ClearGlow; varying vec2 skyTexCoord; #ifdef HAS_OBJECT0 + uniform vec4 m_Object0Color; uniform sampler2D m_Object0ColorMap; - uniform vec4 m_Object0Glow; varying vec2 object0Coord; #endif #ifdef HAS_OBJECT1 + uniform vec4 m_Object1Color; uniform sampler2D m_Object1ColorMap; - uniform vec4 m_Object1Glow; varying vec2 object1Coord; #endif #ifdef HAS_OBJECT2 + uniform vec4 m_Object2Color; uniform sampler2D m_Object2ColorMap; - uniform vec4 m_Object2Glow; varying vec2 object2Coord; #endif #ifdef HAS_OBJECT3 + uniform vec4 m_Object3Color; uniform sampler2D m_Object3ColorMap; - uniform vec4 m_Object3Glow; varying vec2 object3Coord; #endif #ifdef HAS_OBJECT4 + uniform vec4 m_Object4Color; uniform sampler2D m_Object4ColorMap; - uniform vec4 m_Object4Glow; varying vec2 object4Coord; #endif #ifdef HAS_OBJECT5 + uniform vec4 m_Object5Color; uniform sampler2D m_Object5ColorMap; - uniform vec4 m_Object5Glow; varying vec2 object5Coord; #endif @@ -77,7 +53,8 @@ vec4 mixColors(vec4 color0, vec4 color1) { float a0 = color0.a * (1.0 - color1.a); result.a = a0 + color1.a; if (result.a > 0.0) { - result.rgb = (a0 * color0.rgb + color1.a * color1.rgb)/result.a; + result.rgb = (a0 * color0.rgb + color1.a * color1.rgb) + / result.a; } else { result.rgb = vec3(0.0); } @@ -86,14 +63,14 @@ vec4 mixColors(vec4 color0, vec4 color1) { void main() { vec4 stars = vec4(0.0); - vec4 objects = vec4(0.0); #ifdef HAS_OBJECT0 if (floor(object0Coord.s) == 0.0 && floor(object0Coord.t) == 0.0) { - objects = m_Object0Glow; - objects *= texture2D(m_Object0ColorMap, object0Coord); + vec4 object0 = m_Object0Glow; + object0 *= texture2D(m_Object0ColorMap, object0Coord); + objects = object0; } #endif @@ -151,8 +128,7 @@ void main() { clear = mixColors(clear, haze); #endif color = mixColors(color, clear); - // Bright parts of objects shine through the clear areas. color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb) * clear.a; gl_FragColor = color; -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome66/dome66.frag b/SkyLibrary/src/main/resources/Shaders/skies/dome66/dome66.frag index f2a4b0a..7ae11f5 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome66/dome66.frag +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome66/dome66.frag @@ -1,31 +1,7 @@ -/* - Copyright (c) 2014-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * fragment shader used by dome66.j3md + * generated fragment shader used by dome66.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec4 m_ClearColor; @@ -81,43 +57,62 @@ varying vec2 skyTexCoord; uniform vec4 m_Clouds0Color; varying vec2 clouds0Coord; #endif +#ifdef HAS_CLOUDS0_NORMAL + uniform sampler2D m_Clouds0NormalMap; +#endif #ifdef HAS_CLOUDS1 uniform sampler2D m_Clouds1AlphaMap; uniform vec4 m_Clouds1Color; varying vec2 clouds1Coord; #endif +#ifdef HAS_CLOUDS1_NORMAL + uniform sampler2D m_Clouds1NormalMap; +#endif #ifdef HAS_CLOUDS2 uniform sampler2D m_Clouds2AlphaMap; uniform vec4 m_Clouds2Color; varying vec2 clouds2Coord; #endif +#ifdef HAS_CLOUDS2_NORMAL + uniform sampler2D m_Clouds2NormalMap; +#endif #ifdef HAS_CLOUDS3 uniform sampler2D m_Clouds3AlphaMap; uniform vec4 m_Clouds3Color; varying vec2 clouds3Coord; #endif +#ifdef HAS_CLOUDS3_NORMAL + uniform sampler2D m_Clouds3NormalMap; +#endif #ifdef HAS_CLOUDS4 uniform sampler2D m_Clouds4AlphaMap; uniform vec4 m_Clouds4Color; varying vec2 clouds4Coord; #endif +#ifdef HAS_CLOUDS4_NORMAL + uniform sampler2D m_Clouds4NormalMap; +#endif #ifdef HAS_CLOUDS5 uniform sampler2D m_Clouds5AlphaMap; uniform vec4 m_Clouds5Color; varying vec2 clouds5Coord; #endif +#ifdef HAS_CLOUDS5_NORMAL + uniform sampler2D m_Clouds5NormalMap; +#endif vec4 mixColors(vec4 color0, vec4 color1) { vec4 result; float a0 = color0.a * (1.0 - color1.a); result.a = a0 + color1.a; if (result.a > 0.0) { - result.rgb = (a0 * color0.rgb + color1.a * color1.rgb)/result.a; + result.rgb = (a0 * color0.rgb + color1.a * color1.rgb) + / result.a; } else { result.rgb = vec3(0.0); } @@ -130,14 +125,14 @@ void main() { #else vec4 stars = vec4(0.0); #endif - vec4 objects = vec4(0.0); #ifdef HAS_OBJECT0 if (floor(object0Coord.s) == 0.0 && floor(object0Coord.t) == 0.0) { - objects = m_Object0Color; - objects *= texture2D(m_Object0ColorMap, object0Coord); + vec4 object0 = m_Object0Color; + object0 *= texture2D(m_Object0ColorMap, object0Coord); + objects = object0; } #endif @@ -195,44 +190,79 @@ void main() { clear = mixColors(clear, haze); #endif color = mixColors(color, clear); - // Bright parts of objects shine through the clear areas. color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb) * clear.a; #ifdef HAS_CLOUDS0 vec4 clouds0 = m_Clouds0Color; clouds0.a *= texture2D(m_Clouds0AlphaMap, clouds0Coord).r; + #ifdef HAS_CLOUDS0_NORMAL + vec3 normal0 = texture2D(m_Clouds0NormalMap, clouds0Coord).xyz; + normal0 = normal0 * 2.0 - 1.0; + float relief0 = clamp(0.90 + normal0.z * 0.15, 0.75, 1.15); + clouds0.rgb *= relief0; + #endif color = mixColors(color, clouds0); #endif #ifdef HAS_CLOUDS1 vec4 clouds1 = m_Clouds1Color; clouds1.a *= texture2D(m_Clouds1AlphaMap, clouds1Coord).r; + #ifdef HAS_CLOUDS1_NORMAL + vec3 normal1 = texture2D(m_Clouds1NormalMap, clouds1Coord).xyz; + normal1 = normal1 * 2.0 - 1.0; + float relief1 = clamp(0.90 + normal1.z * 0.15, 0.75, 1.15); + clouds1.rgb *= relief1; + #endif color = mixColors(color, clouds1); #endif #ifdef HAS_CLOUDS2 vec4 clouds2 = m_Clouds2Color; clouds2.a *= texture2D(m_Clouds2AlphaMap, clouds2Coord).r; + #ifdef HAS_CLOUDS2_NORMAL + vec3 normal2 = texture2D(m_Clouds2NormalMap, clouds2Coord).xyz; + normal2 = normal2 * 2.0 - 1.0; + float relief2 = clamp(0.90 + normal2.z * 0.15, 0.75, 1.15); + clouds2.rgb *= relief2; + #endif color = mixColors(color, clouds2); #endif #ifdef HAS_CLOUDS3 vec4 clouds3 = m_Clouds3Color; clouds3.a *= texture2D(m_Clouds3AlphaMap, clouds3Coord).r; + #ifdef HAS_CLOUDS3_NORMAL + vec3 normal3 = texture2D(m_Clouds3NormalMap, clouds3Coord).xyz; + normal3 = normal3 * 2.0 - 1.0; + float relief3 = clamp(0.90 + normal3.z * 0.15, 0.75, 1.15); + clouds3.rgb *= relief3; + #endif color = mixColors(color, clouds3); #endif #ifdef HAS_CLOUDS4 vec4 clouds4 = m_Clouds4Color; clouds4.a *= texture2D(m_Clouds4AlphaMap, clouds4Coord).r; + #ifdef HAS_CLOUDS4_NORMAL + vec3 normal4 = texture2D(m_Clouds4NormalMap, clouds4Coord).xyz; + normal4 = normal4 * 2.0 - 1.0; + float relief4 = clamp(0.90 + normal4.z * 0.15, 0.75, 1.15); + clouds4.rgb *= relief4; + #endif color = mixColors(color, clouds4); #endif #ifdef HAS_CLOUDS5 vec4 clouds5 = m_Clouds5Color; clouds5.a *= texture2D(m_Clouds5AlphaMap, clouds5Coord).r; + #ifdef HAS_CLOUDS5_NORMAL + vec3 normal5 = texture2D(m_Clouds5NormalMap, clouds5Coord).xyz; + normal5 = normal5 * 2.0 - 1.0; + float relief5 = clamp(0.90 + normal5.z * 0.15, 0.75, 1.15); + clouds5.rgb *= relief5; + #endif color = mixColors(color, clouds5); #endif gl_FragColor = color; -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome66/dome66.vert b/SkyLibrary/src/main/resources/Shaders/skies/dome66/dome66.vert index 3619db1..de5d9f9 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome66/dome66.vert +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome66/dome66.vert @@ -1,31 +1,7 @@ -/* - Copyright (c) 2014-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * vertex shader used by dome66.j3md + * generated vertex shader used by dome66.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" attribute vec2 inTexCoord; @@ -121,8 +97,7 @@ varying vec2 skyTexCoord; void main(){ skyTexCoord = inTexCoord; /* - * The following cloud texture coordinate calculations must be kept - * consistent with those in SkyMaterial.getTransparency(int,Vector2f) . + * Keep cloud coordinates consistent with material sampling. */ #ifdef HAS_CLOUDS0 clouds0Coord = inTexCoord * m_Clouds0Scale + m_Clouds0Offset; @@ -142,7 +117,6 @@ void main(){ #ifdef HAS_CLOUDS5 clouds5Coord = inTexCoord * m_Clouds5Scale + m_Clouds5Offset; #endif - #ifdef HAS_OBJECT0 object0Offset = inTexCoord - m_Object0Center; object0Coord.x = dot(m_Object0TransformU, object0Offset); @@ -181,4 +155,4 @@ void main(){ #endif gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1); -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Shaders/skies/dome66/dome66glow.frag b/SkyLibrary/src/main/resources/Shaders/skies/dome66/dome66glow.frag index 4634238..4cde18d 100644 --- a/SkyLibrary/src/main/resources/Shaders/skies/dome66/dome66glow.frag +++ b/SkyLibrary/src/main/resources/Shaders/skies/dome66/dome66glow.frag @@ -1,69 +1,45 @@ -/* - Copyright (c) 2014-2022, Stephen Gold - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +/* Generated by :SkyAssets:skyMaterials. Do not edit by hand. */ /* - * fragment shader used by dome66.j3md in its "Glow" technique + * generated glow fragment shader used by dome66.j3md */ #import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec4 m_ClearGlow; varying vec2 skyTexCoord; #ifdef HAS_OBJECT0 + uniform vec4 m_Object0Color; uniform sampler2D m_Object0ColorMap; - uniform vec4 m_Object0Glow; varying vec2 object0Coord; #endif #ifdef HAS_OBJECT1 + uniform vec4 m_Object1Color; uniform sampler2D m_Object1ColorMap; - uniform vec4 m_Object1Glow; varying vec2 object1Coord; #endif #ifdef HAS_OBJECT2 + uniform vec4 m_Object2Color; uniform sampler2D m_Object2ColorMap; - uniform vec4 m_Object2Glow; varying vec2 object2Coord; #endif #ifdef HAS_OBJECT3 + uniform vec4 m_Object3Color; uniform sampler2D m_Object3ColorMap; - uniform vec4 m_Object3Glow; varying vec2 object3Coord; #endif #ifdef HAS_OBJECT4 + uniform vec4 m_Object4Color; uniform sampler2D m_Object4ColorMap; - uniform vec4 m_Object4Glow; varying vec2 object4Coord; #endif #ifdef HAS_OBJECT5 + uniform vec4 m_Object5Color; uniform sampler2D m_Object5ColorMap; - uniform vec4 m_Object5Glow; varying vec2 object5Coord; #endif @@ -77,43 +53,62 @@ varying vec2 skyTexCoord; uniform vec4 m_Clouds0Glow; varying vec2 clouds0Coord; #endif +#ifdef HAS_CLOUDS0_NORMAL + uniform sampler2D m_Clouds0NormalMap; +#endif #ifdef HAS_CLOUDS1 uniform sampler2D m_Clouds1AlphaMap; uniform vec4 m_Clouds1Glow; varying vec2 clouds1Coord; #endif +#ifdef HAS_CLOUDS1_NORMAL + uniform sampler2D m_Clouds1NormalMap; +#endif #ifdef HAS_CLOUDS2 uniform sampler2D m_Clouds2AlphaMap; uniform vec4 m_Clouds2Glow; varying vec2 clouds2Coord; #endif +#ifdef HAS_CLOUDS2_NORMAL + uniform sampler2D m_Clouds2NormalMap; +#endif #ifdef HAS_CLOUDS3 uniform sampler2D m_Clouds3AlphaMap; uniform vec4 m_Clouds3Glow; varying vec2 clouds3Coord; #endif +#ifdef HAS_CLOUDS3_NORMAL + uniform sampler2D m_Clouds3NormalMap; +#endif #ifdef HAS_CLOUDS4 uniform sampler2D m_Clouds4AlphaMap; uniform vec4 m_Clouds4Glow; varying vec2 clouds4Coord; #endif +#ifdef HAS_CLOUDS4_NORMAL + uniform sampler2D m_Clouds4NormalMap; +#endif #ifdef HAS_CLOUDS5 uniform sampler2D m_Clouds5AlphaMap; uniform vec4 m_Clouds5Glow; varying vec2 clouds5Coord; #endif +#ifdef HAS_CLOUDS5_NORMAL + uniform sampler2D m_Clouds5NormalMap; +#endif vec4 mixColors(vec4 color0, vec4 color1) { vec4 result; float a0 = color0.a * (1.0 - color1.a); result.a = a0 + color1.a; if (result.a > 0.0) { - result.rgb = (a0 * color0.rgb + color1.a * color1.rgb)/result.a; + result.rgb = (a0 * color0.rgb + color1.a * color1.rgb) + / result.a; } else { result.rgb = vec3(0.0); } @@ -122,14 +117,14 @@ vec4 mixColors(vec4 color0, vec4 color1) { void main() { vec4 stars = vec4(0.0); - vec4 objects = vec4(0.0); #ifdef HAS_OBJECT0 if (floor(object0Coord.s) == 0.0 && floor(object0Coord.t) == 0.0) { - objects = m_Object0Glow; - objects *= texture2D(m_Object0ColorMap, object0Coord); + vec4 object0 = m_Object0Glow; + object0 *= texture2D(m_Object0ColorMap, object0Coord); + objects = object0; } #endif @@ -187,44 +182,79 @@ void main() { clear = mixColors(clear, haze); #endif color = mixColors(color, clear); - // Bright parts of objects shine through the clear areas. color.rgb += objects.rgb * objects.a * (1.0 - clear.rgb) * clear.a; #ifdef HAS_CLOUDS0 vec4 clouds0 = m_Clouds0Glow; clouds0.a *= texture2D(m_Clouds0AlphaMap, clouds0Coord).r; + #ifdef HAS_CLOUDS0_NORMAL + vec3 normal0 = texture2D(m_Clouds0NormalMap, clouds0Coord).xyz; + normal0 = normal0 * 2.0 - 1.0; + float relief0 = clamp(0.90 + normal0.z * 0.15, 0.75, 1.15); + clouds0.rgb *= relief0; + #endif color = mixColors(color, clouds0); #endif #ifdef HAS_CLOUDS1 vec4 clouds1 = m_Clouds1Glow; clouds1.a *= texture2D(m_Clouds1AlphaMap, clouds1Coord).r; + #ifdef HAS_CLOUDS1_NORMAL + vec3 normal1 = texture2D(m_Clouds1NormalMap, clouds1Coord).xyz; + normal1 = normal1 * 2.0 - 1.0; + float relief1 = clamp(0.90 + normal1.z * 0.15, 0.75, 1.15); + clouds1.rgb *= relief1; + #endif color = mixColors(color, clouds1); #endif #ifdef HAS_CLOUDS2 vec4 clouds2 = m_Clouds2Glow; clouds2.a *= texture2D(m_Clouds2AlphaMap, clouds2Coord).r; + #ifdef HAS_CLOUDS2_NORMAL + vec3 normal2 = texture2D(m_Clouds2NormalMap, clouds2Coord).xyz; + normal2 = normal2 * 2.0 - 1.0; + float relief2 = clamp(0.90 + normal2.z * 0.15, 0.75, 1.15); + clouds2.rgb *= relief2; + #endif color = mixColors(color, clouds2); #endif #ifdef HAS_CLOUDS3 vec4 clouds3 = m_Clouds3Glow; clouds3.a *= texture2D(m_Clouds3AlphaMap, clouds3Coord).r; + #ifdef HAS_CLOUDS3_NORMAL + vec3 normal3 = texture2D(m_Clouds3NormalMap, clouds3Coord).xyz; + normal3 = normal3 * 2.0 - 1.0; + float relief3 = clamp(0.90 + normal3.z * 0.15, 0.75, 1.15); + clouds3.rgb *= relief3; + #endif color = mixColors(color, clouds3); #endif #ifdef HAS_CLOUDS4 vec4 clouds4 = m_Clouds4Glow; clouds4.a *= texture2D(m_Clouds4AlphaMap, clouds4Coord).r; + #ifdef HAS_CLOUDS4_NORMAL + vec3 normal4 = texture2D(m_Clouds4NormalMap, clouds4Coord).xyz; + normal4 = normal4 * 2.0 - 1.0; + float relief4 = clamp(0.90 + normal4.z * 0.15, 0.75, 1.15); + clouds4.rgb *= relief4; + #endif color = mixColors(color, clouds4); #endif #ifdef HAS_CLOUDS5 vec4 clouds5 = m_Clouds5Glow; clouds5.a *= texture2D(m_Clouds5AlphaMap, clouds5Coord).r; + #ifdef HAS_CLOUDS5_NORMAL + vec3 normal5 = texture2D(m_Clouds5NormalMap, clouds5Coord).xyz; + normal5 = normal5 * 2.0 - 1.0; + float relief5 = clamp(0.90 + normal5.z * 0.15, 0.75, 1.15); + clouds5.rgb *= relief5; + #endif color = mixColors(color, clouds5); #endif gl_FragColor = color; -} \ No newline at end of file +} diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/clear.png b/SkyLibrary/src/main/resources/Textures/skies/clouds/clear.png new file mode 100644 index 0000000..436d443 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/clear.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/fbm.png b/SkyLibrary/src/main/resources/Textures/skies/clouds/fbm.png new file mode 100644 index 0000000..6985f6e Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/fbm.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/overcast.png b/SkyLibrary/src/main/resources/Textures/skies/clouds/overcast.png new file mode 100644 index 0000000..f683305 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/overcast.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_heavy/cloudhat_marble02_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_heavy/cloudhat_marble02_ap.dds new file mode 100644 index 0000000..e13ecd3 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_heavy/cloudhat_marble02_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_heavy/detail1_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_heavy/detail1_nrm.dds new file mode 100644 index 0000000..e1f9f22 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_heavy/detail1_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_heavy/skyhat_raintop_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_heavy/skyhat_raintop_ap.dds new file mode 100644 index 0000000..cc29ae5 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_heavy/skyhat_raintop_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_heavy/skyhat_raintop_n.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_heavy/skyhat_raintop_n.dds new file mode 100644 index 0000000..b6b99b2 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_heavy/skyhat_raintop_n.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/cloudhat_altitude_medium.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/cloudhat_altitude_medium.dds new file mode 100644 index 0000000..c2a45fe Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/cloudhat_altitude_medium.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/cloudhat_altitude_medium_n.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/cloudhat_altitude_medium_n.dds new file mode 100644 index 0000000..a6f79c8 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/cloudhat_altitude_medium_n.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/cloudhat_marble02_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/cloudhat_marble02_ap.dds new file mode 100644 index 0000000..e13ecd3 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/cloudhat_marble02_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/detail1_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/detail1_nrm.dds new file mode 100644 index 0000000..e1f9f22 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/detail1_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/skyhat_cirrocumulus01_alt2.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/skyhat_cirrocumulus01_alt2.dds new file mode 100644 index 0000000..f0f368d Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/skyhat_cirrocumulus01_alt2.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/skyhat_cirrocumulus01_alt_n.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/skyhat_cirrocumulus01_alt_n.dds new file mode 100644 index 0000000..d3b6c00 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_light/skyhat_cirrocumulus01_alt_n.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_med/cloudhat_altitude_medium.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_med/cloudhat_altitude_medium.dds new file mode 100644 index 0000000..c2a45fe Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_med/cloudhat_altitude_medium.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_med/cloudhat_altitude_medium_n.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_med/cloudhat_altitude_medium_n.dds new file mode 100644 index 0000000..a6f79c8 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_med/cloudhat_altitude_medium_n.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_med/cloudhat_marble02_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_med/cloudhat_marble02_ap.dds new file mode 100644 index 0000000..e13ecd3 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_med/cloudhat_marble02_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_med/detail1_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_med/detail1_nrm.dds new file mode 100644 index 0000000..e1f9f22 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/alt_med/detail1_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cirrus/cloudhat_cirrus_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cirrus/cloudhat_cirrus_ap.dds new file mode 100644 index 0000000..6e8c9b8 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cirrus/cloudhat_cirrus_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cirrus/cloudhat_cirrus_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cirrus/cloudhat_cirrus_nrm.dds new file mode 100644 index 0000000..fb80c64 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cirrus/cloudhat_cirrus_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/clear/cloudhat_marble02_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/clear/cloudhat_marble02_ap.dds new file mode 100644 index 0000000..e13ecd3 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/clear/cloudhat_marble02_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/clear/detail1_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/clear/detail1_nrm.dds new file mode 100644 index 0000000..e1f9f22 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/clear/detail1_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/clear/new_skyhat_clear01_bot_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/clear/new_skyhat_clear01_bot_ap.dds new file mode 100644 index 0000000..3022ca0 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/clear/new_skyhat_clear01_bot_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/clear/new_skyhat_clear01_bot_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/clear/new_skyhat_clear01_bot_nrm.dds new file mode 100644 index 0000000..ac9ba3e Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/clear/new_skyhat_clear01_bot_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloud-weather-presets.json b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloud-weather-presets.json new file mode 100644 index 0000000..ba8058f --- /dev/null +++ b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloud-weather-presets.json @@ -0,0 +1,876 @@ +{ + "schema": "dev.takesome.sky.cloudPresets.v1", + "purpose": "Resource-side database for cloud weather presets, DDS assets, generated dome profiles, and recovered settings preserved after material/shader autogen.", + "resourceRoot": "Textures/skies/clouds/presets", + "runtime": { + "transitionPolicy": "fade-out current layers, swap alpha/normal/scale/motion while invisible, fade-in target layers", + "defaultTransitionSeconds": 60.0, + "maxCloudLayers": 6, + "normalMapsAreOptional": true, + "normalMapFallback": "If normalMap is null or absent, shader uses legacy alpha-only cloud rendering." + }, + "autogen": { + "gradleTask": ":SkyAssets:skyMaterials", + "source": "SkyAssets/skyMaterials.gradle", + "generatedFamilies": [ + { + "name": "dome02", + "objects": 0, + "clouds": 2 + }, + { + "name": "dome06", + "objects": 0, + "clouds": 6 + }, + { + "name": "dome20", + "objects": 2, + "clouds": 0 + }, + { + "name": "dome22", + "objects": 2, + "clouds": 2 + }, + { + "name": "dome60", + "objects": 6, + "clouds": 0 + }, + { + "name": "dome66", + "objects": 6, + "clouds": 6 + } + ], + "recoveredSettings": { + "materialParameters": [ + "ClearColor", + "ClearGlow", + "TopCoord", + "StarsColorMap", + "HazeColor", + "HazeGlow", + "HazeAlphaMap" + ], + "objectParametersPerLayer": [ + "ObjectNColor", + "ObjectNGlow", + "ObjectNColorMap", + "ObjectNCenter", + "ObjectNTransformU", + "ObjectNTransformV" + ], + "cloudParametersPerLayer": [ + "CloudsNColor", + "CloudsNGlow", + "CloudsNScale", + "CloudsNAlphaMap", + "CloudsNNormalMap", + "CloudsNOffset" + ], + "shaderDefines": [ + "HAS_STARS", + "HAS_OBJECTN", + "HAS_CLOUDSN", + "HAS_CLOUDSN_NORMAL", + "HAS_HAZE" + ], + "cloudUvContract": "cloudsNCoord = inTexCoord * m_CloudsNScale + m_CloudsNOffset", + "blendFunction": "premultiplied alpha-style mixColors(color0, color1) preserved from legacy dome shaders", + "normalRelief": "cloud.rgb *= clamp(0.90 + normal.z * 0.15, 0.75, 1.15)" + } + }, + "managedPresets": { + "CLEAR": { + "description": "No visible clouds; uses the legacy clear PNG fallback.", + "layers": [ + { + "alphaMap": "Textures/skies/clouds/clear.png", + "normalMap": null, + "opacity": 0.0, + "scale": 1.0, + "uRate": 0.0, + "vRate": 0.0 + } + ] + }, + "FAIR": { + "description": "Legacy procedural fair-weather setup using FBM PNG.", + "layers": [ + { + "alphaMap": "Textures/skies/clouds/fbm.png", + "normalMap": null, + "opacity": 0.35, + "scale": 1.5, + "uRate": -0.0005, + "vRate": 0.003 + }, + { + "alphaMap": "Textures/skies/clouds/fbm.png", + "normalMap": null, + "opacity": 0.18, + "scale": 2.15, + "uRate": 0.0003, + "vRate": 0.001 + } + ] + }, + "OVERCAST": { + "description": "Legacy overcast fallback with broad opacity and FBM detail.", + "layers": [ + { + "alphaMap": "Textures/skies/clouds/overcast.png", + "normalMap": null, + "opacity": 0.72, + "scale": 1.0, + "uRate": 0.0, + "vRate": 0.0003 + }, + { + "alphaMap": "Textures/skies/clouds/fbm.png", + "normalMap": null, + "opacity": 0.2, + "scale": 2.4, + "uRate": 0.0002, + "vRate": 0.001 + } + ] + }, + "WISPY": { + "description": "Thin high clouds with cirrocumulus plus wisps.", + "layers": [ + { + "alphaMap": "Textures/skies/clouds/presets/wispy/skyhat_cirrocumulus01_ap.dds", + "normalMap": "Textures/skies/clouds/presets/wispy/skyhat_cirrocumulus01_nrm.dds", + "opacity": 0.28, + "scale": 1.3, + "uRate": 0.0001, + "vRate": 0.0012 + }, + { + "alphaMap": "Textures/skies/clouds/presets/wispy/wisps_ap.dds", + "normalMap": "Textures/skies/clouds/presets/wispy/wisps_nrm.dds", + "opacity": 0.16, + "scale": 2.2, + "uRate": -0.0002, + "vRate": 0.002 + } + ] + }, + "CLOUDY": { + "description": "Medium coverage with detail modulation.", + "layers": [ + { + "alphaMap": "Textures/skies/clouds/presets/cloudy/trialap.dds", + "normalMap": "Textures/skies/clouds/presets/cloudy/trialn.dds", + "opacity": 0.52, + "scale": 1.2, + "uRate": -0.0003, + "vRate": 0.0018 + }, + { + "alphaMap": "Textures/skies/clouds/presets/cloudy/cloudhat_marble02_ap.dds", + "normalMap": "Textures/skies/clouds/presets/cloudy/detail1_nrm.dds", + "opacity": 0.22, + "scale": 3.0, + "uRate": 0.0004, + "vRate": 0.0024 + } + ] + }, + "RAIN": { + "description": "Rain clouds with cloudy base and fast moving detail layer.", + "layers": [ + { + "alphaMap": "Textures/skies/clouds/presets/rain/skyhat_rain02_ap.dds", + "normalMap": "Textures/skies/clouds/presets/rain/skyhat_rain02_n2.dds", + "opacity": 0.68, + "scale": 1.1, + "uRate": -0.0003, + "vRate": 0.0026 + }, + { + "alphaMap": "Textures/skies/clouds/presets/cloudy/trialap.dds", + "normalMap": "Textures/skies/clouds/presets/cloudy/trialn.dds", + "opacity": 0.35, + "scale": 2.0, + "uRate": 0.0002, + "vRate": 0.0018 + }, + { + "alphaMap": "Textures/skies/clouds/presets/cloudy/cloudhat_marble02_ap.dds", + "normalMap": "Textures/skies/clouds/presets/cloudy/detail1_nrm.dds", + "opacity": 0.18, + "scale": 3.5, + "uRate": 0.0004, + "vRate": 0.003 + } + ] + }, + "STORM": { + "description": "Heavy storm front, rain veil, and high-frequency detail.", + "layers": [ + { + "alphaMap": "Textures/skies/clouds/presets/storm/stormclouds_ap.dds", + "normalMap": "Textures/skies/clouds/presets/storm/stormclouds_nrm.dds", + "opacity": 0.85, + "scale": 1.0, + "uRate": -0.0004, + "vRate": 0.0032 + }, + { + "alphaMap": "Textures/skies/clouds/presets/storm/skyhat_rain02_ap.dds", + "normalMap": "Textures/skies/clouds/presets/storm/skyhat_rain02_n2.dds", + "opacity": 0.42, + "scale": 1.8, + "uRate": 0.0002, + "vRate": 0.0026 + }, + { + "alphaMap": "Textures/skies/clouds/presets/storm/cloudhat_marble02_ap.dds", + "normalMap": "Textures/skies/clouds/presets/storm/detail1_nrm.dds", + "opacity": 0.26, + "scale": 4.0, + "uRate": 0.0005, + "vRate": 0.0035 + } + ] + }, + "NIMBUS": { + "description": "Nimbus coverage with detail modulation.", + "layers": [ + { + "alphaMap": "Textures/skies/clouds/presets/nimbus/final_nimbusclouds_ap.dds", + "normalMap": "Textures/skies/clouds/presets/nimbus/final_nimbusclouds_n.dds", + "opacity": 0.76, + "scale": 1.1, + "uRate": -0.0004, + "vRate": 0.002 + }, + { + "alphaMap": "Textures/skies/clouds/presets/nimbus/cloudhat_marble02_ap.dds", + "normalMap": "Textures/skies/clouds/presets/nimbus/detail1_nrm.dds", + "opacity": 0.22, + "scale": 3.5, + "uRate": 0.0003, + "vRate": 0.0024 + } + ] + } + }, + "rawBundles": [ + { + "name": "alt_heavy", + "alphaMaps": [ + "Textures/skies/clouds/presets/alt_heavy/cloudhat_marble02_ap.dds", + "Textures/skies/clouds/presets/alt_heavy/skyhat_raintop_ap.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/alt_heavy/detail1_nrm.dds", + "Textures/skies/clouds/presets/alt_heavy/skyhat_raintop_n.dds" + ] + }, + { + "name": "alt_light", + "alphaMaps": [ + "Textures/skies/clouds/presets/alt_light/cloudhat_altitude_medium.dds", + "Textures/skies/clouds/presets/alt_light/cloudhat_marble02_ap.dds", + "Textures/skies/clouds/presets/alt_light/skyhat_cirrocumulus01_alt2.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/alt_light/cloudhat_altitude_medium_n.dds", + "Textures/skies/clouds/presets/alt_light/detail1_nrm.dds", + "Textures/skies/clouds/presets/alt_light/skyhat_cirrocumulus01_alt_n.dds" + ] + }, + { + "name": "alt_med", + "alphaMaps": [ + "Textures/skies/clouds/presets/alt_med/cloudhat_altitude_medium.dds", + "Textures/skies/clouds/presets/alt_med/cloudhat_marble02_ap.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/alt_med/cloudhat_altitude_medium_n.dds", + "Textures/skies/clouds/presets/alt_med/detail1_nrm.dds" + ] + }, + { + "name": "cirrus", + "alphaMaps": [ + "Textures/skies/clouds/presets/cirrus/cloudhat_cirrus_ap.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/cirrus/cloudhat_cirrus_nrm.dds" + ] + }, + { + "name": "clear", + "alphaMaps": [ + "Textures/skies/clouds/presets/clear/cloudhat_marble02_ap.dds", + "Textures/skies/clouds/presets/clear/new_skyhat_clear01_bot_ap.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/clear/detail1_nrm.dds", + "Textures/skies/clouds/presets/clear/new_skyhat_clear01_bot_nrm.dds" + ] + }, + { + "name": "cloudy", + "alphaMaps": [ + "Textures/skies/clouds/presets/cloudy/cloudhat_marble02_ap.dds", + "Textures/skies/clouds/presets/cloudy/trialap.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/cloudy/detail1_nrm.dds", + "Textures/skies/clouds/presets/cloudy/trialn.dds" + ] + }, + { + "name": "contrails", + "alphaMaps": [ + "Textures/skies/clouds/presets/contrails/contrails_ap3.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/contrails/contrails_nrm3.dds" + ] + }, + { + "name": "horizon", + "alphaMaps": [ + "Textures/skies/clouds/presets/horizon/cloudhat_marble02_ap.dds", + "Textures/skies/clouds/presets/horizon/stripeyclouds_ap.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/horizon/detail1_nrm.dds", + "Textures/skies/clouds/presets/horizon/stripeyclouds_nrm.dds" + ] + }, + { + "name": "nimbus", + "alphaMaps": [ + "Textures/skies/clouds/presets/nimbus/cloudhat_marble02_ap.dds", + "Textures/skies/clouds/presets/nimbus/final_nimbusclouds_ap.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/nimbus/detail1_nrm.dds", + "Textures/skies/clouds/presets/nimbus/final_nimbusclouds_n.dds" + ] + }, + { + "name": "puffs", + "alphaMaps": [ + "Textures/skies/clouds/presets/puffs/cloudhat_marble02_ap.dds", + "Textures/skies/clouds/presets/puffs/cloudhat_puffs_ap.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/puffs/cloudhat_puffs_nrm.dds", + "Textures/skies/clouds/presets/puffs/detail1_nrm.dds" + ] + }, + { + "name": "rain", + "alphaMaps": [ + "Textures/skies/clouds/presets/rain/skyhat_rain02_ap.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/rain/skyhat_rain02_n2.dds" + ] + }, + { + "name": "showers", + "alphaMaps": [ + "Textures/skies/clouds/presets/showers/skyhat_showers01_ap.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/showers/skyhat_showers01_n.dds" + ] + }, + { + "name": "storm", + "alphaMaps": [ + "Textures/skies/clouds/presets/storm/cloudhat_marble02_ap.dds", + "Textures/skies/clouds/presets/storm/skyhat_rain02_ap.dds", + "Textures/skies/clouds/presets/storm/stormclouds_ap.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/storm/detail1_nrm.dds", + "Textures/skies/clouds/presets/storm/skyhat_rain02_n2.dds", + "Textures/skies/clouds/presets/storm/stormclouds_nrm.dds" + ] + }, + { + "name": "test", + "alphaMaps": [ + "Textures/skies/clouds/presets/test/cloudhat_marble02_ap.dds", + "Textures/skies/clouds/presets/test/testwritting.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/test/detail1_nrm.dds", + "Textures/skies/clouds/presets/test/testwritting_nrm.dds" + ] + }, + { + "name": "wispy", + "alphaMaps": [ + "Textures/skies/clouds/presets/wispy/skyhat_cirrocumulus01_ap.dds", + "Textures/skies/clouds/presets/wispy/wisps_ap.dds" + ], + "normalMaps": [ + "Textures/skies/clouds/presets/wispy/skyhat_cirrocumulus01_nrm.dds", + "Textures/skies/clouds/presets/wispy/wisps_nrm.dds" + ] + } + ], + "inventory": [ + { + "path": "Textures/skies/clouds/presets/alt_heavy/cloudhat_marble02_ap.dds", + "folder": "alt_heavy", + "file": "cloudhat_marble02_ap.dds", + "role": "alpha", + "bytes": 43816, + "sha256": "021a1ee4ce81336a794a3a01ca94a0fa5c1682c2d01d057d4452389be8c8b69b" + }, + { + "path": "Textures/skies/clouds/presets/alt_heavy/detail1_nrm.dds", + "folder": "alt_heavy", + "file": "detail1_nrm.dds", + "role": "normal", + "bytes": 2856, + "sha256": "d0254ef1fb973e7ce01eb0fdf49025d68725e0ac2eaf63234bbc2a8fa7873420" + }, + { + "path": "Textures/skies/clouds/presets/alt_heavy/skyhat_raintop_ap.dds", + "folder": "alt_heavy", + "file": "skyhat_raintop_ap.dds", + "role": "alpha", + "bytes": 699176, + "sha256": "c21feac428c8badd412dbed593098bebc60af63029d5c8dc44a5125499508a1e" + }, + { + "path": "Textures/skies/clouds/presets/alt_heavy/skyhat_raintop_n.dds", + "folder": "alt_heavy", + "file": "skyhat_raintop_n.dds", + "role": "normal", + "bytes": 1398224, + "sha256": "d2f6c8b0f7be07ca616d02128a6a1a6a34db61cde92c73f2a39819e837fcd7cc" + }, + { + "path": "Textures/skies/clouds/presets/alt_light/cloudhat_altitude_medium.dds", + "folder": "alt_light", + "file": "cloudhat_altitude_medium.dds", + "role": "alpha", + "bytes": 699176, + "sha256": "faebbdef3e10f79f6263f5fbf12adac69b15ad54de0c17a599cc7ae9f9855f19" + }, + { + "path": "Textures/skies/clouds/presets/alt_light/cloudhat_altitude_medium_n.dds", + "folder": "alt_light", + "file": "cloudhat_altitude_medium_n.dds", + "role": "normal", + "bytes": 1398224, + "sha256": "49505724446ee3aa4bfeee09f66d774f0a8f5807ce6786e564070ab35c82a90e" + }, + { + "path": "Textures/skies/clouds/presets/alt_light/cloudhat_marble02_ap.dds", + "folder": "alt_light", + "file": "cloudhat_marble02_ap.dds", + "role": "alpha", + "bytes": 43816, + "sha256": "021a1ee4ce81336a794a3a01ca94a0fa5c1682c2d01d057d4452389be8c8b69b" + }, + { + "path": "Textures/skies/clouds/presets/alt_light/detail1_nrm.dds", + "folder": "alt_light", + "file": "detail1_nrm.dds", + "role": "normal", + "bytes": 2856, + "sha256": "d0254ef1fb973e7ce01eb0fdf49025d68725e0ac2eaf63234bbc2a8fa7873420" + }, + { + "path": "Textures/skies/clouds/presets/alt_light/skyhat_cirrocumulus01_alt2.dds", + "folder": "alt_light", + "file": "skyhat_cirrocumulus01_alt2.dds", + "role": "alpha", + "bytes": 699176, + "sha256": "f2cbfd755fef940385b06c21126b1f83e9257ef5a325f0c5297b2d6dc3083e1a" + }, + { + "path": "Textures/skies/clouds/presets/alt_light/skyhat_cirrocumulus01_alt_n.dds", + "folder": "alt_light", + "file": "skyhat_cirrocumulus01_alt_n.dds", + "role": "normal", + "bytes": 1398224, + "sha256": "e47be0bfdaf67758f0a8981f48795a1d125ef14c0f59662b027577a250a3a7eb" + }, + { + "path": "Textures/skies/clouds/presets/alt_med/cloudhat_altitude_medium.dds", + "folder": "alt_med", + "file": "cloudhat_altitude_medium.dds", + "role": "alpha", + "bytes": 699176, + "sha256": "faebbdef3e10f79f6263f5fbf12adac69b15ad54de0c17a599cc7ae9f9855f19" + }, + { + "path": "Textures/skies/clouds/presets/alt_med/cloudhat_altitude_medium_n.dds", + "folder": "alt_med", + "file": "cloudhat_altitude_medium_n.dds", + "role": "normal", + "bytes": 1398224, + "sha256": "49505724446ee3aa4bfeee09f66d774f0a8f5807ce6786e564070ab35c82a90e" + }, + { + "path": "Textures/skies/clouds/presets/alt_med/cloudhat_marble02_ap.dds", + "folder": "alt_med", + "file": "cloudhat_marble02_ap.dds", + "role": "alpha", + "bytes": 43816, + "sha256": "021a1ee4ce81336a794a3a01ca94a0fa5c1682c2d01d057d4452389be8c8b69b" + }, + { + "path": "Textures/skies/clouds/presets/alt_med/detail1_nrm.dds", + "folder": "alt_med", + "file": "detail1_nrm.dds", + "role": "normal", + "bytes": 2856, + "sha256": "d0254ef1fb973e7ce01eb0fdf49025d68725e0ac2eaf63234bbc2a8fa7873420" + }, + { + "path": "Textures/skies/clouds/presets/cirrus/cloudhat_cirrus_ap.dds", + "folder": "cirrus", + "file": "cloudhat_cirrus_ap.dds", + "role": "alpha", + "bytes": 699176, + "sha256": "94132ddb03e0a587275f1bdd939f7b1da0473582199e5591d52649d56221c773" + }, + { + "path": "Textures/skies/clouds/presets/cirrus/cloudhat_cirrus_nrm.dds", + "folder": "cirrus", + "file": "cloudhat_cirrus_nrm.dds", + "role": "normal", + "bytes": 1398224, + "sha256": "184a100769a139275fe9606b05fe9cce8ff702cc371580bd4e5eb5091509330f" + }, + { + "path": "Textures/skies/clouds/presets/clear/cloudhat_marble02_ap.dds", + "folder": "clear", + "file": "cloudhat_marble02_ap.dds", + "role": "alpha", + "bytes": 43816, + "sha256": "021a1ee4ce81336a794a3a01ca94a0fa5c1682c2d01d057d4452389be8c8b69b" + }, + { + "path": "Textures/skies/clouds/presets/clear/detail1_nrm.dds", + "folder": "clear", + "file": "detail1_nrm.dds", + "role": "normal", + "bytes": 2856, + "sha256": "d0254ef1fb973e7ce01eb0fdf49025d68725e0ac2eaf63234bbc2a8fa7873420" + }, + { + "path": "Textures/skies/clouds/presets/clear/new_skyhat_clear01_bot_ap.dds", + "folder": "clear", + "file": "new_skyhat_clear01_bot_ap.dds", + "role": "alpha", + "bytes": 699168, + "sha256": "48d1582d5239fae2f894e8e2df428790cba89a4a710bc43c8dd18c4db637652d" + }, + { + "path": "Textures/skies/clouds/presets/clear/new_skyhat_clear01_bot_nrm.dds", + "folder": "clear", + "file": "new_skyhat_clear01_bot_nrm.dds", + "role": "normal", + "bytes": 1398208, + "sha256": "d79765e22aeeb9170873e0101938962ec55d73fb084ac7d3cfd498caa786cffc" + }, + { + "path": "Textures/skies/clouds/presets/cloudy/cloudhat_marble02_ap.dds", + "folder": "cloudy", + "file": "cloudhat_marble02_ap.dds", + "role": "alpha", + "bytes": 43816, + "sha256": "021a1ee4ce81336a794a3a01ca94a0fa5c1682c2d01d057d4452389be8c8b69b" + }, + { + "path": "Textures/skies/clouds/presets/cloudy/detail1_nrm.dds", + "folder": "cloudy", + "file": "detail1_nrm.dds", + "role": "normal", + "bytes": 2856, + "sha256": "d0254ef1fb973e7ce01eb0fdf49025d68725e0ac2eaf63234bbc2a8fa7873420" + }, + { + "path": "Textures/skies/clouds/presets/cloudy/trialap.dds", + "folder": "cloudy", + "file": "trialap.dds", + "role": "alpha", + "bytes": 699168, + "sha256": "f41873b5d7a8ad4fda526324d3f5e8aa36a28307da34fd6c97b8f59794a408fe" + }, + { + "path": "Textures/skies/clouds/presets/cloudy/trialn.dds", + "folder": "cloudy", + "file": "trialn.dds", + "role": "normal", + "bytes": 1398208, + "sha256": "a1653a298b96194a1e6d3924866cad9a1a0e66746c7fb34749cc14e4127ee88d" + }, + { + "path": "Textures/skies/clouds/presets/contrails/contrails_ap3.dds", + "folder": "contrails", + "file": "contrails_ap3.dds", + "role": "alpha", + "bytes": 174848, + "sha256": "20f8d0d8375dcf850e2b8f41f961e964d9dff6db2ac49d8f69d49204654e06ad" + }, + { + "path": "Textures/skies/clouds/presets/contrails/contrails_nrm3.dds", + "folder": "contrails", + "file": "contrails_nrm3.dds", + "role": "normal", + "bytes": 174848, + "sha256": "37fc7a673b4fd00e31b25b1af426ab69a8edc97ae9a2b20f2f22bdafeb0c32a7" + }, + { + "path": "Textures/skies/clouds/presets/horizon/cloudhat_marble02_ap.dds", + "folder": "horizon", + "file": "cloudhat_marble02_ap.dds", + "role": "alpha", + "bytes": 43816, + "sha256": "021a1ee4ce81336a794a3a01ca94a0fa5c1682c2d01d057d4452389be8c8b69b" + }, + { + "path": "Textures/skies/clouds/presets/horizon/detail1_nrm.dds", + "folder": "horizon", + "file": "detail1_nrm.dds", + "role": "normal", + "bytes": 2856, + "sha256": "d0254ef1fb973e7ce01eb0fdf49025d68725e0ac2eaf63234bbc2a8fa7873420" + }, + { + "path": "Textures/skies/clouds/presets/horizon/stripeyclouds_ap.dds", + "folder": "horizon", + "file": "stripeyclouds_ap.dds", + "role": "alpha", + "bytes": 349648, + "sha256": "58e1431115e4b06676af864876114d24b6d84ec5ae0d5f04c16a6a5d92273c31" + }, + { + "path": "Textures/skies/clouds/presets/horizon/stripeyclouds_nrm.dds", + "folder": "horizon", + "file": "stripeyclouds_nrm.dds", + "role": "normal", + "bytes": 699168, + "sha256": "2c980896f6dff8fb469b20d37a3a39c5c4482f6822b1f97b5a000c6778f104fc" + }, + { + "path": "Textures/skies/clouds/presets/nimbus/cloudhat_marble02_ap.dds", + "folder": "nimbus", + "file": "cloudhat_marble02_ap.dds", + "role": "alpha", + "bytes": 43816, + "sha256": "021a1ee4ce81336a794a3a01ca94a0fa5c1682c2d01d057d4452389be8c8b69b" + }, + { + "path": "Textures/skies/clouds/presets/nimbus/detail1_nrm.dds", + "folder": "nimbus", + "file": "detail1_nrm.dds", + "role": "normal", + "bytes": 2856, + "sha256": "d0254ef1fb973e7ce01eb0fdf49025d68725e0ac2eaf63234bbc2a8fa7873420" + }, + { + "path": "Textures/skies/clouds/presets/nimbus/final_nimbusclouds_ap.dds", + "folder": "nimbus", + "file": "final_nimbusclouds_ap.dds", + "role": "alpha", + "bytes": 699168, + "sha256": "8a1f9d840ed720b73886ef63ad089833604e342ee31e26471a545a01c569c98d" + }, + { + "path": "Textures/skies/clouds/presets/nimbus/final_nimbusclouds_n.dds", + "folder": "nimbus", + "file": "final_nimbusclouds_n.dds", + "role": "normal", + "bytes": 1398208, + "sha256": "672c343c91b3b35d27b9572203988a1f76681c03b3581ce05e2c80704c446368" + }, + { + "path": "Textures/skies/clouds/presets/puffs/cloudhat_marble02_ap.dds", + "folder": "puffs", + "file": "cloudhat_marble02_ap.dds", + "role": "alpha", + "bytes": 43816, + "sha256": "021a1ee4ce81336a794a3a01ca94a0fa5c1682c2d01d057d4452389be8c8b69b" + }, + { + "path": "Textures/skies/clouds/presets/puffs/cloudhat_puffs_ap.dds", + "folder": "puffs", + "file": "cloudhat_puffs_ap.dds", + "role": "alpha", + "bytes": 699168, + "sha256": "473db38c19d1558136837bb67d1d338a950e57c942368d5fd01ec7c6c9198702" + }, + { + "path": "Textures/skies/clouds/presets/puffs/cloudhat_puffs_nrm.dds", + "folder": "puffs", + "file": "cloudhat_puffs_nrm.dds", + "role": "normal", + "bytes": 1398208, + "sha256": "1be16cbc09b39d785cc5e7761dd618d12728f0a91a31b2664173f23fa95a97d0" + }, + { + "path": "Textures/skies/clouds/presets/puffs/detail1_nrm.dds", + "folder": "puffs", + "file": "detail1_nrm.dds", + "role": "normal", + "bytes": 2856, + "sha256": "d0254ef1fb973e7ce01eb0fdf49025d68725e0ac2eaf63234bbc2a8fa7873420" + }, + { + "path": "Textures/skies/clouds/presets/rain/skyhat_rain02_ap.dds", + "folder": "rain", + "file": "skyhat_rain02_ap.dds", + "role": "alpha", + "bytes": 699176, + "sha256": "aee1e60ec83fcc0e7b22bbfb8b6157df158de87f4066dc31b4e44cdf5ec902bd" + }, + { + "path": "Textures/skies/clouds/presets/rain/skyhat_rain02_n2.dds", + "folder": "rain", + "file": "skyhat_rain02_n2.dds", + "role": "normal", + "bytes": 1398224, + "sha256": "48804ee4651e8d11852e036e3614ec2a616d31f26bb3f1537a6211ee40f5ce15" + }, + { + "path": "Textures/skies/clouds/presets/showers/skyhat_showers01_ap.dds", + "folder": "showers", + "file": "skyhat_showers01_ap.dds", + "role": "alpha", + "bytes": 699176, + "sha256": "97c87ac12fe799f1b315f489d20f6dd0c8602b13aa6fb90f7aaf4a9541d28cb3" + }, + { + "path": "Textures/skies/clouds/presets/showers/skyhat_showers01_n.dds", + "folder": "showers", + "file": "skyhat_showers01_n.dds", + "role": "normal", + "bytes": 1398224, + "sha256": "53e7349d110f7432c903e1344717190853648dacb25053b26fb17a7c158422b9" + }, + { + "path": "Textures/skies/clouds/presets/storm/cloudhat_marble02_ap.dds", + "folder": "storm", + "file": "cloudhat_marble02_ap.dds", + "role": "alpha", + "bytes": 43816, + "sha256": "021a1ee4ce81336a794a3a01ca94a0fa5c1682c2d01d057d4452389be8c8b69b" + }, + { + "path": "Textures/skies/clouds/presets/storm/detail1_nrm.dds", + "folder": "storm", + "file": "detail1_nrm.dds", + "role": "normal", + "bytes": 2856, + "sha256": "d0254ef1fb973e7ce01eb0fdf49025d68725e0ac2eaf63234bbc2a8fa7873420" + }, + { + "path": "Textures/skies/clouds/presets/storm/skyhat_rain02_ap.dds", + "folder": "storm", + "file": "skyhat_rain02_ap.dds", + "role": "alpha", + "bytes": 699176, + "sha256": "aee1e60ec83fcc0e7b22bbfb8b6157df158de87f4066dc31b4e44cdf5ec902bd" + }, + { + "path": "Textures/skies/clouds/presets/storm/skyhat_rain02_n2.dds", + "folder": "storm", + "file": "skyhat_rain02_n2.dds", + "role": "normal", + "bytes": 1398224, + "sha256": "48804ee4651e8d11852e036e3614ec2a616d31f26bb3f1537a6211ee40f5ce15" + }, + { + "path": "Textures/skies/clouds/presets/storm/stormclouds_ap.dds", + "folder": "storm", + "file": "stormclouds_ap.dds", + "role": "alpha", + "bytes": 349648, + "sha256": "8abd5f25928639f353bfe229824ef174d6fa1f279b6d8d96255c19a581f3d0ba" + }, + { + "path": "Textures/skies/clouds/presets/storm/stormclouds_nrm.dds", + "folder": "storm", + "file": "stormclouds_nrm.dds", + "role": "normal", + "bytes": 699168, + "sha256": "d4e5a6e8bf94b6c0fdd86cee5825464d5903a6d6341c7467b9280491791d4481" + }, + { + "path": "Textures/skies/clouds/presets/test/cloudhat_marble02_ap.dds", + "folder": "test", + "file": "cloudhat_marble02_ap.dds", + "role": "alpha", + "bytes": 43816, + "sha256": "021a1ee4ce81336a794a3a01ca94a0fa5c1682c2d01d057d4452389be8c8b69b" + }, + { + "path": "Textures/skies/clouds/presets/test/detail1_nrm.dds", + "folder": "test", + "file": "detail1_nrm.dds", + "role": "normal", + "bytes": 2856, + "sha256": "d0254ef1fb973e7ce01eb0fdf49025d68725e0ac2eaf63234bbc2a8fa7873420" + }, + { + "path": "Textures/skies/clouds/presets/test/testwritting.dds", + "folder": "test", + "file": "testwritting.dds", + "role": "alpha", + "bytes": 699176, + "sha256": "9163f5d4452f25103ea36c7be0c01bfb14a0bc1b3cc1ebbad61a7c8909c06cb2" + }, + { + "path": "Textures/skies/clouds/presets/test/testwritting_nrm.dds", + "folder": "test", + "file": "testwritting_nrm.dds", + "role": "normal", + "bytes": 699176, + "sha256": "dc26882e1b8278c88e1a87f867170714ad957593c9181fe458453c2ebf6be712" + }, + { + "path": "Textures/skies/clouds/presets/wispy/skyhat_cirrocumulus01_ap.dds", + "folder": "wispy", + "file": "skyhat_cirrocumulus01_ap.dds", + "role": "alpha", + "bytes": 699176, + "sha256": "16a75cfc8ca6d19d37a6ccc29306c4f115b3af352487b687edd664fd7f56d345" + }, + { + "path": "Textures/skies/clouds/presets/wispy/skyhat_cirrocumulus01_nrm.dds", + "folder": "wispy", + "file": "skyhat_cirrocumulus01_nrm.dds", + "role": "normal", + "bytes": 1398224, + "sha256": "1a54736790a2368b86e524495cb3ba782bc979359a3f127a2615e6f862a58473" + }, + { + "path": "Textures/skies/clouds/presets/wispy/wisps_ap.dds", + "folder": "wispy", + "file": "wisps_ap.dds", + "role": "alpha", + "bytes": 349648, + "sha256": "63a916cb97004f1a47a5d806648d5777b1b61d81ac3cb9ccfc4c7b196e744965" + }, + { + "path": "Textures/skies/clouds/presets/wispy/wisps_nrm.dds", + "folder": "wispy", + "file": "wisps_nrm.dds", + "role": "normal", + "bytes": 699168, + "sha256": "35e556144cd0e3f38f9fe5f54007c146d91760b441d719570359a3ab70c77729" + } + ] +} diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloudy/cloudhat_marble02_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloudy/cloudhat_marble02_ap.dds new file mode 100644 index 0000000..e13ecd3 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloudy/cloudhat_marble02_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloudy/detail1_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloudy/detail1_nrm.dds new file mode 100644 index 0000000..e1f9f22 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloudy/detail1_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloudy/trialap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloudy/trialap.dds new file mode 100644 index 0000000..3832744 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloudy/trialap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloudy/trialn.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloudy/trialn.dds new file mode 100644 index 0000000..dc0273b Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/cloudy/trialn.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/contrails/contrails_ap3.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/contrails/contrails_ap3.dds new file mode 100644 index 0000000..4433380 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/contrails/contrails_ap3.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/contrails/contrails_nrm3.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/contrails/contrails_nrm3.dds new file mode 100644 index 0000000..d402702 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/contrails/contrails_nrm3.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/horizon/cloudhat_marble02_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/horizon/cloudhat_marble02_ap.dds new file mode 100644 index 0000000..e13ecd3 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/horizon/cloudhat_marble02_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/horizon/detail1_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/horizon/detail1_nrm.dds new file mode 100644 index 0000000..e1f9f22 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/horizon/detail1_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/horizon/stripeyclouds_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/horizon/stripeyclouds_ap.dds new file mode 100644 index 0000000..779d2aa Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/horizon/stripeyclouds_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/horizon/stripeyclouds_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/horizon/stripeyclouds_nrm.dds new file mode 100644 index 0000000..d52a4ef Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/horizon/stripeyclouds_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/nimbus/cloudhat_marble02_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/nimbus/cloudhat_marble02_ap.dds new file mode 100644 index 0000000..e13ecd3 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/nimbus/cloudhat_marble02_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/nimbus/detail1_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/nimbus/detail1_nrm.dds new file mode 100644 index 0000000..e1f9f22 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/nimbus/detail1_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/nimbus/final_nimbusclouds_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/nimbus/final_nimbusclouds_ap.dds new file mode 100644 index 0000000..d509939 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/nimbus/final_nimbusclouds_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/nimbus/final_nimbusclouds_n.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/nimbus/final_nimbusclouds_n.dds new file mode 100644 index 0000000..3dde031 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/nimbus/final_nimbusclouds_n.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/puffs/cloudhat_marble02_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/puffs/cloudhat_marble02_ap.dds new file mode 100644 index 0000000..e13ecd3 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/puffs/cloudhat_marble02_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/puffs/cloudhat_puffs_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/puffs/cloudhat_puffs_ap.dds new file mode 100644 index 0000000..7ca1bf8 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/puffs/cloudhat_puffs_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/puffs/cloudhat_puffs_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/puffs/cloudhat_puffs_nrm.dds new file mode 100644 index 0000000..06fd3cc Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/puffs/cloudhat_puffs_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/puffs/detail1_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/puffs/detail1_nrm.dds new file mode 100644 index 0000000..e1f9f22 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/puffs/detail1_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/rain/skyhat_rain02_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/rain/skyhat_rain02_ap.dds new file mode 100644 index 0000000..fd91290 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/rain/skyhat_rain02_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/rain/skyhat_rain02_n2.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/rain/skyhat_rain02_n2.dds new file mode 100644 index 0000000..47428b6 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/rain/skyhat_rain02_n2.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/showers/skyhat_showers01_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/showers/skyhat_showers01_ap.dds new file mode 100644 index 0000000..a15ec93 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/showers/skyhat_showers01_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/showers/skyhat_showers01_n.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/showers/skyhat_showers01_n.dds new file mode 100644 index 0000000..48be76c Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/showers/skyhat_showers01_n.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/cloudhat_marble02_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/cloudhat_marble02_ap.dds new file mode 100644 index 0000000..e13ecd3 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/cloudhat_marble02_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/detail1_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/detail1_nrm.dds new file mode 100644 index 0000000..e1f9f22 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/detail1_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/skyhat_rain02_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/skyhat_rain02_ap.dds new file mode 100644 index 0000000..fd91290 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/skyhat_rain02_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/skyhat_rain02_n2.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/skyhat_rain02_n2.dds new file mode 100644 index 0000000..47428b6 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/skyhat_rain02_n2.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/stormclouds_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/stormclouds_ap.dds new file mode 100644 index 0000000..22a0207 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/stormclouds_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/stormclouds_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/stormclouds_nrm.dds new file mode 100644 index 0000000..5324c72 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/storm/stormclouds_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/test/cloudhat_marble02_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/test/cloudhat_marble02_ap.dds new file mode 100644 index 0000000..e13ecd3 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/test/cloudhat_marble02_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/test/detail1_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/test/detail1_nrm.dds new file mode 100644 index 0000000..e1f9f22 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/test/detail1_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/test/testwritting.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/test/testwritting.dds new file mode 100644 index 0000000..1c49cca Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/test/testwritting.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/test/testwritting_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/test/testwritting_nrm.dds new file mode 100644 index 0000000..0824a7a Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/test/testwritting_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/wispy/skyhat_cirrocumulus01_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/wispy/skyhat_cirrocumulus01_ap.dds new file mode 100644 index 0000000..81c2e29 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/wispy/skyhat_cirrocumulus01_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/wispy/skyhat_cirrocumulus01_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/wispy/skyhat_cirrocumulus01_nrm.dds new file mode 100644 index 0000000..3cf93c8 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/wispy/skyhat_cirrocumulus01_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/wispy/wisps_ap.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/wispy/wisps_ap.dds new file mode 100644 index 0000000..4a6dda2 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/wispy/wisps_ap.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/wispy/wisps_nrm.dds b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/wispy/wisps_nrm.dds new file mode 100644 index 0000000..d063fa7 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/clouds/presets/wispy/wisps_nrm.dds differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/full.png b/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/full.png new file mode 100644 index 0000000..4147d3e Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/full.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/waning-crescent.png b/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/waning-crescent.png new file mode 100644 index 0000000..f378061 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/waning-crescent.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/waning-gibbous.png b/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/waning-gibbous.png new file mode 100644 index 0000000..ba95224 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/waning-gibbous.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/waxing-crescent.png b/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/waxing-crescent.png new file mode 100644 index 0000000..5174006 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/waxing-crescent.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/waxing-gibbous.png b/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/waxing-gibbous.png new file mode 100644 index 0000000..373ad1e Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/moon-nonviral/waxing-gibbous.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/ramps/haze.png b/SkyLibrary/src/main/resources/Textures/skies/ramps/haze.png new file mode 100644 index 0000000..73f626a Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/ramps/haze.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/16m/northern.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/16m/northern.png new file mode 100644 index 0000000..6834370 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/16m/northern.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/16m/southern.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/16m/southern.png new file mode 100644 index 0000000..68a9828 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/16m/southern.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/16m/wiltshire.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/16m/wiltshire.png new file mode 100644 index 0000000..08ad265 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/16m/wiltshire.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_back6.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_back6.png new file mode 100644 index 0000000..0f8df0c Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_back6.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_bottom4.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_bottom4.png new file mode 100644 index 0000000..1661aae Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_bottom4.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_front5.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_front5.png new file mode 100644 index 0000000..69b589b Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_front5.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_left2.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_left2.png new file mode 100644 index 0000000..44256da Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_left2.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_right1.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_right1.png new file mode 100644 index 0000000..e7471f4 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_right1.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_top3.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_top3.png new file mode 100644 index 0000000..afdb4f4 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator/equator_top3.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_back6.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_back6.png new file mode 100644 index 0000000..f8618eb Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_back6.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_bottom4.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_bottom4.png new file mode 100644 index 0000000..9e0cb69 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_bottom4.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_front5.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_front5.png new file mode 100644 index 0000000..c90126f Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_front5.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_left2.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_left2.png new file mode 100644 index 0000000..6d2ce06 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_left2.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_right1.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_right1.png new file mode 100644 index 0000000..fff5512 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_right1.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_top3.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_top3.png new file mode 100644 index 0000000..1b51fce Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/equator16m/equator16m_top3.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/northern.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/northern.png new file mode 100644 index 0000000..6177903 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/northern.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/southern.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/southern.png new file mode 100644 index 0000000..e0b130f Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/southern.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/star-maps/wiltshire.png b/SkyLibrary/src/main/resources/Textures/skies/star-maps/wiltshire.png new file mode 100644 index 0000000..3b23a95 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/star-maps/wiltshire.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/suns/chaotic.png b/SkyLibrary/src/main/resources/Textures/skies/suns/chaotic.png new file mode 100644 index 0000000..3b590fb Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/suns/chaotic.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/suns/disc.png b/SkyLibrary/src/main/resources/Textures/skies/suns/disc.png new file mode 100644 index 0000000..8570bea Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/suns/disc.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/suns/hazy-disc.png b/SkyLibrary/src/main/resources/Textures/skies/suns/hazy-disc.png new file mode 100644 index 0000000..c5f13c4 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/suns/hazy-disc.png differ diff --git a/SkyLibrary/src/main/resources/Textures/skies/suns/rayed.png b/SkyLibrary/src/main/resources/Textures/skies/suns/rayed.png new file mode 100644 index 0000000..0f78227 Binary files /dev/null and b/SkyLibrary/src/main/resources/Textures/skies/suns/rayed.png differ diff --git a/SkyLibrary/src/main/resources/helix/lua/sky/commands.lua b/SkyLibrary/src/main/resources/helix/lua/sky/commands.lua new file mode 100644 index 0000000..a15bf43 --- /dev/null +++ b/SkyLibrary/src/main/resources/helix/lua/sky/commands.lua @@ -0,0 +1,79 @@ +-- SkySimulation command ABI manifest. +-- Java executes commands through SkyCommandBus; Lua declares command contract. + +local M = { + schema = "dev.takesome.sky.commands.lua.v1", + module = { + id = "sky.commands", + version = "1.0.0", + capabilities = { + "sky.command.bus", + "sky.atmosphere.commands", + "sky.weather.commands", + "sky.clock.commands", + "sky.environment.commands", + "sky.config.commands" + } + }, + commands = { + ["sky.atmosphere.setGradient"] = { + args = {"gradientStyle", "transitionSeconds?"}, + updates = { + "sky.atmosphere.gradientStyle", + "sky.atmosphere.sunsetIntensity", + "sky.atmosphere.sunHaloIntensity", + "sky.atmosphere.moonHaloIntensity" + }, + events = {"sky.atmosphere.changed", "sky.environment.changed"} + }, + ["sky.atmosphere.setSunsetIntensity"] = { + args = {"intensity", "transitionSeconds?"}, + updates = {"sky.atmosphere.sunsetIntensity"}, + events = {"sky.atmosphere.changed", "sky.environment.changed"} + }, + ["sky.atmosphere.setSunHaloIntensity"] = { + args = {"intensity", "transitionSeconds?"}, + updates = {"sky.atmosphere.sunHaloIntensity"}, + events = {"sky.atmosphere.changed", "sky.environment.changed"} + }, + ["sky.atmosphere.setMoonHaloIntensity"] = { + args = {"intensity", "transitionSeconds?"}, + updates = {"sky.atmosphere.moonHaloIntensity"}, + events = {"sky.atmosphere.changed", "sky.environment.changed"} + }, + ["sky.weather.set"] = { + args = {"weatherId", "transitionSeconds?"}, + updates = {"sky.weather.current", "sky.environment"}, + events = {"sky.weather.changed", "sky.environment.changed"} + }, + ["sky.weather.list"] = { + args = {}, + returns = {"weatherIds"} + }, + ["sky.clock.setTime"] = { + args = {"hour"}, + updates = {"sky.clock.hour", "sky.environment"}, + events = {"sky.clock.changed", "sky.environment.changed"} + }, + ["sky.clock.advance"] = { + args = {"seconds", "secondsPerDay"}, + updates = {"sky.clock.hour", "sky.environment"}, + events = {"sky.clock.changed", "sky.environment.changed"} + }, + ["sky.environment.snapshot"] = { + args = {}, + returns = {"sky.environment.snapshot"} + }, + ["sky.config.reload"] = { + args = {}, + updates = { + "sky.config", + "sky.weather.registry", + "sky.environment" + }, + events = {"sky.config.reloaded", "sky.environment.changed"} + } + } +} + +return M diff --git a/SkyLibrary/src/main/resources/helix/lua/sky/default-sky.lua b/SkyLibrary/src/main/resources/helix/lua/sky/default-sky.lua new file mode 100644 index 0000000..e9e721a --- /dev/null +++ b/SkyLibrary/src/main/resources/helix/lua/sky/default-sky.lua @@ -0,0 +1,72 @@ +-- SkySimulation default configuration ABI. +-- Java remains the native runtime; Lua owns boot configuration shape. + +local gradientPresets = { + REALISTIC = { + gradientStyle = "REALISTIC", + sunsetIntensity = 0.75, + sunHaloIntensity = 0.75, + moonHaloIntensity = 0.65 + }, + CINEMATIC = { + gradientStyle = "CINEMATIC", + sunsetIntensity = 1.25, + sunHaloIntensity = 1.15, + moonHaloIntensity = 1.10 + }, + FANTASY = { + gradientStyle = "FANTASY", + sunsetIntensity = 1.80, + sunHaloIntensity = 1.65, + moonHaloIntensity = 1.55 + } +} + +local activeGradient = gradientPresets.CINEMATIC + +local M = { + schema = "dev.takesome.sky.config.lua.v1", + module = { + id = "sky.config", + version = "1.0.0", + capabilities = { + "sky.config.default", + "sky.atmosphere.profile", + "sky.atmosphere.gradients", + "sky.weather.initial", + "sky.clock.initial", + "sky.rendering.options", + "sky.integration.defaults" + } + }, + gradientPresets = gradientPresets, + atmosphere = { + profile = "Config/skies/earthlike-atmosphere.properties", + gradientStyle = activeGradient.gradientStyle, + sunsetIntensity = activeGradient.sunsetIntensity, + sunHaloIntensity = activeGradient.sunHaloIntensity, + moonHaloIntensity = activeGradient.moonHaloIntensity + }, + weather = { + registry = "helix/lua/sky/weather.lua", + initial = "FAIR", + transitionSeconds = 45.0 + }, + clock = { + hour = 12.0, + observerLatitudeDegrees = 51.1788, + solarMonth = 6, + solarDay = 21 + }, + rendering = { + stars = "TwoDomes", + cloudFlattening = 0.9, + cloudsYOffset = 0.4, + lowerDome = true + }, + integration = { + cloudModulation = true + } +} + +return M diff --git a/SkyLibrary/src/main/resources/helix/lua/sky/weather.lua b/SkyLibrary/src/main/resources/helix/lua/sky/weather.lua new file mode 100644 index 0000000..300bc5f --- /dev/null +++ b/SkyLibrary/src/main/resources/helix/lua/sky/weather.lua @@ -0,0 +1,167 @@ +-- SkySimulation Lua Weather ABI. +-- Java remains the native renderer/runtime. Lua owns preset data and ABI shape. + +local M = { + schema = "dev.takesome.sky.weather.lua.v1", + module = { + id = "sky.weather", + version = "1.0.0", + capabilities = { + "sky.weather.registry", + "sky.weather.presets", + "sky.environment.state" + }, + commands = { + "sky.weather.set", + "sky.weather.list", + "sky.weather.snapshot" + }, + state = { + "sky.weather.current", + "sky.environment.visibility", + "sky.environment.precipitation", + "sky.environment.wind" + }, + events = { + "sky.weather.changed", + "sky.environment.changed" + } + }, + defaults = { + transitionSeconds = 60.0, + maxCloudLayers = 6 + }, + presets = {} +} + +local C = "Textures/skies/clouds" +local P = C .. "/presets" + +local function layer(alphaMap, normalMap, opacity, scale, uRate, vRate) + return { + alphaMap = alphaMap, + normalMap = normalMap, + opacity = opacity, + scale = scale, + uRate = uRate, + vRate = vRate + } +end + +local function world(cloudiness, visibility, precipitation, + windStrength, lightningChance) + return { + cloudiness = cloudiness, + visibility = visibility, + precipitation = precipitation, + windStrength = windStrength, + lightningChance = lightningChance + } +end + +M.presets.CLEAR = { + description = "No visible clouds; legacy clear fallback.", + transitionSeconds = 20.0, + world = world(0.0, 1.0, 0.0, 0.05, 0.0), + layers = { + layer(C .. "/clear.png", nil, 0.0, 1.0, 0.0, 0.0) + } +} + +M.presets.FAIR = { + description = "Fair weather with two FBM cloud layers.", + transitionSeconds = 45.0, + world = world(0.35, 0.95, 0.0, 0.10, 0.0), + layers = { + layer(C .. "/fbm.png", nil, 0.35, 1.50, -0.0005, 0.0030), + layer(C .. "/fbm.png", nil, 0.18, 2.15, 0.0003, 0.0010) + } +} + +M.presets.OVERCAST = { + description = "Broad overcast fallback with FBM detail.", + transitionSeconds = 60.0, + world = world(0.72, 0.75, 0.0, 0.25, 0.0), + layers = { + layer(C .. "/overcast.png", nil, 0.72, 1.00, 0.0, 0.0003), + layer(C .. "/fbm.png", nil, 0.20, 2.40, 0.0002, 0.0010) + } +} + +M.presets.WISPY = { + description = "Thin high clouds with cirrocumulus and wisps.", + transitionSeconds = 60.0, + world = world(0.28, 0.92, 0.0, 0.20, 0.0), + layers = { + layer(P .. "/wispy/skyhat_cirrocumulus01_ap.dds", + P .. "/wispy/skyhat_cirrocumulus01_nrm.dds", + 0.28, 1.30, 0.0001, 0.0012), + layer(P .. "/wispy/wisps_ap.dds", + P .. "/wispy/wisps_nrm.dds", + 0.16, 2.20, -0.0002, 0.0020) + } +} + +M.presets.CLOUDY = { + description = "Medium coverage with detail modulation.", + transitionSeconds = 60.0, + world = world(0.55, 0.78, 0.0, 0.35, 0.0), + layers = { + layer(P .. "/cloudy/trialap.dds", + P .. "/cloudy/trialn.dds", + 0.52, 1.20, -0.0003, 0.0018), + layer(P .. "/cloudy/cloudhat_marble02_ap.dds", + P .. "/cloudy/detail1_nrm.dds", + 0.22, 3.00, 0.0004, 0.0024) + } +} + +M.presets.RAIN = { + description = "Rain clouds with cloudy base and fast detail.", + transitionSeconds = 75.0, + world = world(0.75, 0.55, 0.75, 0.55, 0.02), + layers = { + layer(P .. "/rain/skyhat_rain02_ap.dds", + P .. "/rain/skyhat_rain02_n2.dds", + 0.68, 1.10, -0.0003, 0.0026), + layer(P .. "/cloudy/trialap.dds", + P .. "/cloudy/trialn.dds", + 0.35, 2.00, 0.0002, 0.0018), + layer(P .. "/cloudy/cloudhat_marble02_ap.dds", + P .. "/cloudy/detail1_nrm.dds", + 0.18, 3.50, 0.0004, 0.0030) + } +} + +M.presets.STORM = { + description = "Heavy storm front, rain veil, and high-frequency detail.", + transitionSeconds = 90.0, + world = world(0.90, 0.35, 0.95, 0.90, 0.20), + layers = { + layer(P .. "/storm/stormclouds_ap.dds", + P .. "/storm/stormclouds_nrm.dds", + 0.85, 1.00, -0.0004, 0.0032), + layer(P .. "/storm/skyhat_rain02_ap.dds", + P .. "/storm/skyhat_rain02_n2.dds", + 0.42, 1.80, 0.0002, 0.0026), + layer(P .. "/storm/cloudhat_marble02_ap.dds", + P .. "/storm/detail1_nrm.dds", + 0.26, 4.00, 0.0005, 0.0035) + } +} + +M.presets.NIMBUS = { + description = "Nimbus coverage with detail modulation.", + transitionSeconds = 75.0, + world = world(0.82, 0.45, 0.80, 0.70, 0.08), + layers = { + layer(P .. "/nimbus/final_nimbusclouds_ap.dds", + P .. "/nimbus/final_nimbusclouds_n.dds", + 0.76, 1.10, -0.0004, 0.0020), + layer(P .. "/nimbus/cloudhat_marble02_ap.dds", + P .. "/nimbus/detail1_nrm.dds", + 0.22, 3.50, 0.0003, 0.0024) + } +} + +return M diff --git a/SkyLibrary/src/test/java/jme3utilities/sky/test/TestCloudWeatherAssets.java b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestCloudWeatherAssets.java new file mode 100644 index 0000000..2a63d39 --- /dev/null +++ b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestCloudWeatherAssets.java @@ -0,0 +1,110 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.test; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.texture.plugins.AWTLoader; +import com.jme3.texture.plugins.DDSLoader; +import java.io.InputStream; +import jme3utilities.sky.SkyControlCore; +import jme3utilities.sky.SkyMaterial; +import jme3utilities.sky.cloud.SkyCloudAssets; +import jme3utilities.sky.cloud.SkyCloudLayerSpec; +import jme3utilities.sky.cloud.SkyCloudPreset; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test cloud weather assets and material parameters without opening a window. + * + * @author Take Some + */ +public class TestCloudWeatherAssets { + /** Asset manager for classpath resources. */ + final private static AssetManager assetManager = new DesktopAssetManager(); + + /** + * Test loading weather preset alpha and normal maps. + */ + @Test + public void testCloudWeatherAssets() { + registerLoaders(); + + assertRegistryExists(); + SkyMaterial material = new SkyMaterial( + assetManager, 0, SkyControlCore.numCloudLayers); + for (SkyCloudPreset preset : SkyCloudPreset.values()) { + applyPreset(material, preset); + } + } + + /** + * Apply a preset to a material. + * + * @param material material to mutate (not null) + * @param preset preset to apply (not null) + */ + private static void applyPreset( + SkyMaterial material, SkyCloudPreset preset) { + assert material != null; + assert preset != null; + + for (int layerIndex = 0; + layerIndex < SkyControlCore.numCloudLayers; ++layerIndex) { + SkyCloudLayerSpec spec = preset.layer(layerIndex); + assetManager.loadTexture(spec.alphaMap()); + material.addClouds(layerIndex, spec.alphaMap()); + if (spec.normalMap() == null) { + material.setCloudsNormalMap(layerIndex, null); + } else { + material.setCloudsNormalMap(layerIndex, spec.normalMap()); + } + } + } + + /** Register asset locators and loaders. */ + private static void registerLoaders() { + assetManager.registerLoader(AWTLoader.class, "jpg", "png"); + assetManager.registerLoader(DDSLoader.class, "dds"); + assetManager.registerLoader(J3MLoader.class, "j3m", "j3md"); + assetManager.registerLocator(null, ClasspathLocator.class); + } + + /** Assert the registry exists on the classpath. */ + private static void assertRegistryExists() { + String resourceName = "/" + SkyCloudAssets.registry; + try (InputStream stream + = TestCloudWeatherAssets.class.getResourceAsStream( + resourceName)) { + Assert.assertNotNull(resourceName, stream); + } catch (java.io.IOException exception) { + throw new AssertionError(exception); + } + } +} diff --git a/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyAtmosphereTransition.java b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyAtmosphereTransition.java new file mode 100644 index 0000000..11c1743 --- /dev/null +++ b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyAtmosphereTransition.java @@ -0,0 +1,110 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.test; + +import jme3utilities.sky.SkyAtmosphere; +import jme3utilities.sky.atmosphere.SkyAtmosphereTransitionRuntime; +import jme3utilities.sky.atmosphere.SkyGradientStyle; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test runtime atmosphere gradient transitions. + * + * @author Take Some + */ +public class TestSkyAtmosphereTransition { + /** + * Verify gradient preset transitions interpolate runtime scale values. + */ + @Test + public void testGradientTransition() { + SkyAtmosphere atmosphere = new SkyAtmosphere(); + atmosphere.setGradientStyle(SkyGradientStyle.REALISTIC); + atmosphere.setSunsetIntensity(0.75f); + atmosphere.setSunHaloIntensity(0.75f); + atmosphere.setMoonHaloIntensity(0.65f); + SkyAtmosphereTransitionRuntime runtime + = new SkyAtmosphereTransitionRuntime(); + + runtime.transitionStyle(atmosphere, SkyGradientStyle.FANTASY, 2f); + Assert.assertTrue(runtime.isActive()); + Assert.assertEquals(SkyGradientStyle.FANTASY, + atmosphere.getGradientStyle()); + Assert.assertEquals(SkyGradientStyle.REALISTIC.horizonScale(), + atmosphere.getHorizonScale(), 0.0001f); + + runtime.update(atmosphere, 1f); + float expectedHorizon = midpoint( + SkyGradientStyle.REALISTIC.horizonScale(), + SkyGradientStyle.FANTASY.horizonScale()); + Assert.assertEquals(expectedHorizon, + atmosphere.getHorizonScale(), 0.0001f); + Assert.assertEquals(1.275f, + atmosphere.getSunsetIntensity(), 0.0001f); + + runtime.update(atmosphere, 1f); + Assert.assertFalse(runtime.isActive()); + Assert.assertEquals(SkyGradientStyle.FANTASY.horizonScale(), + atmosphere.getHorizonScale(), 0.0001f); + Assert.assertEquals(SkyGradientStyle.FANTASY.presetMoonHalo(), + atmosphere.getMoonHaloIntensity(), 0.0001f); + } + + /** + * Verify individual intensity transitions keep style scales untouched. + */ + @Test + public void testIntensityTransition() { + SkyAtmosphere atmosphere = new SkyAtmosphere(); + SkyAtmosphereTransitionRuntime runtime + = new SkyAtmosphereTransitionRuntime(); + float initialScale = atmosphere.getHaloScale(); + + runtime.transitionMoon(atmosphere, 1.8f, 4f); + runtime.update(atmosphere, 2f); + Assert.assertEquals(1.4f, + atmosphere.getMoonHaloIntensity(), 0.0001f); + Assert.assertEquals(initialScale, atmosphere.getHaloScale(), 0.0001f); + + runtime.update(atmosphere, 2f); + Assert.assertFalse(runtime.isActive()); + Assert.assertEquals(1.8f, + atmosphere.getMoonHaloIntensity(), 0.0001f); + } + + /** + * Return midpoint of two values. + * + * @param a first value + * @param b second value + * @return midpoint + */ + private static float midpoint(float a, float b) { + float result = (a + b) / 2f; + return result; + } +} diff --git a/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyAtmosphereValidation.java b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyAtmosphereValidation.java new file mode 100644 index 0000000..2a6e269 --- /dev/null +++ b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyAtmosphereValidation.java @@ -0,0 +1,51 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.test; + +import jme3utilities.sky.SkyAtmosphere; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test validation in Take Some atmospheric tuning additions. + * + * @author Take Some + */ +public class TestSkyAtmosphereValidation { + /** + * Verify positive-fraction atmospheric tunables reject zero. + */ + @Test + public void testBadFraction() { + SkyAtmosphere atmosphere = new SkyAtmosphere(); + try { + atmosphere.setTwilightLimit(0f); + Assert.fail("setTwilightLimit(0) should fail"); + } catch (IllegalArgumentException exception) { + // expected + } + } +} diff --git a/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyCloudPresetLoader.java b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyCloudPresetLoader.java new file mode 100644 index 0000000..482db88 --- /dev/null +++ b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyCloudPresetLoader.java @@ -0,0 +1,71 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.test; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import jme3utilities.sky.cloud.SkyCloudPresetDefinition; +import jme3utilities.sky.cloud.SkyCloudPresetLoader; +import jme3utilities.sky.cloud.SkyCloudPresetRegistry; +import jme3utilities.sky.runtime.SkyEnvironmentRuntime; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test the Lua weather ABI loader. + * + * @author Take Some + */ +public class TestSkyCloudPresetLoader { + /** + * Verify the default Lua ABI loads as a typed registry. + */ + @Test + public void testLuaWeatherAbi() { + AssetManager assetManager = new DesktopAssetManager(); + assetManager.registerLocator(null, ClasspathLocator.class); + + SkyCloudPresetRegistry registry + = SkyCloudPresetLoader.loadDefault(assetManager); + SkyCloudPresetDefinition storm = registry.require("STORM"); + + Assert.assertEquals("STORM", storm.id()); + Assert.assertEquals(3, storm.layerCount()); + Assert.assertEquals(90f, storm.defaultSeconds(), 0f); + Assert.assertEquals(0.35f, storm.metrics().visibility(), 0.0001f); + Assert.assertEquals(0.95f, + storm.metrics().precipitation(), 0.0001f); + Assert.assertTrue(storm.layer(0).alphaMap().contains("stormclouds")); + Assert.assertNotNull(storm.layer(0).normalMap()); + + SkyEnvironmentRuntime runtime = new SkyEnvironmentRuntime(); + runtime.setWeather(storm, storm.defaultSeconds()); + Assert.assertTrue(runtime.isStorm()); + Assert.assertEquals(0.90f, runtime.windStrength(), 0.0001f); + Assert.assertEquals("STORM", runtime.weather().presetId()); + } +} diff --git a/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyCommandAbi.java b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyCommandAbi.java new file mode 100644 index 0000000..8738a92 --- /dev/null +++ b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyCommandAbi.java @@ -0,0 +1,133 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.test; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.renderer.Camera; +import com.jme3.texture.plugins.AWTLoader; +import com.jme3.texture.plugins.DDSLoader; +import jme3utilities.sky.SkyControl; +import jme3utilities.sky.atmosphere.SkyGradientStyle; +import jme3utilities.sky.command.SkyCommandBus; +import jme3utilities.sky.command.SkyCommandIds; +import jme3utilities.sky.command.SkyCommandResult; +import jme3utilities.sky.config.SkySimulationConfig; +import jme3utilities.sky.config.SkySimulationConfigLoader; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test the Java executor for the Sky command ABI. + * + * @author Take Some + */ +public class TestSkyCommandAbi { + /** + * Verify supported command ids mutate and query sky state. + */ + @Test + public void testSkyCommandBus() { + AssetManager assetManager = new DesktopAssetManager(); + assetManager.registerLoader(J3MLoader.class, "j3m", "j3md"); + assetManager.registerLoader(AWTLoader.class, "jpg", "png"); + assetManager.registerLoader(DDSLoader.class, "dds"); + assetManager.registerLocator(null, ClasspathLocator.class); + + SkySimulationConfig config + = SkySimulationConfigLoader.loadDefault(assetManager); + SkyControl skyControl = config.createControl( + assetManager, new Camera(640, 480)); + SkyCommandBus commandBus = new SkyCommandBus( + assetManager, skyControl); + + SkyCommandResult gradient = commandBus.execute( + SkyCommandIds.atmosphereSetGradient, "FANTASY"); + Assert.assertTrue(gradient.succeeded()); + Assert.assertEquals(SkyGradientStyle.FANTASY, + skyControl.getAtmosphere().getGradientStyle()); + Assert.assertEquals(1.80f, + skyControl.getAtmosphere().getSunsetIntensity(), 0.0001f); + Assert.assertEquals(1.65f, + skyControl.getAtmosphere().getSunHaloIntensity(), 0.0001f); + Assert.assertEquals(1.55f, + skyControl.getAtmosphere().getMoonHaloIntensity(), 0.0001f); + + SkyCommandResult sunset = commandBus.execute( + SkyCommandIds.atmosphereSetSunsetIntensity, "2.0"); + Assert.assertTrue(sunset.succeeded()); + Assert.assertEquals(2f, + skyControl.getAtmosphere().getSunsetIntensity(), 0.0001f); + + SkyCommandResult sunHalo = commandBus.execute( + SkyCommandIds.atmosphereSetSunHaloIntensity, "1.7"); + Assert.assertTrue(sunHalo.succeeded()); + Assert.assertEquals(1.7f, + skyControl.getAtmosphere().getSunHaloIntensity(), 0.0001f); + + SkyCommandResult moonHalo = commandBus.execute( + SkyCommandIds.atmosphereSetMoonHaloIntensity, "1.8"); + Assert.assertTrue(moonHalo.succeeded()); + Assert.assertEquals(1.8f, + skyControl.getAtmosphere().getMoonHaloIntensity(), 0.0001f); + + SkyCommandResult list = commandBus.execute(SkyCommandIds.weatherList); + Assert.assertTrue(list.succeeded()); + Assert.assertTrue(list.values().contains("STORM")); + + SkyCommandResult weather = commandBus.execute( + SkyCommandIds.weatherSet, "STORM", "0"); + Assert.assertTrue(weather.succeeded()); + Assert.assertTrue(skyControl.environment().isStorm()); + Assert.assertEquals("STORM", skyControl.environment().weather().id()); + + SkyCommandResult setTime = commandBus.execute( + SkyCommandIds.clockSetTime, "18.5"); + Assert.assertTrue(setTime.succeeded()); + Assert.assertEquals(18.5f, + skyControl.getSunAndStars().getHour(), 0.0001f); + + SkyCommandResult advance = commandBus.execute( + SkyCommandIds.clockAdvance, "3600", "86400"); + Assert.assertTrue(advance.succeeded()); + Assert.assertEquals(19.5f, + skyControl.getSunAndStars().getHour(), 0.0001f); + + SkyCommandResult snapshot = commandBus.execute( + SkyCommandIds.environmentSnapshot); + Assert.assertTrue(snapshot.succeeded()); + Assert.assertNotNull(snapshot.snapshot()); + Assert.assertEquals("STORM", snapshot.snapshot().weatherId()); + + SkyCommandResult reload = commandBus.execute( + SkyCommandIds.configReload); + Assert.assertTrue(reload.succeeded()); + Assert.assertEquals("FAIR", skyControl.environment().weather().id()); + Assert.assertTrue(skyControl.getCloudModulation()); + } +} diff --git a/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyEnvironmentRuntime.java b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyEnvironmentRuntime.java new file mode 100644 index 0000000..35005d0 --- /dev/null +++ b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyEnvironmentRuntime.java @@ -0,0 +1,185 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.test; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import jme3utilities.sky.cloud.SkyCloudPreset; +import jme3utilities.sky.cloud.SkyCloudPresetDefinition; +import jme3utilities.sky.runtime.SkyEnvironmentRuntime; +import jme3utilities.sky.runtime.SkyEnvironmentSnapshot; +import jme3utilities.sky.runtime.SkyLightingSnapshot; +import jme3utilities.sky.runtime.SkyLightingState; +import jme3utilities.sky.runtime.SkyWeatherEvent; +import jme3utilities.sky.runtime.SkyWeatherFilters; +import jme3utilities.sky.runtime.SkyWeatherListener; +import jme3utilities.sky.runtime.SkyWeatherSubscription; +import jme3utilities.sky.runtime.SkyWorldClock; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test the game-facing sky environment runtime. + * + * @author Take Some + */ +public class TestSkyEnvironmentRuntime { + /** + * Test clock, weather, lighting, and snapshot behavior. + */ + @Test + public void testEnvironmentRuntime() { + final float[] appliedTime = {-1f}; + final SkyCloudPreset[] appliedPreset = new SkyCloudPreset[1]; + final float[] appliedSeconds = {-1f}; + + SkyEnvironmentRuntime runtime = new SkyEnvironmentRuntime( + new SkyWorldClock.TimeApplier() { + @Override + public void applyTimeOfDay(float timeOfDayHours) { + appliedTime[0] = timeOfDayHours; + } + }, + new SkyEnvironmentRuntime.WeatherApplier() { + @Override + public void applyWeather( + SkyCloudPreset preset, float seconds) { + appliedPreset[0] = preset; + appliedSeconds[0] = seconds; + } + + @Override + public void applyWeather( + SkyCloudPresetDefinition definition, + float seconds) { + appliedPreset[0] = definition.builtIn(); + appliedSeconds[0] = seconds; + } + }); + + runtime.clock().setTimeOfDay(18.5f); + Assert.assertEquals(18.5f, appliedTime[0], 0f); + Assert.assertEquals(18.5f, runtime.clock().timeOfDayHours(), 0f); + + runtime.setWeather(SkyCloudPreset.STORM, 45f); + Assert.assertEquals(SkyCloudPreset.STORM, appliedPreset[0]); + Assert.assertEquals(45f, appliedSeconds[0], 0f); + Assert.assertTrue(runtime.isStorm()); + Assert.assertEquals(0.35f, runtime.visibility(), 0.0001f); + Assert.assertEquals(0.95f, runtime.precipitation(), 0.0001f); + + ColorRGBA ambient = new ColorRGBA(0.2f, 0.3f, 0.4f, 1f); + ColorRGBA background = new ColorRGBA(0.1f, 0.15f, 0.2f, 1f); + ColorRGBA main = new ColorRGBA(0.8f, 0.7f, 0.6f, 1f); + Vector3f direction = new Vector3f(0f, 1f, 0f); + SkyLightingState state = new SkyLightingState( + 0.7f, 0.4f, true, false); + SkyLightingSnapshot lighting = new SkyLightingSnapshot( + ambient, background, main, direction, state); + runtime.updateLighting(lighting); + + ambient.set(0f, 0f, 0f, 0f); + Assert.assertEquals(0.2f, + runtime.lighting().ambientColor(null).r, 0.0001f); + Assert.assertFalse(runtime.isNight()); + Assert.assertEquals((0.2f + 0.3f + 0.4f) / 3f, + runtime.ambientLightLevel(), 0.0001f); + + SkyEnvironmentSnapshot snapshot = runtime.snapshot(); + Assert.assertEquals(18.5f, snapshot.timeOfDayHours(), 0f); + Assert.assertEquals(SkyCloudPreset.STORM, snapshot.cloudPreset()); + Assert.assertEquals(0.9f, snapshot.windStrength(), 0.0001f); + Assert.assertEquals(0.7f, snapshot.bloom(), 0.0001f); + Assert.assertEquals(0.4f, snapshot.shadowIntensity(), 0.0001f); + } + + /** + * Test weather subscriptions and filtered delivery. + */ + @Test + public void testWeatherSubscriptions() { + SkyEnvironmentRuntime runtime = new SkyEnvironmentRuntime(); + final int[] allCount = {0}; + final int[] stormCount = {0}; + final int[] rainLikeCount = {0}; + final String[] previousId = {null}; + final String[] currentId = {null}; + final float[] transitionSeconds = {-1f}; + + SkyWeatherSubscription all = runtime.subscribeWeather( + new SkyWeatherListener() { + @Override + public void onWeatherChanged(SkyWeatherEvent event) { + ++allCount[0]; + previousId[0] = event.previousWeatherId(); + currentId[0] = event.currentWeatherId(); + transitionSeconds[0] = event.transitionSeconds(); + } + }); + runtime.subscribeWeather("storm", new SkyWeatherListener() { + @Override + public void onWeatherChanged(SkyWeatherEvent event) { + ++stormCount[0]; + } + }); + runtime.subscribeWeather(SkyWeatherFilters.precipitating(0.5f), + new SkyWeatherListener() { + @Override + public void onWeatherChanged(SkyWeatherEvent event) { + ++rainLikeCount[0]; + } + }); + + Assert.assertEquals(3, runtime.weatherSubscriptionCount()); + runtime.setWeather(SkyCloudPreset.STORM, 12f); + Assert.assertEquals(1, allCount[0]); + Assert.assertEquals(1, stormCount[0]); + Assert.assertEquals(1, rainLikeCount[0]); + Assert.assertEquals("fair", previousId[0]); + Assert.assertEquals("storm", currentId[0]); + Assert.assertEquals(12f, transitionSeconds[0], 0f); + + Assert.assertTrue(all.cancel()); + Assert.assertFalse(all.isActive()); + Assert.assertEquals(2, runtime.weatherSubscriptionCount()); + runtime.setWeather(SkyCloudPreset.FAIR, 0f); + Assert.assertEquals(1, allCount[0]); + Assert.assertEquals(1, stormCount[0]); + Assert.assertEquals(1, rainLikeCount[0]); + + final int[] currentReplayCount = {0}; + runtime.subscribeWeather(SkyWeatherFilters.any(), + new SkyWeatherListener() { + @Override + public void onWeatherChanged(SkyWeatherEvent event) { + if (event.isCurrentReplay()) { + ++currentReplayCount[0]; + } + } + }, true); + Assert.assertEquals(1, currentReplayCount[0]); + } +} diff --git a/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyGradientModel.java b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyGradientModel.java new file mode 100644 index 0000000..31dfced --- /dev/null +++ b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyGradientModel.java @@ -0,0 +1,117 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.test; + +import com.jme3.math.ColorRGBA; +import jme3utilities.sky.SkyAtmosphere; +import jme3utilities.sky.atmosphere.SkyGradientStyle; +import jme3utilities.sky.atmosphere.SkyLightingModel; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test atmospheric, sun, and moon gradient math. + * + * @author Take Some + */ +public class TestSkyGradientModel { + /** + * Verify horizon weighting peaks at the horizon. + */ + @Test + public void testHorizonWeight() { + SkyAtmosphere atmosphere = new SkyAtmosphere(); + float limit = atmosphere.getTwilightLimit(); + + Assert.assertEquals(1f, + SkyLightingModel.horizonWeight(0f, limit), 0f); + Assert.assertEquals(0f, + SkyLightingModel.horizonWeight(limit, limit), 0f); + Assert.assertEquals(0f, + SkyLightingModel.horizonWeight(-limit, limit), 0f); + } + + /** + * Verify low sun is warmer than high sun. + */ + @Test + public void testSunsetWarmth() { + SkyAtmosphere atmosphere = new SkyAtmosphere(); + ColorRGBA high = SkyLightingModel.daylightColor(atmosphere, 1f); + ColorRGBA horizon = SkyLightingModel.daylightColor(atmosphere, 0.01f); + + Assert.assertTrue(warmth(horizon) > warmth(high)); + } + + /** + * Verify sun and moon glow gradients diverge from source colors. + */ + @Test + public void testObjectGlow() { + SkyAtmosphere atmosphere = new SkyAtmosphere(); + ColorRGBA sunColor = SkyLightingModel.daylightColor(atmosphere, 0f); + ColorRGBA sunGlow = SkyLightingModel.sunGlowColor(atmosphere, 0f); + ColorRGBA highMoon = SkyLightingModel.lunarColor(atmosphere, 1f); + ColorRGBA lowMoon = SkyLightingModel.lunarColor(atmosphere, 0f); + + Assert.assertTrue(sunGlow.r > sunColor.r); + Assert.assertTrue(warmth(lowMoon) > warmth(highMoon)); + } + + /** + * Verify ABI-facing intensity controls affect output. + */ + @Test + public void testStyleIntensity() { + SkyAtmosphere realistic = new SkyAtmosphere(); + realistic.setGradientStyle(SkyGradientStyle.REALISTIC); + realistic.setSunsetIntensity(0.75f); + SkyAtmosphere fantasy = new SkyAtmosphere(); + fantasy.setGradientStyle(SkyGradientStyle.FANTASY); + fantasy.setSunsetIntensity(1.8f); + fantasy.setSunHaloIntensity(1.6f); + fantasy.setMoonHaloIntensity(1.5f); + + ColorRGBA tame = SkyLightingModel.horizonColor(realistic, 0f); + ColorRGBA wild = SkyLightingModel.horizonColor(fantasy, 0f); + ColorRGBA tameMoon = SkyLightingModel.moonGlowColor(realistic, 0f); + ColorRGBA wildMoon = SkyLightingModel.moonGlowColor(fantasy, 0f); + + Assert.assertTrue(warmth(wild) > warmth(tame)); + Assert.assertTrue(wildMoon.r > tameMoon.r); + } + + /** + * Return a red/blue warmth ratio. + * + * @param color color to inspect + * @return warmth ratio + */ + private static float warmth(ColorRGBA color) { + float result = color.r / color.b; + return result; + } +} diff --git a/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkySimulationConfigLoader.java b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkySimulationConfigLoader.java new file mode 100644 index 0000000..62bc639 --- /dev/null +++ b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkySimulationConfigLoader.java @@ -0,0 +1,93 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.test; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.renderer.Camera; +import com.jme3.texture.plugins.AWTLoader; +import jme3utilities.math.MyMath; +import jme3utilities.sky.SkyControl; +import jme3utilities.sky.StarsOption; +import jme3utilities.sky.atmosphere.SkyGradientStyle; +import jme3utilities.sky.config.SkySimulationConfig; +import jme3utilities.sky.config.SkySimulationConfigLoader; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test the Lua SkySimulation config ABI loader. + * + * @author Take Some + */ +public class TestSkySimulationConfigLoader { + /** + * Verify the default config loads and can construct a control. + */ + @Test + public void testDefaultSkyConfig() { + AssetManager assetManager = new DesktopAssetManager(); + assetManager.registerLoader(J3MLoader.class, "j3m", "j3md"); + assetManager.registerLoader(AWTLoader.class, "jpg", "png"); + assetManager.registerLocator(null, ClasspathLocator.class); + + SkySimulationConfig config + = SkySimulationConfigLoader.loadDefault(assetManager); + Assert.assertEquals(12f, config.clock().hour(), 0f); + Assert.assertEquals(StarsOption.TwoDomes, + config.rendering().starsOption()); + Assert.assertEquals(0.9f, + config.rendering().cloudFlattening(), 0.0001f); + Assert.assertEquals("FAIR", + config.integration().initialWeatherId()); + Assert.assertEquals(SkyGradientStyle.CINEMATIC, + config.atmosphere().gradientStyle()); + Assert.assertEquals(1.25f, + config.atmosphere().sunsetIntensity(), 0.0001f); + Assert.assertEquals(1.15f, + config.atmosphere().sunHaloIntensity(), 0.0001f); + Assert.assertEquals(1.10f, + config.atmosphere().moonHaloIntensity(), 0.0001f); + + Camera camera = new Camera(640, 480); + SkyControl skyControl = config.createControl(assetManager, camera); + Assert.assertEquals(12f, + skyControl.getSunAndStars().getHour(), 0f); + Assert.assertEquals(MyMath.toRadians(51.1788f), + skyControl.getSunAndStars().getObserverLatitude(), 0.0001f); + Assert.assertEquals(0.4f, skyControl.getCloudsYOffset(), 0.0001f); + Assert.assertTrue(skyControl.getCloudModulation()); + Assert.assertEquals(SkyGradientStyle.CINEMATIC, + skyControl.getAtmosphere().getGradientStyle()); + Assert.assertEquals(1.25f, + skyControl.getAtmosphere().getSunsetIntensity(), 0.0001f); + Assert.assertEquals("FAIR", skyControl.environment().weather().id()); + Assert.assertEquals(0.95f, + skyControl.environment().visibility(), 0.0001f); + } +} diff --git a/build.gradle b/build.gradle index e8b0b75..ab44f0b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -// Gradle script to build the SkyControl project +// Gradle script to build the SkySimulation project plugins { id 'base' // to add a "clean" task to the root project @@ -6,9 +6,13 @@ plugins { ext { jmeTarget = '' // distinguish non-JME libraries built for specific JME releases - skySnapshot = '-SNAPSHOT' // for development builds - //skySnapshot = '' // for release builds - skycontrolVersion = '1.1.1' + jmeTarget + skySnapshot + skySimulationBaseVersion = '1.4.3' + skySnapshot = hasProperty('releaseBuild') ? '' : '-SNAPSHOT' + skySimulationGroup = 'dev.takesome' + skySimulationArtifact = 'sky-simulation' + skySimulationVersion = (findProperty('skySimulationVersion') + ?: skySimulationBaseVersion + jmeTarget + skySnapshot).toString() + skycontrolVersion = skySimulationVersion // compatibility alias } subprojects { @@ -34,7 +38,15 @@ tasks.register('checkstyle') { tasks.register('install') { dependsOn ':SkyLibrary:install' - description = 'Installs the Maven artifacts to the local repository.' + description = 'Installs the Take Some SkySimulation package to Maven Local.' +} +tasks.register('packageLocal') { + dependsOn ':SkyLibrary:install' + description = 'Builds and installs dev.takesome:sky-simulation locally.' +} +tasks.register('publishGithubPackage') { + dependsOn ':SkyLibrary:publishGithubPackage' + description = 'Publishes dev.takesome:sky-simulation to GitHub Packages.' } tasks.register('release') { dependsOn ':SkyLibrary:release' diff --git a/docs/sky-simulation-banner.svg b/docs/sky-simulation-banner.svg new file mode 100644 index 0000000..e0264c4 --- /dev/null +++ b/docs/sky-simulation-banner.svg @@ -0,0 +1,37 @@ + + SkySimulation banner + A stylized sky runtime banner with sun, moon, clouds, stars, and atmosphere layers. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SkySimulation + Take Some() sky, weather, atmosphere and world-runtime infrastructure + dev.takesome:sky-simulation:1.4.3 + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3bd131e..f76dc63 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ checkstyle = "12.3.1" jme = "3.10.0-alpha4" +luaj = "3.0.1" [libraries] @@ -24,6 +25,7 @@ jme3-testdata = { module = "org.jmonkeyengine:jme3-testdata", version.ref = "jme jme3-utilities-nifty = "com.github.stephengold:jme3-utilities-nifty:0.9.37" junit4 = "junit:junit:4.13.2" +luaj-jse = { module = "org.luaj:luaj-jse", version.ref = "luaj" } nifty-style-black = "com.github.nifty-gui:nifty-style-black:1.4.3" [bundles] diff --git a/settings.gradle b/settings.gradle index 1415978..4f522d2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ -// global build settings for the SkyControl project +// global build settings for the SkySimulation project -rootProject.name = 'SkyControl' +rootProject.name = 'SkySimulation' dependencyResolutionManagement { repositories {