├── .idea ├── .name ├── .gitignore ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── compiler.xml ├── kotlinc.xml ├── misc.xml └── gradle.xml ├── app ├── .gitignore ├── src │ └── main │ │ ├── ic_launcher-playstore.png │ │ ├── res │ │ ├── drawable-nodpi │ │ │ ├── kids.jpg │ │ │ ├── work.jpg │ │ │ ├── photo.jpg │ │ │ ├── travel.jpg │ │ │ ├── friends.jpg │ │ │ └── workout.jpg │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── values │ │ │ ├── ic_launcher_background.xml │ │ │ ├── dimensions.xml │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── themes.xml │ │ ├── layout │ │ │ ├── filament_host.xml │ │ │ ├── activity_main.xml │ │ │ ├── list_item_category.xml │ │ │ ├── fragment_wakeup_calls.xml │ │ │ └── fragment_categories.xml │ │ ├── anim │ │ │ ├── slide_in_right.xml │ │ │ ├── slide_in_left.xml │ │ │ ├── slide_out_left.xml │ │ │ └── slide_out_right.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── drawable-v24 │ │ │ ├── outline_add_24.xml │ │ │ ├── add_alarm_24.xml │ │ │ ├── outline_alarm_on_24.xml │ │ │ └── outline_alarm_off_24.xml │ │ ├── drawable │ │ │ ├── sharp_table_rows_24.xml │ │ │ ├── outline_expand_more_24.xml │ │ │ ├── sharp_grid_view_24.xml │ │ │ └── ic_launcher_foreground.xml │ │ └── navigation │ │ │ └── nav_graph.xml │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── wakemeup │ │ │ ├── ui │ │ │ └── theme │ │ │ │ ├── Colors.kt │ │ │ │ ├── Shape.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ ├── math │ │ │ ├── Ray.kt │ │ │ ├── Scalar.kt │ │ │ ├── Matrix.kt │ │ │ └── Vector.kt │ │ │ ├── Text.kt │ │ │ ├── MainActivity.kt │ │ │ ├── WakeupCalls.kt │ │ │ ├── Filament.kt │ │ │ ├── CategoriesFragment.kt │ │ │ ├── graphics │ │ │ ├── Viewer.kt │ │ │ └── Atmosphere.kt │ │ │ └── WakeupCallsFragment.kt │ │ └── materials │ │ └── sky.mat ├── proguard-rules.pro └── build.gradle ├── assets ├── keynote_demo_1.png └── keynote_demo_2.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── filament └── bin │ └── README.md ├── .gitignore ├── LICENSE ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /.idea/.name: -------------------------------------------------------------------------------- 1 | Wake Me Up -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /assets/keynote_demo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/assets/keynote_demo_1.png -------------------------------------------------------------------------------- /assets/keynote_demo_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/assets/keynote_demo_2.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/kids.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/drawable-nodpi/kids.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/work.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/drawable-nodpi/work.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/drawable-nodpi/photo.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/travel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/drawable-nodpi/travel.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/friends.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/drawable-nodpi/friends.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/workout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/drawable-nodpi/workout.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romainguy/sample-wake-me-up/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF3E4757 4 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 160dp 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | rootProject.name = "Wake Me Up" 9 | include ':app' 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/filament_host.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_out_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_out_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /filament/bin/README.md: -------------------------------------------------------------------------------- 1 | To build this demo, you must first download a copy of 2 | [Filament](https://github.com/google/filament/releases) for your operating system. 3 | 4 | After downloading the release, extract it and copy the binary called `matc` (Linux and macOS) or 5 | `matc.exe` (Windows) into folder `./filament/bin`. This binary is necessary to compile the shaders 6 | used by this demo. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | /filament/bin/matc 14 | /filament/bin/matc.exe 15 | /app/src/main/assets/materials/*.filamat 16 | .externalNativeBuild 17 | .cxx 18 | local.properties 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/outline_add_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sharp_table_rows_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_expand_more_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Alarms 3 | Wake Me Up 4 | Add alarm 5 | Add group 6 | Grid view 7 | List view 8 | Most recent 9 | Decoration 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF000000 4 | #FFFFFFFF 5 | 6 | #FF3E4757 7 | #FF3E4757 8 | #FF1A1B1E 9 | #FFC4C4C4 10 | #FF9DECFC 11 | #FF9DECFC 12 | #FF000000 13 | #FF9DECFC 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/add_alarm_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/outline_alarm_on_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sharp_grid_view_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/outline_alarm_off_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Romain Guy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 18 | 19 | 20 | 24 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/ui/theme/Colors.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.example.wakemeup.ui.theme 26 | 27 | import androidx.compose.ui.graphics.Color 28 | 29 | val NightBlue = Color(0xFF14598F) 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/math/Ray.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.example.wakemeup.math 26 | 27 | data class Ray(var origin: Float3 = Float3(), var direction: Float3) 28 | 29 | fun pointAt(r: Ray, t: Float) = r.origin + r.direction * t 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wake Me Up 2 | 3 | Wake Me Up is a sample app showcased in the 4 | [Google I/O 2021 Developer Keynote](https://youtu.be/D_mVOAXcrtc?t=817) that demonstrates 5 | interoperability between [Jetpack Compose](https://developer.android.com/jetpack/compose) and the 6 | Android UI Toolkit, including fragments, `SurfaceView`, and more. 7 | 8 | ## How to build 9 | 10 | To build this demo, you must first download a copy of 11 | [Filament](https://github.com/google/filament/releases) for your operating system. Make sure to 12 | select a release that matches the version listed in [build.gradle](./build.gradle). The current 13 | release to use is [1.12.8](https://github.com/google/filament/releases/tag/v1.12.8). 14 | 15 | After downloading the release, extract it and copy the binary called `matc` (Linux and macOS) or 16 | `matc.exe` (Windows) into this project's `./filament/bin`. This binary is necessary to compile the 17 | shaders used by this demo. 18 | 19 | # Screenshots 20 | 21 |

22 | Main Screen 23 | Alarms Screen 24 |

25 | 26 | ## License 27 | 28 | All photos licensed under [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/) 29 | and available on [Flickr](https://www.flickr.com/photos/romainguy/). 30 | 31 | Licensed under MIT License. Please see [LICENSE](./LICENSE) for more information. 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item_category.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 18 | 19 | 25 | 26 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.example.wakemeup.ui.theme 26 | 27 | import androidx.compose.foundation.shape.RoundedCornerShape 28 | import androidx.compose.material.Shapes 29 | import androidx.compose.ui.unit.dp 30 | 31 | val Shapes = Shapes( 32 | small = RoundedCornerShape(4.dp), 33 | medium = RoundedCornerShape(4.dp), 34 | large = RoundedCornerShape(0.dp) 35 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/Text.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.example.wakemeup 26 | 27 | import com.example.wakemeup.math.fract 28 | import java.time.LocalDate 29 | import java.time.LocalDateTime 30 | import java.time.LocalTime 31 | import java.time.format.DateTimeFormatter 32 | import java.time.format.FormatStyle 33 | import kotlin.math.floor 34 | 35 | fun formattedTime(time: Float): String { 36 | val dateTime = LocalDateTime.of( 37 | LocalDate.now(), 38 | LocalTime.of(floor(time).toInt(), (fract(time) * 60.0f).toInt()) 39 | ) 40 | val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT) 41 | return dateTime.format(formatter) 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.example.wakemeup 26 | 27 | import android.os.Bundle 28 | import androidx.appcompat.app.AppCompatActivity 29 | import androidx.core.view.WindowCompat 30 | import com.example.wakemeup.databinding.ActivityMainBinding 31 | import com.google.android.filament.Filament 32 | 33 | class MainActivity : AppCompatActivity() { 34 | companion object { 35 | init { Filament.init() } 36 | } 37 | 38 | override fun onCreate(savedInstanceState: Bundle?) { 39 | super.onCreate(savedInstanceState) 40 | 41 | WindowCompat.setDecorFitsSystemWindows(window, false) 42 | 43 | val binding = ActivityMainBinding.inflate(layoutInflater) 44 | setContentView(binding.root) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.example.wakemeup.ui.theme 26 | 27 | import androidx.compose.material.MaterialTheme 28 | import androidx.compose.material.lightColors 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.ui.graphics.Color 31 | 32 | private val LightColorPalette = lightColors( 33 | primary = Color(0xFF3E4757), 34 | primaryVariant = Color(0xFF1A1B1E), 35 | secondary = Color(0xFF9DECFC), 36 | secondaryVariant = Color(0xFFC4C4C4), 37 | 38 | background = Color(0xFF3E4757), 39 | onBackground = Color(0xFF9DECFC), 40 | surface = Color(0xFFFFFFFF), 41 | onSurface = Color(0xFF000000), 42 | onPrimary = Color(0xFF9DECFC), 43 | onSecondary = Color(0xFF000000), 44 | ) 45 | 46 | @Composable 47 | fun WakeMeUpTheme(content: @Composable() () -> Unit) { 48 | MaterialTheme( 49 | colors = LightColorPalette, 50 | typography = Typography, 51 | shapes = Shapes, 52 | content = content 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/math/Scalar.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | @file:Suppress("NOTHING_TO_INLINE") 26 | 27 | package com.example.wakemeup.math 28 | 29 | import kotlin.math.floor 30 | 31 | const val Pi = 3.1415926536f 32 | const val HalfPi = Pi * 0.5f 33 | const val TwoPi = Pi * 2.0f 34 | const val FourPi = Pi * 4.0f 35 | const val InvPi = 1.0f / Pi 36 | const val InvTwoPi = InvPi * 0.5f 37 | const val InvFourPi = InvPi * 0.25f 38 | 39 | inline fun clamp(x: Float, min: Float, max: Float)= if (x < min) min else (if (x > max) max else x) 40 | 41 | inline fun saturate(x: Float) = clamp(x, 0.0f, 1.0f) 42 | 43 | inline fun mix(a: Float, b: Float, x: Float) = a * (1.0f - x) + b * x 44 | 45 | inline fun degrees(v: Float) = v * (180.0f * InvPi) 46 | 47 | inline fun radians(v: Float) = v * (Pi / 180.0f) 48 | 49 | inline fun fract(v: Float) = v - floor(v) 50 | 51 | inline fun sqr(v: Float) = v * v 52 | 53 | inline fun pow(x: Float, y: Float) = StrictMath.pow(x.toDouble(), y.toDouble()).toFloat() 54 | 55 | inline fun smoothstep(e0: Float, e1: Float, x: Float): Float { 56 | val t = clamp((x - e0) / (e1 - e0), 0.0f, 1.0f) 57 | return t * t * (3.0f - 2.0f * t) 58 | } 59 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | apply plugin: FilamentToolsPlugin 7 | 8 | android { 9 | compileSdk 33 10 | buildToolsVersion "33.0.0" 11 | 12 | defaultConfig { 13 | applicationId "com.example.wakemeup" 14 | minSdk 28 15 | targetSdk 33 16 | versionCode 1 17 | versionName "1.0" 18 | 19 | vectorDrawables { 20 | useSupportLibrary true 21 | } 22 | } 23 | 24 | namespace 'com.example.wakemeup' 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled true 29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | 38 | kotlinOptions { 39 | jvmTarget = '1.8' 40 | } 41 | 42 | buildFeatures { 43 | compose true 44 | viewBinding true 45 | } 46 | 47 | composeOptions { 48 | kotlinCompilerExtensionVersion compose_version 49 | } 50 | 51 | aaptOptions { 52 | noCompress("filamat", "ktx", "glb") 53 | } 54 | } 55 | 56 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { compile -> 57 | kotlinOptions { 58 | // Allow use of @OptIn 59 | freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" 60 | } 61 | } 62 | 63 | dependencies { 64 | implementation "androidx.core:core-ktx:1.9.0" 65 | implementation 'androidx.appcompat:appcompat:1.5.1' 66 | implementation "androidx.compose.ui:ui:$compose_version" 67 | implementation "androidx.compose.material:material:$compose_version" 68 | implementation "androidx.compose.ui:ui-tooling:$compose_version" 69 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1" 70 | implementation "androidx.navigation:navigation-fragment-ktx:2.5.3" 71 | implementation "androidx.navigation:navigation-ui-ktx:2.5.3" 72 | implementation "com.google.android.material:material:1.7.0" 73 | 74 | implementation 'org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.5' 75 | 76 | implementation "net.e175.klaus:solarpositioning:0.0.9" 77 | 78 | implementation "com.google.android.filament:filament-android:$filament_version" 79 | implementation "com.google.android.filament:filament-utils-android:$filament_version" 80 | } 81 | 82 | filamentTools { 83 | materialInputDir.value(project.layout.projectDirectory.dir("src/main/materials")) 84 | materialOutputDir.value(project.layout.projectDirectory.dir("src/main/assets/materials")) 85 | } 86 | 87 | clean.doFirst { 88 | delete "src/main/assets" 89 | } 90 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_wakeup_calls.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 27 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | 47 | 48 | 53 | 54 | 55 | 56 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.example.wakemeup.ui.theme 26 | 27 | import androidx.compose.material.Typography 28 | import androidx.compose.ui.text.TextStyle 29 | import androidx.compose.ui.text.font.Font 30 | import androidx.compose.ui.text.font.FontFamily 31 | import androidx.compose.ui.text.font.FontWeight 32 | import androidx.compose.ui.unit.em 33 | import androidx.compose.ui.unit.sp 34 | import com.example.wakemeup.R 35 | 36 | val Typography= Typography( 37 | h1 = TextStyle( 38 | fontWeight = FontWeight.W100, 39 | fontSize = 76.sp, 40 | ), 41 | h2 = TextStyle( 42 | fontWeight = FontWeight.SemiBold, 43 | fontSize = 44.sp, 44 | letterSpacing = 1.5.sp 45 | ), 46 | h3 = TextStyle( 47 | fontWeight = FontWeight.W400, 48 | fontSize = 18.sp 49 | ), 50 | h4 = TextStyle( 51 | fontWeight = FontWeight.W700, 52 | fontSize = 18.sp 53 | ), 54 | h5 = TextStyle( 55 | fontWeight = FontWeight.W400, 56 | fontSize = 18.sp 57 | ), 58 | h6 = TextStyle( 59 | fontWeight = FontWeight.W400, 60 | fontSize = 15.sp 61 | ), 62 | subtitle1 = TextStyle( 63 | fontWeight = FontWeight.Light, 64 | fontSize = 14.sp, 65 | lineHeight = 20.sp, 66 | letterSpacing = 3.sp 67 | ), 68 | subtitle2 = TextStyle( 69 | fontWeight = FontWeight.Normal, 70 | fontSize = 14.sp, 71 | letterSpacing = 0.1.em 72 | ), 73 | body1 = TextStyle( 74 | fontWeight = FontWeight.Normal, 75 | fontSize = 16.sp, 76 | letterSpacing = 0.1.em 77 | ), 78 | body2 = TextStyle( 79 | fontWeight = FontWeight.Normal, 80 | fontSize = 14.sp, 81 | lineHeight = 20.sp, 82 | letterSpacing = 0.1.em 83 | ), 84 | button = TextStyle( 85 | fontWeight = FontWeight.Bold, 86 | fontSize = 14.sp, 87 | lineHeight = 16.sp, 88 | letterSpacing = 0.2.em 89 | ), 90 | caption = TextStyle( 91 | fontWeight = FontWeight.W500, 92 | fontSize = 12.sp 93 | ), 94 | overline = TextStyle( 95 | fontWeight = FontWeight.W500, 96 | fontSize = 10.sp 97 | ) 98 | ) 99 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 26 | 27 | 28 | 32 | 33 | 37 | 38 | 42 | 43 | 49 | 50 | 56 | 57 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/WakeupCalls.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.example.wakemeup 26 | 27 | import androidx.compose.runtime.getValue 28 | import androidx.compose.runtime.mutableStateOf 29 | import androidx.compose.runtime.setValue 30 | import com.example.wakemeup.graphics.computeSunriseTime 31 | import com.example.wakemeup.graphics.computeSunsetTime 32 | import java.util.* 33 | 34 | class WakeupCall( 35 | val id: Long, 36 | enabled: Boolean, 37 | time: Float, 38 | location: Pair, 39 | elevation: Double, 40 | timeZone: TimeZone, 41 | name: String 42 | ) { 43 | var enabled by mutableStateOf(enabled) 44 | var time by mutableStateOf(time) 45 | var location by mutableStateOf(location) 46 | var elevation by mutableStateOf(elevation) 47 | var timeZone by mutableStateOf(timeZone) 48 | var name by mutableStateOf(name) 49 | } 50 | 51 | data class Category(val id: Long, val name: String, val photo: Int) 52 | 53 | val Categories = listOf( 54 | Category(0, "Work", R.drawable.work), 55 | Category(1, "Friends", R.drawable.friends), 56 | Category(2, "Travel", R.drawable.travel), 57 | Category(3, "Kids", R.drawable.kids), 58 | Category(4, "Photo", R.drawable.photo), 59 | Category(5, "Workout", R.drawable.workout) 60 | ) 61 | 62 | val WakeupCalls = listOf( 63 | WakeupCall( 64 | 0, 65 | true, 66 | 10.50f, 67 | 37.45 to -122.18, 68 | 10.0, 69 | TimeZone.getTimeZone("America/Los_Angeles"), 70 | "Mountain View" 71 | ), 72 | WakeupCall( 73 | 2, 74 | true, 75 | computeSunsetTime(48.85 to 2.35, TimeZone.getTimeZone("Europe/Paris")) - 0.3f, 76 | 48.85 to 2.35, 77 | 30.0, 78 | TimeZone.getTimeZone("Europe/Paris"), 79 | "Paris" 80 | ), 81 | WakeupCall( 82 | 3, 83 | false, 84 | computeSunriseTime(48.85 to 2.35, TimeZone.getTimeZone("Europe/Paris")) + 1.5f, 85 | 48.85 to 2.35, 86 | 30.0, 87 | TimeZone.getTimeZone("Europe/Paris"), 88 | "Paris" 89 | ), 90 | WakeupCall( 91 | 4, 92 | true, 93 | 5.84f, 94 | 64.15 to -21.95, 95 | 60.0, 96 | TimeZone.getTimeZone("UTC+0"), 97 | "Reykjavík" 98 | ), 99 | WakeupCall( 100 | 5, 101 | false, 102 | 15.75f, 103 | 37.45 to -122.18, 104 | 10.0, 105 | TimeZone.getTimeZone("America/Los_Angeles"), 106 | "Mountain View" 107 | ), 108 | ) 109 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_categories.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 26 | 27 | 45 | 46 | 61 | 62 | 73 | 74 | 84 | 85 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 119 | 120 | 122 | 123 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/Filament.kt: -------------------------------------------------------------------------------- 1 | package com.example.wakemeup 2 | 3 | import android.content.res.AssetManager 4 | import com.example.wakemeup.math.Float4 5 | import com.example.wakemeup.math.radians 6 | import com.google.android.filament.* 7 | import java.nio.ByteBuffer 8 | import java.nio.ByteOrder 9 | import java.nio.channels.Channels 10 | import kotlin.math.cos 11 | import kotlin.math.sin 12 | 13 | const val SunAngularRadius = 2.2f 14 | const val SunHaloSize = 2.0f 15 | const val SunHaloFalloff = 1.0f 16 | /* 17 | * MIT License 18 | * 19 | * Copyright (c) 2021 Romain Guy 20 | * 21 | * Permission is hereby granted, free of charge, to any person obtaining a copy 22 | * of this software and associated documentation files (the "Software"), to deal 23 | * in the Software without restriction, including without limitation the rights 24 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | * copies of the Software, and to permit persons to whom the Software is 26 | * furnished to do so, subject to the following conditions: 27 | * 28 | * The above copyright notice and this permission notice shall be included in all 29 | * copies or substantial portions of the Software. 30 | * 31 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | * SOFTWARE. 38 | */ 39 | const val SunLightIntensity = 100_000.0f 40 | 41 | const val Aperture = 16f 42 | const val ShutterSpeed = 1f / 125f 43 | const val Sensitivity = 100f 44 | 45 | data class SkyScene( 46 | val engine: Engine, 47 | val scene: Scene, 48 | val skybox: Int, 49 | val skyboxMaterial: MaterialInstance 50 | ) 51 | 52 | fun computeSunDisc( 53 | radius: Float = radians(SunAngularRadius), 54 | haloSize: Float = SunHaloSize, 55 | haloFalloff: Float = SunHaloFalloff 56 | ) = Float4( 57 | cos(radius), 58 | sin(radius), 59 | 1.0f / (cos(radius * haloSize) - cos(radius)), 60 | haloFalloff 61 | ) 62 | 63 | fun exposure(aperture: Float, shutterSpeed: Float, sensitivity: Float): Float { 64 | val e = (aperture * aperture) / shutterSpeed * 100.0f / sensitivity 65 | return 1.0f / (1.2f * e) 66 | } 67 | 68 | fun readUncompressedAsset(assets: AssetManager, assetName: String): ByteBuffer { 69 | assets.openFd(assetName).use { fd -> 70 | val input = fd.createInputStream() 71 | val dst = ByteBuffer.allocate(fd.length.toInt()) 72 | 73 | val src = Channels.newChannel(input) 74 | src.read(dst) 75 | src.close() 76 | 77 | return dst.apply { rewind() } 78 | } 79 | } 80 | 81 | fun createFullscreenTriangle(engine: Engine): Pair { 82 | val floatSize = 4 83 | val shortSize = 2 84 | val vertexSize = 4 * floatSize 85 | 86 | fun ByteBuffer.put(v: Float4): ByteBuffer { 87 | putFloat(v.x) 88 | putFloat(v.y) 89 | putFloat(v.z) 90 | putFloat(v.w) 91 | return this 92 | } 93 | 94 | val vertexCount = 3 95 | val vertexData = ByteBuffer.allocate(vertexCount * vertexSize) 96 | .order(ByteOrder.nativeOrder()) 97 | .put(Float4(-1.0f, -1.0f, 1.0f, 1.0f)) 98 | .put(Float4( 3.0f, -1.0f, 1.0f, 1.0f)) 99 | .put(Float4(-1.0f, 3.0f, 1.0f, 1.0f)) 100 | .flip() 101 | 102 | val vertexBuffer = VertexBuffer.Builder() 103 | .bufferCount(1) 104 | .vertexCount(vertexCount) 105 | .attribute( 106 | VertexBuffer.VertexAttribute.POSITION, 0, 107 | VertexBuffer.AttributeType.FLOAT4, 0, 108 | vertexSize 109 | ) 110 | .build(engine) 111 | vertexBuffer.setBufferAt(engine, 0, vertexData) 112 | 113 | val indexData = ByteBuffer.allocate(vertexCount * shortSize) 114 | .order(ByteOrder.nativeOrder()) 115 | .putShort(0) 116 | .putShort(1) 117 | .putShort(2) 118 | .flip() 119 | 120 | val indexBuffer = IndexBuffer.Builder() 121 | .indexCount(3) 122 | .bufferType(IndexBuffer.Builder.IndexType.USHORT) 123 | .build(engine) 124 | indexBuffer.setBuffer(engine, indexData) 125 | 126 | return indexBuffer to vertexBuffer 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/CategoriesFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.example.wakemeup 26 | 27 | import android.os.Bundle 28 | import android.view.LayoutInflater 29 | import android.view.View 30 | import android.view.ViewGroup 31 | import android.widget.ImageView 32 | import android.widget.TextView 33 | import androidx.core.view.ViewCompat 34 | import androidx.core.view.WindowInsetsCompat 35 | import androidx.core.view.updatePadding 36 | import androidx.fragment.app.Fragment 37 | import androidx.navigation.fragment.findNavController 38 | import androidx.recyclerview.widget.GridLayoutManager 39 | import androidx.recyclerview.widget.RecyclerView 40 | import com.example.wakemeup.databinding.FragmentCategoriesBinding 41 | import com.example.wakemeup.databinding.ListItemCategoryBinding 42 | 43 | class CategoriesFragment : Fragment() { 44 | private var _binding: FragmentCategoriesBinding? = null 45 | private val binding get() = _binding!! 46 | 47 | override fun onCreateView( 48 | inflater: LayoutInflater, container: ViewGroup?, 49 | savedInstanceState: Bundle? 50 | ): View { 51 | _binding = FragmentCategoriesBinding.inflate(inflater, container, false) 52 | return binding.root 53 | } 54 | 55 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 56 | super.onViewCreated(view, savedInstanceState) 57 | 58 | ViewCompat.setOnApplyWindowInsetsListener(binding.categoriesListContainer) { v, insets -> 59 | v.updatePadding(top = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top) 60 | insets 61 | } 62 | 63 | val recyclerView: RecyclerView = binding.categoriesList 64 | recyclerView.layoutManager = GridLayoutManager(view.context, 2) 65 | 66 | val onClickListener = View.OnClickListener { itemView -> 67 | val item = itemView.tag as Category 68 | val bundle = Bundle() 69 | bundle.putLong( 70 | WakeupCallsFragment.ARG_CATEGORY_ID, 71 | item.id 72 | ) 73 | findNavController().navigate(R.id.show_category_wakeupcalls, bundle) 74 | } 75 | 76 | setupRecyclerView(recyclerView, onClickListener) 77 | } 78 | 79 | private fun setupRecyclerView( 80 | recyclerView: RecyclerView, 81 | onClickListener: View.OnClickListener 82 | ) { 83 | recyclerView.adapter = SimpleItemRecyclerViewAdapter( 84 | Categories, 85 | onClickListener 86 | ) 87 | } 88 | 89 | class SimpleItemRecyclerViewAdapter( 90 | private val values: List, 91 | private val onClickListener: View.OnClickListener 92 | ) : 93 | RecyclerView.Adapter() { 94 | 95 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 96 | val binding = ListItemCategoryBinding.inflate( 97 | LayoutInflater.from(parent.context), parent, false) 98 | return ViewHolder(binding) 99 | } 100 | 101 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 102 | val item = values[position] 103 | holder.categoryTitleView.text = item.name 104 | holder.categoryImageView.setImageResource(item.photo) 105 | 106 | with(holder.itemView) { 107 | tag = item 108 | setOnClickListener(onClickListener) 109 | } 110 | } 111 | 112 | override fun getItemCount() = values.size 113 | 114 | inner class ViewHolder(binding: ListItemCategoryBinding) : 115 | RecyclerView.ViewHolder(binding.root) { 116 | val categoryTitleView: TextView = binding.categoryTitle 117 | val categoryImageView: ImageView = binding.categoryImage 118 | } 119 | } 120 | 121 | override fun onDestroyView() { 122 | super.onDestroyView() 123 | _binding = null 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/graphics/Viewer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.example.wakemeup.graphics 26 | 27 | import android.view.Surface 28 | import android.view.SurfaceView 29 | import com.example.wakemeup.Aperture 30 | import com.example.wakemeup.Sensitivity 31 | import com.example.wakemeup.ShutterSpeed 32 | import com.google.android.filament.* 33 | import com.google.android.filament.android.DisplayHelper 34 | import com.google.android.filament.android.UiHelper 35 | 36 | private const val NearPlane = 0.5 37 | private const val FarPlane = 100.0 38 | private const val FovDegrees = 60.0 39 | 40 | class Viewer( 41 | val engine: Engine, 42 | val surfaceView: SurfaceView 43 | ) { 44 | val view: View = engine.createView() 45 | val camera: Camera = EntityManager.get().create().run { 46 | engine.createCamera(this).apply { setExposure(Aperture, ShutterSpeed, Sensitivity) } 47 | } 48 | 49 | var scene: Scene? = null 50 | set(value) { 51 | view.scene = value 52 | field = value 53 | } 54 | 55 | private val uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK) 56 | private var displayHelper: DisplayHelper 57 | private var swapChain: SwapChain? = null 58 | private val renderer: Renderer = engine.createRenderer() 59 | 60 | init { 61 | view.camera = camera 62 | 63 | displayHelper = DisplayHelper(surfaceView.context) 64 | uiHelper.renderCallback = SurfaceCallback() 65 | uiHelper.attachTo(surfaceView) 66 | addDetachListener(surfaceView) 67 | 68 | camera.lookAt( 69 | 0.0, 0.0, 0.0, 70 | 0.0, 0.3, -1.0, 71 | 0.0, 1.0, 0.0 72 | ) 73 | } 74 | 75 | fun render(frameTimeNanos: Long) { 76 | if (!uiHelper.isReadyToRender) { 77 | return 78 | } 79 | 80 | // Render the scene, unless the renderer wants to skip the frame. 81 | if (renderer.beginFrame(swapChain!!, frameTimeNanos)) { 82 | renderer.render(view) 83 | renderer.endFrame() 84 | } 85 | } 86 | 87 | private fun addDetachListener(view: android.view.View) { 88 | class AttachListener : android.view.View.OnAttachStateChangeListener { 89 | var detached = false 90 | 91 | override fun onViewAttachedToWindow(v: android.view.View) { detached = false } 92 | 93 | override fun onViewDetachedFromWindow(v: android.view.View) { 94 | if (!detached) { 95 | uiHelper.detach() 96 | 97 | engine.destroyRenderer(renderer) 98 | engine.destroyView(this@Viewer.view) 99 | engine.destroyCameraComponent(camera.entity) 100 | 101 | detached = true 102 | } 103 | } 104 | } 105 | view.addOnAttachStateChangeListener(AttachListener()) 106 | } 107 | 108 | inner class SurfaceCallback : UiHelper.RendererCallback { 109 | override fun onNativeWindowChanged(surface: Surface) { 110 | swapChain?.let { engine.destroySwapChain(it) } 111 | swapChain = engine.createSwapChain(surface) 112 | displayHelper.attach(renderer, surfaceView.display) 113 | } 114 | 115 | override fun onDetachedFromSurface() { 116 | displayHelper.detach() 117 | swapChain?.let { 118 | engine.destroySwapChain(it) 119 | engine.flushAndWait() 120 | swapChain = null 121 | } 122 | } 123 | 124 | override fun onResized(width: Int, height: Int) { 125 | view.viewport = Viewport(0, 0, width, height) 126 | android.util.Log.d("Filament", "w=$width, h=$height") 127 | val aspect = width.toDouble() / height.toDouble() 128 | camera.setProjection(FovDegrees, aspect, NearPlane, FarPlane, Camera.Fov.VERTICAL) 129 | } 130 | } 131 | } 132 | 133 | fun setupViewer(viewer: Viewer) { 134 | viewer.view.run { 135 | antiAliasing = View.AntiAliasing.NONE 136 | 137 | dynamicResolutionOptions = dynamicResolutionOptions.apply { 138 | enabled = true 139 | } 140 | 141 | renderQuality = View.RenderQuality().apply { 142 | hdrColorBuffer = View.QualityLevel.MEDIUM 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/graphics/Atmosphere.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * Copyright (c) 2021 Felix Westin 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | // The atmosphere scattering code is adapted from https://github.com/Fewes/MinimalAtmosphere 27 | 28 | package com.example.wakemeup.graphics 29 | 30 | import androidx.compose.ui.graphics.Color 31 | import androidx.compose.ui.graphics.colorspace.ColorSpaces 32 | import com.example.wakemeup.WakeupCall 33 | import com.example.wakemeup.math.* 34 | import com.example.wakemeup.ui.theme.NightBlue 35 | import net.e175.klaus.solarpositioning.DeltaT 36 | import net.e175.klaus.solarpositioning.SPA 37 | import java.util.* 38 | import kotlin.math.* 39 | 40 | private const val Sunrise = 0 41 | private const val Sunset = 2 42 | 43 | private const val ScatteringSampleCount = 32 44 | private const val OpticalDepthSampleCount = 8 45 | 46 | private const val Eps = 1e-6f 47 | 48 | private const val Exposure = 40.0f 49 | 50 | private const val PlanetRadius = 6_371_000.0f 51 | private const val AtmosphereDensity = 1.0f 52 | private const val AtmosphereHeight = 100_000.0f 53 | private const val RayleighHeight = AtmosphereHeight * 0.080f 54 | private const val MieHeight = AtmosphereHeight * 0.012f 55 | 56 | private val PlanetCenter = Float3(0.0f, -PlanetRadius, 0.0f) 57 | 58 | private val CoefficientsRayleigh = Float3(5.802f, 13.558f, 33.100f) * Eps 59 | private val CoefficientsMie = Float3(3.996f, 3.996f, 3.996f) * Eps 60 | private val CoefficientsOzone = Float3(0.650f, 1.881f, 0.085f) * Eps 61 | 62 | private fun sphereIntersection( 63 | rayStart: Float3, 64 | rayDir: Float3, 65 | sphereCenter: Float3, 66 | sphereRadius: Float 67 | ): Float2 { 68 | val start = rayStart - sphereCenter 69 | val a = dot(rayDir, rayDir) 70 | val b = 2.0f * dot(start, rayDir) 71 | val c = dot(start, start) - (sphereRadius * sphereRadius) 72 | val d = b * b - 4.0f * a * c 73 | return if (d < 0.0) { 74 | Float2(-1.0f) 75 | } else { 76 | val e = sqrt(d) 77 | Float2(-b - e, -b + e) / (2.0f * a) 78 | } 79 | } 80 | 81 | private fun planetIntersection(rayStart: Float3, rayDir: Float3) = 82 | sphereIntersection(rayStart, rayDir, PlanetCenter, PlanetRadius) 83 | 84 | private fun atmosphereIntersection(rayStart: Float3, rayDir: Float3) = 85 | sphereIntersection(rayStart, rayDir, PlanetCenter, PlanetRadius + AtmosphereHeight) 86 | 87 | private fun phaseMie(costh: Float, g: Float = 0.85f): Float { 88 | val mg = min(g, 0.9381f) 89 | val k = 1.55f * g - 0.55f * mg * mg * mg 90 | val kcosth = k * costh 91 | return (1.0f - k * k) / ((4.0f * Pi) * (1.0f - kcosth) * (1.0f - kcosth)) 92 | } 93 | 94 | private fun phaseRayleigh(costh: Float) = 3.0f * (1.0f + costh * costh) / (16.0f * Pi) 95 | 96 | private fun atmosphereHeight(positionWS: Float3) = distance(positionWS, PlanetCenter) - PlanetRadius 97 | 98 | private fun densityRayleigh(h: Float) = exp(-max(0.0f, h / RayleighHeight)) 99 | 100 | private fun densityMie(h: Float) = exp(-max(0.0f, h / MieHeight)) 101 | 102 | private fun densityOzone(h: Float) = max(0.0f, 1.0f - abs(h - 25000.0f) / 15000.0f) 103 | 104 | private fun atmosphereDensity(h: Float) = Float3(densityRayleigh(h), densityMie(h), densityOzone(h)) 105 | 106 | private fun integrateOpticalDepth(rayStart: Float3, rayDir: Float3): Float3 { 107 | val intersection = atmosphereIntersection(rayStart, rayDir) 108 | val rayLength = intersection.y 109 | 110 | val sampleCount = OpticalDepthSampleCount 111 | val stepSize = rayLength / sampleCount.toFloat() 112 | 113 | var opticalDepth = Float3(0.0f) 114 | 115 | for (i in 0 until sampleCount) { 116 | val localPosition = rayStart + rayDir * (i.toFloat() + 0.5f) * stepSize 117 | val localHeight = atmosphereHeight(localPosition) 118 | val localDensity = atmosphereDensity(localHeight) * stepSize 119 | 120 | opticalDepth += localDensity 121 | } 122 | 123 | return opticalDepth 124 | } 125 | 126 | private fun absorb(opticalDepth: Float3): Float3 { 127 | val absorption = 128 | opticalDepth.x * CoefficientsRayleigh + 129 | opticalDepth.y * CoefficientsMie * 1.1f + 130 | opticalDepth.z * CoefficientsOzone 131 | return Float3( 132 | exp(-absorption.x * AtmosphereDensity), 133 | exp(-absorption.y * AtmosphereDensity), 134 | exp(-absorption.z * AtmosphereDensity) 135 | ) 136 | } 137 | 138 | private fun integrateScattering( 139 | rayStart: Float3, 140 | rayDir: Float3, 141 | rayLength: Float, 142 | lightDir: Float3, 143 | lightColor: Float3 144 | ): Float3 { 145 | val rayHeight = atmosphereHeight(rayStart) 146 | val sampleDistributionExponent = 1.0f + saturate(1.0f - rayHeight / AtmosphereHeight) * 8.0f 147 | 148 | val intersection = atmosphereIntersection(rayStart, rayDir) 149 | var start = rayStart 150 | var length = min(rayLength, intersection.y) 151 | if (intersection.x > 0.0f) { 152 | start += rayDir * intersection.x 153 | length -= intersection.x 154 | } 155 | 156 | val costh = dot(rayDir, lightDir) 157 | val phaseR = phaseRayleigh(costh) 158 | val phaseM = phaseMie(costh) 159 | 160 | val sampleCount = ScatteringSampleCount 161 | 162 | var opticalDepth = Float3(0.0f) 163 | var rayleigh = Float3(0.0f) 164 | var mie = Float3(0.0f) 165 | 166 | var prevRayTime = 0.0f 167 | 168 | for (i in 0 until sampleCount) { 169 | val rayTime = pow(i / sampleCount.toFloat(), sampleDistributionExponent) * length 170 | val stepSize = rayTime - prevRayTime 171 | 172 | val localPosition = start + rayDir * rayTime 173 | val localHeight = atmosphereHeight(localPosition) 174 | val localDensity = atmosphereDensity(localHeight) * stepSize 175 | 176 | opticalDepth += localDensity 177 | 178 | val opticalDepthlight = integrateOpticalDepth(localPosition, lightDir) 179 | val lightTransmittance = absorb(opticalDepth + opticalDepthlight) 180 | 181 | rayleigh += lightTransmittance * phaseR * localDensity.x 182 | mie += lightTransmittance * phaseM * localDensity.y 183 | 184 | prevRayTime = rayTime 185 | } 186 | 187 | // We don't need to return the transmittance for our needs here 188 | // transmittance = absorb(opticalDepth); 189 | 190 | return (rayleigh * CoefficientsRayleigh + mie * CoefficientsMie) * lightColor 191 | } 192 | 193 | private fun integrateScatteringColor( 194 | lightDir: Float3, 195 | rayDir: Float3, 196 | rayStart: Float3 = Float3(0.0f), 197 | rayLength: Float = Float.POSITIVE_INFINITY, 198 | lightColor: Float3 = Float3(1.0f) 199 | ): Color { 200 | val color = 201 | (integrateScattering(rayStart, rayDir, rayLength, lightDir, lightColor) * Exposure).apply { 202 | this / max(this) 203 | } 204 | return Color( 205 | saturate(color.x), 206 | saturate(color.y), 207 | saturate(color.z), 208 | 1.0f, 209 | ColorSpaces.LinearSrgb 210 | ) 211 | } 212 | 213 | fun computeSunDirection(call: WakeupCall): Float3 { 214 | val refTime = GregorianCalendar() 215 | val dateTime = GregorianCalendar(call.timeZone) 216 | dateTime.set( 217 | refTime.get(GregorianCalendar.YEAR), 218 | refTime.get(GregorianCalendar.MONTH), 219 | refTime.get(GregorianCalendar.DAY_OF_MONTH), 220 | floor(call.time).toInt(), 221 | (fract(call.time) * 60.0f).toInt() 222 | ) 223 | 224 | val position = SPA.calculateSolarPosition( 225 | dateTime, 226 | call.location.first, 227 | call.location.second, 228 | call.elevation, 229 | DeltaT.estimate(dateTime) 230 | ) 231 | 232 | val zenith = HalfPi - radians(position.zenithAngle.toFloat()) 233 | val azimuth = radians(position.azimuth.toFloat()) 234 | 235 | return Float3( 236 | cos(zenith) * sin(azimuth), 237 | sin(zenith), 238 | cos(zenith) * cos(azimuth) 239 | ) 240 | } 241 | 242 | fun computeSunsetTime( 243 | location: Pair, 244 | timeZone: TimeZone 245 | ) = computeSunEventTime(location, timeZone, Sunset, 18.5f) 246 | 247 | fun computeSunriseTime( 248 | location: Pair, 249 | timeZone: TimeZone 250 | ) = computeSunEventTime(location, timeZone, Sunrise, 6.5f) 251 | 252 | private fun computeSunEventTime( 253 | location: Pair, 254 | timeZone: TimeZone, 255 | event: Int, 256 | defaultTime: Float 257 | ): Float { 258 | val refDate = GregorianCalendar() 259 | val date = GregorianCalendar(timeZone) 260 | date.set( 261 | refDate.get(GregorianCalendar.YEAR), 262 | refDate.get(GregorianCalendar.MONTH), 263 | refDate.get(GregorianCalendar.DAY_OF_MONTH) 264 | ) 265 | 266 | // TODO: We should use a proper deltaT 267 | val events = SPA.calculateSunriseTransitSet(date, location.first, location.second, 68.0) 268 | val sunEventTime = events[event] 269 | if (sunEventTime != null) { 270 | val hour = sunEventTime.get(GregorianCalendar.HOUR_OF_DAY) 271 | val minutes = sunEventTime.get(GregorianCalendar.MINUTE) 272 | return hour.toFloat() + minutes / 60.0f 273 | } 274 | 275 | // The sun is always below the horizon, return a made up time 276 | return defaultTime 277 | } 278 | 279 | fun computeSunColor(direction: Float3) = integrateScatteringColor(direction, direction) 280 | 281 | fun computeSunUiColor(direction: Float3, sunColor: Color) = 282 | if (planetIntersection(Float3(10.0f), direction).y > 0.0) { 283 | NightBlue 284 | } else { 285 | sunColor 286 | } 287 | -------------------------------------------------------------------------------- /app/src/main/materials/sky.mat: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 Romain Guy 4 | // Copyright (c) 2021 Felix Westin 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | // The following code is adapted from https://github.com/Fewes/MinimalAtmosphere 25 | 26 | material { 27 | name : Skybox, 28 | parameters : [ 29 | { 30 | type : float3, 31 | name : lightDirection 32 | }, 33 | { 34 | type : float3, 35 | name : lightColor 36 | }, 37 | { 38 | type : float4, 39 | name : sun 40 | }, 41 | { 42 | type : float, 43 | name : lightIntensity 44 | } 45 | ], 46 | variables : [ 47 | eyeDirection 48 | ], 49 | vertexDomain : device, 50 | depthWrite : false, 51 | shadingModel : unlit, 52 | variantFilter : [ skinning, shadowReceiver, vsm ], 53 | culling: none 54 | } 55 | 56 | fragment { 57 | #define SCATTERING_SAMPLE_COUNT 32.0 58 | #define OPTICAL_DEPTH_SAMPLE_COUNT 8.0 59 | 60 | #define EPS 1e-6.0 61 | #define INFINITY 1.0 / 0.0 62 | #define PLANET_RADIUS 6371000.0 63 | #define PLANET_CENTER float3(0, -PLANET_RADIUS, 0.0) 64 | #define ATMOSPHERE_HEIGHT 100000.0 65 | #define RAYLEIGH_HEIGHT (ATMOSPHERE_HEIGHT * 0.08) 66 | #define MIE_HEIGHT (ATMOSPHERE_HEIGHT * 0.012) 67 | 68 | // ------------------------------------- 69 | // Coefficients 70 | #define C_RAYLEIGH (float3(5.802, 13.558, 33.100) * 1e-6) 71 | #define C_MIE (float3(3.996, 3.996, 3.996) * 1e-6) 72 | #define C_OZONE (float3(0.650, 1.881, 0.085) * 1e-6) 73 | 74 | #define ATMOSPHERE_DENSITY 1.0 75 | #define EXPOSURE 20.0 76 | 77 | float2 sphereIntersection( 78 | float3 rayStart, 79 | float3 rayDir, 80 | float3 sphereCenter, 81 | float sphereRadius 82 | ) { 83 | rayStart -= sphereCenter; 84 | float a = dot(rayDir, rayDir); 85 | float b = 2.0 * dot(rayStart, rayDir); 86 | float c = dot(rayStart, rayStart) - (sphereRadius * sphereRadius); 87 | float d = b * b - 4.0 * a * c; 88 | if (d < 0.0) { 89 | return float2(-1.0); 90 | } else { 91 | d = sqrt(d); 92 | return float2(-b - d, -b + d) / (2.0 * a); 93 | } 94 | } 95 | 96 | float2 planetIntersection(float3 rayStart, float3 rayDir) { 97 | return sphereIntersection(rayStart, rayDir, PLANET_CENTER, PLANET_RADIUS); 98 | } 99 | 100 | float2 atmosphereIntersection(float3 rayStart, float3 rayDir) { 101 | return sphereIntersection(rayStart, rayDir, PLANET_CENTER, PLANET_RADIUS + ATMOSPHERE_HEIGHT); 102 | } 103 | 104 | // Phase functions 105 | float phaseRayleigh(float costh) { 106 | return 3.0 * (1.0 + costh * costh) / (16.0 * PI); 107 | } 108 | 109 | float phaseMie(float costh) { 110 | float g = 0.85; // TODO: default value, use an overload to pass g as a parameter 111 | g = min(g, 0.9381); 112 | float k = 1.55 * g - 0.55 * g * g * g; 113 | float kcosth = k * costh; 114 | return (1.0 - k * k) / ((4.0 * PI) * (1.0 - kcosth) * (1.0 - kcosth)); 115 | } 116 | 117 | // Atmosphere 118 | float atmosphereHeight(float3 positionWS) { 119 | return distance(positionWS, PLANET_CENTER) - PLANET_RADIUS; 120 | } 121 | 122 | float densityRayleigh (float h) { 123 | return exp(-max(0.0, h / RAYLEIGH_HEIGHT)); 124 | } 125 | 126 | float densityMie(float h) { 127 | return exp(-max(0.0, h / MIE_HEIGHT)); 128 | } 129 | 130 | float densityOzone(float h) { 131 | // The ozone layer is represented as a tent function with a width of 30km, centered 132 | // around an altitude of 25km. 133 | return max(0.0, 1.0 - abs(h - 25000.0) / 15000.0); 134 | } 135 | 136 | float3 atmosphereDensity(float h) { 137 | return float3(densityRayleigh(h), densityMie(h), densityOzone(h)); 138 | } 139 | 140 | // Optical depth is a unitless measurement of the amount of absorption of a participating medium 141 | // (such as the atmosphere). 142 | // This function calculates just that for our three atmospheric elements: 143 | // R: Rayleigh 144 | // G: Mie 145 | // B: Ozone 146 | // If you find the term "optical depth" confusing, you can think of it as "how much density 147 | // was found along the ray in total". 148 | float3 integrateOpticalDepth(float3 rayStart, float3 rayDir) { 149 | float2 intersection = atmosphereIntersection(rayStart, rayDir); 150 | float rayLength = intersection.y; 151 | 152 | float sampleCount = OPTICAL_DEPTH_SAMPLE_COUNT; 153 | float stepSize = rayLength / sampleCount; 154 | 155 | float3 opticalDepth = float3(0.0); 156 | 157 | for (float i = 0.0; i < sampleCount; i += 1.0) { 158 | float3 localPosition = rayStart + rayDir * (i + 0.5) * stepSize; 159 | float localHeight = atmosphereHeight(localPosition); 160 | float3 localDensity = atmosphereDensity(localHeight) * stepSize; 161 | 162 | opticalDepth += localDensity; 163 | } 164 | 165 | return opticalDepth; 166 | } 167 | 168 | // Calculate a luminance transmittance value from optical depth. 169 | float3 absorb(float3 opticalDepth) { 170 | // Note that Mie results in slightly more light absorption than scattering, about 10% 171 | return exp( 172 | -(opticalDepth.x * C_RAYLEIGH + opticalDepth.y * C_MIE * 1.1 + 173 | opticalDepth.z * C_OZONE) * ATMOSPHERE_DENSITY); 174 | } 175 | 176 | // Integrate scattering over a ray for a single directional light source. 177 | // Also return the transmittance for the same ray as we are already calculating the 178 | // optical depth anyway. 179 | float3 integrateScattering( 180 | float3 rayStart, 181 | float3 rayDir, 182 | float rayLength, 183 | float3 lightDir, 184 | float3 lightColor, 185 | out float3 transmittance 186 | ) { 187 | // We can reduce the number of atmospheric samples required to converge by spacing them 188 | // exponentially closer to the camera. 189 | // This breaks space view however, so let's compensate for that with an exponent that 190 | // "fades" to 1 as we leave the atmosphere. 191 | float rayHeight = atmosphereHeight(rayStart); 192 | float sampleDistributionExponent = 193 | 1.0 + saturate(1.0 - rayHeight / ATMOSPHERE_HEIGHT) * 8.0; 194 | 195 | float2 intersection = atmosphereIntersection(rayStart, rayDir); 196 | rayLength = min(rayLength, intersection.y); 197 | if (intersection.x > 0.0) { 198 | // Advance ray to the atmosphere entry point 199 | rayStart += rayDir * intersection.x; 200 | rayLength -= intersection.x; 201 | } 202 | 203 | float costh = dot(rayDir, lightDir); 204 | float phaseR = phaseRayleigh(costh); 205 | float phaseM = phaseMie(costh); 206 | 207 | float sampleCount = SCATTERING_SAMPLE_COUNT; 208 | 209 | float3 opticalDepth = float3(0.0); 210 | float3 rayleigh = float3(0.0); 211 | float3 mie = float3(0.0); 212 | 213 | float prevRayTime = 0.0; 214 | 215 | for (float i = 0.0; i < sampleCount; i += 1.0) { 216 | float rayTime = pow(i / sampleCount, sampleDistributionExponent) * rayLength; 217 | // Because we are distributing the samples exponentially, we have to calculate 218 | // the step size per sample. 219 | float stepSize = (rayTime - prevRayTime); 220 | 221 | float3 localPosition = rayStart + rayDir * rayTime; 222 | float localHeight = atmosphereHeight(localPosition); 223 | float3 localDensity = atmosphereDensity(localHeight) * stepSize; 224 | 225 | opticalDepth += localDensity; 226 | 227 | float3 opticalDepthLight = integrateOpticalDepth(localPosition, lightDir); 228 | float3 lightTransmittance = absorb(opticalDepth + opticalDepthLight); 229 | 230 | rayleigh += lightTransmittance * phaseR * localDensity.x; 231 | mie += lightTransmittance * phaseM * localDensity.y; 232 | 233 | prevRayTime = rayTime; 234 | } 235 | 236 | transmittance = absorb(opticalDepth); 237 | 238 | return (rayleigh * C_RAYLEIGH + mie * C_MIE) * lightColor * EXPOSURE; 239 | } 240 | 241 | 242 | void material(inout MaterialInputs material) { 243 | prepareMaterial(material); 244 | 245 | float3 rayStart = getWorldCameraPosition() + getWorldOffset(); 246 | float3 rayDir = normalize(getWorldPosition() - getWorldCameraPosition()); 247 | float rayLength = INFINITY; 248 | 249 | float3 transmittance; 250 | float3 lightDir = materialParams.lightDirection; 251 | float3 lightColor = materialParams.lightColor; 252 | 253 | float4 sky; 254 | sky.rgb = integrateScattering( 255 | rayStart, rayDir, rayLength, lightDir, float3(1.0), transmittance); 256 | sky.a = 1.0; 257 | 258 | if (planetIntersection(rayStart, rayDir).x < 0.0) { 259 | float3 direction = normalize(variable_eyeDirection.xyz); 260 | float3 sun = lightColor * (materialParams.lightIntensity * (4.0 * PI)); 261 | float cosAngle = dot(direction, lightDir); 262 | float x = (cosAngle - materialParams.sun.x) * materialParams.sun.z; 263 | float gradient = pow(1.0 - saturate(x), materialParams.sun.w); 264 | sky.rgb += gradient * sun; 265 | } 266 | 267 | material.baseColor = sky; 268 | } 269 | } 270 | 271 | vertex { 272 | void materialVertex(inout MaterialVertexInputs material) { 273 | float3 p = getPosition().xyz; 274 | float3 unprojected = mulMat4x4Float3(getViewFromClipMatrix(), p).xyz; 275 | material.eyeDirection.xyz = mulMat3x3Float3(getWorldFromViewMatrix(), unprojected); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/WakeupCallsFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.example.wakemeup 26 | 27 | import android.os.Bundle 28 | import android.view.LayoutInflater 29 | import android.view.SurfaceView 30 | import android.view.View 31 | import android.view.ViewGroup 32 | import android.widget.FrameLayout 33 | import androidx.compose.animation.AnimatedVisibility 34 | import androidx.compose.animation.ExperimentalAnimationApi 35 | import androidx.compose.foundation.clickable 36 | import androidx.compose.foundation.layout.* 37 | import androidx.compose.foundation.shape.RoundedCornerShape 38 | import androidx.compose.material.* 39 | import androidx.compose.runtime.* 40 | import androidx.compose.ui.Alignment 41 | import androidx.compose.ui.Modifier 42 | import androidx.compose.ui.draw.clip 43 | import androidx.compose.ui.graphics.Color 44 | import androidx.compose.ui.res.painterResource 45 | import androidx.compose.ui.unit.dp 46 | import androidx.compose.ui.viewinterop.AndroidView 47 | import androidx.core.view.ViewCompat 48 | import androidx.core.view.WindowInsetsCompat 49 | import androidx.core.view.updatePadding 50 | import androidx.fragment.app.Fragment 51 | import com.example.wakemeup.databinding.FragmentWakeupCallsBinding 52 | import com.example.wakemeup.graphics.* 53 | import com.example.wakemeup.math.Float3 54 | import com.example.wakemeup.math.normalize 55 | import com.example.wakemeup.math.pow 56 | import com.example.wakemeup.math.saturate 57 | import com.example.wakemeup.ui.theme.WakeMeUpTheme 58 | import com.google.android.filament.* 59 | import kotlin.math.max 60 | 61 | class WakeupCallsFragment : Fragment() { 62 | //region Initialization 63 | private lateinit var engine: Engine 64 | private lateinit var skyMaterial: Material 65 | private lateinit var fullscreenTriangle: Pair 66 | private var filamentInitialized = false 67 | private val scenes = mutableMapOf() 68 | 69 | companion object { 70 | const val ARG_CATEGORY_ID = "category_id" 71 | } 72 | 73 | private var category: Category? = null 74 | private lateinit var wakeupCalls: List 75 | 76 | private var _binding: FragmentWakeupCallsBinding? = null 77 | private val binding get() = _binding!! 78 | 79 | override fun onCreate(savedInstanceState: Bundle?) { 80 | super.onCreate(savedInstanceState) 81 | 82 | arguments?.let { 83 | if (it.containsKey(ARG_CATEGORY_ID)) { 84 | val id = it.getLong(ARG_CATEGORY_ID) 85 | category = Categories.find { category -> category.id == id} 86 | wakeupCalls = WakeupCalls 87 | } 88 | } 89 | 90 | initFilament() 91 | } 92 | //endregion 93 | 94 | override fun onCreateView( 95 | inflater: LayoutInflater, container: ViewGroup?, 96 | savedInstanceState: Bundle? 97 | ): View { 98 | val rootView = setupBindings(inflater, container) 99 | 100 | binding.composeView.setContent { 101 | WakeMeUpTheme { 102 | Column { 103 | Header() 104 | for (wakeupCall in wakeupCalls) { 105 | AlarmCard(wakeupCall) 106 | } 107 | } 108 | } 109 | } 110 | 111 | return rootView 112 | } 113 | 114 | @OptIn(ExperimentalAnimationApi::class) 115 | @Composable 116 | fun AlarmCard(wakeupCall: WakeupCall) { 117 | val sunDirection = remember(wakeupCall.time) { computeSunDirection(wakeupCall) } 118 | val sunColor = remember(sunDirection) { computeSunColor(sunDirection) } 119 | val expanded = remember { mutableStateOf(false) } 120 | 121 | Surface( 122 | modifier = Modifier 123 | .fillMaxWidth() 124 | .padding(16.dp, 6.dp) 125 | .clip(RoundedCornerShape(12.dp)), 126 | color = cardColor(wakeupCall), 127 | contentColor = cardContentColor(wakeupCall) 128 | ) { 129 | Column( 130 | modifier = Modifier.clickable { expanded.value = !expanded.value } 131 | ) { 132 | AlarmTitle(wakeupCall) 133 | 134 | AnimatedVisibility(visible = expanded.value) { 135 | AlarmEditor(wakeupCall, sunDirection, sunColor) 136 | } 137 | } 138 | } 139 | } 140 | 141 | @Composable 142 | fun Header() { 143 | Row(modifier = Modifier.padding(32.dp, 16.dp, 32.dp, 8.dp)) { 144 | Text( 145 | modifier = Modifier.weight(1.0f), 146 | text = "ALARM", 147 | style = MaterialTheme.typography.h6, 148 | color = MaterialTheme.colors.secondaryVariant 149 | ) 150 | Text( 151 | text = "LOCATION", 152 | style = MaterialTheme.typography.h6, 153 | color = MaterialTheme.colors.secondaryVariant 154 | ) 155 | } 156 | } 157 | 158 | @Composable 159 | fun AlarmTitle(wakeupCall: WakeupCall) { 160 | Row( 161 | modifier = Modifier 162 | .fillMaxWidth() 163 | .padding(16.dp) 164 | ) { 165 | Icon( 166 | modifier = Modifier 167 | .align(Alignment.CenterVertically) 168 | .padding(end = 8.dp), 169 | painter = if (wakeupCall.enabled) { 170 | painterResource(id = R.drawable.outline_alarm_on_24) 171 | } else { 172 | painterResource(id = R.drawable.outline_alarm_off_24) 173 | }, 174 | tint = LocalContentColor.current, 175 | contentDescription = "Alarm state" 176 | ) 177 | 178 | Text( 179 | modifier = Modifier.weight(1.0f), 180 | text = formattedTime(wakeupCall.time), 181 | style = MaterialTheme.typography.h4 182 | ) 183 | 184 | Text( 185 | modifier = Modifier.align(Alignment.CenterVertically), 186 | text = wakeupCall.name, 187 | style = MaterialTheme.typography.h5 188 | ) 189 | } 190 | } 191 | 192 | @Composable 193 | fun AlarmEditor(wakeupCall: WakeupCall, sunDirection: Float3, sunColor: Color) { 194 | val sunUiColor = remember(sunDirection) { computeSunUiColor(sunDirection, sunColor) } 195 | 196 | Box { 197 | SkyViewer(wakeupCall, sunDirection, sunColor) 198 | 199 | Slider( 200 | modifier = Modifier 201 | .align(Alignment.BottomCenter) 202 | .padding(horizontal = 16.dp), 203 | colors = SliderDefaults.colors( 204 | thumbColor = sunUiColor, 205 | activeTrackColor = sunUiColor 206 | ), 207 | valueRange = 0.0f..23.99f, 208 | value = wakeupCall.time, 209 | onValueChange = { v -> wakeupCall.time = v } 210 | ) 211 | } 212 | } 213 | 214 | @Composable 215 | fun SkyViewer(wakeupCall: WakeupCall, sunDirection: Float3, sunColor: Color) { 216 | var viewer by remember { mutableStateOf(null) } 217 | 218 | LaunchedEffect(sunDirection, sunColor, viewer) { 219 | withFrameNanos { frameTimeNanos -> 220 | val skyScene = scenes[wakeupCall.id]!! 221 | setSkyProperties(skyScene.skyboxMaterial, sunDirection, sunColor) 222 | val direction = normalize(Float3(sunDirection.x, 0.0f, sunDirection.z)) 223 | viewer?.camera?.lookAt( 224 | 0.0, 0.0, 0.0, 225 | direction.x.toDouble(), 0.3, direction.z.toDouble(), 226 | 0.0, 1.0, 0.0 227 | ) 228 | viewer?.render(frameTimeNanos) 229 | } 230 | } 231 | 232 | AndroidView({ context -> 233 | LayoutInflater.from(context).inflate( 234 | R.layout.filament_host, FrameLayout(context), false 235 | ).apply { 236 | val skyScene = createScene(wakeupCall.id) 237 | viewer = Viewer(skyScene.engine, this as SurfaceView).also { 238 | setupViewer(it) 239 | it.scene = skyScene.scene 240 | } 241 | } 242 | }) 243 | } 244 | 245 | //region Support function 246 | @Composable 247 | private fun cardContentColor(wakeupCall: WakeupCall) = if (wakeupCall.enabled) { 248 | MaterialTheme.colors.onSurface 249 | } else { 250 | MaterialTheme.colors.surface 251 | } 252 | 253 | @Composable 254 | private fun cardColor(wakeupCall: WakeupCall) = if (wakeupCall.enabled) { 255 | MaterialTheme.colors.surface 256 | } else { 257 | MaterialTheme.colors.background 258 | } 259 | 260 | private fun setupBindings( 261 | inflater: LayoutInflater, 262 | container: ViewGroup? 263 | ): View { 264 | _binding = FragmentWakeupCallsBinding.inflate(inflater, container, false) 265 | 266 | val rootView = binding.root 267 | ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets -> 268 | val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) 269 | v.updatePadding(top = systemBarInsets.top, bottom = systemBarInsets.bottom) 270 | insets 271 | } 272 | 273 | binding.toolbarLayout.title = category?.name 274 | 275 | return rootView 276 | } 277 | 278 | override fun onDestroyView() { 279 | super.onDestroyView() 280 | _binding = null 281 | } 282 | 283 | override fun onDestroy() { 284 | super.onDestroy() 285 | 286 | if (filamentInitialized) { 287 | filamentInitialized = false 288 | 289 | scenes.forEach { 290 | engine.destroyScene(it.value.scene) 291 | engine.destroyEntity(it.value.skybox) 292 | engine.destroyMaterialInstance(it.value.skyboxMaterial) 293 | } 294 | 295 | engine.destroyIndexBuffer(fullscreenTriangle.first) 296 | engine.destroyVertexBuffer(fullscreenTriangle.second) 297 | engine.destroyMaterial(skyMaterial) 298 | engine.destroy() 299 | } 300 | } 301 | 302 | private fun initFilament() { 303 | if (!filamentInitialized) { 304 | filamentInitialized = true 305 | 306 | engine = Engine.create() 307 | 308 | readUncompressedAsset(context?.assets!!, "materials/sky.filamat").let { 309 | skyMaterial = Material.Builder().payload(it, it.remaining()).build(engine) 310 | } 311 | 312 | fullscreenTriangle = createFullscreenTriangle(engine) 313 | } 314 | } 315 | 316 | private fun createScene(id: Long): SkyScene { 317 | val scene = engine.createScene() 318 | 319 | val skyboxMaterial = skyMaterial.createInstance() 320 | val sun = computeSunDisc() 321 | skyboxMaterial.setParameter("sun", sun.x, sun.y, sun.z, sun.w) 322 | 323 | val skybox = EntityManager.get().create() 324 | RenderableManager.Builder(1) 325 | .geometry(0, RenderableManager.PrimitiveType.TRIANGLES, 326 | fullscreenTriangle.second, 327 | fullscreenTriangle.first 328 | ) 329 | .material(0, skyboxMaterial) 330 | .castShadows(false) 331 | .receiveShadows(false) 332 | .priority(0x7) 333 | .culling(false) 334 | .build(engine, skybox) 335 | 336 | scene.addEntity(skybox) 337 | 338 | scenes[id] = SkyScene(engine, scene, skybox, skyboxMaterial) 339 | 340 | return scenes[id]!! 341 | } 342 | 343 | private fun setSkyProperties(sky: MaterialInstance, lightDirection: Float3, lightColor: Color) { 344 | sky.setParameter("lightDirection", lightDirection.x, lightDirection.y, lightDirection.z) 345 | sky.setParameter("lightColor", lightColor.red, lightColor.green, lightColor.blue) 346 | // TODO: we should compute the actual intensity of the sun disc in the given direction 347 | sky.setParameter("lightIntensity", 348 | SunLightIntensity * saturate(pow(max(lightDirection.y, 1e-3f), 0.6f)) * 349 | exposure(Aperture, ShutterSpeed, Sensitivity)) 350 | } 351 | //endregion 352 | } 353 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/math/Matrix.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.example.wakemeup.math 26 | 27 | import kotlin.math.* 28 | 29 | enum class MatrixColumn { 30 | X, Y, Z, W 31 | } 32 | 33 | data class Mat2( 34 | var x: Float2 = Float2(x = 1.0f), 35 | var y: Float2 = Float2(y = 1.0f)) { 36 | constructor(m: Mat2) : this(m.x.copy(), m.y.copy()) 37 | 38 | companion object { 39 | fun of(vararg a: Float): Mat2 { 40 | require(a.size >= 4) 41 | return Mat2( 42 | Float2(a[0], a[2]), 43 | Float2(a[1], a[3]) 44 | ) 45 | } 46 | 47 | fun identity() = Mat2() 48 | } 49 | 50 | operator fun get(column: Int) = when(column) { 51 | 0 -> x 52 | 1 -> y 53 | else -> throw IllegalArgumentException("column must be in 0..1") 54 | } 55 | operator fun get(column: Int, row: Int) = get(column)[row] 56 | 57 | operator fun get(column: MatrixColumn) = when(column) { 58 | MatrixColumn.X -> x 59 | MatrixColumn.Y -> y 60 | else -> throw IllegalArgumentException("column must be X or Y") 61 | } 62 | operator fun get(column: MatrixColumn, row: Int) = get(column)[row] 63 | 64 | operator fun invoke(row: Int, column: Int) = get(column - 1)[row - 1] 65 | operator fun invoke(row: Int, column: Int, v: Float) = set(column - 1, row - 1, v) 66 | 67 | operator fun set(column: Int, v: Float2) { 68 | this[column].xy = v 69 | } 70 | operator fun set(column: Int, row: Int, v: Float) { 71 | this[column][row] = v 72 | } 73 | 74 | operator fun unaryMinus() = Mat2(-x, -y) 75 | operator fun inc(): Mat2 { 76 | x++ 77 | y++ 78 | return this 79 | } 80 | operator fun dec(): Mat2 { 81 | x-- 82 | y-- 83 | return this 84 | } 85 | 86 | operator fun plus(v: Float) = Mat2(x + v, y + v) 87 | operator fun minus(v: Float) = Mat2(x - v, y - v) 88 | operator fun times(v: Float) = Mat2(x * v, y * v) 89 | operator fun div(v: Float) = Mat2(x / v, y / v) 90 | 91 | operator fun times(m: Mat2): Mat2 { 92 | val t = transpose(this) 93 | return Mat2( 94 | Float2(dot(t.x, m.x), dot(t.y, m.x)), 95 | Float2(dot(t.x, m.y), dot(t.y, m.y)) 96 | ) 97 | } 98 | 99 | operator fun times(v: Float2): Float2 { 100 | val t = transpose(this) 101 | return Float2(dot(t.x, v), dot(t.y, v)) 102 | } 103 | 104 | fun toFloatArray() = floatArrayOf( 105 | x.x, y.x, 106 | x.y, y.y 107 | ) 108 | 109 | override fun toString(): String { 110 | return """ 111 | |${x.x} ${y.x}| 112 | |${x.y} ${y.y}| 113 | """.trimIndent() 114 | } 115 | 116 | } 117 | 118 | data class Mat3( 119 | var x: Float3 = Float3(x = 1.0f), 120 | var y: Float3 = Float3(y = 1.0f), 121 | var z: Float3 = Float3(z = 1.0f)) { 122 | constructor(m: Mat3) : this(m.x.copy(), m.y.copy(), m.z.copy()) 123 | 124 | companion object { 125 | fun of(vararg a: Float): Mat3 { 126 | require(a.size >= 9) 127 | return Mat3( 128 | Float3(a[0], a[3], a[6]), 129 | Float3(a[1], a[4], a[7]), 130 | Float3(a[2], a[5], a[8]) 131 | ) 132 | } 133 | 134 | fun identity() = Mat3() 135 | } 136 | 137 | operator fun get(column: Int) = when(column) { 138 | 0 -> x 139 | 1 -> y 140 | 2 -> z 141 | else -> throw IllegalArgumentException("column must be in 0..2") 142 | } 143 | operator fun get(column: Int, row: Int) = get(column)[row] 144 | 145 | operator fun get(column: MatrixColumn) = when(column) { 146 | MatrixColumn.X -> x 147 | MatrixColumn.Y -> y 148 | MatrixColumn.Z -> z 149 | else -> throw IllegalArgumentException("column must be X, Y or Z") 150 | } 151 | operator fun get(column: MatrixColumn, row: Int) = get(column)[row] 152 | 153 | operator fun invoke(row: Int, column: Int) = get(column - 1)[row - 1] 154 | operator fun invoke(row: Int, column: Int, v: Float) = set(column - 1, row - 1, v) 155 | 156 | operator fun set(column: Int, v: Float3) { 157 | this[column].xyz = v 158 | } 159 | operator fun set(column: Int, row: Int, v: Float) { 160 | this[column][row] = v 161 | } 162 | 163 | operator fun unaryMinus() = Mat3(-x, -y, -z) 164 | operator fun inc(): Mat3 { 165 | x++ 166 | y++ 167 | z++ 168 | return this 169 | } 170 | operator fun dec(): Mat3 { 171 | x-- 172 | y-- 173 | z-- 174 | return this 175 | } 176 | 177 | operator fun plus(v: Float) = Mat3(x + v, y + v, z + v) 178 | operator fun minus(v: Float) = Mat3(x - v, y - v, z - v) 179 | operator fun times(v: Float) = Mat3(x * v, y * v, z * v) 180 | operator fun div(v: Float) = Mat3(x / v, y / v, z / v) 181 | 182 | operator fun times(m: Mat3): Mat3 { 183 | val t = transpose(this) 184 | return Mat3( 185 | Float3(dot(t.x, m.x), dot(t.y, m.x), dot(t.z, m.x)), 186 | Float3(dot(t.x, m.y), dot(t.y, m.y), dot(t.z, m.y)), 187 | Float3(dot(t.x, m.z), dot(t.y, m.z), dot(t.z, m.z)) 188 | ) 189 | } 190 | 191 | operator fun times(v: Float3): Float3 { 192 | val t = transpose(this) 193 | return Float3(dot(t.x, v), dot(t.y, v), dot(t.z, v)) 194 | } 195 | 196 | fun toFloatArray() = floatArrayOf( 197 | x.x, y.x, z.x, 198 | x.y, y.y, z.y, 199 | x.z, y.z, z.z 200 | ) 201 | 202 | override fun toString(): String { 203 | return """ 204 | |${x.x} ${y.x} ${z.x}| 205 | |${x.y} ${y.y} ${z.y}| 206 | |${x.z} ${y.z} ${z.z}| 207 | """.trimIndent() 208 | } 209 | } 210 | 211 | data class Mat4( 212 | var x: Float4 = Float4(x = 1.0f), 213 | var y: Float4 = Float4(y = 1.0f), 214 | var z: Float4 = Float4(z = 1.0f), 215 | var w: Float4 = Float4(w = 1.0f)) { 216 | constructor(right: Float3, up: Float3, forward: Float3, position: Float3 = Float3()) : 217 | this(Float4(right), Float4(up), Float4(forward), Float4(position, 1.0f)) 218 | constructor(m: Mat4) : this(m.x.copy(), m.y.copy(), m.z.copy(), m.w.copy()) 219 | 220 | companion object { 221 | fun of(vararg a: Float): Mat4 { 222 | require(a.size >= 16) 223 | return Mat4( 224 | Float4(a[0], a[4], a[8], a[12]), 225 | Float4(a[1], a[5], a[9], a[13]), 226 | Float4(a[2], a[6], a[10], a[14]), 227 | Float4(a[3], a[7], a[11], a[15]) 228 | ) 229 | } 230 | 231 | fun identity() = Mat4() 232 | } 233 | 234 | inline var right: Float3 235 | get() = x.xyz 236 | set(value) { 237 | x.xyz = value 238 | } 239 | inline var up: Float3 240 | get() = y.xyz 241 | set(value) { 242 | y.xyz = value 243 | } 244 | inline var forward: Float3 245 | get() = z.xyz 246 | set(value) { 247 | z.xyz = value 248 | } 249 | inline var position: Float3 250 | get() = w.xyz 251 | set(value) { 252 | w.xyz = value 253 | } 254 | 255 | inline val scale: Float3 256 | get() = Float3(length(x.xyz), length(y.xyz), length(z.xyz)) 257 | inline val translation: Float3 258 | get() = w.xyz 259 | val rotation: Float3 260 | get() { 261 | val x = normalize(right) 262 | val y = normalize(up) 263 | val z = normalize(forward) 264 | 265 | return when { 266 | z.y <= -1.0f -> Float3(degrees(-HalfPi), 0.0f, degrees(atan2( x.z, y.z))) 267 | z.y >= 1.0f -> Float3(degrees( HalfPi), 0.0f, degrees(atan2(-x.z, -y.z))) 268 | else -> Float3( 269 | degrees(-asin(z.y)), degrees(-atan2(z.x, z.z)), degrees(atan2( x.y, y.y))) 270 | } 271 | } 272 | 273 | inline val upperLeft: Mat3 274 | get() = Mat3(x.xyz, y.xyz, z.xyz) 275 | 276 | operator fun get(column: Int) = when(column) { 277 | 0 -> x 278 | 1 -> y 279 | 2 -> z 280 | 3 -> w 281 | else -> throw IllegalArgumentException("column must be in 0..3") 282 | } 283 | operator fun get(column: Int, row: Int) = get(column)[row] 284 | 285 | operator fun get(column: MatrixColumn) = when(column) { 286 | MatrixColumn.X -> x 287 | MatrixColumn.Y -> y 288 | MatrixColumn.Z -> z 289 | MatrixColumn.W -> w 290 | } 291 | operator fun get(column: MatrixColumn, row: Int) = get(column)[row] 292 | 293 | operator fun invoke(row: Int, column: Int) = get(column - 1)[row - 1] 294 | operator fun invoke(row: Int, column: Int, v: Float) = set(column - 1, row - 1, v) 295 | 296 | operator fun set(column: Int, v: Float4) { 297 | this[column].xyzw = v 298 | } 299 | operator fun set(column: Int, row: Int, v: Float) { 300 | this[column][row] = v 301 | } 302 | 303 | operator fun unaryMinus() = Mat4(-x, -y, -z, -w) 304 | operator fun inc(): Mat4 { 305 | x++ 306 | y++ 307 | z++ 308 | w++ 309 | return this 310 | } 311 | operator fun dec(): Mat4 { 312 | x-- 313 | y-- 314 | z-- 315 | w-- 316 | return this 317 | } 318 | 319 | operator fun plus(v: Float) = Mat4(x + v, y + v, z + v, w + v) 320 | operator fun minus(v: Float) = Mat4(x - v, y - v, z - v, w - v) 321 | operator fun times(v: Float) = Mat4(x * v, y * v, z * v, w * v) 322 | operator fun div(v: Float) = Mat4(x / v, y / v, z / v, w / v) 323 | 324 | operator fun times(m: Mat4): Mat4 { 325 | val t = transpose(this) 326 | return Mat4( 327 | Float4(dot(t.x, m.x), dot(t.y, m.x), dot(t.z, m.x), dot(t.w, m.x)), 328 | Float4(dot(t.x, m.y), dot(t.y, m.y), dot(t.z, m.y), dot(t.w, m.y)), 329 | Float4(dot(t.x, m.z), dot(t.y, m.z), dot(t.z, m.z), dot(t.w, m.z)), 330 | Float4(dot(t.x, m.w), dot(t.y, m.w), dot(t.z, m.w), dot(t.w, m.w)) 331 | ) 332 | } 333 | 334 | operator fun times(v: Float4): Float4 { 335 | val t = transpose(this) 336 | return Float4(dot(t.x, v), dot(t.y, v), dot(t.z, v), dot(t.w, v)) 337 | } 338 | 339 | fun toFloatArray() = floatArrayOf( 340 | x.x, y.x, z.x, w.x, 341 | x.y, y.y, z.y, w.y, 342 | x.z, y.z, z.z, w.z, 343 | x.w, y.w, z.w, w.w 344 | ) 345 | 346 | override fun toString(): String { 347 | return """ 348 | |${x.x} ${y.x} ${z.x} ${w.x}| 349 | |${x.y} ${y.y} ${z.y} ${w.y}| 350 | |${x.z} ${y.z} ${z.z} ${w.z}| 351 | |${x.w} ${y.w} ${z.w} ${w.w}| 352 | """.trimIndent() 353 | } 354 | } 355 | 356 | fun transpose(m: Mat2) = Mat2( 357 | Float2(m.x.x, m.y.x), 358 | Float2(m.x.y, m.y.y) 359 | ) 360 | 361 | fun transpose(m: Mat3) = Mat3( 362 | Float3(m.x.x, m.y.x, m.z.x), 363 | Float3(m.x.y, m.y.y, m.z.y), 364 | Float3(m.x.z, m.y.z, m.z.z) 365 | ) 366 | fun inverse(m: Mat3): Mat3 { 367 | val a = m.x.x 368 | val b = m.x.y 369 | val c = m.x.z 370 | val d = m.y.x 371 | val e = m.y.y 372 | val f = m.y.z 373 | val g = m.z.x 374 | val h = m.z.y 375 | val i = m.z.z 376 | 377 | val A = e * i - f * h 378 | val B = f * g - d * i 379 | val C = d * h - e * g 380 | 381 | val det = a * A + b * B + c * C 382 | 383 | return Mat3.of( 384 | A / det, B / det, C / det, 385 | (c * h - b * i) / det, (a * i - c * g) / det, (b * g - a * h) / det, 386 | (b * f - c * e) / det, (c * d - a * f) / det, (a * e - b * d) / det 387 | ) 388 | } 389 | 390 | fun transpose(m: Mat4) = Mat4( 391 | Float4(m.x.x, m.y.x, m.z.x, m.w.x), 392 | Float4(m.x.y, m.y.y, m.z.y, m.w.y), 393 | Float4(m.x.z, m.y.z, m.z.z, m.w.z), 394 | Float4(m.x.w, m.y.w, m.z.w, m.w.w) 395 | ) 396 | fun inverse(m: Mat4): Mat4 { 397 | val result = Mat4() 398 | 399 | var pair0 = m.z.z * m.w.w 400 | var pair1 = m.w.z * m.z.w 401 | var pair2 = m.y.z * m.w.w 402 | var pair3 = m.w.z * m.y.w 403 | var pair4 = m.y.z * m.z.w 404 | var pair5 = m.z.z * m.y.w 405 | var pair6 = m.x.z * m.w.w 406 | var pair7 = m.w.z * m.x.w 407 | var pair8 = m.x.z * m.z.w 408 | var pair9 = m.z.z * m.x.w 409 | var pair10 = m.x.z * m.y.w 410 | var pair11 = m.y.z * m.x.w 411 | 412 | result.x.x = pair0 * m.y.y + pair3 * m.z.y + pair4 * m.w.y 413 | result.x.x -= pair1 * m.y.y + pair2 * m.z.y + pair5 * m.w.y 414 | result.x.y = pair1 * m.x.y + pair6 * m.z.y + pair9 * m.w.y 415 | result.x.y -= pair0 * m.x.y + pair7 * m.z.y + pair8 * m.w.y 416 | result.x.z = pair2 * m.x.y + pair7 * m.y.y + pair10 * m.w.y 417 | result.x.z -= pair3 * m.x.y + pair6 * m.y.y + pair11 * m.w.y 418 | result.x.w = pair5 * m.x.y + pair8 * m.y.y + pair11 * m.z.y 419 | result.x.w -= pair4 * m.x.y + pair9 * m.y.y + pair10 * m.z.y 420 | result.y.x = pair1 * m.y.x + pair2 * m.z.x + pair5 * m.w.x 421 | result.y.x -= pair0 * m.y.x + pair3 * m.z.x + pair4 * m.w.x 422 | result.y.y = pair0 * m.x.x + pair7 * m.z.x + pair8 * m.w.x 423 | result.y.y -= pair1 * m.x.x + pair6 * m.z.x + pair9 * m.w.x 424 | result.y.z = pair3 * m.x.x + pair6 * m.y.x + pair11 * m.w.x 425 | result.y.z -= pair2 * m.x.x + pair7 * m.y.x + pair10 * m.w.x 426 | result.y.w = pair4 * m.x.x + pair9 * m.y.x + pair10 * m.z.x 427 | result.y.w -= pair5 * m.x.x + pair8 * m.y.x + pair11 * m.z.x 428 | 429 | pair0 = m.z.x * m.w.y 430 | pair1 = m.w.x * m.z.y 431 | pair2 = m.y.x * m.w.y 432 | pair3 = m.w.x * m.y.y 433 | pair4 = m.y.x * m.z.y 434 | pair5 = m.z.x * m.y.y 435 | pair6 = m.x.x * m.w.y 436 | pair7 = m.w.x * m.x.y 437 | pair8 = m.x.x * m.z.y 438 | pair9 = m.z.x * m.x.y 439 | pair10 = m.x.x * m.y.y 440 | pair11 = m.y.x * m.x.y 441 | 442 | result.z.x = pair0 * m.y.w + pair3 * m.z.w + pair4 * m.w.w 443 | result.z.x -= pair1 * m.y.w + pair2 * m.z.w + pair5 * m.w.w 444 | result.z.y = pair1 * m.x.w + pair6 * m.z.w + pair9 * m.w.w 445 | result.z.y -= pair0 * m.x.w + pair7 * m.z.w + pair8 * m.w.w 446 | result.z.z = pair2 * m.x.w + pair7 * m.y.w + pair10 * m.w.w 447 | result.z.z -= pair3 * m.x.w + pair6 * m.y.w + pair11 * m.w.w 448 | result.z.w = pair5 * m.x.w + pair8 * m.y.w + pair11 * m.z.w 449 | result.z.w -= pair4 * m.x.w + pair9 * m.y.w + pair10 * m.z.w 450 | result.w.x = pair2 * m.z.z + pair5 * m.w.z + pair1 * m.y.z 451 | result.w.x -= pair4 * m.w.z + pair0 * m.y.z + pair3 * m.z.z 452 | result.w.y = pair8 * m.w.z + pair0 * m.x.z + pair7 * m.z.z 453 | result.w.y -= pair6 * m.z.z + pair9 * m.w.z + pair1 * m.x.z 454 | result.w.z = pair6 * m.y.z + pair11 * m.w.z + pair3 * m.x.z 455 | result.w.z -= pair10 * m.w.z + pair2 * m.x.z + pair7 * m.y.z 456 | result.w.w = pair10 * m.z.z + pair4 * m.x.z + pair9 * m.y.z 457 | result.w.w -= pair8 * m.y.z + pair11 * m.z.z + pair5 * m.x.z 458 | 459 | val determinant = m.x.x * result.x.x + m.y.x * result.x.y + m.z.x * result.x.z + m.w.x * result.x.w 460 | 461 | return result / determinant 462 | } 463 | 464 | fun scale(s: Float3) = Mat4(Float4(x = s.x), Float4(y = s.y), Float4(z = s.z)) 465 | fun scale(m: Mat4) = scale(m.scale) 466 | 467 | fun translation(t: Float3) = Mat4(w = Float4(t, 1.0f)) 468 | fun translation(m: Mat4) = translation(m.translation) 469 | 470 | fun rotation(m: Mat4) = Mat4(normalize(m.right), normalize(m.up), normalize(m.forward)) 471 | fun rotation(d: Float3): Mat4 { 472 | val r = transform(d, ::radians) 473 | val c = transform(r, { x -> cos(x) }) 474 | val s = transform(r, { x -> sin(x) }) 475 | 476 | return Mat4.of( 477 | c.y * c.z, -c.x * s.z + s.x * s.y * c.z, s.x * s.z + c.x * s.y * c.z, 0.0f, 478 | c.y * s.z, c.x * c.z + s.x * s.y * s.z, -s.x * c.z + c.x * s.y * s.z, 0.0f, 479 | -s.y , s.x * c.y , c.x * c.y , 0.0f, 480 | 0.0f , 0.0f , 0.0f , 1.0f 481 | ) 482 | } 483 | fun rotation(axis: Float3, angle: Float): Mat4 { 484 | val x = axis.x 485 | val y = axis.y 486 | val z = axis.z 487 | 488 | val r = radians(angle) 489 | val c = cos(r) 490 | val s = sin(r) 491 | val d = 1.0f - c 492 | 493 | return Mat4.of( 494 | x * x * d + c , x * y * d - z * s, x * z * d + y * s, 0.0f, 495 | y * x * d + z * s, y * y * d + c , y * z * d - x * s, 0.0f, 496 | z * x * d - y * s, z * y * d + x * s, z * z * d + c , 0.0f, 497 | 0.0f , 0.0f , 0.0f , 1.0f 498 | ) 499 | } 500 | 501 | fun normal(m: Mat4) = scale(1.0f / Float3(length2(m.right), length2(m.up), length2(m.forward))) * m 502 | 503 | fun lookAt(eye: Float3, target: Float3, up: Float3 = Float3(z = 1.0f)): Mat4 { 504 | return lookTowards(eye, target - eye, up) 505 | } 506 | 507 | fun lookTowards(eye: Float3, forward: Float3, up: Float3 = Float3(z = 1.0f)): Mat4 { 508 | val f = normalize(forward) 509 | val r = normalize(f x up) 510 | val u = normalize(r x f) 511 | return Mat4(Float4(r), Float4(u), Float4(f), Float4(eye, 1.0f)) 512 | } 513 | 514 | fun perspective(fov: Float, ratio: Float, near: Float, far: Float): Mat4 { 515 | val t = 1.0f / tan(radians(fov) * 0.5f) 516 | val a = (far + near) / (far - near) 517 | val b = (2.0f * far * near) / (far - near) 518 | val c = t / ratio 519 | return Mat4(Float4(x = c), Float4(y = t), Float4(z = a, w = 1.0f), Float4(z = -b)) 520 | } 521 | 522 | fun ortho(l: Float, r: Float, b: Float, t: Float, n: Float, f: Float) = Mat4( 523 | Float4(x = 2.0f / (r - 1.0f)), 524 | Float4(y = 2.0f / (t - b)), 525 | Float4(z = -2.0f / (f - n)), 526 | Float4(-(r + l) / (r - l), -(t + b) / (t - b), -(f + n) / (f - n), 1.0f) 527 | ) 528 | 529 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/wakemeup/math/Vector.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Romain Guy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | @file:Suppress("NOTHING_TO_INLINE") 26 | 27 | package com.example.wakemeup.math 28 | 29 | import kotlin.math.abs 30 | import kotlin.math.max 31 | import kotlin.math.min 32 | import kotlin.math.sqrt 33 | 34 | enum class VectorComponent { 35 | X, Y, Z, W, 36 | R, G, B, A, 37 | S, T, P, Q 38 | } 39 | 40 | data class Float2(var x: Float = 0.0f, var y: Float = 0.0f) { 41 | constructor(v: Float) : this(v, v) 42 | constructor(v: Float2) : this(v.x, v.y) 43 | 44 | inline var r: Float 45 | get() = x 46 | set(value) { 47 | x = value 48 | } 49 | inline var g: Float 50 | get() = y 51 | set(value) { 52 | y = value 53 | } 54 | 55 | inline var s: Float 56 | get() = x 57 | set(value) { 58 | x = value 59 | } 60 | inline var t: Float 61 | get() = y 62 | set(value) { 63 | y = value 64 | } 65 | 66 | inline var xy: Float2 67 | get() = Float2(x, y) 68 | set(value) { 69 | x = value.x 70 | y = value.y 71 | } 72 | inline var rg: Float2 73 | get() = Float2(x, y) 74 | set(value) { 75 | x = value.x 76 | y = value.y 77 | } 78 | inline var st: Float2 79 | get() = Float2(x, y) 80 | set(value) { 81 | x = value.x 82 | y = value.y 83 | } 84 | 85 | operator fun get(index: VectorComponent) = when (index) { 86 | VectorComponent.X, VectorComponent.R, VectorComponent.S -> x 87 | VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y 88 | else -> throw IllegalArgumentException("index must be X, Y, R, G, S or T") 89 | } 90 | 91 | operator fun get(index1: VectorComponent, index2: VectorComponent): Float2 { 92 | return Float2(get(index1), get(index2)) 93 | } 94 | 95 | operator fun get(index: Int) = when (index) { 96 | 0 -> x 97 | 1 -> y 98 | else -> throw IllegalArgumentException("index must be in 0..1") 99 | } 100 | 101 | operator fun get(index1: Int, index2: Int) = Float2(get(index1), get(index2)) 102 | 103 | inline operator fun invoke(index: Int) = get(index - 1) 104 | 105 | operator fun set(index: Int, v: Float) = when (index) { 106 | 0 -> x = v 107 | 1 -> y = v 108 | else -> throw IllegalArgumentException("index must be in 0..1") 109 | } 110 | 111 | operator fun set(index1: Int, index2: Int, v: Float) { 112 | set(index1, v) 113 | set(index2, v) 114 | } 115 | 116 | operator fun set(index: VectorComponent, v: Float) = when (index) { 117 | VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v 118 | VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v 119 | else -> throw IllegalArgumentException("index must be X, Y, R, G, S or T") 120 | } 121 | 122 | operator fun set(index1: VectorComponent, index2: VectorComponent, v: Float) { 123 | set(index1, v) 124 | set(index2, v) 125 | } 126 | 127 | operator fun unaryMinus() = Float2(-x, -y) 128 | operator fun inc(): Float2 { 129 | x += 1.0f 130 | y += 1.0f 131 | return this 132 | } 133 | 134 | operator fun dec(): Float2 { 135 | x -= 1.0f 136 | y -= 1.0f 137 | return this 138 | } 139 | 140 | inline operator fun plus(v: Float) = Float2(x + v, y + v) 141 | inline operator fun minus(v: Float) = Float2(x - v, y - v) 142 | inline operator fun times(v: Float) = Float2(x * v, y * v) 143 | inline operator fun div(v: Float) = Float2(x / v, y / v) 144 | 145 | inline operator fun plus(v: Float2) = Float2(x + v.x, y + v.y) 146 | inline operator fun minus(v: Float2) = Float2(x - v.x, y - v.y) 147 | inline operator fun times(v: Float2) = Float2(x * v.x, y * v.y) 148 | inline operator fun div(v: Float2) = Float2(x / v.x, y / v.y) 149 | 150 | inline fun transform(block: (Float) -> Float): Float2 { 151 | x = block(x) 152 | y = block(y) 153 | return this 154 | } 155 | } 156 | 157 | data class Float3(var x: Float = 0.0f, var y: Float = 0.0f, var z: Float = 0.0f) { 158 | constructor(v: Float) : this(v, v, v) 159 | constructor(v: Float2, z: Float = 0.0f) : this(v.x, v.y, z) 160 | constructor(v: Float3) : this(v.x, v.y, v.z) 161 | 162 | inline var r: Float 163 | get() = x 164 | set(value) { 165 | x = value 166 | } 167 | inline var g: Float 168 | get() = y 169 | set(value) { 170 | y = value 171 | } 172 | inline var b: Float 173 | get() = z 174 | set(value) { 175 | z = value 176 | } 177 | 178 | inline var s: Float 179 | get() = x 180 | set(value) { 181 | x = value 182 | } 183 | inline var t: Float 184 | get() = y 185 | set(value) { 186 | y = value 187 | } 188 | inline var p: Float 189 | get() = z 190 | set(value) { 191 | z = value 192 | } 193 | 194 | inline var xy: Float2 195 | get() = Float2(x, y) 196 | set(value) { 197 | x = value.x 198 | y = value.y 199 | } 200 | inline var rg: Float2 201 | get() = Float2(x, y) 202 | set(value) { 203 | x = value.x 204 | y = value.y 205 | } 206 | inline var st: Float2 207 | get() = Float2(x, y) 208 | set(value) { 209 | x = value.x 210 | y = value.y 211 | } 212 | 213 | inline var rgb: Float3 214 | get() = Float3(x, y, z) 215 | set(value) { 216 | x = value.x 217 | y = value.y 218 | z = value.z 219 | } 220 | inline var xyz: Float3 221 | get() = Float3(x, y, z) 222 | set(value) { 223 | x = value.x 224 | y = value.y 225 | z = value.z 226 | } 227 | inline var stp: Float3 228 | get() = Float3(x, y, z) 229 | set(value) { 230 | x = value.x 231 | y = value.y 232 | z = value.z 233 | } 234 | 235 | operator fun get(index: VectorComponent) = when (index) { 236 | VectorComponent.X, VectorComponent.R, VectorComponent.S -> x 237 | VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y 238 | VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z 239 | else -> throw IllegalArgumentException("index must be X, Y, Z, R, G, B, S, T or P") 240 | } 241 | 242 | operator fun get(index1: VectorComponent, index2: VectorComponent): Float2 { 243 | return Float2(get(index1), get(index2)) 244 | } 245 | operator fun get( 246 | index1: VectorComponent, index2: VectorComponent, index3: VectorComponent): Float3 { 247 | return Float3(get(index1), get(index2), get(index3)) 248 | } 249 | 250 | operator fun get(index: Int) = when (index) { 251 | 0 -> x 252 | 1 -> y 253 | 2 -> z 254 | else -> throw IllegalArgumentException("index must be in 0..2") 255 | } 256 | 257 | operator fun get(index1: Int, index2: Int) = Float2(get(index1), get(index2)) 258 | operator fun get(index1: Int, index2: Int, index3: Int): Float3 { 259 | return Float3(get(index1), get(index2), get(index3)) 260 | } 261 | 262 | inline operator fun invoke(index: Int) = get(index - 1) 263 | 264 | operator fun set(index: Int, v: Float) = when (index) { 265 | 0 -> x = v 266 | 1 -> y = v 267 | 2 -> z = v 268 | else -> throw IllegalArgumentException("index must be in 0..2") 269 | } 270 | 271 | operator fun set(index1: Int, index2: Int, v: Float) { 272 | set(index1, v) 273 | set(index2, v) 274 | } 275 | 276 | operator fun set(index1: Int, index2: Int, index3: Int, v: Float) { 277 | set(index1, v) 278 | set(index2, v) 279 | set(index3, v) 280 | } 281 | 282 | operator fun set(index: VectorComponent, v: Float) = when (index) { 283 | VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v 284 | VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v 285 | VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z = v 286 | else -> throw IllegalArgumentException("index must be X, Y, Z, R, G, B, S, T or P") 287 | } 288 | 289 | operator fun set(index1: VectorComponent, index2: VectorComponent, v: Float) { 290 | set(index1, v) 291 | set(index2, v) 292 | } 293 | 294 | operator fun set( 295 | index1: VectorComponent, 296 | index2: VectorComponent, 297 | index3: VectorComponent, 298 | v: Float) { 299 | set(index1, v) 300 | set(index2, v) 301 | set(index3, v) 302 | } 303 | 304 | operator fun unaryMinus() = Float3(-x, -y, -z) 305 | operator fun inc(): Float3 { 306 | x += 1.0f 307 | y += 1.0f 308 | z += 1.0f 309 | return this 310 | } 311 | 312 | operator fun dec(): Float3 { 313 | x -= 1.0f 314 | y -= 1.0f 315 | z -= 1.0f 316 | return this 317 | } 318 | 319 | inline operator fun plus(v: Float) = Float3(x + v, y + v, z + v) 320 | inline operator fun minus(v: Float) = Float3(x - v, y - v, z - v) 321 | inline operator fun times(v: Float) = Float3(x * v, y * v, z * v) 322 | inline operator fun div(v: Float) = Float3(x / v, y / v, z / v) 323 | 324 | inline operator fun plus(v: Float2) = Float3(x + v.x, y + v.y, z) 325 | inline operator fun minus(v: Float2) = Float3(x - v.x, y - v.y, z) 326 | inline operator fun times(v: Float2) = Float3(x * v.x, y * v.y, z) 327 | inline operator fun div(v: Float2) = Float3(x / v.x, y / v.y, z) 328 | 329 | inline operator fun plus(v: Float3) = Float3(x + v.x, y + v.y, z + v.z) 330 | inline operator fun minus(v: Float3) = Float3(x - v.x, y - v.y, z - v.z) 331 | inline operator fun times(v: Float3) = Float3(x * v.x, y * v.y, z * v.z) 332 | inline operator fun div(v: Float3) = Float3(x / v.x, y / v.y, z / v.z) 333 | 334 | inline fun transform(block: (Float) -> Float): Float3 { 335 | x = block(x) 336 | y = block(y) 337 | z = block(z) 338 | return this 339 | } 340 | } 341 | 342 | data class Float4( 343 | var x: Float = 0.0f, 344 | var y: Float = 0.0f, 345 | var z: Float = 0.0f, 346 | var w: Float = 0.0f) { 347 | constructor(v: Float) : this(v, v, v, v) 348 | constructor(v: Float2, z: Float = 0.0f, w: Float = 0.0f) : this(v.x, v.y, z, w) 349 | constructor(v: Float3, w: Float = 0.0f) : this(v.x, v.y, v.z, w) 350 | constructor(v: Float4) : this(v.x, v.y, v.z, v.w) 351 | 352 | inline var r: Float 353 | get() = x 354 | set(value) { 355 | x = value 356 | } 357 | inline var g: Float 358 | get() = y 359 | set(value) { 360 | y = value 361 | } 362 | inline var b: Float 363 | get() = z 364 | set(value) { 365 | z = value 366 | } 367 | inline var a: Float 368 | get() = w 369 | set(value) { 370 | w = value 371 | } 372 | 373 | inline var s: Float 374 | get() = x 375 | set(value) { 376 | x = value 377 | } 378 | inline var t: Float 379 | get() = y 380 | set(value) { 381 | y = value 382 | } 383 | inline var p: Float 384 | get() = z 385 | set(value) { 386 | z = value 387 | } 388 | inline var q: Float 389 | get() = w 390 | set(value) { 391 | w = value 392 | } 393 | 394 | inline var xy: Float2 395 | get() = Float2(x, y) 396 | set(value) { 397 | x = value.x 398 | y = value.y 399 | } 400 | inline var rg: Float2 401 | get() = Float2(x, y) 402 | set(value) { 403 | x = value.x 404 | y = value.y 405 | } 406 | inline var st: Float2 407 | get() = Float2(x, y) 408 | set(value) { 409 | x = value.x 410 | y = value.y 411 | } 412 | 413 | inline var rgb: Float3 414 | get() = Float3(x, y, z) 415 | set(value) { 416 | x = value.x 417 | y = value.y 418 | z = value.z 419 | } 420 | inline var xyz: Float3 421 | get() = Float3(x, y, z) 422 | set(value) { 423 | x = value.x 424 | y = value.y 425 | z = value.z 426 | } 427 | inline var stp: Float3 428 | get() = Float3(x, y, z) 429 | set(value) { 430 | x = value.x 431 | y = value.y 432 | z = value.z 433 | } 434 | 435 | inline var rgba: Float4 436 | get() = Float4(x, y, z, w) 437 | set(value) { 438 | x = value.x 439 | y = value.y 440 | z = value.z 441 | w = value.w 442 | } 443 | inline var xyzw: Float4 444 | get() = Float4(x, y, z, w) 445 | set(value) { 446 | x = value.x 447 | y = value.y 448 | z = value.z 449 | w = value.w 450 | } 451 | inline var stpq: Float4 452 | get() = Float4(x, y, z, w) 453 | set(value) { 454 | x = value.x 455 | y = value.y 456 | z = value.z 457 | w = value.w 458 | } 459 | 460 | operator fun get(index: VectorComponent) = when (index) { 461 | VectorComponent.X, VectorComponent.R, VectorComponent.S -> x 462 | VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y 463 | VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z 464 | VectorComponent.W, VectorComponent.A, VectorComponent.Q -> w 465 | } 466 | 467 | operator fun get(index1: VectorComponent, index2: VectorComponent): Float2 { 468 | return Float2(get(index1), get(index2)) 469 | } 470 | operator fun get( 471 | index1: VectorComponent, 472 | index2: VectorComponent, 473 | index3: VectorComponent): Float3 { 474 | return Float3(get(index1), get(index2), get(index3)) 475 | } 476 | operator fun get( 477 | index1: VectorComponent, 478 | index2: VectorComponent, 479 | index3: VectorComponent, 480 | index4: VectorComponent): Float4 { 481 | return Float4(get(index1), get(index2), get(index3), get(index4)) 482 | } 483 | 484 | operator fun get(index: Int) = when (index) { 485 | 0 -> x 486 | 1 -> y 487 | 2 -> z 488 | 3 -> w 489 | else -> throw IllegalArgumentException("index must be in 0..3") 490 | } 491 | 492 | operator fun get(index1: Int, index2: Int) = Float2(get(index1), get(index2)) 493 | operator fun get(index1: Int, index2: Int, index3: Int): Float3 { 494 | return Float3(get(index1), get(index2), get(index3)) 495 | } 496 | operator fun get(index1: Int, index2: Int, index3: Int, index4: Int): Float4 { 497 | return Float4(get(index1), get(index2), get(index3), get(index4)) 498 | } 499 | 500 | inline operator fun invoke(index: Int) = get(index - 1) 501 | 502 | operator fun set(index: Int, v: Float) = when (index) { 503 | 0 -> x = v 504 | 1 -> y = v 505 | 2 -> z = v 506 | 3 -> w = v 507 | else -> throw IllegalArgumentException("index must be in 0..3") 508 | } 509 | 510 | operator fun set(index1: Int, index2: Int, v: Float) { 511 | set(index1, v) 512 | set(index2, v) 513 | } 514 | 515 | operator fun set(index1: Int, index2: Int, index3: Int, v: Float) { 516 | set(index1, v) 517 | set(index2, v) 518 | set(index3, v) 519 | } 520 | 521 | operator fun set(index1: Int, index2: Int, index3: Int, index4: Int, v: Float) { 522 | set(index1, v) 523 | set(index2, v) 524 | set(index3, v) 525 | set(index4, v) 526 | } 527 | 528 | operator fun set(index: VectorComponent, v: Float) = when (index) { 529 | VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v 530 | VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v 531 | VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z = v 532 | VectorComponent.W, VectorComponent.A, VectorComponent.Q -> w = v 533 | } 534 | 535 | operator fun set(index1: VectorComponent, index2: VectorComponent, v: Float) { 536 | set(index1, v) 537 | set(index2, v) 538 | } 539 | 540 | operator fun set(index1: VectorComponent, index2: VectorComponent, index3: VectorComponent, 541 | v: Float) { 542 | set(index1, v) 543 | set(index2, v) 544 | set(index3, v) 545 | } 546 | 547 | operator fun set(index1: VectorComponent, index2: VectorComponent, 548 | index3: VectorComponent, index4: VectorComponent, v: Float) { 549 | set(index1, v) 550 | set(index2, v) 551 | set(index3, v) 552 | set(index4, v) 553 | } 554 | 555 | operator fun unaryMinus() = Float4(-x, -y, -z, -w) 556 | operator fun inc(): Float4 { 557 | x += 1.0f 558 | y += 1.0f 559 | z += 1.0f 560 | w += 1.0f 561 | return this 562 | } 563 | 564 | operator fun dec(): Float4 { 565 | x -= 1.0f 566 | y -= 1.0f 567 | z -= 1.0f 568 | w -= 1.0f 569 | return this 570 | } 571 | 572 | inline operator fun plus(v: Float) = Float4(x + v, y + v, z + v, w + v) 573 | inline operator fun minus(v: Float) = Float4(x - v, y - v, z - v, w - v) 574 | inline operator fun times(v: Float) = Float4(x * v, y * v, z * v, w * v) 575 | inline operator fun div(v: Float) = Float4(x / v, y / v, z / v, w / v) 576 | 577 | inline operator fun plus(v: Float2) = Float4(x + v.x, y + v.y, z, w) 578 | inline operator fun minus(v: Float2) = Float4(x - v.x, y - v.y, z, w) 579 | inline operator fun times(v: Float2) = Float4(x * v.x, y * v.y, z, w) 580 | inline operator fun div(v: Float2) = Float4(x / v.x, y / v.y, z, w) 581 | 582 | inline operator fun plus(v: Float3) = Float4(x + v.x, y + v.y, z + v.z, w) 583 | inline operator fun minus(v: Float3) = Float4(x - v.x, y - v.y, z - v.z, w) 584 | inline operator fun times(v: Float3) = Float4(x * v.x, y * v.y, z * v.z, w) 585 | inline operator fun div(v: Float3) = Float4(x / v.x, y / v.y, z / v.z, w) 586 | 587 | inline operator fun plus(v: Float4) = Float4(x + v.x, y + v.y, z + v.z, w + v.w) 588 | inline operator fun minus(v: Float4) = Float4(x - v.x, y - v.y, z - v.z, w - v.w) 589 | inline operator fun times(v: Float4) = Float4(x * v.x, y * v.y, z * v.z, w * v.w) 590 | inline operator fun div(v: Float4) = Float4(x / v.x, y / v.y, z / v.z, w / v.w) 591 | 592 | inline fun transform(block: (Float) -> Float): Float4 { 593 | x = block(x) 594 | y = block(y) 595 | z = block(z) 596 | w = block(w) 597 | return this 598 | } 599 | } 600 | 601 | inline operator fun Float.plus(v: Float2) = Float2(this + v.x, this + v.y) 602 | inline operator fun Float.minus(v: Float2) = Float2(this - v.x, this - v.y) 603 | inline operator fun Float.times(v: Float2) = Float2(this * v.x, this * v.y) 604 | inline operator fun Float.div(v: Float2) = Float2(this / v.x, this / v.y) 605 | 606 | inline fun abs(v: Float2) = Float2(abs(v.x), abs(v.y)) 607 | inline fun length(v: Float2) = sqrt(v.x * v.x + v.y * v.y) 608 | inline fun length2(v: Float2) = v.x * v.x + v.y * v.y 609 | inline fun distance(a: Float2, b: Float2) = length(a - b) 610 | inline fun dot(a: Float2, b: Float2) = a.x * b.x + a.y * b.y 611 | fun normalize(v: Float2): Float2 { 612 | val l = 1.0f / length(v) 613 | return Float2(v.x * l, v.y * l) 614 | } 615 | 616 | inline fun reflect(i: Float2, n: Float2) = i - 2.0f * dot(n, i) * n 617 | fun refract(i: Float2, n: Float2, eta: Float): Float2 { 618 | val d = dot(n, i) 619 | val k = 1.0f - eta * eta * (1.0f - sqr(d)) 620 | return if (k < 0.0f) Float2(0.0f) else eta * i - (eta * d + sqrt(k)) * n 621 | } 622 | 623 | inline fun clamp(v: Float2, min: Float, max: Float): Float2 { 624 | return Float2( 625 | clamp(v.x, min, max), 626 | clamp(v.y, min, max)) 627 | } 628 | 629 | inline fun clamp(v: Float2, min: Float2, max: Float2): Float2 { 630 | return Float2( 631 | clamp(v.x, min.x, max.x), 632 | clamp(v.y, min.y, max.y)) 633 | } 634 | 635 | inline fun mix(a: Float2, b: Float2, x: Float): Float2 { 636 | return Float2( 637 | mix(a.x, b.x, x), 638 | mix(a.y, b.y, x)) 639 | } 640 | 641 | inline fun mix(a: Float2, b: Float2, x: Float2): Float2 { 642 | return Float2( 643 | mix(a.x, b.x, x.x), 644 | mix(a.y, b.y, x.y)) 645 | } 646 | 647 | inline fun min(v: Float2) = min(v.x, v.y) 648 | inline fun min(a: Float2, b: Float2) = Float2(min(a.x, b.x), min(a.y, b.y)) 649 | inline fun max(v: Float2) = max(v.x, v.y) 650 | inline fun max(a: Float2, b: Float2) = Float2(max(a.x, b.x), max(a.y, b.y)) 651 | 652 | inline fun transform(v: Float2, block: (Float) -> Float) = v.copy().transform(block) 653 | 654 | inline fun lessThan(a: Float2, b: Float) = Bool2(a.x < b, a.y < b) 655 | inline fun lessThan(a: Float2, b: Float2) = Bool2(a.x < b.x, a.y < b.y) 656 | inline fun lessThanEqual(a: Float2, b: Float) = Bool2(a.x <= b, a.y <= b) 657 | inline fun lessThanEqual(a: Float2, b: Float2) = Bool2(a.x <= b.x, a.y <= b.y) 658 | inline fun greaterThan(a: Float2, b: Float) = Bool2(a.x > b, a.y > b) 659 | inline fun greaterThan(a: Float2, b: Float2) = Bool2(a.x > b.y, a.y > b.y) 660 | inline fun greaterThanEqual(a: Float2, b: Float) = Bool2(a.x >= b, a.y >= b) 661 | inline fun greaterThanEqual(a: Float2, b: Float2) = Bool2(a.x >= b.x, a.y >= b.y) 662 | inline fun equal(a: Float2, b: Float) = Bool2(a.x == b, a.y == b) 663 | inline fun equal(a: Float2, b: Float2) = Bool2(a.x == b.x, a.y == b.y) 664 | inline fun notEqual(a: Float2, b: Float) = Bool2(a.x != b, a.y != b) 665 | inline fun notEqual(a: Float2, b: Float2) = Bool2(a.x != b.x, a.y != b.y) 666 | 667 | inline infix fun Float2.lt(b: Float) = Bool2(x < b, y < b) 668 | inline infix fun Float2.lt(b: Float2) = Bool2(x < b.x, y < b.y) 669 | inline infix fun Float2.lte(b: Float) = Bool2(x <= b, y <= b) 670 | inline infix fun Float2.lte(b: Float2) = Bool2(x <= b.x, y <= b.y) 671 | inline infix fun Float2.gt(b: Float) = Bool2(x > b, y > b) 672 | inline infix fun Float2.gt(b: Float2) = Bool2(x > b.x, y > b.y) 673 | inline infix fun Float2.gte(b: Float) = Bool2(x >= b, y >= b) 674 | inline infix fun Float2.gte(b: Float2) = Bool2(x >= b.x, y >= b.y) 675 | inline infix fun Float2.eq(b: Float) = Bool2(x == b, y == b) 676 | inline infix fun Float2.eq(b: Float2) = Bool2(x == b.x, y == b.y) 677 | inline infix fun Float2.neq(b: Float) = Bool2(x != b, y != b) 678 | inline infix fun Float2.neq(b: Float2) = Bool2(x != b.x, y != b.y) 679 | 680 | inline fun any(v: Bool2) = v.x || v.y 681 | inline fun all(v: Bool2) = v.x && v.y 682 | 683 | inline operator fun Float.plus(v: Float3) = Float3(this + v.x, this + v.y, this + v.z) 684 | inline operator fun Float.minus(v: Float3) = Float3(this - v.x, this - v.y, this - v.z) 685 | inline operator fun Float.times(v: Float3) = Float3(this * v.x, this * v.y, this * v.z) 686 | inline operator fun Float.div(v: Float3) = Float3(this / v.x, this / v.y, this / v.z) 687 | 688 | inline fun abs(v: Float3) = Float3(abs(v.x), abs(v.y), abs(v.z)) 689 | inline fun length(v: Float3) = sqrt(v.x * v.x + v.y * v.y + v.z * v.z) 690 | inline fun length2(v: Float3) = v.x * v.x + v.y * v.y + v.z * v.z 691 | inline fun distance(a: Float3, b: Float3) = length(a - b) 692 | inline fun dot(a: Float3, b: Float3) = a.x * b.x + a.y * b.y + a.z * b.z 693 | inline fun cross(a: Float3, b: Float3): Float3 { 694 | return Float3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x) 695 | } 696 | inline infix fun Float3.x(v: Float3): Float3 { 697 | return Float3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x) 698 | } 699 | fun normalize(v: Float3): Float3 { 700 | val l = 1.0f / length(v) 701 | return Float3(v.x * l, v.y * l, v.z * l) 702 | } 703 | 704 | inline fun reflect(i: Float3, n: Float3) = i - 2.0f * dot(n, i) * n 705 | fun refract(i: Float3, n: Float3, eta: Float): Float3 { 706 | val d = dot(n, i) 707 | val k = 1.0f - eta * eta * (1.0f - sqr(d)) 708 | return if (k < 0.0f) Float3(0.0f) else eta * i - (eta * d + sqrt(k)) * n 709 | } 710 | 711 | inline fun clamp(v: Float3, min: Float, max: Float): Float3 { 712 | return Float3( 713 | clamp(v.x, min, max), 714 | clamp(v.y, min, max), 715 | clamp(v.z, min, max)) 716 | } 717 | 718 | inline fun clamp(v: Float3, min: Float3, max: Float3): Float3 { 719 | return Float3( 720 | clamp(v.x, min.x, max.x), 721 | clamp(v.y, min.y, max.y), 722 | clamp(v.z, min.z, max.z)) 723 | } 724 | 725 | inline fun mix(a: Float3, b: Float3, x: Float): Float3 { 726 | return Float3( 727 | mix(a.x, b.x, x), 728 | mix(a.y, b.y, x), 729 | mix(a.z, b.z, x)) 730 | } 731 | 732 | inline fun mix(a: Float3, b: Float3, x: Float3): Float3 { 733 | return Float3( 734 | mix(a.x, b.x, x.x), 735 | mix(a.y, b.y, x.y), 736 | mix(a.z, b.z, x.z)) 737 | } 738 | 739 | inline fun min(v: Float3) = min(v.x, min(v.y, v.z)) 740 | inline fun min(a: Float3, b: Float3) = Float3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)) 741 | inline fun max(v: Float3) = max(v.x, max(v.y, v.z)) 742 | inline fun max(a: Float3, b: Float3) = Float3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)) 743 | 744 | inline fun transform(v: Float3, block: (Float) -> Float) = v.copy().transform(block) 745 | 746 | inline fun lessThan(a: Float3, b: Float) = Bool3(a.x < b, a.y < b, a.z < b) 747 | inline fun lessThan(a: Float3, b: Float3) = Bool3(a.x < b.x, a.y < b.y, a.z < b.z) 748 | inline fun lessThanEqual(a: Float3, b: Float) = Bool3(a.x <= b, a.y <= b, a.z <= b) 749 | inline fun lessThanEqual(a: Float3, b: Float3) = Bool3(a.x <= b.x, a.y <= b.y, a.z <= b.z) 750 | inline fun greaterThan(a: Float3, b: Float) = Bool3(a.x > b, a.y > b, a.z > b) 751 | inline fun greaterThan(a: Float3, b: Float3) = Bool3(a.x > b.y, a.y > b.y, a.z > b.z) 752 | inline fun greaterThanEqual(a: Float3, b: Float) = Bool3(a.x >= b, a.y >= b, a.z >= b) 753 | inline fun greaterThanEqual(a: Float3, b: Float3) = Bool3(a.x >= b.x, a.y >= b.y, a.z >= b.z) 754 | inline fun equal(a: Float3, b: Float) = Bool3(a.x == b, a.y == b, a.z == b) 755 | inline fun equal(a: Float3, b: Float3) = Bool3(a.x == b.x, a.y == b.y, a.z == b.z) 756 | inline fun notEqual(a: Float3, b: Float) = Bool3(a.x != b, a.y != b, a.z != b) 757 | inline fun notEqual(a: Float3, b: Float3) = Bool3(a.x != b.x, a.y != b.y, a.z != b.z) 758 | 759 | inline infix fun Float3.lt(b: Float) = Bool3(x < b, y < b, z < b) 760 | inline infix fun Float3.lt(b: Float3) = Bool3(x < b.x, y < b.y, z < b.z) 761 | inline infix fun Float3.lte(b: Float) = Bool3(x <= b, y <= b, z <= b) 762 | inline infix fun Float3.lte(b: Float3) = Bool3(x <= b.x, y <= b.y, z <= b.z) 763 | inline infix fun Float3.gt(b: Float) = Bool3(x > b, y > b, z > b) 764 | inline infix fun Float3.gt(b: Float3) = Bool3(x > b.x, y > b.y, z > b.z) 765 | inline infix fun Float3.gte(b: Float) = Bool3(x >= b, y >= b, z >= b) 766 | inline infix fun Float3.gte(b: Float3) = Bool3(x >= b.x, y >= b.y, z >= b.z) 767 | inline infix fun Float3.eq(b: Float) = Bool3(x == b, y == b, z == b) 768 | inline infix fun Float3.eq(b: Float3) = Bool3(x == b.x, y == b.y, z == b.z) 769 | inline infix fun Float3.neq(b: Float) = Bool3(x != b, y != b, z != b) 770 | inline infix fun Float3.neq(b: Float3) = Bool3(x != b.x, y != b.y, z != b.z) 771 | 772 | inline fun any(v: Bool3) = v.x || v.y || v.z 773 | inline fun all(v: Bool3) = v.x && v.y && v.z 774 | 775 | inline operator fun Float.plus(v: Float4) = Float4(this + v.x, this + v.y, this + v.z, this + v.w) 776 | inline operator fun Float.minus(v: Float4) = Float4(this - v.x, this - v.y, this - v.z, this - v.w) 777 | inline operator fun Float.times(v: Float4) = Float4(this * v.x, this * v.y, this * v.z, this * v.w) 778 | inline operator fun Float.div(v: Float4) = Float4(this / v.x, this / v.y, this / v.z, this / v.w) 779 | 780 | inline fun abs(v: Float4) = Float4(abs(v.x), abs(v.y), abs(v.z), abs(v.w)) 781 | inline fun length(v: Float4) = sqrt(v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w) 782 | inline fun length2(v: Float4) = v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w 783 | inline fun distance(a: Float4, b: Float4) = length(a - b) 784 | inline fun dot(a: Float4, b: Float4) = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w 785 | fun normalize(v: Float4): Float4 { 786 | val l = 1.0f / length(v) 787 | return Float4(v.x * l, v.y * l, v.z * l, v.w * l) 788 | } 789 | 790 | inline fun clamp(v: Float4, min: Float, max: Float): Float4 { 791 | return Float4( 792 | clamp(v.x, min, max), 793 | clamp(v.y, min, max), 794 | clamp(v.z, min, max), 795 | clamp(v.w, min, max)) 796 | } 797 | 798 | inline fun clamp(v: Float4, min: Float4, max: Float4): Float4 { 799 | return Float4( 800 | clamp(v.x, min.x, max.x), 801 | clamp(v.y, min.y, max.y), 802 | clamp(v.z, min.z, max.z), 803 | clamp(v.w, min.z, max.w)) 804 | } 805 | 806 | inline fun mix(a: Float4, b: Float4, x: Float): Float4 { 807 | return Float4( 808 | mix(a.x, b.x, x), 809 | mix(a.y, b.y, x), 810 | mix(a.z, b.z, x), 811 | mix(a.w, b.w, x)) 812 | } 813 | 814 | inline fun mix(a: Float4, b: Float4, x: Float4): Float4 { 815 | return Float4( 816 | mix(a.x, b.x, x.x), 817 | mix(a.y, b.y, x.y), 818 | mix(a.z, b.z, x.z), 819 | mix(a.w, b.w, x.w)) 820 | } 821 | 822 | inline fun min(v: Float4) = min(v.x, min(v.y, min(v.z, v.w))) 823 | inline fun min(a: Float4, b: Float4): Float4 { 824 | return Float4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)) 825 | } 826 | inline fun max(v: Float4) = max(v.x, max(v.y, max(v.z, v.w))) 827 | inline fun max(a: Float4, b: Float4): Float4 { 828 | return Float4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)) 829 | } 830 | 831 | inline fun transform(v: Float4, block: (Float) -> Float) = v.copy().transform(block) 832 | 833 | inline fun lessThan(a: Float4, b: Float) = Bool4(a.x < b, a.y < b, a.z < b, a.w < b) 834 | inline fun lessThan(a: Float4, b: Float4) = Bool4(a.x < b.x, a.y < b.y, a.z < b.z, a.w < b.w) 835 | inline fun lessThanEqual(a: Float4, b: Float) = Bool4(a.x <= b, a.y <= b, a.z <= b, a.w <= b) 836 | inline fun lessThanEqual(a: Float4, b: Float4) = Bool4(a.x <= b.x, a.y <= b.y, a.z <= b.z, a.w <= b.w) 837 | inline fun greaterThan(a: Float4, b: Float) = Bool4(a.x > b, a.y > b, a.z > b, a.w > b) 838 | inline fun greaterThan(a: Float4, b: Float4) = Bool4(a.x > b.y, a.y > b.y, a.z > b.z, a.w > b.w) 839 | inline fun greaterThanEqual(a: Float4, b: Float) = Bool4(a.x >= b, a.y >= b, a.z >= b, a.w >= b) 840 | inline fun greaterThanEqual(a: Float4, b: Float4) = Bool4(a.x >= b.x, a.y >= b.y, a.z >= b.z, a.w >= b.w) 841 | inline fun equal(a: Float4, b: Float) = Bool4(a.x == b, a.y == b, a.z == b, a.w == b) 842 | inline fun equal(a: Float4, b: Float4) = Bool4(a.x == b.x, a.y == b.y, a.z == b.z, a.w == b.w) 843 | inline fun notEqual(a: Float4, b: Float) = Bool4(a.x != b, a.y != b, a.z != b, a.w != b) 844 | inline fun notEqual(a: Float4, b: Float4) = Bool4(a.x != b.x, a.y != b.y, a.z != b.z, a.w != b.w) 845 | 846 | inline infix fun Float4.lt(b: Float) = Bool4(x < b, y < b, z < b, a < b) 847 | inline infix fun Float4.lt(b: Float4) = Bool4(x < b.x, y < b.y, z < b.z, w < b.w) 848 | inline infix fun Float4.lte(b: Float) = Bool4(x <= b, y <= b, z <= b, w <= b) 849 | inline infix fun Float4.lte(b: Float4) = Bool4(x <= b.x, y <= b.y, z <= b.z, w <= b.w) 850 | inline infix fun Float4.gt(b: Float) = Bool4(x > b, y > b, z > b, w > b) 851 | inline infix fun Float4.gt(b: Float4) = Bool4(x > b.x, y > b.y, z > b.z, w > b.w) 852 | inline infix fun Float4.gte(b: Float) = Bool4(x >= b, y >= b, z >= b, w >= b) 853 | inline infix fun Float4.gte(b: Float4) = Bool4(x >= b.x, y >= b.y, z >= b.z, w >= b.w) 854 | inline infix fun Float4.eq(b: Float) = Bool4(x == b, y == b, z == b, w == b) 855 | inline infix fun Float4.eq(b: Float4) = Bool4(x == b.x, y == b.y, z == b.z, w == b.w) 856 | inline infix fun Float4.neq(b: Float) = Bool4(x != b, y != b, z != b, w != b) 857 | inline infix fun Float4.neq(b: Float4) = Bool4(x != b.x, y != b.y, z != b.z, w != b.w) 858 | 859 | inline fun any(v: Bool4) = v.x || v.y || v.z || v.w 860 | inline fun all(v: Bool4) = v.x && v.y && v.z && v.w 861 | 862 | data class Bool2(var x: Boolean = false, var y: Boolean = false) { 863 | constructor(v: Bool2) : this(v.x, v.y) 864 | 865 | inline var r: Boolean 866 | get() = x 867 | set(value) { 868 | x = value 869 | } 870 | inline var g: Boolean 871 | get() = y 872 | set(value) { 873 | y = value 874 | } 875 | 876 | inline var s: Boolean 877 | get() = x 878 | set(value) { 879 | x = value 880 | } 881 | inline var t: Boolean 882 | get() = y 883 | set(value) { 884 | y = value 885 | } 886 | 887 | inline var xy: Bool2 888 | get() = Bool2(x, y) 889 | set(value) { 890 | x = value.x 891 | y = value.y 892 | } 893 | inline var rg: Bool2 894 | get() = Bool2(x, y) 895 | set(value) { 896 | x = value.x 897 | y = value.y 898 | } 899 | inline var st: Bool2 900 | get() = Bool2(x, y) 901 | set(value) { 902 | x = value.x 903 | y = value.y 904 | } 905 | 906 | operator fun get(index: VectorComponent) = when (index) { 907 | VectorComponent.X, VectorComponent.R, VectorComponent.S -> x 908 | VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y 909 | else -> throw IllegalArgumentException("index must be X, Y, R, G, S or T") 910 | } 911 | 912 | operator fun get(index1: VectorComponent, index2: VectorComponent): Bool2 { 913 | return Bool2(get(index1), get(index2)) 914 | } 915 | 916 | operator fun get(index: Int) = when (index) { 917 | 0 -> x 918 | 1 -> y 919 | else -> throw IllegalArgumentException("index must be in 0..1") 920 | } 921 | 922 | operator fun get(index1: Int, index2: Int) = Bool2(get(index1), get(index2)) 923 | 924 | inline operator fun invoke(index: Int) = get(index - 1) 925 | 926 | operator fun set(index: Int, v: Boolean) = when (index) { 927 | 0 -> x = v 928 | 1 -> y = v 929 | else -> throw IllegalArgumentException("index must be in 0..1") 930 | } 931 | 932 | operator fun set(index1: Int, index2: Int, v: Boolean) { 933 | set(index1, v) 934 | set(index2, v) 935 | } 936 | 937 | operator fun set(index: VectorComponent, v: Boolean) = when (index) { 938 | VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v 939 | VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v 940 | else -> throw IllegalArgumentException("index must be X, Y, R, G, S or T") 941 | } 942 | 943 | operator fun set(index1: VectorComponent, index2: VectorComponent, v: Boolean) { 944 | set(index1, v) 945 | set(index2, v) 946 | } 947 | } 948 | 949 | data class Bool3(var x: Boolean = false, var y: Boolean = false, var z: Boolean = false) { 950 | constructor(v: Bool2, z: Boolean = false) : this(v.x, v.y, z) 951 | constructor(v: Bool3) : this(v.x, v.y, v.z) 952 | 953 | inline var r: Boolean 954 | get() = x 955 | set(value) { 956 | x = value 957 | } 958 | inline var g: Boolean 959 | get() = y 960 | set(value) { 961 | y = value 962 | } 963 | inline var b: Boolean 964 | get() = z 965 | set(value) { 966 | z = value 967 | } 968 | 969 | inline var s: Boolean 970 | get() = x 971 | set(value) { 972 | x = value 973 | } 974 | inline var t: Boolean 975 | get() = y 976 | set(value) { 977 | y = value 978 | } 979 | inline var p: Boolean 980 | get() = z 981 | set(value) { 982 | z = value 983 | } 984 | 985 | inline var xy: Bool2 986 | get() = Bool2(x, y) 987 | set(value) { 988 | x = value.x 989 | y = value.y 990 | } 991 | inline var rg: Bool2 992 | get() = Bool2(x, y) 993 | set(value) { 994 | x = value.x 995 | y = value.y 996 | } 997 | inline var st: Bool2 998 | get() = Bool2(x, y) 999 | set(value) { 1000 | x = value.x 1001 | y = value.y 1002 | } 1003 | 1004 | inline var rgb: Bool3 1005 | get() = Bool3(x, y, z) 1006 | set(value) { 1007 | x = value.x 1008 | y = value.y 1009 | z = value.z 1010 | } 1011 | inline var xyz: Bool3 1012 | get() = Bool3(x, y, z) 1013 | set(value) { 1014 | x = value.x 1015 | y = value.y 1016 | z = value.z 1017 | } 1018 | inline var stp: Bool3 1019 | get() = Bool3(x, y, z) 1020 | set(value) { 1021 | x = value.x 1022 | y = value.y 1023 | z = value.z 1024 | } 1025 | 1026 | operator fun get(index: VectorComponent) = when (index) { 1027 | VectorComponent.X, VectorComponent.R, VectorComponent.S -> x 1028 | VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y 1029 | VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z 1030 | else -> throw IllegalArgumentException("index must be X, Y, Z, R, G, B, S, T or P") 1031 | } 1032 | 1033 | operator fun get(index1: VectorComponent, index2: VectorComponent): Bool2 { 1034 | return Bool2(get(index1), get(index2)) 1035 | } 1036 | operator fun get( 1037 | index1: VectorComponent, index2: VectorComponent, index3: VectorComponent): Bool3 { 1038 | return Bool3(get(index1), get(index2), get(index3)) 1039 | } 1040 | 1041 | operator fun get(index: Int) = when (index) { 1042 | 0 -> x 1043 | 1 -> y 1044 | 2 -> z 1045 | else -> throw IllegalArgumentException("index must be in 0..2") 1046 | } 1047 | 1048 | operator fun get(index1: Int, index2: Int) = Bool2(get(index1), get(index2)) 1049 | operator fun get(index1: Int, index2: Int, index3: Int): Bool3 { 1050 | return Bool3(get(index1), get(index2), get(index3)) 1051 | } 1052 | 1053 | inline operator fun invoke(index: Int) = get(index - 1) 1054 | 1055 | operator fun set(index: Int, v: Boolean) = when (index) { 1056 | 0 -> x = v 1057 | 1 -> y = v 1058 | 2 -> z = v 1059 | else -> throw IllegalArgumentException("index must be in 0..2") 1060 | } 1061 | 1062 | operator fun set(index1: Int, index2: Int, v: Boolean) { 1063 | set(index1, v) 1064 | set(index2, v) 1065 | } 1066 | 1067 | operator fun set(index1: Int, index2: Int, index3: Int, v: Boolean) { 1068 | set(index1, v) 1069 | set(index2, v) 1070 | set(index3, v) 1071 | } 1072 | 1073 | operator fun set(index: VectorComponent, v: Boolean) = when (index) { 1074 | VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v 1075 | VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v 1076 | VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z = v 1077 | else -> throw IllegalArgumentException("index must be X, Y, Z, R, G, B, S, T or P") 1078 | } 1079 | 1080 | operator fun set(index1: VectorComponent, index2: VectorComponent, v: Boolean) { 1081 | set(index1, v) 1082 | set(index2, v) 1083 | } 1084 | 1085 | operator fun set( 1086 | index1: VectorComponent, 1087 | index2: VectorComponent, 1088 | index3: VectorComponent, 1089 | v: Boolean) { 1090 | set(index1, v) 1091 | set(index2, v) 1092 | set(index3, v) 1093 | } 1094 | } 1095 | 1096 | data class Bool4( 1097 | var x: Boolean = false, 1098 | var y: Boolean = false, 1099 | var z: Boolean = false, 1100 | var w: Boolean = false) { 1101 | constructor(v: Bool2, z: Boolean = false, w: Boolean = false) : this(v.x, v.y, z, w) 1102 | constructor(v: Bool3, w: Boolean = false) : this(v.x, v.y, v.z, w) 1103 | constructor(v: Bool4) : this(v.x, v.y, v.z, v.w) 1104 | 1105 | inline var r: Boolean 1106 | get() = x 1107 | set(value) { 1108 | x = value 1109 | } 1110 | inline var g: Boolean 1111 | get() = y 1112 | set(value) { 1113 | y = value 1114 | } 1115 | inline var b: Boolean 1116 | get() = z 1117 | set(value) { 1118 | z = value 1119 | } 1120 | inline var a: Boolean 1121 | get() = w 1122 | set(value) { 1123 | w = value 1124 | } 1125 | 1126 | inline var s: Boolean 1127 | get() = x 1128 | set(value) { 1129 | x = value 1130 | } 1131 | inline var t: Boolean 1132 | get() = y 1133 | set(value) { 1134 | y = value 1135 | } 1136 | inline var p: Boolean 1137 | get() = z 1138 | set(value) { 1139 | z = value 1140 | } 1141 | inline var q: Boolean 1142 | get() = w 1143 | set(value) { 1144 | w = value 1145 | } 1146 | 1147 | inline var xy: Bool2 1148 | get() = Bool2(x, y) 1149 | set(value) { 1150 | x = value.x 1151 | y = value.y 1152 | } 1153 | inline var rg: Bool2 1154 | get() = Bool2(x, y) 1155 | set(value) { 1156 | x = value.x 1157 | y = value.y 1158 | } 1159 | inline var st: Bool2 1160 | get() = Bool2(x, y) 1161 | set(value) { 1162 | x = value.x 1163 | y = value.y 1164 | } 1165 | 1166 | inline var rgb: Bool3 1167 | get() = Bool3(x, y, z) 1168 | set(value) { 1169 | x = value.x 1170 | y = value.y 1171 | z = value.z 1172 | } 1173 | inline var xyz: Bool3 1174 | get() = Bool3(x, y, z) 1175 | set(value) { 1176 | x = value.x 1177 | y = value.y 1178 | z = value.z 1179 | } 1180 | inline var stp: Bool3 1181 | get() = Bool3(x, y, z) 1182 | set(value) { 1183 | x = value.x 1184 | y = value.y 1185 | z = value.z 1186 | } 1187 | 1188 | inline var rgba: Bool4 1189 | get() = Bool4(x, y, z, w) 1190 | set(value) { 1191 | x = value.x 1192 | y = value.y 1193 | z = value.z 1194 | w = value.w 1195 | } 1196 | inline var xyzw: Bool4 1197 | get() = Bool4(x, y, z, w) 1198 | set(value) { 1199 | x = value.x 1200 | y = value.y 1201 | z = value.z 1202 | w = value.w 1203 | } 1204 | inline var stpq: Bool4 1205 | get() = Bool4(x, y, z, w) 1206 | set(value) { 1207 | x = value.x 1208 | y = value.y 1209 | z = value.z 1210 | w = value.w 1211 | } 1212 | 1213 | operator fun get(index: VectorComponent) = when (index) { 1214 | VectorComponent.X, VectorComponent.R, VectorComponent.S -> x 1215 | VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y 1216 | VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z 1217 | VectorComponent.W, VectorComponent.A, VectorComponent.Q -> w 1218 | } 1219 | 1220 | operator fun get(index1: VectorComponent, index2: VectorComponent): Bool2 { 1221 | return Bool2(get(index1), get(index2)) 1222 | } 1223 | operator fun get( 1224 | index1: VectorComponent, 1225 | index2: VectorComponent, 1226 | index3: VectorComponent): Bool3 { 1227 | return Bool3(get(index1), get(index2), get(index3)) 1228 | } 1229 | operator fun get( 1230 | index1: VectorComponent, 1231 | index2: VectorComponent, 1232 | index3: VectorComponent, 1233 | index4: VectorComponent): Bool4 { 1234 | return Bool4(get(index1), get(index2), get(index3), get(index4)) 1235 | } 1236 | 1237 | operator fun get(index: Int) = when (index) { 1238 | 0 -> x 1239 | 1 -> y 1240 | 2 -> z 1241 | 3 -> w 1242 | else -> throw IllegalArgumentException("index must be in 0..3") 1243 | } 1244 | 1245 | operator fun get(index1: Int, index2: Int) = Bool2(get(index1), get(index2)) 1246 | operator fun get(index1: Int, index2: Int, index3: Int): Bool3 { 1247 | return Bool3(get(index1), get(index2), get(index3)) 1248 | } 1249 | operator fun get(index1: Int, index2: Int, index3: Int, index4: Int): Bool4 { 1250 | return Bool4(get(index1), get(index2), get(index3), get(index4)) 1251 | } 1252 | 1253 | inline operator fun invoke(index: Int) = get(index - 1) 1254 | 1255 | operator fun set(index: Int, v: Boolean) = when (index) { 1256 | 0 -> x = v 1257 | 1 -> y = v 1258 | 2 -> z = v 1259 | 3 -> w = v 1260 | else -> throw IllegalArgumentException("index must be in 0..3") 1261 | } 1262 | 1263 | operator fun set(index1: Int, index2: Int, v: Boolean) { 1264 | set(index1, v) 1265 | set(index2, v) 1266 | } 1267 | 1268 | operator fun set(index1: Int, index2: Int, index3: Int, v: Boolean) { 1269 | set(index1, v) 1270 | set(index2, v) 1271 | set(index3, v) 1272 | } 1273 | 1274 | operator fun set(index1: Int, index2: Int, index3: Int, index4: Int, v: Boolean) { 1275 | set(index1, v) 1276 | set(index2, v) 1277 | set(index3, v) 1278 | set(index4, v) 1279 | } 1280 | 1281 | operator fun set(index: VectorComponent, v: Boolean) = when (index) { 1282 | VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v 1283 | VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v 1284 | VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z = v 1285 | VectorComponent.W, VectorComponent.A, VectorComponent.Q -> w = v 1286 | } 1287 | 1288 | operator fun set(index1: VectorComponent, index2: VectorComponent, v: Boolean) { 1289 | set(index1, v) 1290 | set(index2, v) 1291 | } 1292 | 1293 | operator fun set(index1: VectorComponent, index2: VectorComponent, index3: VectorComponent, 1294 | v: Boolean) { 1295 | set(index1, v) 1296 | set(index2, v) 1297 | set(index3, v) 1298 | } 1299 | 1300 | operator fun set(index1: VectorComponent, index2: VectorComponent, 1301 | index3: VectorComponent, index4: VectorComponent, v: Boolean) { 1302 | set(index1, v) 1303 | set(index2, v) 1304 | set(index3, v) 1305 | set(index4, v) 1306 | } 1307 | } 1308 | --------------------------------------------------------------------------------