├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE ├── README.md ├── RELEASE-NOTES.md ├── api ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── glimpse │ │ ├── Angle.kt │ │ ├── Angles.kt │ │ ├── AnglesRange.kt │ │ ├── Color.kt │ │ ├── GlimpseException.kt │ │ ├── Matrices.kt │ │ ├── Matrix.kt │ │ ├── MatrixBuilder.kt │ │ ├── Point.kt │ │ ├── SquareMatrix.kt │ │ ├── TextureCoordinates.kt │ │ ├── Vector.kt │ │ ├── buffers │ │ └── Buffers.kt │ │ ├── cameras │ │ ├── Camera.kt │ │ ├── CameraBuilder.kt │ │ ├── CameraProjection.kt │ │ ├── CameraView.kt │ │ ├── FreeformCameraView.kt │ │ ├── FreeformCameraViewBuilder.kt │ │ ├── OrthographicCameraProjection.kt │ │ ├── OrthographicCameraProjectionBuilder.kt │ │ ├── PerspectiveCameraProjection.kt │ │ ├── PerspectiveCameraProjectionBuilder.kt │ │ ├── TargetedCameraView.kt │ │ └── TargetedCameraViewBuilder.kt │ │ ├── gles │ │ ├── BlendFactor.kt │ │ ├── CullFaceMode.kt │ │ ├── DepthTestFunction.kt │ │ ├── Disposable.kt │ │ ├── GLES.kt │ │ ├── Handles.kt │ │ ├── Viewport.kt │ │ └── delegates │ │ │ ├── DisposableLazyDelegate.kt │ │ │ ├── EnumPairSetAndRememberDelegate.kt │ │ │ ├── EnumSetAndRememberDelegate.kt │ │ │ ├── GLESDelegate.kt │ │ │ └── SetAndRememberDelegate.kt │ │ ├── io │ │ ├── Properties.kt │ │ ├── Resource.kt │ │ └── ResourceNotFoundException.kt │ │ ├── lights │ │ ├── Light.kt │ │ └── LightBuilder.kt │ │ ├── materials │ │ ├── Material.kt │ │ └── ShaderHelper.kt │ │ ├── models │ │ ├── Curve.kt │ │ ├── CurveBuilder.kt │ │ ├── Face.kt │ │ ├── Mesh.kt │ │ ├── MeshBuilder.kt │ │ ├── Model.kt │ │ ├── ObjMeshBuilder.kt │ │ ├── PlatonicSolids.kt │ │ ├── Polygon.kt │ │ ├── Prisms.kt │ │ ├── Revolutions.kt │ │ └── Vertex.kt │ │ ├── shaders │ │ ├── Program.kt │ │ ├── ProgramBuilder.kt │ │ ├── ProgramLinkException.kt │ │ ├── Shader.kt │ │ ├── ShaderCompileException.kt │ │ └── ShaderType.kt │ │ └── textures │ │ ├── Texture.kt │ │ ├── TextureBuilder.kt │ │ ├── TextureMagnificationFilter.kt │ │ ├── TextureMinificationFilter.kt │ │ └── TextureWrapping.kt │ └── test │ ├── kotlin │ └── glimpse │ │ ├── AffineTransformationSpec.kt │ │ ├── AngleSpec.kt │ │ ├── ColorSpec.kt │ │ ├── MatrixSpec.kt │ │ ├── PointSpec.kt │ │ ├── ProjectionMatrixSpec.kt │ │ ├── SquareMatrixSpec.kt │ │ ├── TextureCoordinatesSpec.kt │ │ ├── VectorSpec.kt │ │ ├── cameras │ │ ├── CameraSpec.kt │ │ ├── CameraVisibilitySpec.kt │ │ ├── FreeformCameraViewBuilderSpec.kt │ │ ├── OrthographicCameraProjectionBuilderSpec.kt │ │ ├── PerspectiveCameraProjectionBuilderSpec.kt │ │ └── TargetedCameraViewBuilderSpec.kt │ │ ├── gles │ │ ├── DisposableSpec.kt │ │ ├── ViewportSpec.kt │ │ └── delegates │ │ │ ├── DisposableLazyDelegateSpec.kt │ │ │ ├── EnumPairSetAndRememberDelegateSpec.kt │ │ │ ├── EnumSetAndRememberDelegateSpec.kt │ │ │ └── SetAndRememberDelegateSpec.kt │ │ ├── io │ │ ├── PropertiesSpec.kt │ │ └── ResourceSpec.kt │ │ ├── lights │ │ ├── DirectionLightBuilderSpec.kt │ │ ├── PointLightBuilderSpec.kt │ │ └── SpotlightBuilderSpec.kt │ │ ├── material │ │ └── ShaderHelperSpec.kt │ │ ├── models │ │ ├── CurveBuilderSpec.kt │ │ ├── CurveSpec.kt │ │ ├── MeshBuilderSpec.kt │ │ ├── MeshSpec.kt │ │ ├── MeshTransformationSpec.kt │ │ ├── ModelTransformationSpec.kt │ │ ├── ObjMeshBuilderSpec.kt │ │ ├── PlatonicSolidsSpec.kt │ │ ├── PrismSpec.kt │ │ └── SphereSpec.kt │ │ ├── shaders │ │ ├── ProgramBuilderSpec.kt │ │ └── ProgramSpec.kt │ │ ├── test │ │ ├── FloatMatchers.kt │ │ ├── GlimpseSpec.kt │ │ └── _Gen.kt │ │ └── textures │ │ ├── TextureBuilderSpec.kt │ │ └── TextureSpec.kt │ └── resources │ └── glimpse │ ├── io │ ├── lines.txt │ └── sample.properties │ ├── models │ ├── triangle.obj │ ├── triangle_no_textures.obj │ └── two_triangles.obj │ └── textures │ └── empty_file.png ├── build.gradle ├── checkstyle.xml ├── codecov.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jogl ├── build.gradle └── src │ └── main │ └── kotlin │ └── glimpse │ └── jogl │ ├── Frames.kt │ ├── GlimpseFrame.kt │ ├── Menus.kt │ ├── Mouse.kt │ ├── gles │ ├── EnumMappings.kt │ ├── GLES.kt │ └── delegates │ │ └── EnableDisableDelegate.kt │ └── io │ ├── FileChoosers.kt │ ├── FileFilters.kt │ └── PredicateFileFilter.kt ├── materials ├── build.gradle └── src │ └── main │ ├── kotlin │ └── glimpse │ │ └── materials │ │ ├── AbstractMaterial.kt │ │ ├── Plastic.kt │ │ └── Textured.kt │ └── resources │ └── glimpse │ └── materials │ ├── Plastic_fragment.glsl │ ├── Plastic_vertex.glsl │ ├── Textured_fragment.glsl │ └── Textured_vertex.glsl ├── preview ├── build.gradle └── src │ └── main │ ├── kotlin │ └── glimpse │ │ └── preview │ │ └── GlimpsePreview.kt │ └── resources │ └── glimpse │ └── preview │ ├── ambient.png │ ├── diffuse.png │ └── specular.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Java target 2 | target/ 3 | */target/ 4 | *.class 5 | 6 | # Gradle 7 | .gradle/ 8 | build/ 9 | */build/ 10 | 11 | # IntelliJ IDEA 12 | *.iml 13 | .idea/ 14 | classes/ 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | - openjdk7 6 | 7 | after_success: 8 | - bash <(curl -s https://codecov.io/bash) 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | * **Focus on the topic.** 4 | Sometimes a discussion might go astray. Before posing a new comment, check if it is releted to the original topic. 5 | The topic usually involves the code or a framework feature. If you focus on a person, you are most probably far afield. 6 | * **Use professional language.** 7 | Always review your comments before posting. Consider whether the words you used, are best choice in this context. 8 | * **Assume good intentions.** 9 | Miscommunication is nothing uncommon, especially in an internetional community. 10 | Before you report an abuse, think what the other person really wanted to say. 11 | Remember that offence is taken, not given. If somebody has intentionally insulted you, 12 | their comment is most probably against one of the previous rules. 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Glimpse Framework Guidelines for Contributors 2 | 3 | First of all, new contributors are always welcome in Glimpse Framework. 4 | 5 | > Programming is a social activity. ― Robert C. Martin 6 | 7 | ## Issues 8 | 9 | Please do not use [labels](https://github.com/GlimpseFramework/glimpse-framework/labels) other than: 10 | * android—for issues related to Android implementation 11 | * bug 12 | * feature 13 | * refactoring 14 | 15 | ### Bugs 16 | 17 | If you have found a bug, don't hesitate to report it—even if you're not sure if it's a real bug. 18 | False alarms are better than hidden bugs. 19 | 20 | [Label](https://github.com/GlimpseFramework/glimpse-framework/labels) the issue as a **bug**. 21 | 22 | ### Feature Requests 23 | 24 | If you'd like to see a new feature in Glimpse Framework, 25 | just create a [new issue](https://github.com/GlimpseFramework/glimpse-framework/issues). 26 | Each request will be taken under consideration. 27 | 28 | [Label](https://github.com/GlimpseFramework/glimpse-framework/labels) the issue as a **feature**. 29 | 30 | ## Source Code 31 | 32 | ### Technical rules 33 | 34 | * Indentation with tabs, and tabs only. 35 | * Lines no longer than 160 characters—a maximum line length visible in a typical IDE without scrolling. 36 | A tab counts as 4 characters. 37 | 38 | ### Keep Code Clean 39 | 40 | The following rules are direct quotes from Uncle Bob's 41 | _[Clean Code: A Handbook of Agile Software Craftsmanship](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)_: 42 | 43 | * It is not enough for code to work. 44 | * Leave the campground cleaner than you found it. 45 | * A long descriptive name is better than a short enigmatic name. 46 | A long descriptive name is better than a long descriptive comment. 47 | * When you see commented-out code, delete it! 48 | * Building a project should be a single trivial operation. 49 | 50 | If you haven't read [Uncle Bob Martin](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) yet, 51 | you probably should. 52 | 53 | ### Testing 54 | 55 | Whenever you submit a pull request, make sure your changes are tested. 56 | 57 | #### Code Coverage 58 | 59 | Typically, [CodeCov](https://codecov.io/github/GlimpseFramework/glimpse-framework) will approve pull requests with: 60 | * at least 85% overall coverage, 61 | * at least 75% coverage of your changes. 62 | 63 | However, these are only suggestions, and every change will treated individually. 64 | Your pull request might be accepted even when the coverage is 0%, if you can justify it. 65 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | I have found a *bug* in Glimpse Framework. 2 | 3 | ### How to reproduce the issue 4 | [AS ACCURATE DESCRIPTION AS POSSIBLE] 5 | [PROVIDE ANY CODE RELATED TO THE ISSUE] 6 | 7 | ### What happens 8 | [DESCRIBE ACTUAL BEHAVIOUR] 9 | 10 | ### What should happen 11 | [DESCRIBE EXPECTED BEHAVIOUR] 12 | 13 | -- OR -- 14 | 15 | I would like to request a new *feature* in Glimpse Framework. 16 | 17 | ### General idea 18 | [PROVIDE A GENERAL IDEA BEHIND THE FEATURE] 19 | 20 | ### Code example 21 | [PROVIDE A CODE SNIPPET DEMONSTRATING THE FEATURE] 22 | 23 | ### Details 24 | [PROVIDE AS MANY DETAILS AS POSSIBLE] 25 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | Fixes #[ISSUE NUMBER] 2 | OR 3 | Implements #[ISSUE NUMBER] 4 | OR 5 | [DESCRIBE WHAT YOU DID] 6 | 7 | Changes: 8 | * [DETAILED DESCRIPTION OF WHAT YOU DID] 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## This repository is no longer maintained 2 | 3 | Glimpse is now further developed as a Kotlin Multiplatform project under [glimpse-graphics/glimpse](https://github.com/glimpse-graphics/glimpse). 4 | -------------------------------------------------------------------------------- /RELEASE-NOTES.md: -------------------------------------------------------------------------------- 1 | # GlimpseFramework Release Notes 2 | 3 | ## Version 0.4 4 | 5 | * **Release date:** Oct 25, 2016 6 | * **Git tag:** glimpse-framework-0.4 7 | 8 | ### New features 9 | 10 | * Issue #38 – Properties of lights are lambdas. 11 | * Issue #39 – It is now possible to dispose and reinitialize materials. 12 | * Issue #40 – Added `loadObjMeshes` extension method to `List`, `InputStream`, and `File`. 13 | * Loading properties from resource file. 14 | * JOGL implementation: File choosers for OBJ files and textures. 15 | * JOGL implementation: Added actions running in GLES context. 16 | 17 | ### Other changes 18 | 19 | * Issue #44 – Refactoring (changed API): 20 | * `readTexture` renamed to `loadTexture`, 21 | * `loadObjMesh` renamed to `loadObjMeshes`. 22 | * Improved tests logging in build. 23 | * Launch4j configuration for preview application. 24 | 25 | ## Version 0.3 26 | 27 | * **Release date:** Sep 25, 2016 28 | * **Git tag:** glimpse-framework-0.3 29 | 30 | ### New features 31 | 32 | * Issue #14 – OBJ format support. 33 | * Issue #15 – Lights support: 34 | * directional lights, 35 | * point lights, 36 | * spotlights. 37 | * More camera features: 38 | * Issue #22 – orthographic camera projection, 39 | * Issue #23 – free-form camera position. 40 | * Issue #24 – Removed uncomfortable `init` and `dispose` calls for materials. 41 | * JOGL implementation: Getting current mouse position. 42 | 43 | ### Fixed bugs 44 | 45 | * Issue #28 – Direct buffers were created once per frame, causing dramatic FPS loss. 46 | * Issue #33 – Transformation of a `Model` was passed by value (not by name), when transformed again. 47 | * Minor fixes in transformations. 48 | 49 | ### Other changes 50 | 51 | * Updated dependencies 52 | 53 | ## Version 0.2 54 | 55 | * **Release date:** Sep 15, 2016 56 | * **Git tag:** glimpse-framework-0.2 57 | 58 | First Kotlin release. 59 | 60 | ## Version 0.1 61 | 62 | * **Release date:** Dec 23, 2015 63 | * **Git tag:** glimpse-framework-0.1 64 | 65 | Initial release. 66 | -------------------------------------------------------------------------------- /api/build.gradle: -------------------------------------------------------------------------------- 1 | description = 'Glimpse Framework API' 2 | 3 | dependencies { 4 | compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" 5 | testCompile "io.kotlintest:kotlintest:${kotlintest_version}" 6 | testCompile "com.nhaarman:mockito-kotlin:${mockito_kotlin_version}" 7 | } 8 | 9 | dokka { 10 | moduleName = 'Glimpse Framework API' 11 | outputFormat = 'javadoc' 12 | } 13 | 14 | task sourcesJar(type: Jar, dependsOn: project.classes) { 15 | classifier = 'sources' 16 | from sourceSets.main.allSource 17 | } 18 | 19 | task dokkaJar(type: Jar, dependsOn: project.dokka) { 20 | classifier = 'javadoc' 21 | from dokka 22 | } 23 | 24 | artifacts { 25 | archives sourcesJar 26 | archives dokkaJar 27 | } 28 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/Angle.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | /** 4 | * Two-dimensional angle. 5 | * 6 | * @property deg Angle measure in degrees. 7 | * @property rad Angle measure in radians. 8 | */ 9 | data class Angle private constructor(val deg: Float, val rad: Float) : Comparable { 10 | 11 | companion object { 12 | 13 | /** 14 | * Null angle. 15 | */ 16 | val NULL = Angle(0f, 0f) 17 | 18 | /** 19 | * Full angle. 20 | */ 21 | val FULL = Angle(360f, (2.0 * Math.PI).toFloat()) 22 | 23 | /** 24 | * Straight angle. 25 | */ 26 | val STRAIGHT = FULL / 2f 27 | 28 | /** 29 | * Right angle. 30 | */ 31 | val RIGHT = STRAIGHT / 2f 32 | 33 | /** 34 | * Creates [Angle] from degrees. 35 | */ 36 | fun fromDeg(deg: Float) = Angle(deg, (deg * Math.PI / 180.0).toFloat()) 37 | 38 | /** 39 | * Creates [Angle] from radians. 40 | */ 41 | fun fromRad(rad: Float) = Angle((rad * 180.0 / Math.PI).toFloat(), rad) 42 | } 43 | 44 | /** 45 | * Returns a string representation of the [Angle]. 46 | */ 47 | override fun toString() = "%.1f\u00B0".format(deg) 48 | 49 | /** 50 | * Returns an angle opposite to this angle. 51 | */ 52 | operator fun unaryMinus() = Angle(-deg, -rad) 53 | 54 | /** 55 | * Returns a sum of this angle and the [other] angle. 56 | */ 57 | operator fun plus(other: Angle) = Angle(deg + other.deg, rad + other.rad) 58 | 59 | /** 60 | * Returns a difference of this angle and the [other] angle. 61 | */ 62 | operator fun minus(other: Angle) = Angle(deg - other.deg, rad - other.rad) 63 | 64 | /** 65 | * Returns a product of this angle and a [number]. 66 | */ 67 | operator fun times(number: Float) = Angle(deg * number, rad * number) 68 | 69 | /** 70 | * Returns a quotient of this angle and a [number]. 71 | */ 72 | operator fun div(number: Float) = Angle(deg / number, rad / number) 73 | 74 | /** 75 | * Returns a quotient of this angle and the [other] angle. 76 | */ 77 | operator fun div(other: Angle) = rad / other.rad 78 | 79 | /** 80 | * Returns a remainder of dividing this angle by the [other] angle. 81 | */ 82 | operator fun mod(other: Angle) = (deg % other.deg).degrees 83 | 84 | /** 85 | * Returns a range of angles. 86 | */ 87 | operator fun rangeTo(other: Angle) = AnglesRange(this, other) 88 | 89 | /** 90 | * Returns an angle coterminal to this angle, closest to [other] angle in clockwise direction. 91 | */ 92 | infix fun clockwiseFrom(other: Angle) = 93 | other - (other - this) % FULL - if (other < this) FULL else NULL 94 | 95 | /** 96 | * Returns an angle coterminal to this angle, closest to [other] angle in counter-clockwise direction. 97 | */ 98 | infix fun counterClockwiseFrom(other: Angle) = 99 | other + (this - other) % FULL + if (other > this) FULL else NULL 100 | 101 | /** 102 | * Compares this angle to the [other] angle. 103 | * 104 | * Returns zero if this angle is equal to the [other] angle, 105 | * a negative number if it is less than [other], 106 | * or a positive number if it is greater than [other]. 107 | */ 108 | override operator fun compareTo(other: Angle) = rad.compareTo(other.rad) 109 | } 110 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/Angles.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | /** 4 | * Creates [Angle] from degrees. 5 | */ 6 | val Int.degrees: Angle get() = this.toFloat().degrees 7 | 8 | /** 9 | * Creates [Angle] from radians. 10 | */ 11 | val Int.radians: Angle get() = this.toFloat().radians 12 | 13 | /** 14 | * Creates [Angle] from degrees. 15 | */ 16 | val Long.degrees: Angle get() = this.toFloat().degrees 17 | 18 | /** 19 | * Creates [Angle] from radians. 20 | */ 21 | val Long.radians: Angle get() = this.toFloat().radians 22 | 23 | /** 24 | * Creates [Angle] from degrees. 25 | */ 26 | val Float.degrees: Angle get() = Angle.fromDeg(this) 27 | 28 | /** 29 | * Creates [Angle] from radians. 30 | */ 31 | val Float.radians: Angle get() = Angle.fromRad(this) 32 | 33 | /** 34 | * Creates [Angle] from degrees. 35 | */ 36 | val Double.degrees: Angle get() = this.toFloat().degrees 37 | 38 | /** 39 | * Creates [Angle] from radians. 40 | */ 41 | val Double.radians: Angle get() = this.toFloat().radians 42 | 43 | 44 | /** 45 | * Returns the trigonometric sine of an [angle]. 46 | */ 47 | fun sin(angle: Angle) = Math.sin(angle.rad.toDouble()).toFloat() 48 | 49 | /** 50 | * Returns the trigonometric cosine of an [angle]. 51 | */ 52 | fun cos(angle: Angle) = Math.cos(angle.rad.toDouble()).toFloat() 53 | 54 | /** 55 | * Returns the trigonometric tangent of an [angle]. 56 | */ 57 | fun tan(angle: Angle) = Math.tan(angle.rad.toDouble()).toFloat() 58 | 59 | /** 60 | * Returns the angle _theta_ from the conversion of rectangular coordinates ([x], [y]) to polar coordinates (r, _theta_). 61 | */ 62 | fun atan2(y: Float, x: Float) = Math.atan2(y.toDouble(), x.toDouble()).radians 63 | 64 | /** 65 | * Returns absolute value of the [angle]. 66 | */ 67 | fun abs(angle: Angle) = if (angle < Angle.NULL) -angle else angle 68 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/AnglesRange.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | /** 4 | * Range of angles. 5 | */ 6 | data class AnglesRange(override val start: Angle, override val endInclusive: Angle): ClosedRange { 7 | 8 | /** 9 | * Returns an even partition of the range split in [count] parts. 10 | */ 11 | infix fun partition(count: Int) = 12 | (0..count).map { it.toFloat() / count.toFloat() }.map { start + (endInclusive - start) * it } 13 | } 14 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/Color.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | import glimpse.buffers.toDirectFloatBuffer 4 | 5 | /** 6 | * RGBA color. 7 | * 8 | * @property red Red channel. 9 | * @property green Green channel. 10 | * @property blue Blue channel. 11 | * @property alpha Alpha channel. 12 | */ 13 | data class Color(val red: Float, val green: Float, val blue: Float, val alpha: Float = 1f) { 14 | 15 | companion object { 16 | val BLACK = Color(0.0f, 0.0f, 0.0f) 17 | val GRAY = Color(0.5f, 0.5f, 0.5f) 18 | val WHITE = Color(1.0f, 1.0f, 1.0f) 19 | 20 | val RED = Color(1.0f, 0.0f, 0.0f) 21 | val GREEN = Color(0.0f, 1.0f, 0.0f) 22 | val BLUE = Color(0.0f, 0.0f, 1.0f) 23 | 24 | val CYAN = Color(0.0f, 1.0f, 1.0f) 25 | val MAGENTA = Color(1.0f, 0.0f, 1.0f) 26 | val YELLOW = Color(1.0f, 1.0f, 0.0f) 27 | } 28 | 29 | internal val _3f: Array by lazy { arrayOf(red, green, blue) } 30 | internal val _4f: Array by lazy { arrayOf(red, green, blue, alpha) } 31 | 32 | /** 33 | * Returns a new [Color] with [newAlpha] channel. 34 | */ 35 | infix fun transparent(newAlpha: Float) = Color(red, green, blue, newAlpha) 36 | 37 | /** 38 | * Returns a string representation of the [Color]. 39 | */ 40 | override fun toString() = 41 | "#%02x%02x%02x%02x".format( 42 | alpha.toIntChannel(), red.toIntChannel(), green.toIntChannel(), blue.toIntChannel()) 43 | 44 | private fun Float.toIntChannel() = (this * 255f).toInt() 45 | } 46 | 47 | /** 48 | * Returns a direct buffer containing values of RGB channels of colors in the original list. 49 | */ 50 | fun List.toDirectBuffer() = toDirectFloatBuffer(size * 3) { it._3f } 51 | 52 | /** 53 | * Returns a direct buffer containing values of RGBA channels of colors in the original list. 54 | */ 55 | fun List.toDirectBufferWithAlpha() = toDirectFloatBuffer(size * 4) { it._4f } 56 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/GlimpseException.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | /** 4 | * Common superclass for all Glimpse Framework exceptions. 5 | * 6 | * @param message Exception message. 7 | */ 8 | abstract class GlimpseException(message: String = "Exception in Glimpse Framework") : Exception(message) 9 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/Matrices.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | /** 4 | * Returns a frustum projection [Matrix]. 5 | * The [left] to [right], [top] to [bottom] rectangle is the [near] clipping plane of the frustum. 6 | */ 7 | fun frustumProjectionMatrix(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float): Matrix { 8 | val width = right - left 9 | val height = top - bottom 10 | val depth = near - far 11 | return Matrix(listOf( 12 | 2f * near / width, 0f, 0f, 0f, 13 | 0f, 2f * near / height, 0f, 0f, 14 | (right + left) / width, (top + bottom) / height, (far + near) / depth, -1f, 15 | 0f, 0f, 2f * far * near / depth, 0f)) 16 | } 17 | 18 | /** 19 | * Returns a perspective projection [Matrix]. 20 | * 21 | * @param fovY Field of view angle for Y-axis (viewport height axis). 22 | * @param aspect Width aspect ratio against height. 23 | * @param near Near clipping plane distance. 24 | * @param far Far clipping plane distance. 25 | */ 26 | fun perspectiveProjectionMatrix(fovY: Angle, aspect: Float, near: Float, far: Float): Matrix { 27 | val top = tan(fovY / 2f) * near 28 | val right = aspect * top 29 | val depth = near - far 30 | return Matrix(listOf( 31 | 1f / right, 0f, 0f, 0f, 32 | 0f, 1f / top, 0f, 0f, 33 | 0f, 0f, (near + far) / depth, -1f, 34 | 0f, 0f, 2 * near * far / depth, 0f)) 35 | } 36 | 37 | /** 38 | * Returns an orthographic projection [Matrix]. 39 | */ 40 | fun orthographicProjectionMatrix(left: Float, right: Float, bottom: Float, top: Float, near: Float, far: Float): Matrix { 41 | val width = right - left 42 | val height = top - bottom 43 | val depth = far - near 44 | return Matrix(listOf( 45 | 2f / width, 0f, 0f, 0f, 46 | 0f, 2f / height, 0f, 0f, 47 | 0f, 0f, -2f / depth, 0f, 48 | -(right + left) / width, -(top + bottom) / height, -(near + far) / depth, 1f)) 49 | } 50 | 51 | 52 | /** 53 | * Returns a look-at view [Matrix]. 54 | */ 55 | fun lookAtViewMatrix(eye: Point, center: Point, up: Vector): Matrix { 56 | val f = (eye to center).normalize 57 | val s = (f * up).normalize 58 | val u = s * f 59 | return Matrix(listOf( 60 | s.x, u.x, -f.x, 0f, 61 | s.y, u.y, -f.y, 0f, 62 | s.z, u.z, -f.z, 0f, 63 | 0f, 0f, 0f, 1f)) * translationMatrix(-eye.toVector()) 64 | } 65 | 66 | 67 | /** 68 | * Returns a transformation [Matrix] for a translation by an [vector]. 69 | */ 70 | fun translationMatrix(vector: Vector): Matrix { 71 | val (x, y, z) = vector 72 | return Matrix(listOf( 73 | 1f, 0f, 0f, 0f, 74 | 0f, 1f, 0f, 0f, 75 | 0f, 0f, 1f, 0f, 76 | x, y, z, 1f)) 77 | } 78 | 79 | /** 80 | * Returns a transformation [Matrix] for a rotation by an [angle] around an [axis]. 81 | */ 82 | fun rotationMatrix(axis: Vector, angle: Angle): Matrix { 83 | val (x, y, z) = axis.normalize 84 | val sin = sin(angle) 85 | val cos = cos(angle) 86 | val nCos = 1f - cos(angle) 87 | return Matrix(listOf( 88 | cos + x * x * nCos, x * y * nCos + z * sin, x * z * nCos - y * sin, 0f, 89 | x * y * nCos - z * sin, cos + y * y * nCos, y * z * nCos + x * sin, 0f, 90 | x * z * nCos + y * sin, y * z * nCos - x * sin, cos + z * z * nCos, 0f, 91 | 0f, 0f, 0f, 1f)) 92 | } 93 | 94 | /** 95 | * Returns a transformation [Matrix] for a rotation by an [angle] around X axis. 96 | */ 97 | fun rotationMatrixX(angle: Angle): Matrix { 98 | val sin = sin(angle) 99 | val cos = cos(angle) 100 | return Matrix(listOf( 101 | 1f, 0f, 0f, 0f, 102 | 0f, cos, sin, 0f, 103 | 0f, -sin, cos, 0f, 104 | 0f, 0f, 0f, 1f)) 105 | } 106 | 107 | /** 108 | * Returns a transformation [Matrix] for a rotation by an [angle] around Y axis. 109 | */ 110 | fun rotationMatrixY(angle: Angle): Matrix { 111 | val sin = sin(angle) 112 | val cos = cos(angle) 113 | return Matrix(listOf( 114 | cos, 0f, -sin, 0f, 115 | 0f, 1f, 0f, 0f, 116 | sin, 0f, cos, 0f, 117 | 0f, 0f, 0f, 1f)) 118 | } 119 | 120 | /** 121 | * Returns a transformation [Matrix] for a rotation by an [angle] around Z axis. 122 | */ 123 | fun rotationMatrixZ(angle: Angle): Matrix { 124 | val sin = sin(angle) 125 | val cos = cos(angle) 126 | return Matrix(listOf( 127 | cos, sin, 0f, 0f, 128 | -sin, cos, 0f, 0f, 129 | 0f, 0f, 1f, 0f, 130 | 0f, 0f, 0f, 1f)) 131 | } 132 | 133 | /** 134 | * Returns a transformation [Matrix] for uniform scaling. 135 | */ 136 | fun scalingMatrix(scale: Float): Matrix = scalingMatrix(scale, scale, scale) 137 | 138 | /** 139 | * Returns a transformation [Matrix] for scaling. 140 | */ 141 | fun scalingMatrix(x: Float = 1f, y: Float = 1f, z: Float = 1f): Matrix = 142 | Matrix(listOf( 143 | x, 0f, 0f, 0f, 144 | 0f, y, 0f, 0f, 145 | 0f, 0f, z, 0f, 146 | 0f, 0f, 0f, 1f)) 147 | 148 | /** 149 | * Returns a transformation [Matrix] for reflection through a plane, 150 | * defined by a [normal] vector and a [point] on the plane. 151 | */ 152 | fun reflectionMatrix(normal: Vector, point: Point): Matrix { 153 | val (a, b, c) = normal.normalize 154 | val d = -point.toVector() dot (normal.normalize) 155 | return Matrix(listOf( 156 | 1f - 2f * a * a, -2f * b * a, -2f * c * a, 0f, 157 | -2f * a * b, 1f - 2f * b * b, -2f * c * b, 0f, 158 | -2f * a * c, -2f * b * c, 1f - 2f * c * c, 0f, 159 | -2f * a * d, -2f * b * d, -2f * c * d, 1f)) 160 | } 161 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/Matrix.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | /** 4 | * Matrix defining three-dimensional affine transformations. 5 | */ 6 | data class Matrix(private val matrix: List) { 7 | 8 | companion object { 9 | 10 | /** 11 | * Identity matrix. 12 | */ 13 | val IDENTITY = SquareMatrix.identity(4).asMatrix() 14 | 15 | /** 16 | * Null matrix. 17 | */ 18 | val NULL = SquareMatrix.nullMatrix(4).asMatrix() 19 | } 20 | 21 | init { 22 | require(matrix.size === 16) { 23 | "Matrix must consist of exactly 16 (4×4) elements. ${matrix.size} elements were provided instead." 24 | } 25 | } 26 | 27 | /** 28 | * Matrix trimmed to 3 by 3, and supplemented with identity matrix values. 29 | */ 30 | val trimmed: Matrix by lazy { 31 | val subMatrix = squareMatrix.sub(3, 3) 32 | SquareMatrix(4) { row, col -> 33 | if (row < 3 && col < 3) subMatrix[row, col] 34 | else IDENTITY[row, col] 35 | }.asMatrix() 36 | } 37 | 38 | internal val _16f : Array by lazy { matrix.toTypedArray() } 39 | 40 | internal val squareMatrix: SquareMatrix by lazy { SquareMatrix(4) { row, col -> this[row, col] } } 41 | 42 | /** 43 | * Returns a string representation of the [Matrix]. 44 | */ 45 | override fun toString() = 46 | (0..3).map { row -> 47 | (0..3).map { col -> 48 | "%8.2f".format(this[row, col]) 49 | }.joinToString(separator = " ", prefix = "| ", postfix = " |") { it } 50 | }.joinToString(separator = "\n", prefix = "\n", postfix = "\n") { it } 51 | 52 | /** 53 | * Gets element of the matrix at the given position. 54 | * 55 | * @param row Row index. 56 | * @param col Column index. 57 | */ 58 | operator fun get(row: Int, col: Int) = matrix[col * 4 + row] 59 | 60 | /** 61 | * Multiplies this matrix by the [other] matrix. 62 | */ 63 | operator fun times(other: Matrix): Matrix = (squareMatrix * other.squareMatrix).asMatrix() 64 | 65 | /** 66 | * Multiplies this matrix by a [vector]. 67 | */ 68 | operator fun times(vector: Vector): Vector { 69 | val v = timesArray(vector._4f) 70 | return Vector(v[0], v[1], v[2]) 71 | } 72 | 73 | private fun timesArray(array: Array): List { 74 | val result = (0..3).map { row -> 75 | (0..3).map { col -> 76 | this[row, col] * array[col] 77 | }.sum() 78 | } 79 | return result.map { it / result[3] } 80 | } 81 | 82 | /** 83 | * Multiplies this matrix by a [point]. 84 | */ 85 | operator fun times(point: Point): Point { 86 | val p = timesArray(point._4f) 87 | return Point(p[0], p[1], p[2]) 88 | } 89 | 90 | /** 91 | * Returns a transposed matrix. 92 | */ 93 | fun transpose(): Matrix = squareMatrix.transpose().asMatrix() 94 | 95 | /** 96 | * Returns an inverted matrix. 97 | */ 98 | fun inverse(): Matrix = squareMatrix.inverse().asMatrix() 99 | } 100 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/MatrixBuilder.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | /** 4 | * Affine transformation matrix builder. 5 | * 6 | * @param matrix Initial matrix. 7 | */ 8 | class MatrixBuilder(internal var matrix: Matrix = Matrix.IDENTITY) { 9 | 10 | private fun transform(transformation: Matrix) { 11 | matrix = transformation * matrix 12 | } 13 | 14 | /** 15 | * Translates by a vector. 16 | */ 17 | fun translate(vector: Vector) { 18 | transform(translationMatrix(vector)) 19 | } 20 | 21 | /** 22 | * Rotates by an [angle] around an [axis]. 23 | */ 24 | fun rotate(axis: Vector, angle: Angle) { 25 | transform(rotationMatrix(axis, angle)) 26 | } 27 | 28 | /** 29 | * Rotates by an [angle] around X axis. 30 | */ 31 | fun rotateX(angle: Angle) { 32 | transform(rotationMatrixX(angle)) 33 | } 34 | 35 | /** 36 | * Rotates by an [angle] around Y axis. 37 | */ 38 | fun rotateY(angle: Angle) { 39 | transform(rotationMatrixY(angle)) 40 | } 41 | 42 | /** 43 | * Rotates by an [angle] around Z axis. 44 | */ 45 | fun rotateZ(angle: Angle) { 46 | transform(rotationMatrixZ(angle)) 47 | } 48 | 49 | /** 50 | * Scales by a [scale] factor. 51 | */ 52 | fun scale(scale: Float) { 53 | transform(scalingMatrix(scale)) 54 | } 55 | 56 | /** 57 | * Scales by a [x], [y], [z] factors along X, Y and Z axes. 58 | */ 59 | fun scale(x: Float = 1f, y: Float = 1f, z: Float = 1f) { 60 | transform(scalingMatrix(x, y, z)) 61 | } 62 | 63 | /** 64 | * Reflects through a plane, defined by a [normal] vector and a [point] on the plane. 65 | */ 66 | fun reflect(normal: Vector, point: Point) { 67 | transform(reflectionMatrix(normal, point)) 68 | } 69 | } 70 | 71 | /** 72 | * Builds an affine [transformation] matrix function. 73 | */ 74 | fun matrix(transformation: MatrixBuilder.() -> Unit): () -> Matrix = { 75 | val builder = MatrixBuilder() 76 | builder.transformation() 77 | builder.matrix 78 | } 79 | 80 | /** 81 | * Builds an affine [transformation] matrix function, starting from given [transformationMatrix]. 82 | */ 83 | fun matrix(transformationMatrix: Matrix, transformation: MatrixBuilder.() -> Unit): () -> Matrix = { 84 | val builder = MatrixBuilder(transformationMatrix) 85 | builder.transformation() 86 | builder.matrix 87 | } 88 | 89 | /** 90 | * Builds an affine [transformation] matrix function, starting from given [transformationMatrix] lambda. 91 | */ 92 | fun matrix(transformationMatrix: () -> Matrix, transformation: MatrixBuilder.() -> Unit): () -> Matrix = { 93 | val builder = MatrixBuilder(transformationMatrix()) 94 | builder.transformation() 95 | builder.matrix 96 | } 97 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/Point.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | import glimpse.buffers.toDirectFloatBuffer 4 | 5 | /** 6 | * A point in three-dimensional space. 7 | * 8 | * @param x X coordinate. 9 | * @param y Y coordinate. 10 | * @param z Z coordinate. 11 | */ 12 | data class Point(val x: Float, val y: Float, val z: Float = 0f) { 13 | 14 | companion object { 15 | 16 | /** 17 | * Origin of the coordinate system. 18 | */ 19 | val ORIGIN = Point(0f, 0f, 0f) 20 | 21 | } 22 | 23 | internal val _3f: Array by lazy { arrayOf(x, y, z) } 24 | internal val _4f: Array by lazy { arrayOf(x, y, z, 1f) } 25 | 26 | /** 27 | * Returns a string representation of the [Point]. 28 | */ 29 | override fun toString() = "(%.1f, %.1f, %.1f)".format(x, y, z) 30 | 31 | /** 32 | * Returns a point translated by a [vector]. 33 | */ 34 | infix fun translateBy(vector: Vector) = Point(x + vector.x, y + vector.y, z + vector.z) 35 | 36 | /** 37 | * Returns a vector starting at this [Point] and ending at the [other] point. 38 | */ 39 | infix fun to(other: Point) = Vector(other.x - x, other.y - y, other.z - z) 40 | 41 | /** 42 | * Converts the [Point] to a [Vector]. 43 | */ 44 | fun toVector() = Vector(x, y, z) 45 | } 46 | 47 | /** 48 | * Returns a direct buffer containing values of X, Y, Z coordinates of points from the original list. 49 | */ 50 | fun List.toDirectBuffer() = toDirectFloatBuffer(size * 3) { it._3f } 51 | 52 | /** 53 | * Returns a direct buffer containing values of X, Y, Z, 1 coordinates of augmented points from the original list. 54 | */ 55 | fun List.toDirectBufferAugmented() = toDirectFloatBuffer(size * 4) { it._4f } 56 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/SquareMatrix.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | internal class SquareMatrix(val size: Int, private val elements: (Int, Int) -> Float) { 4 | 5 | companion object { 6 | 7 | fun nullMatrix(size: Int) = SquareMatrix(size) { row, col -> 0f } 8 | 9 | fun identity(size: Int) = SquareMatrix(size) { row, col -> 10 | if (row == col) 1f else 0f 11 | } 12 | } 13 | 14 | operator fun get(row: Int, col: Int): Float { 15 | require(row in 0..size - 1) { "Row ${row} out of bounds: 0..${size - 1}" } 16 | require(col in 0..size - 1) { "Column ${col} out of bounds: 0..${size - 1}" } 17 | return elements(row, col) 18 | } 19 | 20 | /** 21 | * Determinant of the matrix. 22 | */ 23 | val det: Float by lazy { 24 | if (size == 1) this[0, 0] 25 | else (0..size - 1).map { item -> this[0, item] * comatrix[0, item] }.sum() 26 | } 27 | 28 | private val comatrix: SquareMatrix by lazy { 29 | SquareMatrix(size) { row, col -> cofactor(row, col) } 30 | } 31 | 32 | private fun cofactor(row: Int, col: Int): Float = 33 | minor(row, col) * if ((row + col) % 2 == 0) 1f else -1f 34 | 35 | private fun minor(row: Int, col: Int): Float = 36 | sub(row, col).det 37 | 38 | internal fun sub(delRow: Int, delCol: Int) = SquareMatrix(size - 1) { row, col -> 39 | this[if (row < delRow) row else row + 1, if (col < delCol) col else col + 1] 40 | } 41 | 42 | /** 43 | * Adjugate matrix. 44 | */ 45 | val adj: SquareMatrix by lazy { comatrix.transpose() } 46 | 47 | fun transpose(): SquareMatrix = SquareMatrix(size) { row, col -> this[col, row] } 48 | 49 | fun inverse(): SquareMatrix = SquareMatrix(size) { row, col -> adj[row, col] / det } 50 | 51 | operator fun times(other: SquareMatrix): SquareMatrix { 52 | require(other.size == size) { "Cannot multiply matrices of different sizes." } 53 | return SquareMatrix(size) { row, col -> 54 | (0..size - 1).map {this[row, it] * other[it, col] }.sum() 55 | } 56 | } 57 | 58 | fun asList(): List = (0..size * size - 1).map { this[it % size, it / size] } 59 | 60 | fun asMatrix(): Matrix = Matrix(asList()) 61 | } 62 | 63 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/TextureCoordinates.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | import glimpse.buffers.toDirectFloatBuffer 4 | 5 | /** 6 | * Texture coordinates. 7 | * 8 | * @property u U coordinate. 9 | * @property v V coordinate. 10 | */ 11 | data class TextureCoordinates(val u: Float, val v: Float) { 12 | 13 | companion object { 14 | 15 | /** 16 | * Bottom left corner of a texture. 17 | */ 18 | val BOTTOM_LEFT = TextureCoordinates(0f, 0f) 19 | 20 | /** 21 | * Bottom right corner of a texture. 22 | */ 23 | val BOTTOM_RIGHT = TextureCoordinates(1f, 0f) 24 | 25 | /** 26 | * Top left corner of a texture. 27 | */ 28 | val TOP_LEFT = TextureCoordinates(0f, 1f) 29 | 30 | /** 31 | * Top right corner of a texture. 32 | */ 33 | val TOP_RIGHT = TextureCoordinates(1f, 1f) 34 | } 35 | 36 | internal val _2f: Array by lazy { arrayOf(u, v) } 37 | 38 | /** 39 | * Returns a string representation of the [TextureCoordinates]. 40 | */ 41 | override fun toString() = "(%.1f, %.1f)".format(u, v) 42 | } 43 | 44 | /** 45 | * Returns a direct buffer containing values of UV coordinates in the original list. 46 | */ 47 | fun List.toDirectBuffer() = toDirectFloatBuffer(size * 2) { it._2f } 48 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/Vector.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | import glimpse.buffers.toDirectFloatBuffer 4 | 5 | /** 6 | * A vector in three-dimensional space. 7 | * 8 | * @param x X coordinate. 9 | * @param y Y coordinate. 10 | * @param z Z coordinate. 11 | */ 12 | data class Vector(val x: Float, val y: Float, val z: Float) { 13 | 14 | /** 15 | * Constructs a [Vector] from spherical coordinates. 16 | * 17 | * @param radius Radial coordinate. 18 | * @param inclination Inclination. 19 | * @param azimuth Azimuth. 20 | */ 21 | constructor(radius: Float, inclination: Angle, azimuth: Angle) : this( 22 | radius * sin(inclination) * cos(azimuth), 23 | radius * sin(inclination) * sin(azimuth), 24 | radius * cos(inclination)) 25 | 26 | companion object { 27 | 28 | /** 29 | * Null vector. 30 | */ 31 | val NULL = Vector(0f, 0f, 0f) 32 | 33 | /** 34 | * Unit vector in the direction of the X axis. 35 | */ 36 | val X_UNIT = Vector(1f, 0f, 0f) 37 | 38 | /** 39 | * Unit vector in the direction of the Y axis. 40 | */ 41 | val Y_UNIT = Vector(0f, 1f, 0f) 42 | 43 | /** 44 | * Unit vector in the direction of the Z axis. 45 | */ 46 | val Z_UNIT = Vector(0f, 0f, 1f) 47 | } 48 | 49 | internal val _3f: Array by lazy { arrayOf(x, y, z) } 50 | internal val _4f: Array by lazy { arrayOf(x, y, z, 1f) } 51 | 52 | /** 53 | * Returns a string representation of the [Vector]. 54 | */ 55 | override fun toString() = "[%.1f, %.1f, %.1f]".format(x, y, z) 56 | 57 | /** 58 | * Magnitude of the [Vector]. 59 | */ 60 | val magnitude by lazy { Math.sqrt((x * x + y * y + z * z).toDouble()).toFloat() } 61 | 62 | /** 63 | * Inclination of the vector in spherical coordinate system. 64 | */ 65 | val inclination by lazy { atan2(Vector(x, y, 0f).magnitude, z) } 66 | 67 | /** 68 | * Azimuth of the vector in spherical coordinate system. 69 | */ 70 | val azimuth by lazy { atan2(y, x) } 71 | 72 | /** 73 | * Returns a unit vector in the direction of this [Vector]. 74 | */ 75 | val normalize by lazy { normalize(1f) } 76 | 77 | /** 78 | * Returns a vector opposit to this vector. 79 | */ 80 | operator fun unaryMinus() = Vector(-x, -y, -z) 81 | 82 | /** 83 | * Returns a sum of this vector and the [other] vector. 84 | */ 85 | operator fun plus(other: Vector) = Vector(x + other.x, y + other.y, z + other.z) 86 | 87 | /** 88 | * Returns a difference of this vector and the [other] vector. 89 | */ 90 | operator fun minus(other: Vector) = Vector(x - other.x, y - other.y, z - other.z) 91 | 92 | /** 93 | * Returns a cross product of this vector and the [other] vector. 94 | */ 95 | operator fun times(other: Vector): Vector = Vector( 96 | y * other.z - z * other.y, 97 | z * other.x - x * other.z, 98 | x * other.y - y * other.x) 99 | 100 | /** 101 | * Returns a dot product of this vector and the [other] vector. 102 | */ 103 | infix fun dot(other: Vector): Float = _3f.zip(other._3f).map { it.first * it.second }.sum() 104 | 105 | /** 106 | * Returns a product of this vector and the [number]. 107 | */ 108 | operator fun times(number: Float) = Vector(x * number, y * number, z * number) 109 | 110 | /** 111 | * Returns a quotient of this vector and the [number]. 112 | */ 113 | operator fun div(number: Float) = Vector(x / number, y / number, z / number) 114 | 115 | /** 116 | * Returns `true` if this vector is parallel to the [other] vector. 117 | */ 118 | infix fun parallelTo(other: Vector) = this * other == NULL 119 | 120 | /** 121 | * Returns a vector of the given [magnitude], in the direction of this vector. 122 | */ 123 | fun normalize(magnitude: Float): Vector { 124 | require(this.magnitude > 0f) { "Cannot normalize a null vector. Division by 0." } 125 | return this * magnitude / this.magnitude 126 | } 127 | 128 | /** 129 | * Converts the [Vector] to a [Point]. 130 | */ 131 | fun toPoint() = Point(x, y, z) 132 | } 133 | 134 | /** 135 | * Returns a direct buffer containing values of X, Y, Z coordinates of vectors from the original list. 136 | */ 137 | fun List.toDirectBuffer() = toDirectFloatBuffer(size * 3) { it._3f } 138 | 139 | /** 140 | * Returns a direct buffer containing values of X, Y, Z, 1 coordinates of augmented vectors from the original list. 141 | */ 142 | fun List.toDirectBufferAugmented() = toDirectFloatBuffer(size * 4) { it._4f } 143 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/buffers/Buffers.kt: -------------------------------------------------------------------------------- 1 | package glimpse.buffers 2 | 3 | import java.nio.ByteBuffer 4 | import java.nio.ByteOrder 5 | import java.nio.FloatBuffer 6 | 7 | internal fun directByteBuffer(size: Int): ByteBuffer = 8 | ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()) 9 | 10 | internal fun directFloatBuffer(size: Int): FloatBuffer = 11 | directByteBuffer(size * 4).asFloatBuffer() 12 | 13 | internal fun List.toDirectFloatBuffer(size: Int, transform: (T) -> Array): FloatBuffer = 14 | directFloatBuffer(size).put(fold(emptyList()) { list, next -> list + transform(next) }.toFloatArray()) 15 | 16 | internal fun FloatBuffer.toList(): List { 17 | rewind() 18 | val array = FloatArray(capacity()) 19 | get(array) 20 | return array.toList() 21 | } 22 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/cameras/Camera.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.Matrix 4 | import glimpse.Point 5 | 6 | /** 7 | * Camera. 8 | * 9 | * @property view Camera view. 10 | * @property projection Camera projection. 11 | */ 12 | class Camera(val view: CameraView, val projection: CameraProjection) { 13 | 14 | /** 15 | * Camera transformation matrix. 16 | */ 17 | val cameraMatrix: Matrix 18 | get() = projection.projectionMatrix * view.viewMatrix 19 | 20 | /** 21 | * Camera position. 22 | */ 23 | val position: Point 24 | get() = view.position 25 | } 26 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/cameras/CameraBuilder.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import kotlin.properties.Delegates 4 | 5 | /** 6 | * Camera builder. 7 | */ 8 | class CameraBuilder { 9 | 10 | internal var cameraView: CameraView by Delegates.notNull() 11 | internal var cameraProjection: CameraProjection by Delegates.notNull() 12 | 13 | internal fun build(): Camera = Camera(cameraView, cameraProjection) 14 | } 15 | 16 | /** 17 | * Builds a [Camera] initialized with an [init] function. 18 | */ 19 | fun camera(init: CameraBuilder.() -> Unit): Camera { 20 | val builder = CameraBuilder() 21 | builder.init() 22 | return builder.build() 23 | } 24 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/cameras/CameraProjection.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.Matrix 4 | 5 | /** 6 | * Camera projection. 7 | */ 8 | interface CameraProjection { 9 | 10 | /** 11 | * Projection matrix. 12 | */ 13 | val projectionMatrix: Matrix 14 | } -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/cameras/CameraView.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.Matrix 4 | import glimpse.Point 5 | 6 | /** 7 | * Camera view. 8 | */ 9 | interface CameraView { 10 | 11 | /** 12 | * View matrix. 13 | */ 14 | val viewMatrix: Matrix 15 | 16 | /** 17 | * Camera position. 18 | */ 19 | val position: Point 20 | } -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/cameras/FreeformCameraView.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.Angle 4 | import glimpse.Matrix 5 | import glimpse.Point 6 | import glimpse.matrix 7 | 8 | /** 9 | * Free-form camera view. 10 | * 11 | * @property cameraPosition Camera position lambda. 12 | * @property roll Camera roll lambda. 13 | * @property pitch Camera pitch lambda. 14 | * @property yaw Camera yaw lambda. 15 | */ 16 | class FreeformCameraView(val cameraPosition: () -> Point, val roll: () -> Angle, val pitch: () -> Angle, val yaw: () -> Angle) : CameraView { 17 | 18 | companion object { 19 | 20 | private val DEFAULT_MATRIX = Matrix(listOf( 21 | 0.0f, 1.0f, 0.0f, 0.0f, 22 | 0.0f, 0.0f, -1.0f, 0.0f, 23 | -1.0f, 0.0f, 0.0f, 0.0f, 24 | 0.0f, 0.0f, 0.0f, 1.0f)) 25 | } 26 | 27 | private val viewMatrixFunction = matrix(DEFAULT_MATRIX) { 28 | translate(-position.toVector()) 29 | rotateZ(-yaw()) 30 | rotateY(-pitch()) 31 | rotateX(-roll()) 32 | } 33 | 34 | override val viewMatrix: Matrix 35 | get() = viewMatrixFunction() 36 | 37 | override val position: Point 38 | get() = cameraPosition() 39 | } -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/cameras/FreeformCameraViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.Angle 4 | import glimpse.Point 5 | 6 | /** 7 | * Builder of a [FreeformCameraView]. 8 | */ 9 | class FreeformCameraViewBuilder() { 10 | 11 | private var position: () -> Point = { Point.ORIGIN } 12 | private var roll: () -> Angle = { Angle.NULL } 13 | private var pitch: () -> Angle = { Angle.NULL } 14 | private var yaw: () -> Angle = { Angle.NULL } 15 | 16 | /** 17 | * Sets camera position lambda. 18 | */ 19 | fun position(position: () -> Point) { 20 | this.position = position 21 | } 22 | 23 | /** 24 | * Sets camera roll angle lambda. 25 | */ 26 | fun roll(roll: () -> Angle) { 27 | this.roll = roll 28 | } 29 | 30 | /** 31 | * Sets camera pitch angle lambda. 32 | */ 33 | fun pitch(pitch: () -> Angle) { 34 | this.pitch = pitch 35 | } 36 | 37 | /** 38 | * Sets camera yaw angle lambda. 39 | */ 40 | fun yaw(yaw: () -> Angle) { 41 | this.yaw = yaw 42 | } 43 | 44 | internal fun build(): FreeformCameraView = FreeformCameraView(position, roll, pitch, yaw) 45 | } 46 | 47 | /** 48 | * Builds free-form camera view initialized with an [init] function. 49 | */ 50 | fun CameraBuilder.freeform(init: FreeformCameraViewBuilder.() -> Unit) { 51 | val cameraViewBuilder = FreeformCameraViewBuilder() 52 | cameraViewBuilder.init() 53 | cameraView = cameraViewBuilder.build() 54 | } 55 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/cameras/OrthographicCameraProjection.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.Angle 4 | import glimpse.Matrix 5 | import glimpse.orthographicProjectionMatrix 6 | import glimpse.perspectiveProjectionMatrix 7 | 8 | /** 9 | * Orthographic camera projection. 10 | * 11 | * @property height Projected region height lambda. 12 | * @property aspect Width aspect ratio against height lambda. 13 | * @property near Near clipping plane distance. 14 | * @property far Far clipping plane distance. 15 | */ 16 | class OrthographicCameraProjection(val height: () -> Float, val aspect: () -> Float, val near: Float, val far: Float) : CameraProjection { 17 | 18 | override val projectionMatrix: Matrix 19 | get() { 20 | val top = height() * .5f 21 | val right = top * aspect() 22 | return orthographicProjectionMatrix(-right, right, -top, top, near, far) 23 | } 24 | } -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/cameras/OrthographicCameraProjectionBuilder.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | /** 4 | * Builder of an [OrthographicCameraProjection]. 5 | */ 6 | class OrthographicCameraProjectionBuilder { 7 | 8 | private var height: () -> Float = { 2f } 9 | private var aspect: () -> Float = { 1f } 10 | private var near: Float = 1f 11 | private var far: Float = 100f 12 | 13 | /** 14 | * Sets camera projected region height lambda. 15 | */ 16 | fun height(height: () -> Float) { 17 | this.height = height 18 | } 19 | 20 | /** 21 | * Sets camera aspect ratio lambda. 22 | */ 23 | fun aspect(aspect: () -> Float) { 24 | this.aspect = aspect 25 | } 26 | 27 | /** 28 | * Sets camera near and far clipping planes. 29 | */ 30 | fun distanceRange(range: Pair) { 31 | near = range.first 32 | far = range.second 33 | } 34 | 35 | internal fun build(): OrthographicCameraProjection = OrthographicCameraProjection(height, aspect, near, far) 36 | } 37 | 38 | /** 39 | * Builds orthographic camera projecion initialized with an [init] function. 40 | */ 41 | fun CameraBuilder.orthographic(init: OrthographicCameraProjectionBuilder.() -> Unit) { 42 | val cameraProjectionBuilder = OrthographicCameraProjectionBuilder() 43 | cameraProjectionBuilder.init() 44 | cameraProjection = cameraProjectionBuilder.build() 45 | } 46 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/cameras/PerspectiveCameraProjection.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.Angle 4 | import glimpse.Matrix 5 | import glimpse.perspectiveProjectionMatrix 6 | 7 | /** 8 | * Perspective camera projection. 9 | * 10 | * @property fovY Field of view angle for Y-axis (viewport height axis) lambda. 11 | * @property aspect Width aspect ratio against height lambda. 12 | * @property near Near clipping plane distance. 13 | * @property far Far clipping plane distance. 14 | */ 15 | class PerspectiveCameraProjection(val fovY: () -> Angle, val aspect: () -> Float, val near: Float, val far: Float) : CameraProjection { 16 | 17 | override val projectionMatrix: Matrix 18 | get() = perspectiveProjectionMatrix(fovY(), aspect(), near, far) 19 | } -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/cameras/PerspectiveCameraProjectionBuilder.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.Angle 4 | import glimpse.degrees 5 | 6 | /** 7 | * Builder of a [PerspectiveCameraProjection]. 8 | */ 9 | class PerspectiveCameraProjectionBuilder { 10 | 11 | private var fovY: () -> Angle = { 60.degrees } 12 | private var aspect: () -> Float = { 1f } 13 | private var near: Float = 1f 14 | private var far: Float = 100f 15 | 16 | /** 17 | * Sets camera field of view angle for Y-axis (viewport height axis) lambda. 18 | */ 19 | fun fov(fovY: () -> Angle) { 20 | this.fovY = fovY 21 | } 22 | 23 | /** 24 | * Sets camera aspect ratio lambda. 25 | */ 26 | fun aspect(aspect: () -> Float) { 27 | this.aspect = aspect 28 | } 29 | 30 | /** 31 | * Sets camera near and far clipping planes. 32 | */ 33 | fun distanceRange(range: Pair) { 34 | near = range.first 35 | far = range.second 36 | } 37 | 38 | internal fun build(): PerspectiveCameraProjection = PerspectiveCameraProjection(fovY, aspect, near, far) 39 | } 40 | 41 | /** 42 | * Builds perspective camera projecion initialized with an [init] function. 43 | */ 44 | fun CameraBuilder.perspective(init: PerspectiveCameraProjectionBuilder.() -> Unit) { 45 | val cameraProjectionBuilder = PerspectiveCameraProjectionBuilder() 46 | cameraProjectionBuilder.init() 47 | cameraProjection = cameraProjectionBuilder.build() 48 | } 49 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/cameras/TargetedCameraView.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.Matrix 4 | import glimpse.Point 5 | import glimpse.Vector 6 | import glimpse.lookAtViewMatrix 7 | 8 | /** 9 | * Targeted camera view. 10 | * 11 | * @property cameraPosition Camera position lambda. 12 | * @property target Camera target lambda. 13 | * @property up Camera up vector lambda. 14 | */ 15 | class TargetedCameraView(val cameraPosition: () -> Point, val target: () -> Point = { Point.ORIGIN }, val up: () -> Vector = { Vector.Z_UNIT }) : CameraView { 16 | 17 | override val viewMatrix: Matrix 18 | get() = lookAtViewMatrix(cameraPosition(), target(), up()) 19 | 20 | override val position: Point 21 | get() = cameraPosition() 22 | } -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/cameras/TargetedCameraViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.Point 4 | import glimpse.Vector 5 | 6 | /** 7 | * Builder of a [TargetedCameraView]. 8 | */ 9 | class TargetedCameraViewBuilder() { 10 | 11 | private var position: () -> Point = { Vector.X_UNIT.toPoint() } 12 | private var target: () -> Point = { Point.ORIGIN } 13 | private var up: () -> Vector = { Vector.Z_UNIT } 14 | 15 | /** 16 | * Sets camera position lambda. 17 | */ 18 | fun position(position: () -> Point) { 19 | this.position = position 20 | } 21 | 22 | /** 23 | * Sets camera target lambda. 24 | */ 25 | fun target(target: () -> Point) { 26 | this.target = target 27 | } 28 | 29 | /** 30 | * Sets camera up vector lambda. 31 | */ 32 | fun up(up: () -> Vector) { 33 | this.up = up 34 | } 35 | 36 | internal fun build(): TargetedCameraView = TargetedCameraView(position, target, up) 37 | } 38 | 39 | /** 40 | * Builds targeted camera view initialized with an [init] function. 41 | */ 42 | fun CameraBuilder.targeted(init: TargetedCameraViewBuilder.() -> Unit) { 43 | val cameraViewBuilder = TargetedCameraViewBuilder() 44 | cameraViewBuilder.init() 45 | cameraView = cameraViewBuilder.build() 46 | } 47 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/gles/BlendFactor.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles 2 | 3 | /** 4 | * Blending factor. 5 | */ 6 | enum class BlendFactor() { 7 | ZERO, 8 | ONE, 9 | SRC_COLOR, 10 | ONE_MINUS_SRC_COLOR, 11 | DST_COLOR, 12 | ONE_MINUS_DST_COLOR, 13 | SRC_ALPHA, 14 | ONE_MINUS_SRC_ALPHA, 15 | DST_ALPHA, 16 | ONE_MINUS_DST_ALPHA, 17 | CONSTANT_COLOR, 18 | ONE_MINUS_CONSTANT_COLOR, 19 | CONSTANT_ALPHA, 20 | ONE_MINUS_CONSTANT_ALPHA 21 | } 22 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/gles/CullFaceMode.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles 2 | 3 | /** 4 | * Face culling mode. 5 | */ 6 | enum class CullFaceMode { 7 | FRONT, 8 | BACK, 9 | FRONT_AND_BACK 10 | } 11 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/gles/DepthTestFunction.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles 2 | 3 | /** 4 | * Depth test function. 5 | */ 6 | enum class DepthTestFunction { 7 | NEVER, 8 | LESS, 9 | EQUAL, 10 | LESS_OR_EQUAL, 11 | GREATER, 12 | NOT_EQUAL, 13 | GREATER_OR_EQUAL, 14 | ALWAYS; 15 | } 16 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/gles/Disposable.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles 2 | 3 | /** 4 | * Disposable GLES-related object. 5 | */ 6 | interface Disposable { 7 | 8 | /** 9 | * Disposes the object. 10 | */ 11 | fun dispose() 12 | 13 | /** 14 | * Registers the [Disposable] object. 15 | */ 16 | fun registerDisposable() = Disposables.register(this) 17 | } 18 | 19 | /** 20 | * Container for disposable objects. 21 | */ 22 | object Disposables { 23 | 24 | internal var isDisposing = false 25 | 26 | private val disposables = mutableSetOf() 27 | 28 | /** 29 | * Registers a [disposable] object. 30 | */ 31 | fun register(disposable: Disposable) { 32 | disposables.add(disposable) 33 | } 34 | 35 | /** 36 | * Disposes all registered objects. 37 | */ 38 | fun disposeAll() { 39 | isDisposing = true 40 | disposables.forEach { it.dispose() } 41 | disposables.clear() 42 | isDisposing = false 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/gles/Handles.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles 2 | 3 | /** 4 | * Buffer handle. 5 | * 6 | * @property value Handle value. 7 | */ 8 | data class BufferHandle(val value: Int) { 9 | init { 10 | require(value != 0) { "Invalid buffer handle: $value" } 11 | } 12 | } 13 | 14 | /** 15 | * Shader uniform location. 16 | * 17 | * @property value Location. 18 | */ 19 | data class UniformLocation(val value: Int) { 20 | init { 21 | require(value >= 0) { "Invalid uniform location: $value" } 22 | } 23 | } 24 | 25 | /** 26 | * Shader attribute location. 27 | * 28 | * @property value Location. 29 | */ 30 | data class AttributeLocation(val value: Int) { 31 | init { 32 | require(value >= 0) { "Invalid attribute location: $value" } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/gles/Viewport.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles 2 | 3 | /** 4 | * Rendering viewport. 5 | * 6 | * @property width Viewport width. 7 | * @property height Viewport height. 8 | */ 9 | data class Viewport(val width: Int, val height: Int) { 10 | 11 | /** 12 | * Viewport aspect ratio. 13 | */ 14 | val aspect: Float by lazy { width.toFloat() / height.toFloat() } 15 | 16 | /** 17 | * Returns a string representation of the [Viewport]. 18 | */ 19 | override fun toString(): String = "$width×$height" 20 | } 21 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/gles/delegates/DisposableLazyDelegate.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles.delegates 2 | 3 | import glimpse.gles.Disposable 4 | import glimpse.gles.Disposables 5 | import kotlin.reflect.KProperty 6 | 7 | /** 8 | * Disposable lazy property delegate. 9 | * 10 | * @param T Property type. 11 | * @property init Lazy initialization function. 12 | */ 13 | class DisposableLazyDelegate(private val init: () -> T?) : Disposable { 14 | 15 | private var value: T? = null 16 | 17 | /** 18 | * Gets delegated property value. 19 | */ 20 | operator fun getValue(thisRef: Any?, property: KProperty<*>): T? { 21 | if (value == null && !Disposables.isDisposing) { 22 | value = init() 23 | registerDisposable() 24 | } 25 | return value 26 | } 27 | 28 | /** 29 | * Disposes property value. 30 | */ 31 | override fun dispose() { 32 | value = null 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/gles/delegates/EnumPairSetAndRememberDelegate.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles.delegates 2 | 3 | import kotlin.reflect.KProperty 4 | 5 | /** 6 | * GLES delegate remembering and converting an enum pair property [value] and calling additional [setter] lambda. 7 | * 8 | * @param E Enum type. 9 | * @param T Converted enum value type. 10 | * 11 | * @property value Delegated property value. 12 | * @property mapping Enum to value mapping. 13 | * @property setter Setter lambda. 14 | */ 15 | class EnumPairSetAndRememberDelegate, T>(var value: Pair, val mapping: Map, val setter: (Pair) -> Unit) { 16 | 17 | /** 18 | * Gets delegated property value. 19 | */ 20 | operator fun getValue(thisRef: Any?, property: KProperty<*>): Pair = value 21 | 22 | /** 23 | * Sets delegated property value. 24 | */ 25 | operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Pair) { 26 | setter(mapping[value.first]!! to mapping[value.second]!!) 27 | this.value = value 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/gles/delegates/EnumSetAndRememberDelegate.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles.delegates 2 | 3 | import kotlin.reflect.KProperty 4 | 5 | /** 6 | * GLES delegate remembering and converting an enum property [value] and calling additional [setter] lambda. 7 | * 8 | * @param E Enum type. 9 | * @param T Converted enum value type. 10 | * 11 | * @property value Delegated property value. 12 | * @property mapping Enum to value mapping. 13 | * @property setter Setter lambda. 14 | */ 15 | class EnumSetAndRememberDelegate, T>(var value: E, val mapping: Map, val setter: (T) -> Unit) { 16 | 17 | /** 18 | * Gets delegated property value. 19 | */ 20 | operator fun getValue(thisRef: Any?, property: KProperty<*>): E = value 21 | 22 | /** 23 | * Sets delegated property value. 24 | */ 25 | operator fun setValue(thisRef: Any?, property: KProperty<*>, value: E) { 26 | setter(mapping[value]!!) 27 | this.value = value 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/gles/delegates/GLESDelegate.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles.delegates 2 | 3 | import glimpse.gles.GLES 4 | import kotlin.reflect.KProperty 5 | 6 | /** 7 | * GLES implementation delegate. 8 | * 9 | * Holds singleton instance of GLES. 10 | */ 11 | object GLESDelegate { 12 | 13 | private var gles: GLES? = null 14 | 15 | /** 16 | * Gets delegated property value. 17 | */ 18 | operator fun getValue(thisRef: Any?, property: KProperty<*>): GLES = gles ?: throw IllegalStateException("GLES not initialized") 19 | 20 | /** 21 | * Sets delegated property value. 22 | */ 23 | operator fun setValue(thisRef: Any?, property: KProperty<*>, value: GLES) { 24 | GLESDelegate(value) 25 | } 26 | 27 | /** 28 | * Initializes GLES implementation. 29 | */ 30 | operator fun invoke(gles: GLES) { 31 | GLESDelegate.gles = gles 32 | } 33 | } -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/gles/delegates/SetAndRememberDelegate.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles.delegates 2 | 3 | import kotlin.reflect.KProperty 4 | 5 | /** 6 | * GLES delegate remembering property [value] and calling additional [setter] lambda. 7 | * 8 | * @property value Delegated property value. 9 | * @property setter Setter lambda. 10 | */ 11 | class SetAndRememberDelegate(var value: T, val setter: (T) -> Unit) { 12 | 13 | /** 14 | * Gets delegated property value. 15 | */ 16 | operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value 17 | 18 | /** 19 | * Sets delegated property value. 20 | */ 21 | operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 22 | setter(value) 23 | this.value = value 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/io/Properties.kt: -------------------------------------------------------------------------------- 1 | package glimpse.io 2 | 3 | import java.util.* 4 | 5 | /** 6 | * Returns properties loaded from a resource file. 7 | */ 8 | fun Any.properties(resourceName: String): Properties { 9 | val properties = Properties() 10 | properties.load(resource(resourceName).inputStream) 11 | return properties 12 | } 13 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/io/Resource.kt: -------------------------------------------------------------------------------- 1 | package glimpse.io 2 | 3 | import java.io.InputStream 4 | 5 | /** 6 | * A resource file wrapper. 7 | * 8 | * @property context Class context of the resource. 9 | * @property name Resource name. 10 | */ 11 | data class Resource(val context: Class, val name: String) { 12 | 13 | /** 14 | * Newly opened resource input stream. 15 | */ 16 | val inputStream: InputStream get() = context.getResourceAsStream(name) ?: throw ResourceNotFoundException(this) 17 | 18 | /** 19 | * Lines in the resource file. 20 | */ 21 | val lines: List by lazy { inputStream.reader().readLines() } 22 | } 23 | 24 | /** 25 | * Returns a resource with the given [name] in this object's context. 26 | */ 27 | fun Any.resource(name: String): Resource = Resource(javaClass, name) 28 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/io/ResourceNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package glimpse.io 2 | 3 | import glimpse.GlimpseException 4 | 5 | /** 6 | * Resource not found exception. 7 | * 8 | * @param resource Resource. 9 | */ 10 | class ResourceNotFoundException(resource: Resource) : GlimpseException("${resource.context.`package`.name}.${resource.name}") 11 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/lights/Light.kt: -------------------------------------------------------------------------------- 1 | package glimpse.lights 2 | 3 | import glimpse.Angle 4 | import glimpse.Color 5 | import glimpse.Point 6 | import glimpse.Vector 7 | 8 | /** 9 | * A single light source. 10 | * 11 | * @property type Light type identifier. 12 | * @property color Light color lambda. 13 | */ 14 | sealed class Light(val type: Int, val color: () -> Color) { 15 | 16 | /** 17 | * Direction light source. 18 | * 19 | * @property direction Light direction lambda. 20 | * @property color Light color lambda. 21 | */ 22 | class DirectionLight( 23 | val direction: () -> Vector, 24 | color: () -> Color = { Color.WHITE }) : Light(1, color) 25 | 26 | /** 27 | * Point light source. 28 | * 29 | * @property position Light position lambda. 30 | * @property distance Light maximum distance lambda. 31 | * @property color Light color lambda. 32 | */ 33 | class PointLight( 34 | val position: () -> Point, 35 | val distance: () -> Float, 36 | color: () -> Color = { Color.WHITE }) : Light(2, color) 37 | 38 | /** 39 | * Spotlight source. 40 | * 41 | * @property position Light position lambda. 42 | * @property target Light cone target lambda. 43 | * @property angle Light cone angle lambda. 44 | * @property distance Light maximum distance lambda. 45 | * @property color Light color lambda. 46 | */ 47 | class Spotlight( 48 | val position: () -> Point, 49 | val target: () -> Point, 50 | val angle: () -> Angle, 51 | val distance: () -> Float, 52 | color: () -> Color = { Color.WHITE }) : Light(3, color) 53 | } 54 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/lights/LightBuilder.kt: -------------------------------------------------------------------------------- 1 | package glimpse.lights 2 | 3 | import glimpse.* 4 | 5 | /** 6 | * Light source builder. 7 | */ 8 | sealed class LightBuilder { 9 | 10 | protected var color: () -> Color = { Color.WHITE } 11 | 12 | internal abstract fun build(): Light 13 | 14 | /** 15 | * Sets color lambda. 16 | */ 17 | fun color(color: () -> Color) { 18 | this.color = color 19 | } 20 | 21 | /** 22 | * Direction light source builder. 23 | */ 24 | class DirectionLightBuilder : LightBuilder() { 25 | 26 | private var direction: () -> Vector = { -Vector.Z_UNIT } 27 | 28 | /** 29 | * Sets light direction lambda. 30 | */ 31 | fun direction(direction: () -> Vector) { 32 | this.direction = direction 33 | } 34 | 35 | override fun build() = Light.DirectionLight(direction, color) 36 | } 37 | 38 | /** 39 | * Point light source builder. 40 | */ 41 | class PointLightBuilder : LightBuilder() { 42 | 43 | private var position: () -> Point = { Point.ORIGIN } 44 | private var distance: () -> Float = { 100f } 45 | 46 | /** 47 | * Sets light position lambda. 48 | */ 49 | fun position(position: () -> Point) { 50 | this.position = position 51 | } 52 | 53 | /** 54 | * Sets light maximum distance lambda. 55 | */ 56 | fun distance(distance: () -> Float) { 57 | this.distance = distance 58 | } 59 | 60 | override fun build() = Light.PointLight(position, distance, color) 61 | } 62 | 63 | /** 64 | * Spotlight source builder. 65 | */ 66 | class SpotlightBuilder : LightBuilder() { 67 | 68 | private var position: () -> Point = { Vector.Z_UNIT.toPoint() } 69 | private var target: () -> Point = { Point.ORIGIN } 70 | private var angle: () -> Angle = { 60.degrees } 71 | private var distance: () -> Float = { 100f } 72 | 73 | /** 74 | * Sets light position lambda. 75 | */ 76 | fun position(position: () -> Point) { 77 | this.position = position 78 | } 79 | 80 | /** 81 | * Sets light cone target lambda. 82 | */ 83 | fun target(target: () -> Point) { 84 | this.target = target 85 | } 86 | 87 | /** 88 | * Sets light cone angle lambda. 89 | */ 90 | fun angle(angle: () -> Angle) { 91 | this.angle = angle 92 | } 93 | 94 | /** 95 | * Sets light maximum distance lambda. 96 | */ 97 | fun distance(distance: () -> Float) { 98 | this.distance = distance 99 | } 100 | 101 | override fun build() = Light.Spotlight(position, target, angle, distance, color) 102 | } 103 | } 104 | 105 | /** 106 | * Builds a direction light source initialized with an [init] function. 107 | */ 108 | fun directionLight(init: LightBuilder.DirectionLightBuilder.() -> Unit): Light.DirectionLight { 109 | val builder = LightBuilder.DirectionLightBuilder() 110 | builder.init() 111 | return builder.build() 112 | } 113 | 114 | /** 115 | * Builds a point light source initialized with an [init] function. 116 | */ 117 | fun pointLight(init: LightBuilder.PointLightBuilder.() -> Unit): Light.PointLight { 118 | val builder = LightBuilder.PointLightBuilder() 119 | builder.init() 120 | return builder.build() 121 | } 122 | 123 | /** 124 | * Builds a spotlight source initialized with an [init] function. 125 | */ 126 | fun spotlight(init: LightBuilder.SpotlightBuilder.() -> Unit): Light.Spotlight { 127 | val builder = LightBuilder.SpotlightBuilder() 128 | builder.init() 129 | return builder.build() 130 | } 131 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/materials/Material.kt: -------------------------------------------------------------------------------- 1 | package glimpse.materials 2 | 3 | import glimpse.cameras.Camera 4 | import glimpse.lights.Light 5 | import glimpse.models.Model 6 | 7 | /** 8 | * Material defining a way in which a [Model] is rendered. 9 | */ 10 | interface Material { 11 | 12 | /** 13 | * Renders a [Model] with a single light using this [Material]. 14 | */ 15 | fun render(model: Model, camera: Camera, light: Light) = 16 | render(model, camera, listOf(light)) 17 | 18 | /** 19 | * Renders a [Model] with a list of lights using this [Material]. 20 | */ 21 | fun render(model: Model, camera: Camera, lights: List) 22 | } 23 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/materials/ShaderHelper.kt: -------------------------------------------------------------------------------- 1 | package glimpse.materials 2 | 3 | import glimpse.Color 4 | import glimpse.Matrix 5 | import glimpse.Point 6 | import glimpse.Vector 7 | import glimpse.gles.* 8 | import glimpse.gles.delegates.GLESDelegate 9 | import glimpse.models.Mesh 10 | import glimpse.shaders.Program 11 | import glimpse.shaders.ProgramHandle 12 | import glimpse.textures.Texture 13 | import java.nio.FloatBuffer 14 | 15 | /** 16 | * Common superclass for shader helpers. 17 | */ 18 | abstract class ShaderHelper : Disposable { 19 | 20 | /** 21 | * GLES implementation. 22 | */ 23 | protected val gles: GLES by GLESDelegate 24 | 25 | /** 26 | * Shader program. 27 | */ 28 | protected abstract val program: Program? 29 | 30 | /** 31 | * Location of a shader attribute with the given name. 32 | */ 33 | protected val String.attributeLocation: AttributeLocation 34 | get() = gles.getAttributeLocation(program?.handle ?: ProgramHandle(0), this) 35 | 36 | /** 37 | * Location of a shader uniform with the given name. 38 | */ 39 | protected val String.uniformLocation: UniformLocation 40 | get() = gles.getUniformLocation(program?.handle ?: ProgramHandle(0), this) 41 | 42 | /** 43 | * Name of a shader attribute containing vertex position. 44 | */ 45 | protected abstract val vertexPositionAttributeName: String? 46 | 47 | /** 48 | * Name of a shader attribute containing vertex texture coordinates. 49 | */ 50 | protected abstract val vertexTextureCoordinatesAttributeName: String? 51 | 52 | /** 53 | * Name of a shader attribute containing vertex normal. 54 | */ 55 | protected abstract val vertexNormalAttributeName: String? 56 | 57 | /** 58 | * Tells GLES implementation to use this program. 59 | */ 60 | fun use() { 61 | program?.use() 62 | } 63 | 64 | /** 65 | * Sets [float] shader uniform value of a given [name]. 66 | */ 67 | operator fun set(name: String, float: Float): Unit = gles.uniformFloat(name.uniformLocation, float) 68 | 69 | /** 70 | * Sets [floats] shader uniform values of a given [name]. 71 | */ 72 | operator fun set(name: String, floats: FloatArray): Unit = gles.uniformFloats(name.uniformLocation, floats) 73 | 74 | /** 75 | * Sets [int] shader uniform value of a given [name]. 76 | */ 77 | operator fun set(name: String, int: Int): Unit = gles.uniformInt(name.uniformLocation, int) 78 | 79 | /** 80 | * Sets [ints] shader uniform values of a given [name]. 81 | */ 82 | operator fun set(name: String, ints: IntArray): Unit = gles.uniformInts(name.uniformLocation, ints) 83 | 84 | /** 85 | * Sets [matrix] shader uniform value of a given [name]. 86 | */ 87 | operator fun set(name: String, matrix: Matrix): Unit = gles.uniformMatrix(name.uniformLocation, matrix) 88 | 89 | /** 90 | * Sets [vector] shader uniform value of a given [name]. 91 | */ 92 | operator fun set(name: String, vector: Vector): Unit = gles.uniformVector(name.uniformLocation, vector) 93 | 94 | /** 95 | * Sets [vectors] shader uniform values of a given [name]. 96 | */ 97 | fun setVectors(name: String, vectors: List): Unit = gles.uniformVectors(name.uniformLocation, vectors) 98 | 99 | /** 100 | * Sets [point] shader uniform value of a given [name]. 101 | */ 102 | operator fun set(name: String, point: Point): Unit = gles.uniformPoint(name.uniformLocation, point) 103 | 104 | /** 105 | * Sets [points] shader uniform values of a given [name]. 106 | */ 107 | fun setPoints(name: String, points: List): Unit = gles.uniformPoints(name.uniformLocation, points) 108 | 109 | /** 110 | * Sets [color] shader uniform value of a given [name]. 111 | */ 112 | operator fun set(name: String, color: Color): Unit = gles.uniformColor(name.uniformLocation, color) 113 | 114 | /** 115 | * Sets [colors] shader uniform values of a given [name]. 116 | */ 117 | fun setColors(name: String, colors: List): Unit = gles.uniformColors(name.uniformLocation, colors) 118 | 119 | /** 120 | * Sets [texture] shader uniform value of a given [name]. 121 | */ 122 | operator fun set(name: String, index: Int, texture: Texture): Unit = texture.apply(name.uniformLocation, index) 123 | 124 | /** 125 | * Draws a [mesh] using the shader [program]. 126 | */ 127 | fun drawMesh(mesh: Mesh) { 128 | val positionBuffer = vertexPositionAttributeName?.attributeLocation?.set(mesh.augmentedPositionsBuffer, 4) 129 | val textureCoordinatesBuffer = vertexTextureCoordinatesAttributeName?.attributeLocation?.set(mesh.textureCoordinatesBuffer, 2) 130 | val normalBuffer = vertexNormalAttributeName?.attributeLocation?.set(mesh.augmentedNormalsBuffer, 4) 131 | 132 | gles.drawTriangles(mesh.count) 133 | 134 | positionBuffer?.dismiss() 135 | textureCoordinatesBuffer?.dismiss() 136 | normalBuffer?.dismiss() 137 | } 138 | 139 | /** 140 | * Sets [buffer] of values to the attribute. 141 | */ 142 | protected fun AttributeLocation.set(buffer: FloatBuffer, vectorSize: Int): Pair { 143 | val bufferHandle = gles.createAttributeFloatArray(this, buffer, vectorSize) 144 | gles.enableAttributeArray(this) 145 | return this to bufferHandle 146 | } 147 | 148 | /** 149 | * Dismisses the buffer of attribute values. 150 | */ 151 | protected fun Pair.dismiss() { 152 | gles.disableAttributeArray(first) 153 | gles.deleteAttributeArray(second) 154 | } 155 | 156 | /** 157 | * Disposes shader helper. 158 | */ 159 | override fun dispose() { 160 | program?.delete() 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/models/Curve.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Point 4 | import glimpse.TextureCoordinates 5 | import glimpse.Vector 6 | 7 | /** 8 | * A three-dimensional polygonal curve. 9 | * 10 | * @param positions Positions of the polygonal curve vertices in space. 11 | * @param textureCoordinates Texture coordinates at the polygonal curve vertices. 12 | * @param normals Normal vectors at the polygonal curve vertices. 13 | */ 14 | class Curve(val positions: List, val textureCoordinates: List, val normals: List) { 15 | 16 | init { 17 | require(positions.size % 2 == 0) { "Positions size should divide by 2" } 18 | require(positions.size == textureCoordinates.size) { "Positions and textures coordinates should have the same size" } 19 | require(positions.size == normals.size) { "Positions and normals should have the same size" } 20 | } 21 | 22 | /** 23 | * Polygonal curve vertices. 24 | */ 25 | val vertices: List by lazy { 26 | (positions zip textureCoordinates zip normals).map { Vertex(it.first.first, it.first.second, it.second) } 27 | } 28 | 29 | /** 30 | * Polygonal curve segments. 31 | */ 32 | val segments: List> by lazy { 33 | tailrec fun extractSegments(vertices: List, segments: List>): List> = 34 | if (vertices.isEmpty()) segments 35 | else extractSegments(vertices.drop(2), segments + (vertices[0] to vertices[1])) 36 | extractSegments(vertices, emptyList>()) 37 | } 38 | 39 | /** 40 | * Returns a polygon bounded by this curve and filled with given [faces]. 41 | */ 42 | fun polygon(faces: () -> List>) = 43 | Polygon(this, faces()) 44 | 45 | internal fun toFlatCurve(z: Float = 0f) = 46 | Curve(positions.map { it.copy(z = z) }, textureCoordinates, normals) 47 | } 48 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/models/CurveBuilder.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Point 4 | import glimpse.TextureCoordinates 5 | import glimpse.Vector 6 | 7 | /** 8 | * A three-dimensional polygonal curve builder. 9 | */ 10 | class CurveBuilder { 11 | 12 | private val positions = mutableListOf() 13 | private val textureCoordinates = mutableListOf() 14 | private val normals = mutableListOf() 15 | 16 | internal fun toCurve() = Curve(positions.toList(), textureCoordinates.toList(), normals.toList()) 17 | 18 | private fun push(vertex: Vertex) { 19 | positions.add(vertex.position) 20 | textureCoordinates.add(vertex.textureCoordinates) 21 | normals.add(vertex.normal) 22 | } 23 | 24 | private fun push(vararg vertices: Vertex) { 25 | vertices.forEach { push(it) } 26 | } 27 | 28 | /** 29 | * Adds a segment to the [Curve]. 30 | */ 31 | fun segment(v1: Vertex, v2: Vertex) { 32 | push(v1, v2) 33 | } 34 | 35 | /** 36 | * Adds a segment to the [Curve]. 37 | */ 38 | fun segment(segment: Pair) { 39 | segment(segment.first, segment.second) 40 | } 41 | } 42 | 43 | /** 44 | * Builds a polygonal curve initialized with an [init] function. 45 | */ 46 | fun curve(init: CurveBuilder.() -> Unit): Curve { 47 | val builder = CurveBuilder() 48 | builder.init() 49 | return builder.toCurve() 50 | } 51 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/models/Face.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | /** 4 | * A triangular face of a three-dimensional model. 5 | * 6 | * @param v1 First vertex. 7 | * @param v2 Second vertex. 8 | * @param v3 Third vertex. 9 | */ 10 | data class Face(val v1: Vertex, val v2: Vertex, val v3: Vertex) 11 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/models/Mesh.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.* 4 | import java.nio.FloatBuffer 5 | 6 | /** 7 | * A three-dimensional mesh. 8 | * 9 | * @param positions Positions of the mesh vertices in space. 10 | * @param textureCoordinates Texture coordinates at the mesh vertices. 11 | * @param normals Normal vectors at the mesh vertices. 12 | */ 13 | class Mesh(val positions: List, val textureCoordinates: List, val normals: List) { 14 | 15 | init { 16 | require(positions.size % 3 == 0) { "Positions size should divide by 3" } 17 | require(positions.size == textureCoordinates.size) { "Positions and textures coordinates should have the same size" } 18 | require(positions.size == normals.size) { "Positions and normals should have the same size" } 19 | } 20 | 21 | /** 22 | * Direct buffer containing coordinates of positions. 23 | */ 24 | val positionsBuffer: FloatBuffer by lazy { positions.toDirectBuffer() } 25 | 26 | /** 27 | * Direct buffer containing augmented coordinates of positions. 28 | */ 29 | val augmentedPositionsBuffer: FloatBuffer by lazy { positions.toDirectBufferAugmented() } 30 | 31 | /** 32 | * Direct buffer containing texture coordinates. 33 | */ 34 | val textureCoordinatesBuffer: FloatBuffer by lazy { textureCoordinates.toDirectBuffer() } 35 | 36 | /** 37 | * Direct buffer containing augmented coordinates of normals. 38 | */ 39 | val normalsBuffer: FloatBuffer by lazy { normals.toDirectBuffer() } 40 | 41 | /** 42 | * Direct buffer containing augmented coordinates of normals. 43 | */ 44 | val augmentedNormalsBuffer: FloatBuffer by lazy { normals.toDirectBufferAugmented() } 45 | 46 | /** 47 | * Mesh vertices counter. 48 | */ 49 | val count = positions.size 50 | 51 | /** 52 | * Mesh vertices. 53 | */ 54 | val vertices: List by lazy { 55 | (positions zip textureCoordinates zip normals).map { Vertex(it.first.first, it.first.second, it.second) } 56 | } 57 | 58 | /** 59 | * Mesh faces. 60 | */ 61 | val faces: List by lazy { 62 | tailrec fun extractFaces(vertices: List, faces: List): List = 63 | if (vertices.isEmpty()) faces 64 | else extractFaces(vertices.drop(3), faces + Face(vertices[0], vertices[1], vertices[2])) 65 | extractFaces(vertices, emptyList()) 66 | } 67 | 68 | /** 69 | * Returns a [Model] from the [Mesh], transformed with the [transformationMatrix]. 70 | */ 71 | fun transform(transformationMatrix: Matrix) = 72 | transform(transformationMatrix) {} 73 | 74 | /** 75 | * Returns a [Model] from the [Mesh], transformed with the [transformation] applied to the initial [transformationMatrix]. 76 | */ 77 | fun transform(transformationMatrix: Matrix, transformation: MatrixBuilder.() -> Unit) = 78 | Model(this, matrix(transformationMatrix, transformation)) 79 | 80 | /** 81 | * Returns a [Model] from the [Mesh], transformed with the [transformation]. 82 | */ 83 | fun transform(transformation: MatrixBuilder.() -> Unit) = 84 | Model(this, matrix(transformation)) 85 | } 86 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/models/MeshBuilder.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Point 4 | import glimpse.TextureCoordinates 5 | import glimpse.Vector 6 | 7 | /** 8 | * A three-dimensional mesh builder. 9 | */ 10 | class MeshBuilder { 11 | 12 | private val positions = mutableListOf() 13 | private val textureCoordinates = mutableListOf() 14 | private val normals = mutableListOf() 15 | 16 | internal fun build() = Mesh(positions.toList(), textureCoordinates.toList(), normals.toList()) 17 | 18 | internal fun push(vertex: Vertex) { 19 | positions.add(vertex.position) 20 | textureCoordinates.add(vertex.textureCoordinates) 21 | normals.add(vertex.normal) 22 | } 23 | 24 | private fun push(vararg vertices: Vertex) { 25 | vertices.forEach { push(it) } 26 | } 27 | 28 | /** 29 | * Adds a triangular face to the [Mesh]. 30 | */ 31 | fun triangle(v1: Vertex, v2: Vertex, v3: Vertex) { 32 | push(v1, v2, v3) 33 | } 34 | 35 | /** 36 | * Adds a quadrilateral face to the [Mesh]. 37 | */ 38 | fun quad(v1: Vertex, v2: Vertex, v3: Vertex, v4: Vertex) { 39 | triangle(v1, v2, v3) 40 | triangle(v3, v4, v1) 41 | } 42 | } 43 | 44 | /** 45 | * Builds a mesh initialized with an [init] function. 46 | */ 47 | fun mesh(init: MeshBuilder.() -> Unit): Mesh { 48 | val builder = MeshBuilder() 49 | builder.init() 50 | return builder.build() 51 | } -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/models/Model.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Matrix 4 | import glimpse.MatrixBuilder 5 | import glimpse.matrix 6 | 7 | /** 8 | * A three-dimensional model. 9 | * 10 | * @param mesh Mesh defining model's surface. 11 | * @param transformation Model transformation matrix lambda. 12 | */ 13 | class Model(val mesh: Mesh, val transformation: () -> Matrix = { Matrix.IDENTITY }) { 14 | 15 | /** 16 | * Returns a [Model], transformed with the [transformationMatrix]. 17 | */ 18 | fun transform(transformationMatrix: Matrix) = 19 | Model(mesh) { transformationMatrix * transformation() } 20 | 21 | /** 22 | * Returns a [Model], transformed with the [transformation]. 23 | */ 24 | fun transform(transformation: MatrixBuilder.() -> Unit) = 25 | Model(mesh, matrix(this.transformation, transformation)) 26 | } 27 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/models/ObjMeshBuilder.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Point 4 | import glimpse.TextureCoordinates 5 | import glimpse.Vector 6 | import glimpse.io.Resource 7 | import java.io.File 8 | import java.io.InputStream 9 | 10 | internal class ObjMeshBuilder(lines: List) { 11 | 12 | private val words = lines.map { it.trim().split(" ", "\t") } 13 | 14 | private val positions: List by lazy { 15 | words.filter { it.isPositionLine }.map { it.parsePoint() } 16 | } 17 | 18 | private val textureCoordinates: List by lazy { 19 | words.filter { it.isTextureCoordinatesLine }.map { it.parseTextureCoordinates() } 20 | } 21 | 22 | private val normals: List by lazy { 23 | words.filter { it.isNormalLine }.map { it.parseVector() } 24 | } 25 | 26 | private val List.isCommentLine: Boolean 27 | get() = first() == "#" 28 | 29 | private val List.isNextMeshLine: Boolean 30 | get() = first() == "o" 31 | 32 | private val List.isPositionLine: Boolean 33 | get() = first() == "v" 34 | 35 | private val List.isTextureCoordinatesLine: Boolean 36 | get() = first() == "vt" 37 | 38 | private val List.isNormalLine: Boolean 39 | get() = first() == "vn" 40 | 41 | private val List.isFaceLine: Boolean 42 | get() = first() == "f" 43 | 44 | private fun List.parsePoint(): Point = 45 | Point(this[1].toFloat(), this[2].toFloat(), this[3].toFloat()) 46 | 47 | private fun List.parseTextureCoordinates(): TextureCoordinates = 48 | TextureCoordinates(this[1].toFloat(), this[2].toFloat()) 49 | 50 | private fun List.parseVector(): Vector = 51 | Vector(this[1].toFloat(), this[2].toFloat(), this[3].toFloat()) 52 | 53 | private fun String.parseVertex(): Vertex { 54 | val indices = this.split("/").map { 55 | if(it.isBlank()) -1 56 | else it.toInt() - 1 57 | } 58 | return Vertex(positions(indices[0]), textureCoordinates(indices[1]), normals(indices[2])) 59 | } 60 | 61 | private fun positions(index: Int): Point = 62 | if (index < 0) Point.ORIGIN 63 | else positions[index] 64 | 65 | private fun textureCoordinates(index: Int): TextureCoordinates = 66 | if (index < 0) TextureCoordinates.BOTTOM_LEFT 67 | else textureCoordinates[index] 68 | 69 | private fun normals(index: Int): Vector = 70 | if (index < 0) Vector.NULL 71 | else normals[index] 72 | 73 | internal fun build(): List { 74 | val list = mutableListOf() 75 | var builder = MeshBuilder() 76 | words.filter { it.isNextMeshLine || it.isFaceLine }.dropWhile { it.isNextMeshLine }.forEach { 77 | when { 78 | it.isNextMeshLine -> { 79 | list.add(builder.build()) 80 | builder = MeshBuilder() 81 | } 82 | it.isFaceLine -> it.drop(1).map { it.parseVertex() }.forEach { builder.push(it) } 83 | } 84 | } 85 | list.add(builder.build()) 86 | return list 87 | } 88 | } 89 | 90 | /** 91 | * Builds three-dimensional meshes from OBJ lines. 92 | */ 93 | fun List.loadObjMeshes(): List = ObjMeshBuilder(this).build() 94 | 95 | /** 96 | * Builds three-dimensional meshes from OBJ [InputStream]. 97 | */ 98 | fun InputStream.loadObjMeshes(): List = reader().readLines().loadObjMeshes() 99 | 100 | /** 101 | * Builds three-dimensional meshes from OBJ [File]. 102 | */ 103 | fun File.loadObjMeshes(): List = readLines().loadObjMeshes() 104 | 105 | /** 106 | * Builds three-dimensional meshes from OBJ file [Resource]. 107 | */ 108 | fun Resource.loadObjMeshes(): List = lines.loadObjMeshes() 109 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/models/PlatonicSolids.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Point 4 | import glimpse.TextureCoordinates 5 | import glimpse.Vector 6 | 7 | /** 8 | * Returns a tetrahedron mesh of a given [size]. 9 | */ 10 | fun tetrahedron(size: Float = 1f): Mesh = mesh { 11 | val vectors = arrayOf(Vector(-1f, -1f, -1f), Vector(-1f, 1f, 1f), Vector(1f, -1f, 1f), Vector(1f, 1f, -1f)) 12 | val normals = vectors.map { it.normalize } 13 | val textureCoordinates = vectors.map { TextureCoordinates(Math.max(it.x, 0f), Math.max(it.y, 0f)) } 14 | val positions = vectors.map { (-it * size).toPoint() } 15 | val positionsWithTexture = (positions zip textureCoordinates) 16 | (0..3).forEach { face -> 17 | val vertices = positionsWithTexture.filterIndexed { index, pair -> index != face }.map { Vertex(it.first, it.second, normals[face]) } 18 | triangle(vertices[0], vertices[1], vertices[2]) 19 | } 20 | } 21 | 22 | /** 23 | * Returns a cube mesh of a given [size]. 24 | */ 25 | fun cube(size: Float = 1f): Mesh = prism { 26 | val positions = arrayOf(Point(-size, -size), Point(size, -size), Point(size, size), Point(-size, size)) 27 | val textureCoordinates = positions.map { TextureCoordinates(Math.max(it.x, 0f), Math.max(it.y, 0f)) } 28 | val normals = arrayOf(-Vector.Y_UNIT, Vector.X_UNIT, Vector.Y_UNIT, -Vector.X_UNIT) 29 | curve { 30 | (0..3).map { 31 | segment(Vertex(positions[it], textureCoordinates[it], normals[it]), 32 | Vertex(positions[(it + 1) % 4], textureCoordinates[(it + 1) % 4], normals[it])) 33 | } 34 | }.polygon { 35 | listOf(Triple(1, 3, 5), Triple(5, 7, 1)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/models/Polygon.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | /** 4 | * Polygon. 5 | * 6 | * @property curve Boundary of the polygon. 7 | * @property faces Faces adding up to the interior of the polygon, defined as a list of triples of points indices. 8 | */ 9 | class Polygon(val curve: Curve, val faces: List>) 10 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/models/Prisms.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.TextureCoordinates 4 | import glimpse.Vector 5 | 6 | /** 7 | * Builds a prism with a given polygonal [base] and [height]. 8 | */ 9 | fun prism(height: Float = 1f, base: Polygon): Mesh = mesh { 10 | val topCurve = base.curve.toFlatCurve(height) 11 | val bottomCurve = base.curve.toFlatCurve(-height) 12 | base.faces.forEach { 13 | it.toList().forEach { 14 | push(Vertex(topCurve.positions[it], topCurve.textureCoordinates[it], Vector.Z_UNIT)) 15 | } 16 | it.toList().forEach { 17 | push(Vertex(bottomCurve.positions[it], bottomCurve.textureCoordinates[it], -Vector.Z_UNIT)) 18 | } 19 | } 20 | (topCurve.segments zip bottomCurve.segments).forEach { 21 | quad( 22 | it.first.first.copy(textureCoordinates = TextureCoordinates.TOP_LEFT), 23 | it.second.first.copy(textureCoordinates = TextureCoordinates.BOTTOM_LEFT), 24 | it.second.second.copy(textureCoordinates = TextureCoordinates.BOTTOM_RIGHT), 25 | it.first.second.copy(textureCoordinates = TextureCoordinates.TOP_RIGHT)) 26 | } 27 | } 28 | 29 | /** 30 | * Builds a prism with a given polygonal [base] and [height]. 31 | */ 32 | fun prism(height: Float = 1f, base: () -> Polygon) = prism(height, base()) 33 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/models/Revolutions.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.* 4 | 5 | /** 6 | * Builds a surface of revolution created by rotating a [curve] around Z axis, approximated in a given number [steps]. 7 | */ 8 | fun revolution(steps: Int, curve: Curve) = mesh { 9 | fun Vertex.rotate(matrix: Matrix, angle: Angle): Vertex = Vertex( 10 | (matrix * position.toVector()).toPoint(), 11 | textureCoordinates.copy(u = textureCoordinates.u + angle / Angle.FULL), 12 | matrix * normal) 13 | 14 | val segments = (Angle.NULL..Angle.FULL partition steps).map { angle -> 15 | val matrix = rotationMatrixZ(angle) 16 | curve.segments.map { 17 | it.first.rotate(matrix, angle) to it.second.rotate(matrix, angle) 18 | } 19 | } 20 | for (step in 0..steps - 1) { 21 | for (segment in 0..curve.segments.size - 1) { 22 | val (v1, v2) = segments[step][segment] 23 | val (v4, v3) = segments[step + 1][segment] 24 | quad(v1, v2, v3, v4) 25 | } 26 | } 27 | } 28 | 29 | /** 30 | * Builds a surface of revolution created by rotating a [curve] around Z axis, approximated in a given number of [steps]. 31 | */ 32 | fun revolution(steps: Int, curve: () -> Curve) = revolution(steps, curve()) 33 | 34 | /** 35 | * Builds a sphere of a given [radius], approximated in a given number of [curveSteps] and [rotateSteps]. 36 | */ 37 | fun sphere(curveSteps: Int, rotateSteps: Int = curveSteps * 2, radius: Float = 1f) = revolution(rotateSteps) { 38 | curve { 39 | val vertices = (Angle.NULL..Angle.STRAIGHT partition curveSteps).map { angle -> 40 | val normal = rotationMatrixY(angle) * Vector.Z_UNIT 41 | Vertex((normal * radius).toPoint(), TextureCoordinates(0f, 1f - angle / Angle.STRAIGHT), normal) 42 | } 43 | vertices.dropLast(1).zip(vertices.drop(1)).forEach { 44 | segment(it) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/models/Vertex.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Point 4 | import glimpse.TextureCoordinates 5 | import glimpse.Vector 6 | 7 | /** 8 | * A vertex of a three-dimensional model. 9 | * 10 | * @param position Position in space. 11 | * @param textureCoordinates Texture coordinates. 12 | * @param normal Normal vector. 13 | */ 14 | data class Vertex(val position: Point, val textureCoordinates: TextureCoordinates, val normal: Vector) 15 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/shaders/Program.kt: -------------------------------------------------------------------------------- 1 | package glimpse.shaders 2 | 3 | import glimpse.gles.GLES 4 | import glimpse.gles.delegates.GLESDelegate 5 | 6 | /** 7 | * GLSL shader program. 8 | * 9 | * @property handle Program handle. 10 | * @property shaders Vertex and fragment shaders linked into the program. 11 | */ 12 | class Program(val handle: ProgramHandle, val shaders: List) { 13 | 14 | internal val gles: GLES by GLESDelegate 15 | 16 | internal var deleted = false 17 | 18 | /** 19 | * Tells GLES implementation to use this program. 20 | */ 21 | fun use() { 22 | assert(!deleted) { "Program deleted" } 23 | shaders.forEach { shader -> 24 | assert(!shader.deleted) { "Shader deleted" } 25 | } 26 | gles.useProgram(handle) 27 | } 28 | 29 | /** 30 | * Tells GLES implementation to delete this program. 31 | */ 32 | fun delete() { 33 | shaders.forEach { shader -> 34 | if (!shader.deleted) shader.delete() 35 | } 36 | deleted = true 37 | gles.deleteProgram(handle) 38 | } 39 | } 40 | 41 | /** 42 | * Shader program handle. 43 | * 44 | * @property value Handle value. 45 | */ 46 | data class ProgramHandle(val value: Int) { 47 | init { 48 | require(value != 0) { "Invalid program handle: $value" } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/shaders/ProgramBuilder.kt: -------------------------------------------------------------------------------- 1 | package glimpse.shaders 2 | 3 | import glimpse.gles.GLES 4 | import glimpse.gles.delegates.GLESDelegate 5 | 6 | /** 7 | * GLSL shader program builder. 8 | */ 9 | class ProgramBuilder { 10 | 11 | private val gles: GLES by GLESDelegate 12 | 13 | private val shaders = mutableListOf() 14 | 15 | private operator fun ShaderType.invoke(source: () -> String) { 16 | val handle = gles.createShader(this) 17 | gles.compileShader(handle, source()) 18 | if (!gles.getShaderCompileStatus(handle)) { 19 | throw ShaderCompileException(gles.getShaderLog(handle)) 20 | } 21 | shaders.add(Shader(this, handle)) 22 | } 23 | 24 | /** 25 | * Compiles a vertex shader. 26 | * 27 | * @param source Shader source code lambda. 28 | */ 29 | fun vertexShader(source: () -> String): Unit { 30 | ShaderType.VERTEX(source) 31 | } 32 | 33 | /** 34 | * Compiles a fragment shader. 35 | * 36 | * @param source Shader source code lambda. 37 | */ 38 | fun fragmentShader(source: () -> String): Unit { 39 | ShaderType.FRAGMENT(source) 40 | } 41 | 42 | internal fun build(): Program { 43 | val handle = gles.createProgram() 44 | shaders.forEach { shader -> 45 | gles.attachShader(handle, shader.handle) 46 | } 47 | gles.linkProgram(handle) 48 | if (!gles.getProgramLinkStatus(handle)) { 49 | throw ProgramLinkException(gles.getProgramLog(handle)) 50 | } 51 | return Program(handle, shaders.toList()) 52 | } 53 | } 54 | 55 | /** 56 | * Returns a [Program], initialized with an [init] function. 57 | */ 58 | fun shaderProgram(init: ProgramBuilder.() -> Unit): Program { 59 | val builder = ProgramBuilder() 60 | builder.init() 61 | return builder.build() 62 | } 63 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/shaders/ProgramLinkException.kt: -------------------------------------------------------------------------------- 1 | package glimpse.shaders 2 | 3 | import glimpse.GlimpseException 4 | 5 | /** 6 | * Shader program linking exception. 7 | */ 8 | class ProgramLinkException internal constructor(log: String) : GlimpseException("Program linking failed:\n$log") 9 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/shaders/Shader.kt: -------------------------------------------------------------------------------- 1 | package glimpse.shaders 2 | 3 | import glimpse.gles.GLES 4 | import glimpse.gles.delegates.GLESDelegate 5 | 6 | /** 7 | * GLSL shader. 8 | * 9 | * @property type Shader type. 10 | * @property handle Shader handle. 11 | */ 12 | class Shader(val type: ShaderType, val handle: ShaderHandle) { 13 | 14 | internal val gles: GLES by GLESDelegate 15 | 16 | internal var deleted = false 17 | 18 | /** 19 | * Tells GLES implementation to delete this shader. 20 | */ 21 | fun delete() { 22 | deleted = true 23 | gles.deleteShader(handle) 24 | } 25 | } 26 | 27 | /** 28 | * Shader handle. 29 | * 30 | * @property value Handle value. 31 | */ 32 | data class ShaderHandle(val value: Int) { 33 | init { 34 | require(value != 0) { "Invalid shader handle: $value" } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/shaders/ShaderCompileException.kt: -------------------------------------------------------------------------------- 1 | package glimpse.shaders 2 | 3 | import glimpse.GlimpseException 4 | 5 | /** 6 | * Shader compilation exception. 7 | */ 8 | class ShaderCompileException internal constructor(log: String) : GlimpseException("Shader compilation failed:\n$log") 9 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/shaders/ShaderType.kt: -------------------------------------------------------------------------------- 1 | package glimpse.shaders 2 | 3 | /** 4 | * Shader type. 5 | */ 6 | enum class ShaderType { 7 | VERTEX, 8 | FRAGMENT 9 | } 10 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/textures/Texture.kt: -------------------------------------------------------------------------------- 1 | package glimpse.textures 2 | 3 | import glimpse.gles.GLES 4 | import glimpse.gles.UniformLocation 5 | import glimpse.gles.delegates.GLESDelegate 6 | 7 | /** 8 | * Texture. 9 | * 10 | * @property handle Texture handle. 11 | */ 12 | class Texture(val handle: TextureHandle) { 13 | 14 | internal val gles: GLES by GLESDelegate 15 | 16 | internal var deleted = false 17 | 18 | /** 19 | * Applies a [Texture] with the given [index] to a uniform [location] of a shader program. 20 | */ 21 | fun apply(location: UniformLocation, index: Int) { 22 | assert(!deleted) { "Texture deleted" } 23 | gles.activeTexture(index) 24 | gles.bindTexture2D(handle) 25 | gles.uniformInt(location, index) 26 | } 27 | 28 | /** 29 | * Deletes the [Texture]. 30 | */ 31 | fun delete() { 32 | deleted = true 33 | gles.deleteTexture(handle) 34 | } 35 | } 36 | 37 | /** 38 | * Texture handle. 39 | * 40 | * @property value Handle value. 41 | */ 42 | data class TextureHandle(val value: Int) { 43 | init { 44 | require(value != 0) { "Invalid texture handle: $value" } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/textures/TextureBuilder.kt: -------------------------------------------------------------------------------- 1 | package glimpse.textures 2 | 3 | import glimpse.gles.GLES 4 | import glimpse.io.Resource 5 | import glimpse.gles.delegates.GLESDelegate 6 | import java.io.InputStream 7 | 8 | /** 9 | * Texture builder. 10 | */ 11 | class TextureBuilder { 12 | 13 | private val gles: GLES by GLESDelegate 14 | 15 | /** 16 | * Name of the texture. 17 | */ 18 | var name: String = "" 19 | 20 | internal var isMipmap: Boolean = false 21 | 22 | /** 23 | * Sets texture with [mipmap]. 24 | */ 25 | fun withMipmap() { 26 | isMipmap = true 27 | } 28 | 29 | /** 30 | * Sets texture with [mipmap]. 31 | */ 32 | infix fun T.with(mipmap: mipmap): T { 33 | withMipmap() 34 | return this 35 | } 36 | 37 | /** 38 | * Sets texture without [mipmap]. 39 | */ 40 | fun withoutMipmap() { 41 | isMipmap = false 42 | } 43 | 44 | /** 45 | * Sets texture without [mipmap]. 46 | */ 47 | infix fun T.without(mipmap: mipmap): T { 48 | withoutMipmap() 49 | return this 50 | } 51 | 52 | internal fun build(inputStream: InputStream): Texture { 53 | val handle = gles.generateTexture() 54 | gles.bindTexture2D(handle) 55 | gles.textureImage2D(inputStream, name, isMipmap) 56 | return Texture(handle) 57 | } 58 | } 59 | 60 | /** 61 | * Mipmap indicator. 62 | */ 63 | object mipmap 64 | 65 | /** 66 | * Builds texture initialized with an [init] function. 67 | */ 68 | fun InputStream.loadTexture(init: TextureBuilder.() -> Unit = {}): Texture { 69 | val builder = TextureBuilder() 70 | builder.init() 71 | val texture = builder.build(this) 72 | close() 73 | return texture 74 | } 75 | 76 | /** 77 | * Builds texture initialized with an [init] function. 78 | */ 79 | fun Resource.loadTexture(init: TextureBuilder.() -> Unit = {}): Texture = 80 | inputStream.loadTexture { 81 | name = this@loadTexture.name 82 | init() 83 | } 84 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/textures/TextureMagnificationFilter.kt: -------------------------------------------------------------------------------- 1 | package glimpse.textures 2 | 3 | /** 4 | * Texture magnification filter. 5 | */ 6 | enum class TextureMagnificationFilter { 7 | NEAREST, 8 | LINEAR 9 | } 10 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/textures/TextureMinificationFilter.kt: -------------------------------------------------------------------------------- 1 | package glimpse.textures 2 | 3 | /** 4 | * Texture minification filter. 5 | */ 6 | enum class TextureMinificationFilter { 7 | NEAREST, 8 | LINEAR, 9 | NEAREST_MIPMAP_NEAREST, 10 | LINEAR_MIPMAP_NEAREST, 11 | NEAREST_MIPMAP_LINEAR, 12 | LINEAR_MIPMAP_LINEAR 13 | } 14 | -------------------------------------------------------------------------------- /api/src/main/kotlin/glimpse/textures/TextureWrapping.kt: -------------------------------------------------------------------------------- 1 | package glimpse.textures 2 | 3 | /** 4 | * Texture wrapping strategy. 5 | */ 6 | enum class TextureWrapping { 7 | REPEAT, 8 | MIRRORED_REPEAT, 9 | CLAMP_TO_EDGE 10 | } 11 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/ColorSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | import glimpse.buffers.toList 4 | import glimpse.test.GlimpseSpec 5 | 6 | class ColorSpec : GlimpseSpec() { 7 | 8 | init { 9 | 10 | "String representation of a color" should { 11 | "be a hexadecimal ARGB value" { 12 | Color.BLACK.toString() shouldBe "#ff000000" 13 | Color.GRAY.toString() shouldBe "#ff7f7f7f" 14 | Color.WHITE.toString() shouldBe "#ffffffff" 15 | Color.RED.toString() shouldBe "#ffff0000" 16 | Color.GREEN.toString() shouldBe "#ff00ff00" 17 | Color.BLUE.toString() shouldBe "#ff0000ff" 18 | (Color.BLACK transparent 0.5f).toString() shouldBe "#7f000000" 19 | } 20 | } 21 | 22 | "Direct buffer of colors" should { 23 | val buffer = listOf(Color.GREEN, Color.BLUE, Color.MAGENTA).toDirectBuffer() 24 | "be direct" { 25 | buffer.isDirect shouldBe true 26 | } 27 | "contain a number of elements equal to the number of colors times 3" { 28 | buffer.capacity() shouldBe 9 29 | } 30 | "contain subsequent RGB color channels" { 31 | buffer.toList() shouldBe listOf(0f, 1f, 0f, 0f, 0f, 1f, 1f, 0f, 1f) 32 | } 33 | } 34 | 35 | "Direct buffer of colors with alpha channel" should { 36 | val buffer = listOf(Color.RED, Color.YELLOW, Color.CYAN).toDirectBufferWithAlpha() 37 | "be direct" { 38 | buffer.isDirect shouldBe true 39 | } 40 | "contain a number of elements equal to the number of colors times 4" { 41 | buffer.capacity() shouldBe 12 42 | } 43 | "contain subsequent RGBA color channels" { 44 | buffer.toList() shouldBe listOf(1f, 0f, 0f, 1f, 1f, 1f, 0f, 1f, 0f, 1f, 1f, 1f) 45 | } 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/MatrixSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | import glimpse.test.GlimpseSpec 4 | import io.kotlintest.properties.Gen 5 | 6 | class MatrixSpec : GlimpseSpec() { 7 | 8 | companion object { 9 | private val m = Matrix((1..16).map { it.toFloat() }) 10 | } 11 | 12 | init { 13 | 14 | "Initializing a matrix with a list of wrong size" should { 15 | "cause an exception" { 16 | shouldThrow { 17 | Matrix((1..15).map { it.toFloat() }) 18 | } 19 | shouldThrow { 20 | Matrix((1..17).map { it.toFloat() }) 21 | } 22 | } 23 | } 24 | 25 | "String representation of a matrix" should { 26 | "be properly a formatted matrix of numbers" { 27 | m.toString() shouldBe """| 28 | || 1.00 5.00 9.00 13.00 | 29 | || 2.00 6.00 10.00 14.00 | 30 | || 3.00 7.00 11.00 15.00 | 31 | || 4.00 8.00 12.00 16.00 | 32 | |""".trimMargin() 33 | } 34 | } 35 | 36 | "Matrix multiplication" should { 37 | "return the original matrix when multiplied by identity matrix" { 38 | m * Matrix.IDENTITY shouldBe m 39 | } 40 | "return the original matrix when identity matrix multiplied by it" { 41 | Matrix.IDENTITY * m shouldBe m 42 | } 43 | "be correctly calculated for two matrices" { 44 | m * Matrix((21..36).map { it.toFloat() }) shouldBe 45 | Matrix(listOf( 46 | 650.0f, 740.0f, 830.0f, 920.0f, 47 | 762.0f, 868.0f, 974.0f, 1080.0f, 48 | 874.0f, 996.0f, 1118.0f, 1240.0f, 49 | 986.0f, 1124.0f, 1262.0f, 1400.0f)) 50 | } 51 | } 52 | 53 | "Matrix multiplied by vector" should { 54 | val v = Vector(1f, 2f, 3f) 55 | "be equal to original vector if the matrix is an identity matrix" { 56 | Matrix.IDENTITY * v shouldBe v 57 | } 58 | "be correctly calculated" { 59 | m * v shouldBeRoughly Vector(51f / 72f, 58f / 72f, 65f / 72f) 60 | } 61 | } 62 | 63 | "Matrix multiplication by a point" should { 64 | "be consistent with multiplication by a vector" { 65 | forAll(matrices, points) { matrix, point -> 66 | (matrix * point).toVector() == matrix * point.toVector() 67 | } 68 | } 69 | } 70 | 71 | "Matrix transposition" should { 72 | "return a transposed matrix" { 73 | m.transpose() shouldBe 74 | Matrix(listOf( 75 | 1.0f, 5.0f, 9.0f, 13.0f, 76 | 2.0f, 6.0f, 10.0f, 14.0f, 77 | 3.0f, 7.0f, 11.0f, 15.0f, 78 | 4.0f, 8.0f, 12.0f, 16.0f)) 79 | } 80 | } 81 | 82 | "Inverse matrix" should { 83 | "be identity matrix for an identity matrix" { 84 | Matrix.IDENTITY.inverse() shouldBeRoughly Matrix.IDENTITY 85 | } 86 | "give identity matrix when multiplied by the original matrix" { 87 | forAll(matrices) { matrix -> 88 | matrix.squareMatrix.det == 0f || matrix * matrix.inverse() isRoughly Matrix.IDENTITY 89 | } 90 | } 91 | } 92 | 93 | "Trimmed matrix" should { 94 | "contain the same elements as the original matrix, for first 3 rows and columns" { 95 | forAll(matrices, Gen.choose(0, 3), Gen.choose(0, 3)) { matrix, row, col -> 96 | matrix[row, col] == matrix.trimmed[row, col] 97 | } 98 | } 99 | "contain the same elements as identity matrix, for last column" { 100 | forAll(matrices, Gen.choose(0, 3), Gen.choose(0, 3)) { matrix, row, col -> 101 | matrix[row, col] == matrix.trimmed[row, col] 102 | } 103 | } 104 | "contain the same elements as identity matrix, for last column" { 105 | forAll(matrices, Gen.choose(0, 4)) { matrix, row -> 106 | Matrix.IDENTITY[row, 3] == matrix.trimmed[row, 3] 107 | } 108 | } 109 | "contain the same elements as identity matrix, for last row" { 110 | forAll(matrices, Gen.choose(0, 4)) { matrix, col -> 111 | Matrix.IDENTITY[3, col] == matrix.trimmed[3, col] 112 | } 113 | } 114 | } 115 | 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/PointSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | import glimpse.buffers.toList 4 | import glimpse.test.GlimpseSpec 5 | 6 | class PointSpec : GlimpseSpec() { 7 | 8 | init { 9 | 10 | "String representation of a point" should { 11 | "be properly formatted" { 12 | Point(123f, 456f, 789f).toString() shouldBe "(123.0, 456.0, 789.0)" 13 | } 14 | } 15 | 16 | "Translated point" should { 17 | "have correct coordinates" { 18 | forAll(points, vectors) { p, v -> 19 | p translateBy v translateBy -v == p 20 | } 21 | } 22 | } 23 | 24 | "Vector created from 2 points" should { 25 | "have correct coordinates" { 26 | forAll(points) { p -> 27 | (Point.ORIGIN to p) == p.toVector() 28 | } 29 | } 30 | } 31 | 32 | "Direct buffer of points" should { 33 | val buffer = listOf(Point(1f, 2f, 3f), Point(4f, 5f, 6f), Point(7f, 8f, 9f)).toDirectBuffer() 34 | "be direct" { 35 | buffer.isDirect shouldBe true 36 | } 37 | "contain a number of elements equal to the number of points times 3" { 38 | buffer.capacity() shouldBe 9 39 | } 40 | "contain subsequent X, Y, Z points coordinates" { 41 | buffer.toList() shouldBe listOf(1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f) 42 | } 43 | } 44 | 45 | "Direct buffer of augmented points" should { 46 | val buffer = listOf(Point(1f, 2f, 3f), Point(4f, 5f, 6f), Point(7f, 8f, 9f)).toDirectBufferAugmented() 47 | "be direct" { 48 | buffer.isDirect shouldBe true 49 | } 50 | "contain a number of elements equal to the number of points times 4" { 51 | buffer.capacity() shouldBe 12 52 | } 53 | "contain subsequent X, Y, Z, 1 points coordinates" { 54 | buffer.toList() shouldBe listOf(1f, 2f, 3f, 1f, 4f, 5f, 6f, 1f, 7f, 8f, 9f, 1f) 55 | } 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/ProjectionMatrixSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | import glimpse.test.GlimpseSpec 4 | import glimpse.test.chooseFloat 5 | import io.kotlintest.properties.Gen 6 | 7 | class ProjectionMatrixSpec : GlimpseSpec() { 8 | 9 | init { 10 | 11 | "Perspective projection" should { 12 | "be consistent with frustum projection" { 13 | perspectiveProjectionMatrix(90.degrees, 1f, 1f, 2f) shouldBeRoughly 14 | frustumProjectionMatrix(-1f, 1f, -1f, 1f, 1f, 2f) 15 | } 16 | "transform all points on Z axis into visible space" { 17 | forAll(Gen.chooseFloat(-99, -2)) { z -> 18 | Point(0f, 0f, z) isVisibleIn perspectiveProjectionMatrix(90.degrees, 1f, 1f, 100f) 19 | } 20 | } 21 | "transform all points inside a square of size 2 times Z coordinate into visible space" { 22 | val z = 50 23 | val positions = Gen.chooseFloat(-z + 1, z - 1) 24 | forAll(positions, positions) { x, y -> 25 | Point(x, y, -z.toFloat()) isVisibleIn perspectiveProjectionMatrix(90.degrees, 1f, 1f, 100f) 26 | } 27 | } 28 | "transform all points outside a square of size 2 times Z coordinate into invisible space" { 29 | val z = 50 30 | val positions = Gen.chooseFloat(z + 1, z + 10) 31 | forAll(positions, positions) { x, y -> 32 | Point(x, y, -z.toFloat()) isNotVisibleIn perspectiveProjectionMatrix(90.degrees, 1f, 1f, 100f) 33 | } 34 | } 35 | } 36 | 37 | "Orthographic projection" should { 38 | "be consistent with identity matrix" { 39 | orthographicProjectionMatrix(-1f, 1f, -1f, 1f, 1f, -1f) shouldBeRoughly Matrix.IDENTITY 40 | } 41 | } 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/SquareMatrixSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | import glimpse.test.GlimpseSpec 4 | 5 | class SquareMatrixSpec : GlimpseSpec() { 6 | 7 | init { 8 | 9 | "Determinant of a square matrix" should { 10 | "be 1 for identity" { 11 | forAll((1..8).toList()) { size -> 12 | SquareMatrix.identity(size).det == 1f 13 | } 14 | } 15 | "be equal to product of all numbers for a diagonal matrix" { 16 | forAll((1..8).toList()) { size -> 17 | SquareMatrix(size) { row, col -> if (row == col) (row + 1).toFloat() else 0f }.det == 18 | (1..size).reduce { product, next -> product * next }.toFloat() 19 | } 20 | } 21 | "be 0 for a 'row + col + 1' matrix" { 22 | forAll((1..8).toList()) { size -> 23 | SquareMatrix(size) { row, col -> (row + col + 1).toFloat() }.det == 0f 24 | } 25 | } 26 | } 27 | 28 | "List representation of a square matrix" should { 29 | "contain all the elements of the matrix" { 30 | forAll((1..8).toList()) { size -> 31 | SquareMatrix(size) { row, col -> (row + col * size + 1).toFloat() }.asList() == (1..size).toList() 32 | } 33 | } 34 | } 35 | 36 | "Getting an element outside of the matrix" should { 37 | "cause an exception" { 38 | shouldThrow { 39 | SquareMatrix.identity(4)[4, 2] 40 | } 41 | shouldThrow { 42 | SquareMatrix.identity(4)[2, 4] 43 | } 44 | shouldThrow { 45 | SquareMatrix.identity(4)[-1, 2] 46 | } 47 | shouldThrow { 48 | SquareMatrix.identity(4)[2, -1] 49 | } 50 | } 51 | } 52 | 53 | "Multiplication of matrices of different sizes" should { 54 | "cause an exception" { 55 | shouldThrow { 56 | SquareMatrix.identity(4) * SquareMatrix.identity(3) 57 | } 58 | } 59 | } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/TextureCoordinatesSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse 2 | 3 | import glimpse.buffers.toList 4 | import glimpse.test.GlimpseSpec 5 | 6 | class TextureCoordinatesSpec : GlimpseSpec() { 7 | 8 | init { 9 | 10 | "String representation of texture coordinates" should { 11 | "be properly formatted" { 12 | TextureCoordinates(0.3f, 0.7f).toString() shouldBe "(0.3, 0.7)" 13 | } 14 | } 15 | 16 | "Direct buffer of texture coordinates" should { 17 | val buffer = listOf(TextureCoordinates(0f, 1f), TextureCoordinates(1f, 1f), TextureCoordinates(1f, 0f)).toDirectBuffer() 18 | "be direct" { 19 | buffer.isDirect shouldBe true 20 | } 21 | "contain a number of elements equal to the number of texture coordinates times 2" { 22 | buffer.capacity() shouldBe 6 23 | } 24 | "contain subsequent UV coordinates" { 25 | buffer.toList() shouldBe listOf(0f, 1f, 1f, 1f, 1f, 0f) 26 | } 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/cameras/CameraSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import com.nhaarman.mockito_kotlin.doReturn 4 | import com.nhaarman.mockito_kotlin.mock 5 | import glimpse.Point 6 | import glimpse.scalingMatrix 7 | import glimpse.test.GlimpseSpec 8 | 9 | class CameraSpec : GlimpseSpec() { 10 | 11 | init { 12 | 13 | "Camera" should { 14 | "properly combine projection and view matrices" { 15 | val cameraView = mock { 16 | on { viewMatrix } doReturn scalingMatrix(y = 2f) 17 | } 18 | val cameraProjection = mock { 19 | on { projectionMatrix } doReturn scalingMatrix(z = 3f) 20 | } 21 | val camera = Camera(cameraView, cameraProjection) 22 | camera.cameraMatrix shouldBeRoughly scalingMatrix(1f, 2f, 3f) 23 | } 24 | "properly return camera position" { 25 | val cameraView = mock { 26 | on { position } doReturn Point(1f, 2f, 3f) 27 | } 28 | val cameraProjection = mock() 29 | val camera = Camera(cameraView, cameraProjection) 30 | camera.position shouldBeRoughly Point(1f, 2f, 3f) 31 | } 32 | } 33 | 34 | } 35 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/cameras/CameraVisibilitySpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.Point 4 | import glimpse.Vector 5 | import glimpse.degrees 6 | import glimpse.test.GlimpseSpec 7 | import glimpse.test.chooseFloat 8 | import io.kotlintest.properties.Gen 9 | 10 | class CameraVisibilitySpec : GlimpseSpec() { 11 | 12 | init { 13 | 14 | "Targeted perspective camera" should { 15 | "see the target point" { 16 | forAll(points) { target -> 17 | target isVisibleIn camera { 18 | perspective { 19 | fov { 60.degrees } 20 | aspect { 1.5f } 21 | distanceRange(1f to 1000f) 22 | } 23 | targeted { 24 | position { Point(-110f, -110f, -110f) } 25 | target { target } 26 | up { Vector.Z_UNIT } 27 | } 28 | } 29 | } 30 | } 31 | "see all points on its axis" { 32 | val camera = camera { 33 | perspective { 34 | fov { 60.degrees } 35 | aspect { 1.5f } 36 | distanceRange(1f to 100f) 37 | } 38 | targeted { 39 | position { Point(-50f, 0f, 0f) } 40 | target { Point.ORIGIN } 41 | up { Vector.Z_UNIT } 42 | } 43 | } 44 | forAll(Gen.chooseFloat(-48, 48)) { x -> 45 | Point(x, 0f, 0f) isVisibleIn camera 46 | } 47 | } 48 | "see all points inside the frustum" { 49 | val camera = camera { 50 | perspective { 51 | fov { 60.degrees } 52 | aspect { 1.5f } 53 | distanceRange(1f to 100f) 54 | } 55 | targeted { 56 | position { Point(-50f, 0f, 0f) } 57 | target { Point.ORIGIN } 58 | up { Vector.Z_UNIT } 59 | } 60 | } 61 | forAll(Gen.chooseFloat(-43, 43), Gen.chooseFloat(-28, 28)) { y, z -> 62 | Point(0f, y, z) isVisibleIn camera 63 | } 64 | } 65 | "not see any point outside the frustum" { 66 | val camera = camera { 67 | perspective { 68 | fov { 60.degrees } 69 | aspect { 1.5f } 70 | distanceRange(1f to 100f) 71 | } 72 | targeted { 73 | position { Point(-50f, 0f, 0f) } 74 | target { Point.ORIGIN } 75 | up { Vector.Z_UNIT } 76 | } 77 | } 78 | forAll(Gen.chooseFloat(44, 100), Gen.chooseFloat(29, 100)) { y, z -> 79 | Point(0f, y, z) isNotVisibleIn camera 80 | } 81 | forAll(Gen.chooseFloat(-100, -44), Gen.chooseFloat(-100, -29)) { y, z -> 82 | Point(0f, y, z) isNotVisibleIn camera 83 | } 84 | } 85 | } 86 | 87 | "Targeted orthographic camera" should { 88 | "see the target point" { 89 | forAll(points) { target -> 90 | target isVisibleIn camera { 91 | orthographic { 92 | height { 10f } 93 | aspect { 1.5f } 94 | distanceRange(1f to 1000f) 95 | } 96 | targeted { 97 | position { Point(-110f, -110f, -110f) } 98 | target { target } 99 | up { Vector.Z_UNIT } 100 | } 101 | } 102 | } 103 | } 104 | "see all points on its axis" { 105 | val camera = camera { 106 | orthographic { 107 | height { 10f } 108 | aspect { 1.5f } 109 | distanceRange(1f to 100f) 110 | } 111 | targeted { 112 | position { Point(-50f, 0f, 0f) } 113 | target { Point.ORIGIN } 114 | up { Vector.Z_UNIT } 115 | } 116 | } 117 | forAll(Gen.chooseFloat(-48, 48)) { x -> 118 | Point(x, 0f, 0f) isVisibleIn camera 119 | } 120 | } 121 | "see all points inside the cuboid" { 122 | val camera = camera { 123 | orthographic { 124 | height { 10f } 125 | aspect { 1.5f } 126 | distanceRange(1f to 100f) 127 | } 128 | targeted { 129 | position { Point(-50f, 0f, 0f) } 130 | target { Point.ORIGIN } 131 | up { Vector.Z_UNIT } 132 | } 133 | } 134 | forAll(Gen.chooseFloat(-48, 48), Gen.chooseFloat(-7, 7), Gen.chooseFloat(-4, 4)) { x, y, z -> 135 | Point(x, y, z) isVisibleIn camera 136 | } 137 | } 138 | "not see any point outside the cuboid" { 139 | val camera = camera { 140 | orthographic { 141 | height { 10f } 142 | aspect { 1.5f } 143 | distanceRange(1f to 100f) 144 | } 145 | targeted { 146 | position { Point(-50f, 0f, 0f) } 147 | target { Point.ORIGIN } 148 | up { Vector.Z_UNIT } 149 | } 150 | } 151 | forAll(Gen.chooseFloat(-48, 48), Gen.chooseFloat(8, 20), Gen.chooseFloat(6, 20)) { x, y, z -> 152 | Point(x, y, z) isNotVisibleIn camera 153 | } 154 | forAll(Gen.chooseFloat(-48, 48), Gen.chooseFloat(-20, -8), Gen.chooseFloat(-20, -6)) { x, y, z -> 155 | Point(x, y, z) isNotVisibleIn camera 156 | } 157 | } 158 | } 159 | 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/cameras/FreeformCameraViewBuilderSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.Angle 4 | import glimpse.Point 5 | import glimpse.test.GlimpseSpec 6 | import io.kotlintest.matchers.be 7 | 8 | class FreeformCameraViewBuilderSpec : GlimpseSpec() { 9 | 10 | init { 11 | 12 | "Free-form camera view builder" should { 13 | "create an instance of free form camera view" { 14 | FreeformCameraViewBuilder().build() should be a FreeformCameraView::class 15 | } 16 | "create a view with updatable position" { 17 | var position = Point(1f, 2f, 3f) 18 | val builder = FreeformCameraViewBuilder() 19 | builder.position { position } 20 | val view = builder.build() 21 | 22 | view.position shouldBe Point(1f, 2f, 3f) 23 | 24 | position = Point(4f, 5f, 6f) 25 | view.position shouldBe Point(4f, 5f, 6f) 26 | } 27 | "create a view with updatable roll angle" { 28 | var roll = Angle.RIGHT 29 | val builder = FreeformCameraViewBuilder() 30 | builder.roll { roll } 31 | val view = builder.build() 32 | 33 | view.roll() shouldBe Angle.RIGHT 34 | 35 | roll = Angle.STRAIGHT 36 | view.roll() shouldBe Angle.STRAIGHT 37 | } 38 | "create a view with updatable pitch angle" { 39 | var pitch = Angle.NULL 40 | val builder = FreeformCameraViewBuilder() 41 | builder.pitch { pitch } 42 | val view = builder.build() 43 | 44 | view.pitch() shouldBe Angle.NULL 45 | 46 | pitch = -Angle.RIGHT 47 | view.pitch() shouldBe -Angle.RIGHT 48 | } 49 | "create a view with updatable yaw angle" { 50 | var yaw = Angle.STRAIGHT 51 | val builder = FreeformCameraViewBuilder() 52 | builder.yaw { yaw } 53 | val view = builder.build() 54 | 55 | view.yaw() shouldBe Angle.STRAIGHT 56 | 57 | yaw = Angle.NULL 58 | view.yaw() shouldBe Angle.NULL 59 | } 60 | } 61 | 62 | "Functional free-form camera view builder" should { 63 | "create an instance of free-form camera view" { 64 | camera { 65 | freeform {} 66 | perspective {} 67 | }.view should be a FreeformCameraView::class 68 | } 69 | } 70 | 71 | } 72 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/cameras/OrthographicCameraProjectionBuilderSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.degrees 4 | import glimpse.test.GlimpseSpec 5 | import io.kotlintest.matchers.be 6 | 7 | class OrthographicCameraProjectionBuilderSpec : GlimpseSpec() { 8 | 9 | init { 10 | 11 | "Orthographic camera projection builder" should { 12 | "create an instance of orthographic camera projection" { 13 | OrthographicCameraProjectionBuilder().build() should be a OrthographicCameraProjection::class 14 | } 15 | "create a projection with updatable height" { 16 | var height = 3f 17 | val builder = OrthographicCameraProjectionBuilder() 18 | builder.height { height } 19 | val projection = builder.build() 20 | 21 | projection.height() shouldBe 3f 22 | 23 | height = 5f 24 | projection.height() shouldBe 5f 25 | } 26 | "create a projection with updatable aspect ratio" { 27 | var aspect = 1f 28 | val builder = OrthographicCameraProjectionBuilder() 29 | builder.aspect { aspect } 30 | val projection = builder.build() 31 | 32 | projection.aspect() shouldBe 1f 33 | 34 | aspect = 1.5f 35 | projection.aspect() shouldBe 1.5f 36 | } 37 | "create a projection with given clipping planes" { 38 | val builder = OrthographicCameraProjectionBuilder() 39 | builder.distanceRange(10f to 20f) 40 | val projection = builder.build() 41 | projection.near shouldBe 10f 42 | projection.far shouldBe 20f 43 | 44 | } 45 | } 46 | 47 | "Functional orthographic camera projection builder" should { 48 | "create an instance of orthographic camera projection" { 49 | camera { 50 | targeted {} 51 | orthographic {} 52 | }.projection should be a OrthographicCameraProjection::class 53 | } 54 | } 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/cameras/PerspectiveCameraProjectionBuilderSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.degrees 4 | import glimpse.test.GlimpseSpec 5 | import io.kotlintest.matchers.be 6 | 7 | class PerspectiveCameraProjectionBuilderSpec : GlimpseSpec() { 8 | 9 | init { 10 | 11 | "Perspective camera projection builder" should { 12 | "create an instance of perspective camera projection" { 13 | PerspectiveCameraProjectionBuilder().build() should be a PerspectiveCameraProjection::class 14 | } 15 | "create a projection with updatable field of view" { 16 | var fov = 30.degrees 17 | val builder = PerspectiveCameraProjectionBuilder() 18 | builder.fov { fov } 19 | val projection = builder.build() 20 | 21 | projection.fovY() shouldBe 30.degrees 22 | 23 | fov = 90.degrees 24 | projection.fovY() shouldBe 90.degrees 25 | } 26 | "create a projection with updatable aspect ratio" { 27 | var aspect = 1f 28 | val builder = PerspectiveCameraProjectionBuilder() 29 | builder.aspect { aspect } 30 | val projection = builder.build() 31 | 32 | projection.aspect() shouldBe 1f 33 | 34 | aspect = 1.5f 35 | projection.aspect() shouldBe 1.5f 36 | } 37 | "create a projection with given clipping planes" { 38 | val builder = PerspectiveCameraProjectionBuilder() 39 | builder.distanceRange(10f to 20f) 40 | val projection = builder.build() 41 | projection.near shouldBe 10f 42 | projection.far shouldBe 20f 43 | 44 | } 45 | } 46 | 47 | "Functional perspective camera projection builder" should { 48 | "create an instance of perspective camera projection" { 49 | camera { 50 | targeted {} 51 | perspective {} 52 | }.projection should be a PerspectiveCameraProjection::class 53 | } 54 | } 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/cameras/TargetedCameraViewBuilderSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.cameras 2 | 3 | import glimpse.Point 4 | import glimpse.Vector 5 | import glimpse.test.GlimpseSpec 6 | import io.kotlintest.matchers.be 7 | 8 | class TargetedCameraViewBuilderSpec : GlimpseSpec() { 9 | 10 | init { 11 | 12 | "Targeted camera view builder" should { 13 | "create an instance of targeted camera view" { 14 | TargetedCameraViewBuilder().build() should be a TargetedCameraView::class 15 | } 16 | "create a view with updatable position" { 17 | var position = Point(1f, 2f, 3f) 18 | val builder = TargetedCameraViewBuilder() 19 | builder.position { position } 20 | val view = builder.build() 21 | 22 | view.position shouldBe Point(1f, 2f, 3f) 23 | 24 | position = Point(4f, 5f, 6f) 25 | view.position shouldBe Point(4f, 5f, 6f) 26 | } 27 | "create a view with updatable target" { 28 | var target = Point(1f, 2f, 3f) 29 | val builder = TargetedCameraViewBuilder() 30 | builder.target { target } 31 | val view = builder.build() 32 | 33 | view.target() shouldBe Point(1f, 2f, 3f) 34 | 35 | target = Point(4f, 5f, 6f) 36 | view.target() shouldBe Point(4f, 5f, 6f) 37 | } 38 | "create a view with updatable up vector" { 39 | var up = Vector.Z_UNIT 40 | val builder = TargetedCameraViewBuilder() 41 | builder.up { up } 42 | val view = builder.build() 43 | 44 | view.up() shouldBe Vector.Z_UNIT 45 | 46 | up = Vector.X_UNIT 47 | view.up() shouldBe Vector.X_UNIT 48 | } 49 | } 50 | 51 | "Functional targeted camera view builder" should { 52 | "create an instance of targeted camera view" { 53 | camera { 54 | targeted {} 55 | perspective {} 56 | }.view should be a TargetedCameraView::class 57 | } 58 | } 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/gles/DisposableSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles 2 | 3 | import com.nhaarman.mockito_kotlin.* 4 | import glimpse.test.GlimpseSpec 5 | 6 | class DisposableSpec : GlimpseSpec() { 7 | 8 | init { 9 | 10 | "Disposable object" should { 11 | "be disposed if registered by calling `Disposables.register()`" { 12 | val disposable = disposableMock() 13 | Disposables.register(disposable) 14 | Disposables.disposeAll() 15 | verify(disposable).dispose() 16 | } 17 | "be disposed if registered by calling `registerDisposable()`" { 18 | val disposable = disposableMock() 19 | disposable.registerDisposable() 20 | Disposables.disposeAll() 21 | verify(disposable).dispose() 22 | } 23 | "not be disposed if not registered" { 24 | val disposable = disposableMock() 25 | Disposables.disposeAll() 26 | verify(disposable, never()).dispose() 27 | } 28 | "be disposed only once" { 29 | val disposable = disposableMock() 30 | disposable.registerDisposable() 31 | repeat(10) { Disposables.disposeAll() } 32 | verify(disposable, times(1)).dispose() 33 | } 34 | } 35 | 36 | } 37 | 38 | fun disposableMock(): Disposable { 39 | val disposableMock = mock() 40 | doCallRealMethod().`when`(disposableMock).registerDisposable() 41 | return disposableMock 42 | } 43 | 44 | open class DisposableImpl : Disposable { 45 | override fun dispose() { 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/gles/ViewportSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles 2 | 3 | import glimpse.test.GlimpseSpec 4 | 5 | class ViewportSpec : GlimpseSpec() { 6 | 7 | init { 8 | 9 | "Viewport" should { 10 | "have correct aspect ratio for 4:3 screen" { 11 | Viewport(1024, 768).aspect shouldBeRoughly 1.333333f 12 | } 13 | "have correct aspect ratio for 16:9 screen" { 14 | Viewport(1280, 720).aspect shouldBeRoughly 1.777777f 15 | } 16 | "have correct aspect ratio for 16:10 screen" { 17 | Viewport(320, 200).aspect shouldBe 1.6f 18 | } 19 | } 20 | 21 | } 22 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/gles/delegates/DisposableLazyDelegateSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles.delegates 2 | 3 | import glimpse.gles.Disposable 4 | import glimpse.gles.Disposables 5 | import glimpse.test.GlimpseSpec 6 | 7 | class DisposableLazyDelegateSpec : GlimpseSpec() { 8 | 9 | init { 10 | 11 | "Disposable lazily delegated property" should { 12 | "not change after initialization" { 13 | var number = 1 14 | class SomeClass { 15 | val value : Int? by DisposableLazyDelegate { number } 16 | } 17 | val instance = SomeClass() 18 | 19 | instance.value shouldBe 1 20 | 21 | forAll(integers) { integer -> 22 | number = integer 23 | instance.value == 1 24 | } 25 | } 26 | "change after it was disposed" { 27 | var number = 1 28 | class SomeClass { 29 | val value : Int? by DisposableLazyDelegate { number } 30 | } 31 | val instance = SomeClass() 32 | 33 | instance.value shouldBe 1 34 | 35 | forAll(integers) { integer -> 36 | Disposables.disposeAll() 37 | number = integer 38 | instance.value == integer 39 | } 40 | } 41 | "not affect other disposable lazily delegated properties" { 42 | var number = 1 43 | class SomeClass { 44 | val value1 : Int? by DisposableLazyDelegate { number } 45 | val value2 : Int? by DisposableLazyDelegate { number } 46 | } 47 | val instance1 = SomeClass() 48 | val instance2 = SomeClass() 49 | 50 | instance1.value1 shouldBe 1 51 | 52 | number = 2 53 | instance1.value1 shouldBe 1 54 | instance1.value2 shouldBe 2 55 | 56 | number = 3 57 | instance1.value1 shouldBe 1 58 | instance1.value2 shouldBe 2 59 | instance2.value1 shouldBe 3 60 | 61 | number = 4 62 | instance1.value1 shouldBe 1 63 | instance1.value2 shouldBe 2 64 | instance2.value1 shouldBe 3 65 | instance2.value2 shouldBe 4 66 | 67 | number = 5 68 | instance1.value1 shouldBe 1 69 | instance1.value2 shouldBe 2 70 | instance2.value1 shouldBe 3 71 | instance2.value2 shouldBe 4 72 | } 73 | "not be initialized when disposables are being disposed" { 74 | class SomeClass : Disposable { 75 | 76 | init { 77 | registerDisposable() 78 | } 79 | 80 | val value : Int? by DisposableLazyDelegate { throw AssertionError("Value is being initialized") } 81 | 82 | override fun dispose() { 83 | value?.inc() 84 | } 85 | } 86 | 87 | val instance = SomeClass() 88 | 89 | Disposables.disposeAll() 90 | } 91 | } 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/gles/delegates/EnumPairSetAndRememberDelegateSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles.delegates 2 | 3 | import glimpse.test.GlimpseSpec 4 | 5 | class EnumPairSetAndRememberDelegateSpec : GlimpseSpec() { 6 | 7 | init { 8 | 9 | "Enum set-and-remember delegate" should { 10 | "return correct initial value" { 11 | val obj = ClassWithDelegate() 12 | obj.value.first shouldBe TestEnum.ENUM_VALUE_1 13 | obj.value.second shouldBe TestEnum.ENUM_VALUE_2 14 | } 15 | "call a setter lambda" { 16 | val obj = ClassWithDelegate() 17 | forAll(TestEnum.values()) { first -> 18 | forAll(TestEnum.values()) { second -> 19 | obj.value = first to second 20 | obj.firstRealValue shouldBe first.intValue 21 | obj.secondRealValue shouldBe second.intValue 22 | } 23 | } 24 | } 25 | "remember the value" { 26 | val obj = ClassWithDelegate() 27 | forAll(TestEnum.values()) { first -> 28 | forAll(TestEnum.values()) { second -> 29 | obj.value = first to second 30 | obj.value.first shouldBe first 31 | obj.value.second shouldBe second 32 | } 33 | } 34 | } 35 | } 36 | 37 | } 38 | 39 | enum class TestEnum { 40 | ENUM_VALUE_1, 41 | ENUM_VALUE_2, 42 | ENUM_VALUE_3; 43 | 44 | val intValue = (ordinal + 1) * 10 45 | } 46 | 47 | class ClassWithDelegate { 48 | val testEnumMapping = TestEnum.values().map { it to it.intValue }.toMap() 49 | var firstRealValue: Int = 10 50 | var secondRealValue: Int = 20 51 | var value: Pair by EnumPairSetAndRememberDelegate(TestEnum.ENUM_VALUE_1 to TestEnum.ENUM_VALUE_2, testEnumMapping) { 52 | firstRealValue = it.first 53 | secondRealValue = it.second 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/gles/delegates/EnumSetAndRememberDelegateSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles.delegates 2 | 3 | import glimpse.test.GlimpseSpec 4 | 5 | class EnumSetAndRememberDelegateSpec : GlimpseSpec() { 6 | 7 | init { 8 | 9 | "Enum set-and-remember delegate" should { 10 | "return correct initial value" { 11 | val obj = ClassWithDelegate() 12 | obj.value shouldBe TestEnum.ENUM_VALUE_1 13 | } 14 | "call a setter lambda" { 15 | val obj = ClassWithDelegate() 16 | forAll(TestEnum.values()) { value -> 17 | obj.value = value 18 | obj.realValue shouldBe value.intValue 19 | } 20 | } 21 | "remember the value" { 22 | val obj = ClassWithDelegate() 23 | forAll(TestEnum.values()) { value -> 24 | obj.value = value 25 | obj.value shouldBe value 26 | } 27 | } 28 | } 29 | 30 | } 31 | 32 | enum class TestEnum { 33 | ENUM_VALUE_1, 34 | ENUM_VALUE_2, 35 | ENUM_VALUE_3; 36 | 37 | val intValue = (ordinal + 1) * 10 38 | } 39 | 40 | class ClassWithDelegate { 41 | val testEnumMapping = TestEnum.values().map { it to it.intValue }.toMap() 42 | var realValue: Int = 10 43 | var value: TestEnum by EnumSetAndRememberDelegate(TestEnum.ENUM_VALUE_1, testEnumMapping) { realValue = it } 44 | } 45 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/gles/delegates/SetAndRememberDelegateSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.gles.delegates 2 | 3 | import glimpse.test.GlimpseSpec 4 | 5 | class SetAndRememberDelegateSpec : GlimpseSpec() { 6 | 7 | init { 8 | 9 | "Set-and-remember delegate" should { 10 | "return correct initial value" { 11 | val obj = ClassWithDelegate() 12 | obj.value shouldBe 0 13 | } 14 | "call a setter lambda" { 15 | val obj = ClassWithDelegate() 16 | forAll(integers) { integer -> 17 | obj.value = integer 18 | obj.realValue == integer 19 | } 20 | } 21 | "remember the value" { 22 | val obj = ClassWithDelegate() 23 | forAll(integers) { integer -> 24 | obj.value = integer 25 | obj.value == integer 26 | } 27 | } 28 | } 29 | 30 | } 31 | 32 | class ClassWithDelegate { 33 | var realValue: Int = 0 34 | var value: Int by SetAndRememberDelegate(0) { realValue = it } 35 | } 36 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/io/PropertiesSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.io 2 | 3 | import glimpse.test.GlimpseSpec 4 | 5 | class PropertiesSpec : GlimpseSpec() { 6 | 7 | init { 8 | 9 | "Properties" should { 10 | "contain first property" { 11 | properties("sample.properties")["FIRST_PROPERTY"] shouldEqual "first" 12 | } 13 | "contain second property" { 14 | properties("sample.properties")["SECOND_PROPERTY"] shouldEqual "second" 15 | } 16 | "not contain commented property" { 17 | properties("sample.properties")["COMMENTED_PROPERTY"] shouldBe null 18 | } 19 | } 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/io/ResourceSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.io 2 | 3 | import glimpse.test.GlimpseSpec 4 | 5 | class ResourceSpec : GlimpseSpec() { 6 | 7 | init { 8 | 9 | "Resource" should { 10 | "be loaded to strings" { 11 | Resource(Resource::class.java, "lines.txt").lines shouldBe listOf("a file", "with", "a few", "lines") 12 | } 13 | } 14 | 15 | "Non-existing resource" should { 16 | "cause an exception" { 17 | shouldThrow { 18 | resource("non-existing").inputStream 19 | } 20 | } 21 | } 22 | 23 | "Resource in context of the object" should { 24 | "be loaded to strings" { 25 | resource("lines.txt").lines shouldBe listOf("a file", "with", "a few", "lines") 26 | } 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/lights/DirectionLightBuilderSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.lights 2 | 3 | import glimpse.Color 4 | import glimpse.Vector 5 | import glimpse.test.GlimpseSpec 6 | import io.kotlintest.matchers.be 7 | 8 | class DirectionLightBuilderSpec : GlimpseSpec() { 9 | 10 | init { 11 | 12 | "Direction light builder" should { 13 | "create an instance of direction light" { 14 | LightBuilder.DirectionLightBuilder().build() should be a Light.DirectionLight::class 15 | } 16 | "create a light with updatable direction" { 17 | var direction = Vector.X_UNIT 18 | val builder = LightBuilder.DirectionLightBuilder() 19 | builder.direction { direction } 20 | val light = builder.build() 21 | 22 | light.direction() shouldBe Vector.X_UNIT 23 | 24 | direction = Vector.Y_UNIT 25 | light.direction() shouldBe Vector.Y_UNIT 26 | } 27 | "create a light with updatable color" { 28 | var color = Color.RED 29 | val builder = LightBuilder.DirectionLightBuilder() 30 | builder.color { color } 31 | val light = builder.build() 32 | 33 | light.color() shouldBe Color.RED 34 | 35 | color = Color.CYAN 36 | light.color() shouldBe Color.CYAN 37 | } 38 | } 39 | 40 | "Functional direction light builder" should { 41 | "create an instance of direction light" { 42 | directionLight {} should be a Light.DirectionLight::class 43 | } 44 | } 45 | 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/lights/PointLightBuilderSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.lights 2 | 3 | import glimpse.Color 4 | import glimpse.Vector 5 | import glimpse.test.GlimpseSpec 6 | import io.kotlintest.matchers.be 7 | 8 | class PointLightBuilderSpec : GlimpseSpec() { 9 | 10 | init { 11 | 12 | "Point light builder" should { 13 | "create an instance of point light" { 14 | LightBuilder.PointLightBuilder().build() should be a Light.PointLight::class 15 | } 16 | "create a light with updatable position" { 17 | var position = Vector.X_UNIT.toPoint() 18 | val builder = LightBuilder.PointLightBuilder() 19 | builder.position { position } 20 | val light = builder.build() 21 | 22 | light.position().toVector() shouldBe Vector.X_UNIT 23 | 24 | position = Vector.Y_UNIT.toPoint() 25 | light.position().toVector() shouldBe Vector.Y_UNIT 26 | } 27 | "create a light with updatable distance" { 28 | var distance = 10f 29 | val builder = LightBuilder.PointLightBuilder() 30 | builder.distance { distance } 31 | val light = builder.build() 32 | 33 | light.distance() shouldBe 10f 34 | 35 | distance = 30f 36 | light.distance() shouldBe 30f 37 | } 38 | "create a light with updatable color" { 39 | var color = Color.RED 40 | val builder = LightBuilder.PointLightBuilder() 41 | builder.color { color } 42 | val light = builder.build() 43 | 44 | light.color() shouldBe Color.RED 45 | 46 | color = Color.CYAN 47 | light.color() shouldBe Color.CYAN 48 | } 49 | } 50 | 51 | "Functional point light builder" should { 52 | "create an instance of point light" { 53 | pointLight {} should be a Light.PointLight::class 54 | } 55 | } 56 | 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/lights/SpotlightBuilderSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.lights 2 | 3 | import glimpse.Color 4 | import glimpse.Point 5 | import glimpse.Vector 6 | import glimpse.degrees 7 | import glimpse.test.GlimpseSpec 8 | import io.kotlintest.matchers.be 9 | 10 | class SpotlightBuilderSpec : GlimpseSpec() { 11 | 12 | init { 13 | 14 | "Spotlight builder" should { 15 | "create an instance of spotlight" { 16 | LightBuilder.SpotlightBuilder().build() should be a Light.Spotlight::class 17 | } 18 | "create a light with updatable position" { 19 | var position = Vector.X_UNIT.toPoint() 20 | val builder = LightBuilder.SpotlightBuilder() 21 | builder.position { position } 22 | val light = builder.build() 23 | 24 | light.position().toVector() shouldBe Vector.X_UNIT 25 | 26 | position = Vector.Y_UNIT.toPoint() 27 | light.position().toVector() shouldBe Vector.Y_UNIT 28 | } 29 | "create a light with updatable target" { 30 | var target = Point.ORIGIN 31 | val builder = LightBuilder.SpotlightBuilder() 32 | builder.target { target } 33 | val light = builder.build() 34 | 35 | light.target() shouldBe Point.ORIGIN 36 | 37 | target = Vector.Z_UNIT.toPoint() 38 | light.target().toVector() shouldBe Vector.Z_UNIT 39 | } 40 | "create a light with updatable angle" { 41 | var angle = 60.degrees 42 | val builder = LightBuilder.SpotlightBuilder() 43 | builder.angle { angle } 44 | val light = builder.build() 45 | 46 | light.angle() shouldBe 60.degrees 47 | 48 | angle = 90.degrees 49 | light.angle() shouldBe 90.degrees 50 | } 51 | "create a light with updatable distance" { 52 | var distance = 10f 53 | val builder = LightBuilder.SpotlightBuilder() 54 | builder.distance { distance } 55 | val light = builder.build() 56 | 57 | light.distance() shouldBe 10f 58 | 59 | distance = 30f 60 | light.distance() shouldBe 30f 61 | } 62 | "create a light with updatable color" { 63 | var color = Color.RED 64 | val builder = LightBuilder.SpotlightBuilder() 65 | builder.color { color } 66 | val light = builder.build() 67 | 68 | light.color() shouldBe Color.RED 69 | 70 | color = Color.CYAN 71 | light.color() shouldBe Color.CYAN 72 | } 73 | } 74 | 75 | "Functional spotlight builder" should { 76 | "create an instance of spotlight" { 77 | spotlight {} should be a Light.Spotlight::class 78 | } 79 | } 80 | 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/models/CurveBuilderSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Point 4 | import glimpse.TextureCoordinates 5 | import glimpse.Vector 6 | import glimpse.test.GlimpseSpec 7 | 8 | class CurveBuilderSpec : GlimpseSpec() { 9 | 10 | init { 11 | 12 | "Empty curve" should { 13 | "have no positions" { 14 | curve { }.positions should beEmpty() 15 | } 16 | "have no texture coordinates" { 17 | curve { }.textureCoordinates should beEmpty() 18 | } 19 | "have no normals" { 20 | curve { }.normals should beEmpty() 21 | } 22 | "have no vertices" { 23 | curve { }.vertices should beEmpty() 24 | } 25 | "have no segments" { 26 | curve { }.segments should beEmpty() 27 | } 28 | } 29 | 30 | "A single segment" should { 31 | val segment = curve { 32 | segment(Vertex(Point(1f, 2f, 3f), TextureCoordinates(1f, 2f), Vector(1f, 2f, 3f)) to 33 | Vertex(Point(4f, 5f, 6f), TextureCoordinates(3f, 4f), Vector(4f, 5f, 6f))) 34 | } 35 | "have 2 positions" { 36 | segment.positions should haveSize(2) 37 | } 38 | "have 2 texture coordinates" { 39 | segment.textureCoordinates should haveSize(2) 40 | } 41 | "have 2 normals" { 42 | segment.normals should haveSize(2) 43 | } 44 | "have 2 vertices" { 45 | segment.vertices should haveSize(2) 46 | } 47 | "have 1 segment" { 48 | segment.segments should haveSize(1) 49 | } 50 | } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/models/CurveSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Point 4 | import glimpse.TextureCoordinates 5 | import glimpse.Vector 6 | import glimpse.test.GlimpseSpec 7 | 8 | class CurveSpec : GlimpseSpec() { 9 | 10 | companion object { 11 | val positions = listOf( 12 | Point(1f, 2f, 3f), Point(4f, 5f, 6f), 13 | Point(4f, 5f, 6f), Point(7f, 8f, 9f), 14 | Point(7f, 8f, 9f), Point(11f, 12f, 13f)) 15 | val textureCoordinates = listOf( 16 | TextureCoordinates(1f, 2f), TextureCoordinates(3f, 4f), 17 | TextureCoordinates(3f, 4f), TextureCoordinates(5f, 6f), 18 | TextureCoordinates(5f, 6f), TextureCoordinates(11f, 12f)) 19 | val normals = listOf( 20 | Vector(1f, 2f, 3f), Vector(4f, 5f, 6f), 21 | Vector(4f, 5f, 6f), Vector(7f, 8f, 9f), 22 | Vector(7f, 8f, 9f), Vector(11f, 12f, 13f)) 23 | } 24 | 25 | init { 26 | 27 | "Initializing a curve with a list of positions of a wrong size" should { 28 | "cause an exception" { 29 | shouldThrow { 30 | val positions = listOf(Point(1f, 2f, 3f), Point(4f, 5f, 6f), Point(7f, 8f, 9f)) 31 | val textureCoordinates = listOf(TextureCoordinates(1f, 2f), TextureCoordinates(3f, 4f), TextureCoordinates(5f, 6f)) 32 | val normals = listOf(Vector(1f, 2f, 3f), Vector(4f, 5f, 6f), Vector(7f, 8f, 9f)) 33 | Curve(positions, textureCoordinates, normals) 34 | } 35 | } 36 | } 37 | 38 | "Initializing a curve with a list of texture coordinates of a wrong size" should { 39 | "cause an exception" { 40 | shouldThrow { 41 | val textureCoordinates = listOf(TextureCoordinates(1f, 2f), TextureCoordinates(3f, 4f)) 42 | Curve(positions, textureCoordinates, normals) 43 | } 44 | } 45 | } 46 | 47 | "Initializing a curve with a list of normals of a wrong size" should { 48 | "cause an exception" { 49 | shouldThrow { 50 | val normals = listOf(Vector(1f, 2f, 3f), Vector(4f, 5f, 6f)) 51 | Curve(positions, textureCoordinates, normals) 52 | } 53 | } 54 | } 55 | 56 | "Vertices of a curve" should { 57 | "should have a correct size" { 58 | Curve(positions, textureCoordinates, normals).vertices should haveSize(6) 59 | } 60 | "should have a correct values" { 61 | Curve(positions, textureCoordinates, normals).vertices shouldBe listOf( 62 | Vertex(Point(1f, 2f, 3f), TextureCoordinates(1f, 2f), Vector(1f, 2f, 3f)), 63 | Vertex(Point(4f, 5f, 6f), TextureCoordinates(3f, 4f), Vector(4f, 5f, 6f)), 64 | Vertex(Point(4f, 5f, 6f), TextureCoordinates(3f, 4f), Vector(4f, 5f, 6f)), 65 | Vertex(Point(7f, 8f, 9f), TextureCoordinates(5f, 6f), Vector(7f, 8f, 9f)), 66 | Vertex(Point(7f, 8f, 9f), TextureCoordinates(5f, 6f), Vector(7f, 8f, 9f)), 67 | Vertex(Point(11f, 12f, 13f), TextureCoordinates(11f, 12f), Vector(11f, 12f, 13f))) 68 | } 69 | } 70 | 71 | "Segments of a curve" should { 72 | "should have a correct size" { 73 | Curve(positions, textureCoordinates, normals).segments should haveSize(3) 74 | } 75 | "should have a correct values" { 76 | Curve(positions, textureCoordinates, normals).segments shouldBe listOf( 77 | Vertex(Point(1f, 2f, 3f), TextureCoordinates(1f, 2f), Vector(1f, 2f, 3f)) to 78 | Vertex(Point(4f, 5f, 6f), TextureCoordinates(3f, 4f), Vector(4f, 5f, 6f)), 79 | Vertex(Point(4f, 5f, 6f), TextureCoordinates(3f, 4f), Vector(4f, 5f, 6f)) to 80 | Vertex(Point(7f, 8f, 9f), TextureCoordinates(5f, 6f), Vector(7f, 8f, 9f)), 81 | Vertex(Point(7f, 8f, 9f), TextureCoordinates(5f, 6f), Vector(7f, 8f, 9f)) to 82 | Vertex(Point(11f, 12f, 13f), TextureCoordinates(11f, 12f), Vector(11f, 12f, 13f))) 83 | } 84 | } 85 | 86 | "Flat curve" should { 87 | "have the same Z coordinate for all points" { 88 | Curve(positions, textureCoordinates, normals).toFlatCurve(123.45f).positions.forEach { 89 | it.z shouldBe 123.45f 90 | } 91 | } 92 | "have X and Y coordinates the same as the original curve" { 93 | (Curve(positions, textureCoordinates, normals).toFlatCurve(123.45f).positions zip positions).forEach { 94 | it.first.x shouldBe it.second.x 95 | it.first.y shouldBe it.second.y 96 | } 97 | } 98 | "have normals the same as the original curve" { 99 | (Curve(positions, textureCoordinates, normals).toFlatCurve(123.45f).normals zip normals).forEach { 100 | it.first shouldBe it.second 101 | } 102 | } 103 | "have texture coordinates the same as the original curve" { 104 | (Curve(positions, textureCoordinates, normals).toFlatCurve(123.45f).textureCoordinates zip textureCoordinates).forEach { 105 | it.first shouldBe it.second 106 | } 107 | } 108 | } 109 | 110 | "Polygon bounded by a curve" should { 111 | "have this curve as its boundary" { 112 | val curve = Curve(positions, textureCoordinates, normals) 113 | curve.polygon { emptyList() }.curve shouldBe curve 114 | } 115 | "have given faces" { 116 | val curve = Curve(positions, textureCoordinates, normals) 117 | curve.polygon { listOf(Triple(0, 1, 2), Triple(2, 3, 0)) }.faces shouldBe listOf(Triple(0, 1, 2), Triple(2, 3, 0)) 118 | } 119 | } 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/models/MeshBuilderSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Point 4 | import glimpse.TextureCoordinates 5 | import glimpse.Vector 6 | import glimpse.test.GlimpseSpec 7 | 8 | class MeshBuilderSpec : GlimpseSpec() { 9 | 10 | init { 11 | 12 | "Empty mesh" should { 13 | "have no positions" { 14 | mesh { }.positions should beEmpty() 15 | } 16 | "have no texture coordinates" { 17 | mesh { }.textureCoordinates should beEmpty() 18 | } 19 | "have no normals" { 20 | mesh { }.normals should beEmpty() 21 | } 22 | "have no vertices" { 23 | mesh { }.vertices should beEmpty() 24 | } 25 | "have no faces" { 26 | mesh { }.faces should beEmpty() 27 | } 28 | } 29 | 30 | "A single triangle" should { 31 | val triangle = mesh { 32 | triangle( 33 | Vertex(Point(1f, 2f, 3f), TextureCoordinates(1f, 2f), Vector(1f, 2f, 3f)), 34 | Vertex(Point(4f, 5f, 6f), TextureCoordinates(3f, 4f), Vector(4f, 5f, 6f)), 35 | Vertex(Point(7f, 8f, 9f), TextureCoordinates(5f, 6f), Vector(7f, 8f, 9f))) 36 | } 37 | "have 3 positions" { 38 | triangle.positions should haveSize(3) 39 | } 40 | "have 3 texture coordinates" { 41 | triangle.textureCoordinates should haveSize(3) 42 | } 43 | "have 3 normals" { 44 | triangle.normals should haveSize(3) 45 | } 46 | "have 3 vertices" { 47 | triangle.vertices should haveSize(3) 48 | } 49 | "have 1 face" { 50 | triangle.faces should haveSize(1) 51 | } 52 | } 53 | 54 | "A single quadrilateral" should { 55 | val quad = mesh { 56 | quad( 57 | Vertex(Point(1f, 2f, 3f), TextureCoordinates(1f, 2f), Vector(1f, 2f, 3f)), 58 | Vertex(Point(4f, 5f, 6f), TextureCoordinates(3f, 4f), Vector(4f, 5f, 6f)), 59 | Vertex(Point(7f, 8f, 9f), TextureCoordinates(5f, 6f), Vector(7f, 8f, 9f)), 60 | Vertex(Point(10f, 11f, 12f), TextureCoordinates(7f, 8f), Vector(10f, 11f, 12f))) 61 | } 62 | "have 6 positions" { 63 | quad.positions should haveSize(6) 64 | } 65 | "have 6 texture coordinates" { 66 | quad.textureCoordinates should haveSize(6) 67 | } 68 | "have 6 normals" { 69 | quad.normals should haveSize(6) 70 | } 71 | "have 6 vertices" { 72 | quad.vertices should haveSize(6) 73 | } 74 | "have 2 faces" { 75 | quad.faces should haveSize(2) 76 | } 77 | "consist of correct faces" { 78 | quad.faces shouldBe listOf( 79 | Face( 80 | Vertex(Point(1f, 2f, 3f), TextureCoordinates(1f, 2f), Vector(1f, 2f, 3f)), 81 | Vertex(Point(4f, 5f, 6f), TextureCoordinates(3f, 4f), Vector(4f, 5f, 6f)), 82 | Vertex(Point(7f, 8f, 9f), TextureCoordinates(5f, 6f), Vector(7f, 8f, 9f))), 83 | Face( 84 | Vertex(Point(7f, 8f, 9f), TextureCoordinates(5f, 6f), Vector(7f, 8f, 9f)), 85 | Vertex(Point(10f, 11f, 12f), TextureCoordinates(7f, 8f), Vector(10f, 11f, 12f)), 86 | Vertex(Point(1f, 2f, 3f), TextureCoordinates(1f, 2f), Vector(1f, 2f, 3f)))) 87 | } 88 | } 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/models/MeshTransformationSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Angle 4 | import glimpse.Vector 5 | import glimpse.test.GlimpseSpec 6 | import glimpse.translationMatrix 7 | import io.kotlintest.properties.Gen 8 | 9 | class MeshTransformationSpec : GlimpseSpec() { 10 | 11 | init { 12 | 13 | "Mesh translation" should { 14 | "give results consistent with sum of vectors" { 15 | forAll(vectors, vectors) { vector, translation -> 16 | mesh { }.transform { 17 | translate(translation) 18 | }.transformation() * vector isRoughly vector + translation 19 | } 20 | } 21 | "change over time" { 22 | var translation = Vector.X_UNIT 23 | val model = mesh { }.transform { 24 | translate(translation) 25 | } 26 | model.transformation() * Vector.NULL shouldBeRoughly Vector.X_UNIT 27 | translation = Vector.Z_UNIT 28 | model.transformation() * Vector.NULL shouldBeRoughly Vector.Z_UNIT 29 | } 30 | } 31 | 32 | "Mesh translation matrix" should { 33 | "give results consistent with sum of vectors" { 34 | forAll(vectors, vectors) { vector, translation -> 35 | mesh { }.transform(translationMatrix(translation)).transformation() * vector isRoughly vector + translation 36 | } 37 | } 38 | } 39 | 40 | "Composition of translations" should { 41 | "be equivalent to a translation by the sum vectors" { 42 | forAll(Gen.list(vectors)) { list -> 43 | mesh { }.transform { 44 | list.forEach { vector -> 45 | translate(vector) 46 | } 47 | }.transformation() isRoughly mesh { }.transform { 48 | translate(list.reduce { sum, next -> sum + next }) 49 | }.transformation() 50 | } 51 | } 52 | } 53 | 54 | "Composition of a translation and a rotation" should { 55 | "first translate and then rotate" { 56 | mesh { }.transform { 57 | translate(Vector.X_UNIT) 58 | rotateZ(Angle.RIGHT) 59 | }.transformation() * Vector.X_UNIT shouldBeRoughly Vector.Y_UNIT * 2f 60 | } 61 | } 62 | 63 | "Composition of a rotation and a translation" should { 64 | "first rotate and then translate" { 65 | mesh { }.transform { 66 | rotateZ(Angle.RIGHT) 67 | translate(Vector.X_UNIT) 68 | }.transformation() * Vector.X_UNIT shouldBeRoughly Vector.Y_UNIT + Vector.X_UNIT 69 | } 70 | } 71 | 72 | "Rotation around X axis" should { 73 | "be consistent with rotation around a unit vector in the direction of the X axis" { 74 | forAll(angles) { angle -> 75 | mesh { }.transform { rotateX(angle) }.transformation() isRoughly mesh { }.transform { rotate(Vector.X_UNIT, angle) }.transformation() 76 | } 77 | } 78 | } 79 | 80 | "Rotation around Y axis" should { 81 | "be consistent with rotation around a unit vector in the direction of the Y axis" { 82 | forAll(angles) { angle -> 83 | mesh { }.transform { rotateY(angle) }.transformation() isRoughly mesh { }.transform { rotate(Vector.Y_UNIT, angle) }.transformation() 84 | } 85 | } 86 | } 87 | 88 | "Rotation around Z axis" should { 89 | "be consistent with rotation around a unit vector in the direction of the Z axis" { 90 | forAll(angles) { angle -> 91 | mesh { }.transform { rotateZ(angle) }.transformation() isRoughly mesh { }.transform { rotate(Vector.Z_UNIT, angle) }.transformation() 92 | } 93 | } 94 | } 95 | 96 | "Composition of a translation and a scaling" should { 97 | "first translate and then scale" { 98 | mesh { }.transform { 99 | translate(Vector.X_UNIT) 100 | scale(2f) 101 | }.transformation() * Vector.X_UNIT shouldBeRoughly Vector.X_UNIT * 4f 102 | } 103 | } 104 | 105 | "Composition of a scaling and a translation" should { 106 | "first scale and then translate" { 107 | mesh { }.transform { 108 | scale(2f) 109 | translate(Vector.X_UNIT) 110 | }.transformation() * Vector.X_UNIT shouldBeRoughly Vector.X_UNIT * 3f 111 | } 112 | } 113 | 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/models/ModelTransformationSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Angle 4 | import glimpse.Vector 5 | import glimpse.test.GlimpseSpec 6 | import glimpse.translationMatrix 7 | 8 | class ModelTransformationSpec : GlimpseSpec() { 9 | 10 | init { 11 | 12 | "Model translation" should { 13 | "give results consistent with sum of vectors" { 14 | forAll(vectors, vectors) { vector, translation -> 15 | Model(mesh { }).transform { 16 | translate(translation) 17 | }.transformation() * vector isRoughly vector + translation 18 | } 19 | forAll(vectors, vectors) { vector, translation -> 20 | Model(mesh { }).transform(translationMatrix(translation)).transformation() * vector isRoughly vector + translation 21 | } 22 | } 23 | "change over time" { 24 | var translation = Vector.X_UNIT 25 | val model = Model(mesh { }).transform { 26 | translate(translation) 27 | } 28 | model.transformation() * Vector.NULL shouldBeRoughly Vector.X_UNIT 29 | translation = Vector.Z_UNIT 30 | model.transformation() * Vector.NULL shouldBeRoughly Vector.Z_UNIT 31 | } 32 | "keep mesh transformation changes over time" { 33 | var translation = Vector.X_UNIT 34 | val model = mesh { 35 | }.transform { 36 | translate(translation) 37 | }.transform { 38 | translate(Vector.Y_UNIT) 39 | } 40 | model.transformation() * Vector.NULL shouldBeRoughly Vector(1f, 1f, 0f) 41 | translation = Vector.Z_UNIT 42 | model.transformation() * Vector.NULL shouldBeRoughly Vector(0f, 1f, 1f) 43 | } 44 | } 45 | 46 | "Composition of a translation and a rotation" should { 47 | "first translate and then rotate" { 48 | Model(mesh { }).transform { 49 | translate(Vector.X_UNIT) 50 | rotateZ(Angle.RIGHT) 51 | }.transformation() * Vector.X_UNIT shouldBeRoughly Vector.Y_UNIT * 2f 52 | } 53 | } 54 | 55 | "Composition of a rotation and a translation" should { 56 | "first rotate and then translate" { 57 | Model(mesh { }).transform { 58 | rotateZ(Angle.RIGHT) 59 | translate(Vector.X_UNIT) 60 | }.transformation() * Vector.X_UNIT shouldBeRoughly Vector.Y_UNIT + Vector.X_UNIT 61 | } 62 | } 63 | 64 | "Composition of a translation and a scaling" should { 65 | "first translate and then scale" { 66 | Model(mesh { }).transform { 67 | translate(Vector.X_UNIT) 68 | scale(2f) 69 | }.transformation() * Vector.X_UNIT shouldBeRoughly Vector.X_UNIT * 4f 70 | } 71 | } 72 | 73 | "Composition of a scaling and a translation" should { 74 | "first scale and then translate" { 75 | Model(mesh { }).transform { 76 | scale(2f) 77 | translate(Vector.X_UNIT) 78 | }.transformation() * Vector.X_UNIT shouldBeRoughly Vector.X_UNIT * 3f 79 | } 80 | } 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/models/ObjMeshBuilderSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Point 4 | import glimpse.TextureCoordinates 5 | import glimpse.Vector 6 | import glimpse.io.resource 7 | import glimpse.test.GlimpseSpec 8 | import io.kotlintest.matchers.be 9 | 10 | class ObjMeshBuilderSpec : GlimpseSpec() { 11 | 12 | init { 13 | 14 | "Single triangle built from OBJ file" should { 15 | "be a single instance of mesh" { 16 | val result = loadObjTriangle() 17 | result should haveSize(1) 18 | result[0] should be a Mesh::class 19 | } 20 | "have a single face" { 21 | val result = loadObjTriangle() 22 | result[0].faces should haveSize(1) 23 | } 24 | "have correct positions" { 25 | val result = loadObjTriangle() 26 | result[0].positions shouldBe listOf(Point(1f, 1f, 1f), Point(1f, 1f, -1f), Point(1f, -1f, 1f)) 27 | } 28 | "have correct texture coordinates" { 29 | val result = loadObjTriangle() 30 | result[0].textureCoordinates shouldBe listOf(TextureCoordinates.BOTTOM_LEFT, TextureCoordinates.BOTTOM_RIGHT, TextureCoordinates.TOP_LEFT) 31 | } 32 | "have correct normals" { 33 | val result = loadObjTriangle() 34 | result[0].normals shouldBe listOf(Vector.X_UNIT, Vector.Y_UNIT, Vector.Z_UNIT) 35 | } 36 | } 37 | 38 | "Single triangle with no textures built from OBJ file" should { 39 | "be a single instance of mesh" { 40 | val result = loadObjTriangleNoTextures() 41 | result should haveSize(1) 42 | result[0] should be a Mesh::class 43 | } 44 | "have a single face" { 45 | val result = loadObjTriangleNoTextures() 46 | result[0].faces should haveSize(1) 47 | } 48 | "have correct positions" { 49 | val result = loadObjTriangleNoTextures() 50 | result[0].positions shouldBe listOf(Point(1f, 1f, 1f), Point(1f, 1f, -1f), Point(1f, -1f, 1f)) 51 | } 52 | "always have (0, 0) texture coordinates" { 53 | val result = loadObjTriangleNoTextures() 54 | result[0].textureCoordinates shouldBe listOf(TextureCoordinates.BOTTOM_LEFT, TextureCoordinates.BOTTOM_LEFT, TextureCoordinates.BOTTOM_LEFT) 55 | } 56 | "have correct normals" { 57 | val result = loadObjTriangleNoTextures() 58 | result[0].normals shouldBe listOf(Vector.X_UNIT, Vector.Y_UNIT, Vector.Z_UNIT) 59 | } 60 | } 61 | 62 | "Two triangles built from OBJ file" should { 63 | "be two separate instances of mesh" { 64 | val result = loadObjTwoTriangles() 65 | result should haveSize(2) 66 | result[0] should be a Mesh::class 67 | result[1] should be a Mesh::class 68 | } 69 | "both have a single face" { 70 | val result = loadObjTwoTriangles() 71 | result[0].faces should haveSize(1) 72 | result[1].faces should haveSize(1) 73 | } 74 | "both have correct positions" { 75 | val result = loadObjTwoTriangles() 76 | result[0].positions shouldBe listOf(Point(1f, 1f, 1f), Point(1f, 1f, -1f), Point(1f, -1f, 1f)) 77 | result[1].positions shouldBe listOf(Point(-1f, 1f, 1f), Point(-1f, 1f, -1f), Point(-1f, -1f, 1f)) 78 | } 79 | "both have correct texture coordinates" { 80 | val result = loadObjTwoTriangles() 81 | result[0].textureCoordinates shouldBe listOf(TextureCoordinates.BOTTOM_LEFT, TextureCoordinates.BOTTOM_RIGHT, TextureCoordinates.TOP_LEFT) 82 | result[1].textureCoordinates shouldBe listOf(TextureCoordinates.BOTTOM_RIGHT, TextureCoordinates.BOTTOM_LEFT, TextureCoordinates.TOP_RIGHT) 83 | } 84 | "both have correct normals" { 85 | val result = loadObjTwoTriangles() 86 | result[0].normals shouldBe listOf(Vector.X_UNIT, Vector.Y_UNIT, Vector.Z_UNIT) 87 | result[1].normals shouldBe listOf(Vector(-1f, 0f, 0f), Vector(0f, -1f, 0f), Vector(0f, 0f, -1f)) 88 | } 89 | } 90 | 91 | } 92 | 93 | private fun loadObjTriangle() = resource("triangle.obj").loadObjMeshes() 94 | private fun loadObjTriangleNoTextures() = resource("triangle_no_textures.obj").loadObjMeshes() 95 | private fun loadObjTwoTriangles() = resource("two_triangles.obj").loadObjMeshes() 96 | } 97 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/models/PlatonicSolidsSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.test.GlimpseSpec 4 | 5 | class PlatonicSolidsSpec : GlimpseSpec() { 6 | 7 | init { 8 | 9 | "Tetrahedron" should { 10 | "have 4 faces" { 11 | tetrahedron().faces should haveSize(4) 12 | } 13 | } 14 | 15 | "Cube" should { 16 | "have 12 faces" { 17 | cube().faces should haveSize(12) 18 | } 19 | } 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/models/PrismSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Point 4 | import glimpse.TextureCoordinates 5 | import glimpse.Vector 6 | import glimpse.test.GlimpseSpec 7 | 8 | class PrismSpec : GlimpseSpec() { 9 | 10 | init { 11 | 12 | "Prism" should { 13 | "have correct number of side faces" { 14 | prism { 15 | curve { 16 | segment( 17 | Vertex(Point(1f, 2f), TextureCoordinates(1f, 2f), Vector(1f, 2f, 3f)), 18 | Vertex(Point(3f, 4f), TextureCoordinates(3f, 4f), Vector(4f, 5f, 6f))) 19 | }.polygon { emptyList() } 20 | }.faces should haveSize(2) 21 | } 22 | "have correct number of all faces" { 23 | prism { 24 | curve { 25 | segment( 26 | Vertex(Point(1f, 2f), TextureCoordinates(1f, 2f), Vector(1f, 2f, 3f)), 27 | Vertex(Point(3f, 4f), TextureCoordinates(3f, 4f), Vector(4f, 5f, 6f))) 28 | segment( 29 | Vertex(Point(3f, 4f), TextureCoordinates(3f, 4f), Vector(4f, 5f, 6f)), 30 | Vertex(Point(5f, 6f), TextureCoordinates(5f, 6f), Vector(7f, 8f, 9f))) 31 | segment( 32 | Vertex(Point(5f, 6f), TextureCoordinates(5f, 6f), Vector(7f, 8f, 9f)), 33 | Vertex(Point(1f, 2f), TextureCoordinates(1f, 2f), Vector(1f, 2f, 3f))) 34 | }.polygon { listOf(Triple(0, 2, 4)) } 35 | }.faces should haveSize(8) 36 | } 37 | } 38 | 39 | } 40 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/models/SphereSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.models 2 | 3 | import glimpse.Vector 4 | import glimpse.test.GlimpseSpec 5 | import io.kotlintest.matchers.be 6 | 7 | class SphereSpec : GlimpseSpec() { 8 | 9 | init { 10 | 11 | "Sphere texture coordinates" should{ 12 | "be between 0 and 1" { 13 | sphere(32).textureCoordinates.forEach { 14 | it.u should be inRange 0f..1f 15 | (it.u >= 0f) shouldBe true 16 | (it.u <= 1f) shouldBe true 17 | (it.v >= 0f) shouldBe true 18 | (it.v <= 1f) shouldBe true 19 | } 20 | } 21 | } 22 | 23 | "Sphere normals" should { 24 | "be normalized" { 25 | sphere(32).normals.forEach { 26 | it.magnitude shouldBeRoughly 1f 27 | } 28 | } 29 | "be parallel to position vectors" { 30 | val mesh = sphere(32) 31 | (mesh.positions zip mesh.normals).forEach { 32 | val (position, normal) = it 33 | normal * position.toVector() shouldBeRoughly Vector.NULL 34 | } 35 | } 36 | "not be opposite to position vectors" { 37 | val mesh = sphere(32) 38 | (mesh.positions zip mesh.normals).forEach { 39 | val (position, normal) = it 40 | (normal.normalize dot position.toVector() >= 0f) shouldBe true 41 | } 42 | } 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/shaders/ProgramBuilderSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.shaders 2 | 3 | import com.nhaarman.mockito_kotlin.* 4 | import glimpse.gles.GLES 5 | import glimpse.test.GlimpseSpec 6 | 7 | class ProgramBuilderSpec : GlimpseSpec() { 8 | 9 | init { 10 | 11 | "Program builder" should { 12 | "create both shaders" { 13 | val glesMock = glesMock() 14 | buildShaderProgram() 15 | verify(glesMock).createShader(ShaderType.VERTEX) 16 | verify(glesMock).createShader(ShaderType.FRAGMENT) 17 | } 18 | "compile both shaders" { 19 | val glesMock = glesMock() 20 | buildShaderProgram() 21 | verify(glesMock).compileShader(ShaderHandle(1), "vertex shader code") 22 | verify(glesMock).compileShader(ShaderHandle(2), "fragment shader code") 23 | } 24 | "cause exception if compilation fails" { 25 | glesMock { 26 | on { createShader(any()) } doReturn ShaderHandle(1) doReturn ShaderHandle(2) 27 | on { getShaderCompileStatus(ShaderHandle(1)) } doReturn true 28 | on { getShaderCompileStatus(ShaderHandle(2)) } doReturn false 29 | on { getShaderLog(ShaderHandle(2)) } doReturn "Error" 30 | } 31 | shouldThrow { 32 | buildShaderProgram() 33 | } 34 | } 35 | "create a program" { 36 | val glesMock = glesMock() 37 | buildShaderProgram() 38 | verify(glesMock).createProgram() 39 | } 40 | "attach both shaders to program" { 41 | val glesMock = glesMock() 42 | buildShaderProgram() 43 | verify(glesMock).attachShader(ProgramHandle(3), ShaderHandle(1)) 44 | verify(glesMock).attachShader(ProgramHandle(3), ShaderHandle(2)) 45 | } 46 | "link a program" { 47 | val glesMock = glesMock() 48 | buildShaderProgram() 49 | verify(glesMock).linkProgram(ProgramHandle(3)) 50 | } 51 | "return a program with correct data" { 52 | val glesMock = glesMock() 53 | val program = buildShaderProgram() 54 | program.gles shouldBe glesMock 55 | program.handle shouldBe ProgramHandle(3) 56 | program.shaders should haveSize(2) 57 | program.shaders.map { it.gles }.filter { it != glesMock } should beEmpty() 58 | program.shaders.map { it.type } should containInAnyOrder(*ShaderType.values()) 59 | program.shaders.map { it.handle } should containInAnyOrder(ShaderHandle(1), ShaderHandle(2)) 60 | } 61 | "cause exception if linking fails" { 62 | glesMock { 63 | on { createShader(any()) } doReturn ShaderHandle(1) doReturn ShaderHandle(2) 64 | on { getShaderCompileStatus(ShaderHandle(1)) } doReturn true 65 | on { getShaderCompileStatus(ShaderHandle(2)) } doReturn true 66 | on { createProgram() } doReturn ProgramHandle(3) 67 | on { getProgramLinkStatus(ProgramHandle(3)) } doReturn false 68 | on { getProgramLog(ProgramHandle(3)) } doReturn "Error" 69 | } 70 | shouldThrow { 71 | buildShaderProgram() 72 | } 73 | } 74 | } 75 | 76 | } 77 | 78 | private fun glesMock(): GLES = glesMock { 79 | on { createShader(any()) } doReturn ShaderHandle(1) doReturn ShaderHandle(2) 80 | on { getShaderCompileStatus(ShaderHandle(1)) } doReturn true 81 | on { getShaderCompileStatus(ShaderHandle(2)) } doReturn true 82 | on { createProgram() } doReturn ProgramHandle(3) 83 | on { getProgramLinkStatus(ProgramHandle(3)) } doReturn true 84 | } 85 | 86 | private fun buildShaderProgram(): Program = shaderProgram { 87 | vertexShader { 88 | "vertex shader code" 89 | } 90 | fragmentShader { 91 | "fragment shader code" 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/shaders/ProgramSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.shaders 2 | 3 | import com.nhaarman.mockito_kotlin.verify 4 | import glimpse.gles.GLES 5 | import glimpse.test.GlimpseSpec 6 | 7 | class ProgramSpec : GlimpseSpec() { 8 | 9 | init { 10 | 11 | "Using a program" should { 12 | "succeed if program is not deleted" { 13 | val glesMock = glesMock() 14 | val program = Program(ProgramHandle(3), createShaders()) 15 | program.use() 16 | verify(glesMock).useProgram(ProgramHandle(3)) 17 | } 18 | "cause an exception if program is deleted" { 19 | glesMock() 20 | val program = Program(ProgramHandle(3), createShaders()) 21 | program.deleted = true 22 | shouldThrow { 23 | program.use() 24 | } 25 | } 26 | "cause an exception if any shader is deleted" { 27 | glesMock() 28 | val program = Program(ProgramHandle(3), createShaders()) 29 | program.shaders[0].deleted = true 30 | shouldThrow { 31 | program.use() 32 | } 33 | } 34 | } 35 | 36 | "Deleting a program" should { 37 | "succeed" { 38 | val glesMock = glesMock() 39 | val program = Program(ProgramHandle(3), createShaders()) 40 | program.delete() 41 | verify(glesMock).deleteProgram(ProgramHandle(3)) 42 | program.deleted shouldBe true 43 | } 44 | "cause deleting both shaders" { 45 | val glesMock = glesMock() 46 | val program = Program(ProgramHandle(3), createShaders()) 47 | program.delete() 48 | verify(glesMock).deleteShader(ShaderHandle(1)) 49 | verify(glesMock).deleteShader(ShaderHandle(2)) 50 | forAll(program.shaders) { program -> 51 | program.deleted shouldBe true 52 | } 53 | } 54 | } 55 | 56 | } 57 | 58 | private fun glesMock(): GLES = glesMock {} 59 | 60 | private fun createShaders() = listOf( 61 | Shader(ShaderType.VERTEX, ShaderHandle(1)), 62 | Shader(ShaderType.FRAGMENT, ShaderHandle(2))) 63 | } 64 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/test/FloatMatchers.kt: -------------------------------------------------------------------------------- 1 | package glimpse.test 2 | 3 | import io.kotlintest.matchers.Matcher 4 | 5 | interface FloatMatchers { 6 | infix fun Float.plusOrMinus(tolerance: Float): ToleranceMatcher = ToleranceMatcher(this, tolerance) 7 | 8 | fun exactly(d: Float): Matcher = object : Matcher { 9 | override fun test(value: Float) { 10 | if (value != d) 11 | throw AssertionError("$value is not equal to expected value $d") 12 | } 13 | } 14 | } 15 | 16 | class ToleranceMatcher(val expected: Float, val tolerance: Float) : Matcher { 17 | 18 | override fun test(value: Float) { 19 | if (tolerance == 0.0f) 20 | println("[WARN] When comparing floats consider using tolerance, eg: a shouldBe b plusOrMinus c") 21 | val diff = Math.abs(value - expected) 22 | if (diff > tolerance) 23 | throw AssertionError("$value is not equal to $expected") 24 | } 25 | 26 | infix fun plusOrMinus(tolerance: Float): ToleranceMatcher = ToleranceMatcher(expected, tolerance) 27 | } 28 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/test/GlimpseSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.test 2 | 3 | import com.nhaarman.mockito_kotlin.KStubbing 4 | import com.nhaarman.mockito_kotlin.mock 5 | import glimpse.Angle 6 | import glimpse.Matrix 7 | import glimpse.Point 8 | import glimpse.Vector 9 | import glimpse.cameras.Camera 10 | import glimpse.gles.GLES 11 | import glimpse.gles.delegates.GLESDelegate 12 | import io.kotlintest.matchers.BeWrapper 13 | import io.kotlintest.properties.Gen 14 | import io.kotlintest.specs.WordSpec 15 | 16 | abstract class GlimpseSpec : WordSpec(), FloatMatchers { 17 | 18 | protected companion object { 19 | 20 | val delta = 0.01f 21 | 22 | val integers = Gen.choose(-1000, 1000) 23 | val positiveIntegers = Gen.choose(1, 1000) 24 | val negativeIntegers = Gen.choose(-1000, -1) 25 | val floats = Gen.float() 26 | val positiveFloats = Gen.chooseFloat(1, 100) 27 | val bigFloats = Gen.chooseFloat(-100, 100) 28 | val angles = Gen.angle(Gen.chooseFloat(0, 360)) 29 | val points = Gen.point(bigFloats) 30 | val vectors = Gen.vector(bigFloats) 31 | val matrices = Gen.matrix(Gen.chooseFloat(1, 100)) 32 | } 33 | 34 | protected infix fun Float.isRoughly(other: Float) = 35 | Math.abs(other - this) < Math.max(delta, this * delta) 36 | 37 | protected infix fun Float.shouldBeRoughly(other: Float) { 38 | this isRoughly other shouldBe true 39 | } 40 | 41 | protected infix fun Angle.isRoughly(other: Angle) = 42 | deg isRoughly other.deg && rad isRoughly other.rad 43 | 44 | protected infix fun Angle.shouldBeRoughly(other: Angle) { 45 | this isRoughly other shouldBe true 46 | } 47 | 48 | protected infix fun Point.isRoughly(other: Point) = 49 | _3f.zip(other._3f).all { it.first isRoughly it.second } 50 | 51 | protected infix fun Point.shouldBeRoughly(other: Point) { 52 | this isRoughly other shouldBe true 53 | } 54 | 55 | protected infix fun Vector.isRoughly(other: Vector) = 56 | _3f.zip(other._3f).all { it.first isRoughly it.second } 57 | 58 | protected infix fun Vector.shouldBeRoughly(other: Vector) { 59 | this isRoughly other shouldBe true 60 | } 61 | 62 | protected infix fun Matrix.isRoughly(other: Matrix) = 63 | (0..3).all { row -> 64 | (0..3).all { col -> 65 | this[row, col] isRoughly other[row, col] 66 | } 67 | } 68 | 69 | protected infix fun Matrix.shouldBeRoughly(other: Matrix) { 70 | this isRoughly other shouldBe true 71 | } 72 | 73 | protected infix fun > BeWrapper.inRange(range: ClosedRange): Boolean = range.contains(value) 74 | 75 | protected fun glesMock(stubbing: KStubbing.() -> Unit): GLES { 76 | val glesMock: GLES = mock(stubbing) 77 | GLESDelegate(glesMock) 78 | return glesMock 79 | } 80 | 81 | protected infix fun Point.isVisibleIn(camera: Camera): Boolean = 82 | isVisibleIn(camera.cameraMatrix) 83 | 84 | protected infix fun Vector.isVisibleIn(camera: Camera): Boolean = 85 | isVisibleIn(camera.cameraMatrix) 86 | 87 | protected infix fun Point.isNotVisibleIn(camera: Camera): Boolean = 88 | isNotVisibleIn(camera.cameraMatrix) 89 | 90 | protected infix fun Vector.isNotVisibleIn(camera: Camera): Boolean = 91 | isNotVisibleIn(camera.cameraMatrix) 92 | 93 | protected infix fun Point.isVisibleIn(mvpMatrix: Matrix): Boolean = 94 | (mvpMatrix * this)._3f.all { it in -1f..1f} 95 | 96 | protected infix fun Vector.isVisibleIn(mvpMatrix: Matrix): Boolean = 97 | (mvpMatrix * this)._3f.all { it in -1f..1f} 98 | 99 | protected infix fun Point.isNotVisibleIn(mvpMatrix: Matrix): Boolean = 100 | (mvpMatrix * this)._3f.any { it !in -1f..1f} 101 | 102 | protected infix fun Vector.isNotVisibleIn(mvpMatrix: Matrix): Boolean = 103 | (mvpMatrix * this)._3f.any { it !in -1f..1f} 104 | } 105 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/test/_Gen.kt: -------------------------------------------------------------------------------- 1 | package glimpse.test 2 | 3 | import glimpse.* 4 | import io.kotlintest.properties.Gen 5 | 6 | fun Gen.Companion.chooseFloat(min: Int, max: Int) = object : Gen { 7 | val intGen = choose(min, max) 8 | override fun generate(): Float = intGen.generate().toFloat() 9 | } 10 | 11 | fun Gen.Companion.angle(floatGen: Gen) = object : Gen { 12 | override fun generate(): Angle = floatGen.generate().degrees 13 | } 14 | 15 | fun Gen.Companion.point(floatGen: Gen) = object : Gen { 16 | override fun generate(): Point = Point(floatGen.generate(), floatGen.generate(), floatGen.generate()) 17 | } 18 | 19 | fun Gen.Companion.vector(floatGen: Gen) = object : Gen { 20 | override fun generate(): Vector = Vector(floatGen.generate(), floatGen.generate(), floatGen.generate()) 21 | } 22 | 23 | fun Gen.Companion.matrix(floatGen: Gen) = object : Gen { 24 | override fun generate(): Matrix = Matrix((0..15).map { floatGen.generate() }.toList()) 25 | } 26 | -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/textures/TextureBuilderSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.textures 2 | 3 | import com.nhaarman.mockito_kotlin.* 4 | import glimpse.gles.GLES 5 | import glimpse.io.resource 6 | import glimpse.test.GlimpseSpec 7 | import java.io.InputStream 8 | 9 | class TextureBuilderSpec : GlimpseSpec() { 10 | 11 | init { 12 | 13 | "Texture builder" should { 14 | "create a texture in GLES implementation" { 15 | val glesMock = glesMock() 16 | val inputStreamMock = createInputStreamMock() 17 | val builder = TextureBuilder() 18 | builder.build(inputStreamMock) 19 | verify(glesMock).generateTexture() 20 | verify(glesMock).bindTexture2D(TextureHandle(1)) 21 | verify(glesMock).textureImage2D(inputStreamMock, "", false) 22 | } 23 | "create a texture object" { 24 | val glesMock = glesMock() 25 | val inputStreamMock = createInputStreamMock() 26 | val builder = TextureBuilder() 27 | val texture = builder.build(inputStreamMock) 28 | texture.deleted shouldBe false 29 | texture.gles shouldBe glesMock 30 | texture.handle shouldBe TextureHandle(1) 31 | } 32 | } 33 | 34 | "Texture builder function for input stream" should { 35 | "create a texture with mipmap" { 36 | val glesMock = glesMock() 37 | createInputStreamMock().loadTexture { 38 | name = "mipmapped.png" with mipmap 39 | } 40 | verify(glesMock).generateTexture() 41 | verify(glesMock).bindTexture2D(TextureHandle(1)) 42 | verify(glesMock).textureImage2D(any(), eq("mipmapped.png"), eq(true)) 43 | } 44 | "create a texture without mipmap" { 45 | val glesMock = glesMock() 46 | createInputStreamMock().loadTexture { 47 | name = "linear.png" without mipmap 48 | } 49 | verify(glesMock).generateTexture() 50 | verify(glesMock).bindTexture2D(TextureHandle(1)) 51 | verify(glesMock).textureImage2D(any(), eq("linear.png"), eq(false)) 52 | } 53 | "create a texture object" { 54 | val glesMock = glesMock() 55 | val texture = createInputStreamMock().loadTexture { 56 | name = "linear.png" without mipmap 57 | } 58 | texture.deleted shouldBe false 59 | texture.gles shouldBe glesMock 60 | texture.handle shouldBe TextureHandle(1) 61 | } 62 | } 63 | 64 | "Texture builder function for resource" should { 65 | "create a texture" { 66 | val glesMock = glesMock() 67 | resource("empty_file.png").loadTexture() 68 | verify(glesMock).generateTexture() 69 | verify(glesMock).bindTexture2D(TextureHandle(1)) 70 | verify(glesMock).textureImage2D(any(), eq("empty_file.png"), any()) 71 | } 72 | } 73 | 74 | } 75 | 76 | private fun createInputStreamMock(): InputStream = mock() 77 | 78 | private fun glesMock(): GLES = glesMock { 79 | on { generateTexture() } doReturn TextureHandle(1) 80 | } 81 | } -------------------------------------------------------------------------------- /api/src/test/kotlin/glimpse/textures/TextureSpec.kt: -------------------------------------------------------------------------------- 1 | package glimpse.textures 2 | 3 | import com.nhaarman.mockito_kotlin.verify 4 | import glimpse.gles.GLES 5 | import glimpse.gles.UniformLocation 6 | import glimpse.test.GlimpseSpec 7 | 8 | class TextureSpec : GlimpseSpec() { 9 | 10 | init { 11 | 12 | "Applying a texture" should { 13 | "succeed if texture is not deleted" { 14 | val glesMock = glesMock() 15 | val texture = Texture(TextureHandle(1)) 16 | texture.apply(UniformLocation(10), 5) 17 | verify(glesMock).activeTexture(5) 18 | verify(glesMock).bindTexture2D(TextureHandle(1)) 19 | verify(glesMock).uniformInt(UniformLocation(10), 5) 20 | } 21 | "cause an exception if texture is deleted" { 22 | glesMock() 23 | val texture = Texture(TextureHandle(1)) 24 | texture.deleted = true 25 | shouldThrow { 26 | texture.apply(UniformLocation(10), 5) 27 | } 28 | } 29 | } 30 | 31 | "Deleting a texture" should { 32 | "succeed" { 33 | glesMock() 34 | val texture = Texture(TextureHandle(1)) 35 | texture.delete() 36 | texture.deleted shouldBe true 37 | } 38 | "delete texture in GLES implementation" { 39 | val glesMock = glesMock() 40 | val texture = Texture(TextureHandle(1)) 41 | texture.delete() 42 | verify(glesMock).deleteTexture(TextureHandle(1)) 43 | } 44 | } 45 | 46 | } 47 | 48 | private fun glesMock(): GLES = glesMock {} 49 | } -------------------------------------------------------------------------------- /api/src/test/resources/glimpse/io/lines.txt: -------------------------------------------------------------------------------- 1 | a file 2 | with 3 | a few 4 | lines -------------------------------------------------------------------------------- /api/src/test/resources/glimpse/io/sample.properties: -------------------------------------------------------------------------------- 1 | FIRST_PROPERTY=first 2 | SECOND_PROPERTY=second 3 | # COMMENTED_PROPERTY=should not be loaded 4 | -------------------------------------------------------------------------------- /api/src/test/resources/glimpse/models/triangle.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.69 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | o Triangle 4 | v 1.000000 1.000000 1.000000 5 | v 1.000000 1.000000 -1.000000 6 | v 1.000000 -1.000000 1.000000 7 | vt 0.000000 0.000000 8 | vt 0.000000 1.000000 9 | vt 1.000000 0.000000 10 | vn 0.000000 0.000000 1.000000 11 | vn 0.000000 1.000000 0.000000 12 | vn 1.000000 0.000000 0.000000 13 | f 1/1/3 2/3/2 3/2/1 -------------------------------------------------------------------------------- /api/src/test/resources/glimpse/models/triangle_no_textures.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.69 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | o Triangle.no.textures 4 | v 1.000000 1.000000 1.000000 5 | v 1.000000 1.000000 -1.000000 6 | v 1.000000 -1.000000 1.000000 7 | vn 0.000000 0.000000 1.000000 8 | vn 0.000000 1.000000 0.000000 9 | vn 1.000000 0.000000 0.000000 10 | f 1//3 2//2 3//1 -------------------------------------------------------------------------------- /api/src/test/resources/glimpse/models/two_triangles.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.69 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | o Triangle.001 4 | v 1.000000 1.000000 1.000000 5 | v 1.000000 1.000000 -1.000000 6 | v 1.000000 -1.000000 1.000000 7 | vt 0.000000 0.000000 8 | vt 0.000000 1.000000 9 | vt 1.000000 0.000000 10 | vn 0.000000 0.000000 1.000000 11 | vn 0.000000 1.000000 0.000000 12 | vn 1.000000 0.000000 0.000000 13 | f 1/1/3 2/3/2 3/2/1 14 | o Triangle.002 15 | v -1.000000 1.000000 1.000000 16 | v -1.000000 1.000000 -1.000000 17 | v -1.000000 -1.000000 1.000000 18 | vt 1.000000 0.000000 19 | vt 1.000000 1.000000 20 | vt 0.000000 0.000000 21 | vn 0.000000 0.000000 -1.000000 22 | vn 0.000000 -1.000000 0.000000 23 | vn -1.000000 0.000000 0.000000 24 | f 4/4/6 5/6/5 6/5/4 -------------------------------------------------------------------------------- /api/src/test/resources/glimpse/textures/empty_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glimpse-graphics/glimpse-framework/5069cc1c3f2d8cb344740f3d33bcf11848f94583/api/src/test/resources/glimpse/textures/empty_file.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.0.4' 3 | ext.kotlintest_version = '1.3.4' 4 | ext.mockito_kotlin_version = '0.6.2' 5 | ext.dokka_version = '0.9.9' 6 | ext.jogl_version = '2.3.2' 7 | ext.launch4j_version = '1.6.2' 8 | 9 | repositories { 10 | jcenter() 11 | mavenCentral() 12 | maven { 13 | url "https://plugins.gradle.org/m2/" 14 | } 15 | } 16 | 17 | dependencies { 18 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}" 19 | classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" 20 | classpath "gradle.plugin.edu.sc.seis.gradle:launch4j:${launch4j_version}" 21 | } 22 | } 23 | 24 | allprojects { 25 | apply plugin: 'maven' 26 | 27 | group = 'org.glimpseframework' 28 | version = '0.5-SNAPSHOT' 29 | } 30 | 31 | subprojects { 32 | apply plugin: 'java' 33 | apply plugin: 'kotlin' 34 | apply plugin: 'org.jetbrains.dokka' 35 | apply plugin: 'jacoco' 36 | 37 | sourceCompatibility = 1.6 38 | targetCompatibility = 1.6 39 | 40 | repositories { 41 | jcenter() 42 | } 43 | 44 | test { 45 | testLogging { 46 | events "passed", "skipped", "failed" 47 | showExceptions true 48 | exceptionFormat "full" 49 | showCauses true 50 | showStackTraces true 51 | 52 | afterSuite { desc, result -> 53 | def output = "results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)" 54 | def repeatLength = output.length() 55 | println('\n' + ('-' * repeatLength) + '\n' + desc.name + '\n' + output + '\n' + ('-' * repeatLength)) 56 | } 57 | } 58 | } 59 | 60 | jacocoTestReport { 61 | reports { 62 | xml.enabled = true 63 | html.enabled = true 64 | } 65 | } 66 | 67 | check.dependsOn jacocoTestReport 68 | } 69 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | branch: master 3 | 4 | coverage: 5 | precision: 1 6 | range: "50...100" 7 | status: 8 | project: 9 | default: 10 | target: '85' 11 | patch: 12 | default: 13 | target: '75' 14 | 15 | comment: 16 | layout: header, changes, diff 17 | 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glimpse-graphics/glimpse-framework/5069cc1c3f2d8cb344740f3d33bcf11848f94583/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Apr 07 18:55:51 CEST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.0-all.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /jogl/build.gradle: -------------------------------------------------------------------------------- 1 | description = 'Glimpse Framework JOGL Implementation' 2 | 3 | dependencies { 4 | compile project(":glimpse-framework-api") 5 | 6 | compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" 7 | 8 | compile "org.jogamp.jogl:jogl-all-main:${jogl_version}" 9 | compile "org.jogamp.gluegen:gluegen-rt-main:${jogl_version}" 10 | 11 | testCompile "io.kotlintest:kotlintest:${kotlintest_version}" 12 | } 13 | 14 | dokka { 15 | moduleName = 'Glimpse Framework JOGL Implementation' 16 | outputFormat = 'javadoc' 17 | } 18 | 19 | task sourcesJar(type: Jar, dependsOn: project.classes) { 20 | classifier = 'sources' 21 | from sourceSets.main.allSource 22 | } 23 | 24 | task dokkaJar(type: Jar, dependsOn: project.dokka) { 25 | classifier = 'javadoc' 26 | from dokka 27 | } 28 | 29 | artifacts { 30 | archives sourcesJar 31 | archives dokkaJar 32 | } 33 | -------------------------------------------------------------------------------- /jogl/src/main/kotlin/glimpse/jogl/Frames.kt: -------------------------------------------------------------------------------- 1 | package glimpse.jogl 2 | 3 | import java.awt.event.WindowAdapter 4 | import java.awt.event.WindowEvent 5 | import javax.swing.JFrame 6 | 7 | /** 8 | * Builds a [GlimpseFrame] initialized with an [init] function. 9 | */ 10 | fun glimpseFrame(title: String, width: Int = 640, height: Int = 480, fps: Int = 30, init: GlimpseFrame.() -> Unit) { 11 | val frame = GlimpseFrame(title, width, height, fps) 12 | frame.init() 13 | frame.start() 14 | } 15 | 16 | /** 17 | * Sets action to be executed when the frame is being closed. 18 | */ 19 | fun JFrame.onClose(callback: () -> Unit) { 20 | addWindowListener(OnCloseWindowAdapter(callback)) 21 | } 22 | 23 | private class OnCloseWindowAdapter(val callback: () -> Unit) : WindowAdapter() { 24 | override fun windowClosing(event: WindowEvent?) { 25 | callback() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jogl/src/main/kotlin/glimpse/jogl/GlimpseFrame.kt: -------------------------------------------------------------------------------- 1 | package glimpse.jogl 2 | 3 | import com.jogamp.opengl.GLAutoDrawable 4 | import com.jogamp.opengl.GLEventListener 5 | import com.jogamp.opengl.awt.GLJPanel 6 | import com.jogamp.opengl.util.FPSAnimator 7 | import glimpse.gles.Disposables 8 | import glimpse.gles.GLES 9 | import glimpse.gles.Viewport 10 | import glimpse.gles.delegates.GLESDelegate 11 | import java.util.concurrent.BlockingQueue 12 | import javax.swing.JFrame 13 | 14 | /** 15 | * Glimpse Framework [JFrame] implementation. 16 | * 17 | * @param title Frame title. 18 | * @param width Frame initial width. 19 | * @param height Frame initial height. 20 | * @param fps Frames per second animation rate. 21 | */ 22 | class GlimpseFrame(title: String = "", width: Int = 640, height: Int = 480, fps: Int = 30) : JFrame(title) { 23 | 24 | private val gles: GLES by GLESDelegate 25 | 26 | private var init: GLES.() -> Unit = {} 27 | private var reshape: GLES.(viewport: Viewport) -> Unit = {} 28 | private var display: GLES.() -> Unit = {} 29 | private var dispose: GLES.() -> Unit = {} 30 | 31 | private val canvas = GLJPanel() 32 | private val animator = FPSAnimator(canvas, fps) 33 | 34 | private val eventListener = EventListener() 35 | 36 | private val actions: BlockingQueue Unit> = 37 | java.util.concurrent.LinkedBlockingQueue() 38 | 39 | init { 40 | contentPane.add(canvas) 41 | canvas.addGLEventListener(eventListener) 42 | setSize(width, height) 43 | setLocationRelativeTo(null) 44 | onClose { 45 | animator.stop() 46 | dispose() 47 | System.exit(0) 48 | } 49 | } 50 | 51 | /** 52 | * Starts GL animation. 53 | */ 54 | fun start() { 55 | isVisible = true 56 | animator.start() 57 | } 58 | 59 | /** 60 | * GL initialization lambda. 61 | */ 62 | fun onInit(init: GLES.() -> Unit) { 63 | this.init = init 64 | } 65 | 66 | /** 67 | * GL resize lambda. 68 | */ 69 | fun onResize(reshape: GLES.(Viewport) -> Unit) { 70 | this.reshape = reshape 71 | } 72 | 73 | /** 74 | * GL rendering lambda. 75 | */ 76 | fun onRender(display: GLES.() -> Unit) { 77 | this.display = display 78 | } 79 | 80 | /** 81 | * GL dispose lambda. 82 | */ 83 | fun onDispose(dispose: GLES.() -> Unit) { 84 | this.dispose = dispose 85 | } 86 | 87 | /** 88 | * Enqueues an [action] to be run in GLES context. 89 | */ 90 | fun runInGLESContext(action: GLES.() -> Unit) { 91 | actions.put(action) 92 | } 93 | 94 | private inner class EventListener : GLEventListener { 95 | 96 | override fun init(drawable: GLAutoDrawable?) { 97 | require(drawable!!.gl.isGL2ES2) { "OpenGL does not conform to GL2ES2 profile." } 98 | GLESDelegate(glimpse.jogl.gles.GLES(drawable.gl.gL2ES2)) 99 | gles.init() 100 | runActions() 101 | } 102 | 103 | override fun reshape(drawable: GLAutoDrawable?, x: Int, y: Int, width: Int, height: Int) { 104 | gles.reshape(Viewport(width, height)) 105 | } 106 | 107 | override fun display(drawable: GLAutoDrawable?) { 108 | runActions() 109 | gles.display() 110 | } 111 | 112 | override fun dispose(drawable: GLAutoDrawable?) { 113 | gles.dispose() 114 | Disposables.disposeAll() 115 | } 116 | 117 | private fun runActions() { 118 | while (!actions.isEmpty()) { 119 | actions.poll().invoke(gles) 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /jogl/src/main/kotlin/glimpse/jogl/Menus.kt: -------------------------------------------------------------------------------- 1 | package glimpse.jogl 2 | 3 | import java.awt.event.ActionEvent 4 | import javax.swing.* 5 | 6 | /** 7 | * Builds a [JMenuBar] initialized with an [init] function. 8 | */ 9 | fun JFrame.menuBar(init: JMenuBar.() -> Unit) { 10 | val menuBar = JMenuBar() 11 | menuBar.init() 12 | setJMenuBar(menuBar) 13 | } 14 | 15 | /** 16 | * Builds a [JMenu] initialized with an [init] function. 17 | */ 18 | fun JMenuBar.menu(caption: String, init: JMenu.() -> Unit) { 19 | val menu = JMenu(caption) 20 | menu.init() 21 | add(menu) 22 | } 23 | 24 | /** 25 | * Builds a [JMenu] initialized with an [init] function. 26 | */ 27 | fun JMenu.menu(caption: String, init: JMenu.() -> Unit) { 28 | val menu = JMenu(caption) 29 | menu.init() 30 | add(menu) 31 | } 32 | 33 | /** 34 | * Builds a [JMenuItem] initialized with an [init] function. 35 | */ 36 | fun JMenu.menuItem(caption: String, init: JMenuItem.() -> Unit) { 37 | val menuItem = JMenuItem(caption) 38 | menuItem.init() 39 | add(menuItem) 40 | } 41 | 42 | /** 43 | * Adds a separator to a [JMenu]. 44 | */ 45 | fun JMenu.separator() { 46 | addSeparator() 47 | } 48 | 49 | /** 50 | * Sets an action to be executed when the [JMenuItem] is selected. 51 | */ 52 | fun JMenuItem.onClick(callback: () -> Unit) { 53 | action = MenuItemAction(text, callback) 54 | } 55 | 56 | private class MenuItemAction(caption: String, val callback: () -> Unit) : AbstractAction(caption) { 57 | override fun actionPerformed(e: ActionEvent?) { 58 | callback() 59 | } 60 | } -------------------------------------------------------------------------------- /jogl/src/main/kotlin/glimpse/jogl/Mouse.kt: -------------------------------------------------------------------------------- 1 | package glimpse.jogl 2 | 3 | import glimpse.Point 4 | import java.awt.MouseInfo 5 | 6 | /** 7 | * Returns mouse position with `x` and `y` mapped to the interval: -1…1. 8 | */ 9 | fun mousePosition(): Point { 10 | val x = MouseInfo.getPointerInfo().location.x.toFloat() 11 | val y = MouseInfo.getPointerInfo().location.y.toFloat() 12 | val width = MouseInfo.getPointerInfo().device.displayMode.width.toFloat() 13 | val height = MouseInfo.getPointerInfo().device.displayMode.height.toFloat() 14 | return Point(2f * x / width - 1f, 2f * y / height - 1f) 15 | } 16 | -------------------------------------------------------------------------------- /jogl/src/main/kotlin/glimpse/jogl/gles/EnumMappings.kt: -------------------------------------------------------------------------------- 1 | package glimpse.jogl.gles 2 | 3 | import com.jogamp.opengl.GL2ES2 4 | import glimpse.gles.* 5 | import glimpse.shaders.ShaderType 6 | import glimpse.textures.TextureMagnificationFilter 7 | import glimpse.textures.TextureMinificationFilter 8 | import glimpse.textures.TextureWrapping 9 | 10 | internal val depthTestFunctionMapping = mapOf( 11 | DepthTestFunction.NEVER to GL2ES2.GL_NEVER, 12 | DepthTestFunction.LESS to GL2ES2.GL_LESS, 13 | DepthTestFunction.EQUAL to GL2ES2.GL_EQUAL, 14 | DepthTestFunction.LESS_OR_EQUAL to GL2ES2.GL_LEQUAL, 15 | DepthTestFunction.GREATER to GL2ES2.GL_GREATER, 16 | DepthTestFunction.NOT_EQUAL to GL2ES2.GL_NOTEQUAL, 17 | DepthTestFunction.GREATER_OR_EQUAL to GL2ES2.GL_GEQUAL, 18 | DepthTestFunction.ALWAYS to GL2ES2.GL_ALWAYS) 19 | 20 | internal val blendFactorMapping = mapOf( 21 | BlendFactor.ZERO to GL2ES2.GL_ZERO, 22 | BlendFactor.ONE to GL2ES2.GL_ONE, 23 | BlendFactor.SRC_COLOR to GL2ES2.GL_SRC_COLOR, 24 | BlendFactor.ONE_MINUS_SRC_COLOR to GL2ES2.GL_ONE_MINUS_SRC_COLOR, 25 | BlendFactor.DST_COLOR to GL2ES2.GL_DST_COLOR, 26 | BlendFactor.ONE_MINUS_DST_COLOR to GL2ES2.GL_ONE_MINUS_DST_COLOR, 27 | BlendFactor.SRC_ALPHA to GL2ES2.GL_SRC_ALPHA, 28 | BlendFactor.ONE_MINUS_SRC_ALPHA to GL2ES2.GL_ONE_MINUS_SRC_ALPHA, 29 | BlendFactor.DST_ALPHA to GL2ES2.GL_DST_ALPHA, 30 | BlendFactor.ONE_MINUS_DST_ALPHA to GL2ES2.GL_ONE_MINUS_DST_ALPHA, 31 | BlendFactor.CONSTANT_COLOR to GL2ES2.GL_CONSTANT_COLOR, 32 | BlendFactor.ONE_MINUS_CONSTANT_COLOR to GL2ES2.GL_ONE_MINUS_CONSTANT_COLOR, 33 | BlendFactor.CONSTANT_ALPHA to GL2ES2.GL_CONSTANT_ALPHA, 34 | BlendFactor.ONE_MINUS_CONSTANT_ALPHA to GL2ES2.GL_ONE_MINUS_CONSTANT_ALPHA) 35 | 36 | internal val cullFaceModeMapping = mapOf( 37 | CullFaceMode.BACK to GL2ES2.GL_BACK, 38 | CullFaceMode.FRONT to GL2ES2.GL_FRONT, 39 | CullFaceMode.FRONT_AND_BACK to GL2ES2.GL_FRONT_AND_BACK) 40 | 41 | internal val textureMinificationFilterMapping = mapOf( 42 | TextureMinificationFilter.NEAREST to GL2ES2.GL_NEAREST, 43 | TextureMinificationFilter.LINEAR to GL2ES2.GL_LINEAR, 44 | TextureMinificationFilter.NEAREST_MIPMAP_NEAREST to GL2ES2.GL_NEAREST_MIPMAP_NEAREST, 45 | TextureMinificationFilter.LINEAR_MIPMAP_NEAREST to GL2ES2.GL_LINEAR_MIPMAP_NEAREST, 46 | TextureMinificationFilter.NEAREST_MIPMAP_LINEAR to GL2ES2.GL_NEAREST_MIPMAP_LINEAR, 47 | TextureMinificationFilter.LINEAR_MIPMAP_LINEAR to GL2ES2.GL_LINEAR_MIPMAP_LINEAR) 48 | 49 | internal val textureMagnificationFilterMapping = mapOf( 50 | TextureMagnificationFilter.NEAREST to GL2ES2.GL_NEAREST, 51 | TextureMagnificationFilter.LINEAR to GL2ES2.GL_LINEAR) 52 | 53 | internal val textureWrappingMapping = mapOf( 54 | TextureWrapping.REPEAT to GL2ES2.GL_REPEAT, 55 | TextureWrapping.MIRRORED_REPEAT to GL2ES2.GL_MIRRORED_REPEAT, 56 | TextureWrapping.CLAMP_TO_EDGE to GL2ES2.GL_CLAMP_TO_EDGE) 57 | 58 | internal val shaderTypeMapping = mapOf( 59 | ShaderType.VERTEX to GL2ES2.GL_VERTEX_SHADER, 60 | ShaderType.FRAGMENT to GL2ES2.GL_FRAGMENT_SHADER) 61 | -------------------------------------------------------------------------------- /jogl/src/main/kotlin/glimpse/jogl/gles/delegates/EnableDisableDelegate.kt: -------------------------------------------------------------------------------- 1 | package glimpse.jogl.gles.delegates 2 | 3 | import com.jogamp.opengl.GL2ES2 4 | import kotlin.reflect.KProperty 5 | 6 | /** 7 | * GLES enable/disable flag property delegate. 8 | * 9 | * @property gles GLES implementation. 10 | * @property key GLES enable/disable flag key. 11 | */ 12 | internal class EnableDisableDelegate(val gles: GL2ES2, val key: Int) { 13 | 14 | /** 15 | * Gets delegated property value. 16 | */ 17 | operator fun getValue(thisRef: Any?, property: KProperty<*>): Boolean = 18 | gles.glIsEnabled(key) 19 | 20 | /** 21 | * Sets delegated property value. 22 | */ 23 | operator fun setValue(thisRef: Any?, property: KProperty<*>, enabled: Boolean) = 24 | if (enabled) gles.glEnable(key) 25 | else gles.glDisable(key) 26 | } 27 | -------------------------------------------------------------------------------- /jogl/src/main/kotlin/glimpse/jogl/io/FileChoosers.kt: -------------------------------------------------------------------------------- 1 | package glimpse.jogl.io 2 | 3 | import java.io.File 4 | import javax.swing.JFileChooser 5 | import javax.swing.JFrame 6 | 7 | /** 8 | * Displays a Swing file chooser for opening a file. 9 | * @param fileFilter File filter. 10 | * @param onOpen Action to be run when the user chooses to open the file. 11 | * @param onCancel Action to be run when the user cancels. 12 | */ 13 | fun JFrame.openFile(fileFilter: PredicateFileFilter, onOpen: (File) -> Unit, onCancel: () -> Unit = {}) { 14 | val fileChooser = JFileChooser() 15 | fileChooser.addChoosableFileFilter(fileFilter) 16 | fileChooser.isAcceptAllFileFilterUsed = false 17 | when (fileChooser.showOpenDialog(this)) { 18 | JFileChooser.APPROVE_OPTION -> onOpen(fileChooser.selectedFile) 19 | else -> onCancel() 20 | } 21 | } 22 | 23 | /** 24 | * Displays a Swing file chooser for opening an OBJ file. 25 | * @param onOpen Action to be run when the user chooses to open the file. 26 | */ 27 | fun JFrame.openOBJFile(onOpen: (File) -> Unit): Unit = openFile(objFileFilter, onOpen) 28 | 29 | /** 30 | * Displays a Swing file chooser for opening an image file. 31 | * @param onOpen Action to be run when the user chooses to open the file. 32 | */ 33 | fun JFrame.openImageFile(onOpen: (File) -> Unit): Unit = openFile(imageFileFilter, onOpen) 34 | -------------------------------------------------------------------------------- /jogl/src/main/kotlin/glimpse/jogl/io/FileFilters.kt: -------------------------------------------------------------------------------- 1 | package glimpse.jogl.io 2 | 3 | /** 4 | * OBJ file filter. 5 | */ 6 | val objFileFilter = PredicateFileFilter("OBJ files") { 7 | "obj".equals(extension, true) 8 | } 9 | 10 | /** 11 | * Image file filter. 12 | */ 13 | val imageFileFilter = PredicateFileFilter("Image files") { 14 | extension.toLowerCase() in listOf("bmp", "jpg", "jpeg", "png") 15 | } 16 | -------------------------------------------------------------------------------- /jogl/src/main/kotlin/glimpse/jogl/io/PredicateFileFilter.kt: -------------------------------------------------------------------------------- 1 | package glimpse.jogl.io 2 | 3 | import java.io.File 4 | import javax.swing.filechooser.FileFilter 5 | 6 | /** 7 | * Predicate file filter for Swing file chooser. 8 | * 9 | * @property name Filter name that will be displayed in file chooser. 10 | * @property predicate File display predicate. 11 | */ 12 | class PredicateFileFilter(val name: String, val predicate: File.() -> Boolean) : FileFilter() { 13 | 14 | /** 15 | * Returns `true` if the [file] should be displayed in file chooser. 16 | */ 17 | override fun accept(file: File?): Boolean = file?.shouldBeDisplayed() ?: false 18 | 19 | private fun File.shouldBeDisplayed(): Boolean = isDirectory || predicate() 20 | 21 | /** 22 | * Returns filter description displayed in file chooser. 23 | */ 24 | override fun getDescription(): String = name 25 | } 26 | -------------------------------------------------------------------------------- /materials/build.gradle: -------------------------------------------------------------------------------- 1 | description = 'Glimpse Framework Materials' 2 | 3 | dependencies { 4 | compile project(":glimpse-framework-api") 5 | compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" 6 | testCompile "io.kotlintest:kotlintest:${kotlintest_version}" 7 | } 8 | 9 | dokka { 10 | moduleName = 'Glimpse Framework Materials' 11 | outputFormat = 'javadoc' 12 | } 13 | 14 | task sourcesJar(type: Jar, dependsOn: project.classes) { 15 | classifier = 'sources' 16 | from sourceSets.main.allSource 17 | } 18 | 19 | task dokkaJar(type: Jar, dependsOn: project.dokka) { 20 | classifier = 'javadoc' 21 | from dokka 22 | } 23 | 24 | artifacts { 25 | archives sourcesJar 26 | archives dokkaJar 27 | } 28 | -------------------------------------------------------------------------------- /materials/src/main/kotlin/glimpse/materials/AbstractMaterial.kt: -------------------------------------------------------------------------------- 1 | package glimpse.materials 2 | 3 | import glimpse.Point 4 | import glimpse.Vector 5 | import glimpse.cos 6 | import glimpse.lights.Light 7 | 8 | /** 9 | * Common superclass for materials. 10 | */ 11 | abstract class AbstractMaterial : Material { 12 | 13 | /** 14 | * Sets shader uniforms containing number and properties of the [lights]. 15 | */ 16 | protected operator fun ShaderHelper.set(name: String, lights: List) { 17 | this["${name}sCount"] = lights.size 18 | this["${name}Type"] = lights.map { light -> light.type }.toIntArray() 19 | this.setColors("${name}Color", lights.map { light -> light.color() }) 20 | this.setVectors("${name}Direction", lights.map { light -> 21 | when(light) { 22 | is Light.DirectionLight -> light.direction() 23 | is Light.Spotlight -> light.position() to light.target() 24 | else -> Vector.NULL 25 | } 26 | }) 27 | this.setPoints("${name}Position", lights.map { light -> 28 | when(light) { 29 | is Light.PointLight -> light.position() 30 | is Light.Spotlight -> light.position() 31 | else -> Point.ORIGIN 32 | } 33 | }) 34 | this["${name}Distance"] = lights.map { light -> 35 | when(light) { 36 | is Light.PointLight -> light.distance() 37 | is Light.Spotlight -> light.distance() 38 | else -> Float.MAX_VALUE 39 | } 40 | }.toFloatArray() 41 | this["${name}AngleCos"] = lights.map { light -> 42 | when(light) { 43 | is Light.Spotlight -> cos(light.angle() * .5f) 44 | else -> 0f 45 | } 46 | }.toFloatArray() 47 | } 48 | } -------------------------------------------------------------------------------- /materials/src/main/kotlin/glimpse/materials/Plastic.kt: -------------------------------------------------------------------------------- 1 | package glimpse.materials 2 | 3 | import glimpse.Color 4 | import glimpse.cameras.Camera 5 | import glimpse.gles.delegates.DisposableLazyDelegate 6 | import glimpse.io.resource 7 | import glimpse.lights.Light 8 | import glimpse.models.Model 9 | import glimpse.shaders.Program 10 | import glimpse.shaders.shaderProgram 11 | 12 | /** 13 | * Plastic material. 14 | */ 15 | class Plastic(val diffuse: Color, val ambient: Color = diffuse, val specular: Color = Color.WHITE, val shininess: Float = 100f) : AbstractMaterial() { 16 | 17 | init { 18 | PlasticShaderHelper.registerDisposable() 19 | } 20 | 21 | override fun render(model: Model, camera: Camera, lights: List) { 22 | val mvpMatrix = camera.cameraMatrix * model.transformation() 23 | val viewMatrix = camera.view.viewMatrix 24 | val modelViewMatrix = viewMatrix * model.transformation() 25 | PlasticShaderHelper.use() 26 | PlasticShaderHelper["u_DiffuseColor"] = diffuse 27 | PlasticShaderHelper["u_AmbientColor"] = ambient 28 | PlasticShaderHelper["u_SpecularColor"] = specular 29 | PlasticShaderHelper["u_Shininess"] = shininess 30 | PlasticShaderHelper["u_MVPMatrix"] = mvpMatrix 31 | PlasticShaderHelper["u_MVMatrix"] = modelViewMatrix 32 | PlasticShaderHelper["u_ModelMatrix"] = model.transformation() 33 | PlasticShaderHelper["u_LightMatrix"] = viewMatrix.trimmed 34 | PlasticShaderHelper["u_NormalMatrix"] = modelViewMatrix.trimmed 35 | PlasticShaderHelper["u_Light"] = lights 36 | PlasticShaderHelper.drawMesh(model.mesh) 37 | } 38 | } 39 | 40 | internal object PlasticShaderHelper : ShaderHelper() { 41 | 42 | override val program: Program? by DisposableLazyDelegate { 43 | shaderProgram { 44 | vertexShader { 45 | PlasticShaderHelper.resource("Plastic_vertex.glsl").lines.joinToString(separator = "\n") { it } 46 | } 47 | fragmentShader { 48 | PlasticShaderHelper.resource("Plastic_fragment.glsl").lines.joinToString(separator = "\n") { it } 49 | } 50 | } 51 | } 52 | 53 | override val vertexPositionAttributeName = "a_VertexPosition" 54 | override val vertexTextureCoordinatesAttributeName = null 55 | override val vertexNormalAttributeName = "a_VertexNormal" 56 | } 57 | -------------------------------------------------------------------------------- /materials/src/main/kotlin/glimpse/materials/Textured.kt: -------------------------------------------------------------------------------- 1 | package glimpse.materials 2 | 3 | import glimpse.cameras.Camera 4 | import glimpse.gles.delegates.DisposableLazyDelegate 5 | import glimpse.io.resource 6 | import glimpse.lights.Light 7 | import glimpse.models.Model 8 | import glimpse.shaders.Program 9 | import glimpse.shaders.shaderProgram 10 | import glimpse.textures.Texture 11 | 12 | /** 13 | * Textured material. 14 | */ 15 | class Textured(val shininess: Float = 100f, val texture: (TextureType) -> Texture) : AbstractMaterial() { 16 | 17 | enum class TextureType { 18 | AMBIENT, 19 | DIFFUSE, 20 | SPECULAR 21 | } 22 | 23 | init { 24 | TexturedShaderHelper.registerDisposable() 25 | } 26 | 27 | override fun render(model: Model, camera: Camera, lights: List) { 28 | val mvpMatrix = camera.cameraMatrix * model.transformation() 29 | val viewMatrix = camera.view.viewMatrix 30 | val modelViewMatrix = viewMatrix * model.transformation() 31 | TexturedShaderHelper.use() 32 | TexturedShaderHelper["u_DiffuseTexture", 0] = texture(TextureType.DIFFUSE) 33 | TexturedShaderHelper["u_AmbientTexture", 1] = texture(TextureType.AMBIENT) 34 | TexturedShaderHelper["u_SpecularTexture", 2] = texture(TextureType.SPECULAR) 35 | TexturedShaderHelper["u_Shininess"] = shininess 36 | TexturedShaderHelper["u_MVPMatrix"] = mvpMatrix 37 | TexturedShaderHelper["u_MVMatrix"] = modelViewMatrix 38 | TexturedShaderHelper["u_ModelMatrix"] = model.transformation() 39 | TexturedShaderHelper["u_LightMatrix"] = viewMatrix.trimmed 40 | TexturedShaderHelper["u_NormalMatrix"] = modelViewMatrix.trimmed 41 | TexturedShaderHelper["u_Light"] = lights 42 | TexturedShaderHelper.drawMesh(model.mesh) 43 | } 44 | } 45 | 46 | internal object TexturedShaderHelper : ShaderHelper() { 47 | 48 | override val program: Program? by DisposableLazyDelegate { 49 | shaderProgram { 50 | vertexShader { 51 | TexturedShaderHelper.resource("Textured_vertex.glsl").lines.joinToString(separator = "\n") { it } 52 | } 53 | fragmentShader { 54 | TexturedShaderHelper.resource("Textured_fragment.glsl").lines.joinToString(separator = "\n") { it } 55 | } 56 | } 57 | } 58 | 59 | override val vertexPositionAttributeName = "a_VertexPosition" 60 | override val vertexTextureCoordinatesAttributeName = "a_TextureCoordinates" 61 | override val vertexNormalAttributeName = "a_VertexNormal" 62 | } 63 | -------------------------------------------------------------------------------- /materials/src/main/resources/glimpse/materials/Plastic_fragment.glsl: -------------------------------------------------------------------------------- 1 | #define MAX_LIGHTS_COUNT 8 2 | 3 | #define DIRECTION_LIGHT 1 4 | #define POINT_LIGHT 2 5 | #define SPOTLIGHT 3 6 | 7 | uniform mat4 u_LightMatrix; 8 | uniform mat4 u_NormalMatrix; 9 | 10 | uniform vec4 u_DiffuseColor; 11 | uniform vec4 u_AmbientColor; 12 | uniform vec4 u_SpecularColor; 13 | 14 | uniform float u_Shininess; 15 | 16 | uniform int u_LightsCount; 17 | uniform int u_LightType[MAX_LIGHTS_COUNT]; 18 | uniform vec4 u_LightDirection[MAX_LIGHTS_COUNT]; 19 | uniform vec4 u_LightPosition[MAX_LIGHTS_COUNT]; 20 | uniform float u_LightDistance[MAX_LIGHTS_COUNT]; 21 | uniform float u_LightAngleCos[MAX_LIGHTS_COUNT]; 22 | uniform vec4 u_LightColor[MAX_LIGHTS_COUNT]; 23 | 24 | varying vec3 v_VertexPosition; 25 | varying vec4 v_VertexModelPosition; 26 | varying vec4 v_VertexNormal; 27 | 28 | vec3 lightVector; 29 | float lightPower; 30 | 31 | float positiveDot(vec3 a, vec3 b) { 32 | return max(0.0, dot(a, b)); 33 | } 34 | 35 | float positiveDot(vec4 a, vec4 b) { 36 | return max(0.0, dot(a, b)); 37 | } 38 | 39 | void setupDirectionLight(int index) { 40 | lightVector = normalize((u_LightMatrix * -u_LightDirection[index]).xyz); 41 | lightPower = 1.0; 42 | } 43 | 44 | void setupPointLight(int index) { 45 | vec3 longLightVector = (u_LightMatrix * (u_LightPosition[index] - v_VertexModelPosition)).xyz; 46 | lightVector = normalize(longLightVector); 47 | lightPower = max(0.0, (u_LightDistance[index] - length(longLightVector)) / u_LightDistance[index]); 48 | } 49 | 50 | void setupSpotlight(int index) { 51 | vec3 longLightVector = (u_LightMatrix * (u_LightPosition[index] - v_VertexModelPosition)).xyz; 52 | lightVector = normalize(longLightVector); 53 | vec3 lightTargetDirection = normalize((u_LightMatrix * -u_LightDirection[index]).xyz); 54 | float angleCos = dot(lightVector, lightTargetDirection); 55 | float angleFactor = max(0.0, (angleCos - u_LightAngleCos[index]) / (1.0 - u_LightAngleCos[index])); 56 | lightPower = angleFactor * max(0.0, (u_LightDistance[index] - length(longLightVector)) / u_LightDistance[index]); 57 | } 58 | 59 | void setupLight(int index) { 60 | if (DIRECTION_LIGHT == u_LightType[index]) { 61 | setupDirectionLight(index); 62 | } else if (POINT_LIGHT == u_LightType[index]) { 63 | setupPointLight(index); 64 | } else if (SPOTLIGHT == u_LightType[index]) { 65 | setupSpotlight(index); 66 | } 67 | } 68 | 69 | void main() { 70 | vec3 camera = normalize(-v_VertexPosition); 71 | vec3 normal = normalize((u_NormalMatrix * v_VertexNormal).xyz); 72 | 73 | vec3 diffuseLightValue = vec3(0.0, 0.0, 0.0); 74 | vec3 specularLightValue = vec3(0.0, 0.0, 0.0); 75 | 76 | for (int index = 0; index < u_LightsCount; index++) { 77 | setupLight(index); 78 | vec3 halfVector = normalize(vec3(camera + lightVector)); 79 | diffuseLightValue += u_LightColor[index].rgb * positiveDot(normal, lightVector) * lightPower; 80 | specularLightValue += u_LightColor[index].rgb * pow(positiveDot(halfVector, normal), u_Shininess * (2.0 - lightPower)); 81 | } 82 | 83 | vec3 ambient = u_AmbientColor.rgb * 0.2; 84 | vec3 diffuse = u_DiffuseColor.rgb * diffuseLightValue * 0.8; 85 | vec3 specular = u_SpecularColor.rgb * specularLightValue; 86 | 87 | gl_FragColor = vec4(specular + diffuse + ambient, u_DiffuseColor.a); 88 | } 89 | -------------------------------------------------------------------------------- /materials/src/main/resources/glimpse/materials/Plastic_vertex.glsl: -------------------------------------------------------------------------------- 1 | uniform mat4 u_MVPMatrix; 2 | uniform mat4 u_MVMatrix; 3 | uniform mat4 u_ModelMatrix; 4 | 5 | attribute vec4 a_VertexPosition; 6 | attribute vec4 a_VertexNormal; 7 | 8 | varying vec3 v_VertexPosition; 9 | varying vec4 v_VertexModelPosition; 10 | varying vec4 v_VertexNormal; 11 | 12 | void main() { 13 | gl_Position = u_MVPMatrix * a_VertexPosition; 14 | 15 | v_VertexPosition = (u_MVMatrix * a_VertexPosition).xyz; 16 | v_VertexModelPosition = (u_ModelMatrix * a_VertexPosition); 17 | v_VertexNormal = a_VertexNormal; 18 | } 19 | -------------------------------------------------------------------------------- /materials/src/main/resources/glimpse/materials/Textured_fragment.glsl: -------------------------------------------------------------------------------- 1 | #define MAX_LIGHTS_COUNT 8 2 | 3 | #define DIRECTION_LIGHT 1 4 | #define POINT_LIGHT 2 5 | #define SPOTLIGHT 3 6 | 7 | uniform mat4 u_LightMatrix; 8 | uniform mat4 u_NormalMatrix; 9 | 10 | uniform sampler2D u_DiffuseTexture; 11 | uniform sampler2D u_AmbientTexture; 12 | uniform sampler2D u_SpecularTexture; 13 | 14 | uniform float u_Shininess; 15 | 16 | uniform int u_LightsCount; 17 | uniform int u_LightType[MAX_LIGHTS_COUNT]; 18 | uniform vec4 u_LightDirection[MAX_LIGHTS_COUNT]; 19 | uniform vec4 u_LightPosition[MAX_LIGHTS_COUNT]; 20 | uniform float u_LightDistance[MAX_LIGHTS_COUNT]; 21 | uniform float u_LightAngleCos[MAX_LIGHTS_COUNT]; 22 | uniform vec4 u_LightColor[MAX_LIGHTS_COUNT]; 23 | 24 | varying vec3 v_VertexPosition; 25 | varying vec4 v_VertexModelPosition; 26 | varying vec4 v_VertexNormal; 27 | varying vec2 v_TextureCoordinates; 28 | 29 | vec3 lightVector; 30 | float lightPower; 31 | 32 | float positiveDot(vec3 a, vec3 b) { 33 | return max(0.0, dot(a, b)); 34 | } 35 | 36 | float positiveDot(vec4 a, vec4 b) { 37 | return max(0.0, dot(a, b)); 38 | } 39 | 40 | void setupDirectionLight(int index) { 41 | lightVector = normalize((u_LightMatrix * -u_LightDirection[index]).xyz); 42 | lightPower = 1.0; 43 | } 44 | 45 | void setupPointLight(int index) { 46 | vec3 longLightVector = (u_LightMatrix * (u_LightPosition[index] - v_VertexModelPosition)).xyz; 47 | lightVector = normalize(longLightVector); 48 | lightPower = max(0.0, (u_LightDistance[index] - length(longLightVector)) / u_LightDistance[index]); 49 | } 50 | 51 | void setupSpotlight(int index) { 52 | vec3 longLightVector = (u_LightMatrix * (u_LightPosition[index] - v_VertexModelPosition)).xyz; 53 | lightVector = normalize(longLightVector); 54 | vec3 lightTargetDirection = normalize((u_LightMatrix * -u_LightDirection[index]).xyz); 55 | float angleCos = dot(lightVector, lightTargetDirection); 56 | float angleFactor = max(0.0, (angleCos - u_LightAngleCos[index]) / (1.0 - u_LightAngleCos[index])); 57 | lightPower = angleFactor * max(0.0, (u_LightDistance[index] - length(longLightVector)) / u_LightDistance[index]); 58 | } 59 | 60 | void setupLight(int index) { 61 | if (DIRECTION_LIGHT == u_LightType[index]) { 62 | setupDirectionLight(index); 63 | } else if (POINT_LIGHT == u_LightType[index]) { 64 | setupPointLight(index); 65 | } else if (SPOTLIGHT == u_LightType[index]) { 66 | setupSpotlight(index); 67 | } 68 | } 69 | 70 | void main() { 71 | vec3 camera = normalize(-v_VertexPosition); 72 | vec3 normal = normalize((u_NormalMatrix * v_VertexNormal).xyz); 73 | 74 | vec3 diffuseLightValue = vec3(0.0, 0.0, 0.0); 75 | vec3 specularLightValue = vec3(0.0, 0.0, 0.0); 76 | 77 | for (int index = 0; index < u_LightsCount; index++) { 78 | setupLight(index); 79 | vec3 halfVector = normalize(vec3(camera + lightVector)); 80 | diffuseLightValue += u_LightColor[index].rgb * positiveDot(normal, lightVector) * lightPower; 81 | specularLightValue += u_LightColor[index].rgb * pow(positiveDot(halfVector, normal), u_Shininess * (2.0 - lightPower)); 82 | } 83 | 84 | vec4 ambientColor = texture2D(u_AmbientTexture, v_TextureCoordinates); 85 | vec4 diffuseColor = texture2D(u_DiffuseTexture, v_TextureCoordinates); 86 | vec4 specularColor = texture2D(u_SpecularTexture, v_TextureCoordinates); 87 | 88 | vec3 ambient = ambientColor.rgb; 89 | vec3 diffuse = diffuseColor.rgb * diffuseLightValue; 90 | vec3 specular = specularColor.rgb * specularLightValue; 91 | 92 | gl_FragColor = vec4(specular + diffuse + ambient, diffuseColor.a); 93 | } 94 | -------------------------------------------------------------------------------- /materials/src/main/resources/glimpse/materials/Textured_vertex.glsl: -------------------------------------------------------------------------------- 1 | uniform mat4 u_MVPMatrix; 2 | uniform mat4 u_MVMatrix; 3 | uniform mat4 u_ModelMatrix; 4 | 5 | attribute vec4 a_VertexPosition; 6 | attribute vec4 a_VertexNormal; 7 | attribute vec2 a_TextureCoordinates; 8 | 9 | varying vec3 v_VertexPosition; 10 | varying vec4 v_VertexModelPosition; 11 | varying vec4 v_VertexNormal; 12 | varying vec2 v_TextureCoordinates; 13 | 14 | void main() { 15 | gl_Position = u_MVPMatrix * a_VertexPosition; 16 | 17 | v_VertexPosition = (u_MVMatrix * a_VertexPosition).xyz; 18 | v_VertexModelPosition = (u_ModelMatrix * a_VertexPosition); 19 | v_VertexNormal = a_VertexNormal; 20 | v_TextureCoordinates = a_TextureCoordinates; 21 | } 22 | -------------------------------------------------------------------------------- /preview/build.gradle: -------------------------------------------------------------------------------- 1 | description = 'Glimpse Framework Preview' 2 | 3 | task(generateResources) << { 4 | def propertiesFile = new File("$buildDir/resources/main/app.properties") 5 | propertiesFile.parentFile.mkdirs() 6 | propertiesFile.createNewFile() 7 | def properties = new Properties() 8 | properties.setProperty("APP_NAME", project.description) 9 | properties.setProperty("APP_VERSION", project.version) 10 | properties.store(propertiesFile.newWriter(), "${project.description} v.${project.version}") 11 | } 12 | 13 | processResources.dependsOn generateResources 14 | 15 | apply plugin: 'application' 16 | apply plugin: 'edu.sc.seis.launch4j' 17 | 18 | def mainClass = 'glimpse.preview.GlimpsePreviewKt' 19 | 20 | mainClassName = mainClass 21 | 22 | launch4j { 23 | outfile = 'GlimpsePreview.exe' 24 | mainClassName = mainClass 25 | version = "${project.version}".split("-")[0] 26 | copyright = 'Glimpse Framework' 27 | companyName = 'Glimpse Framework' 28 | description = project.description 29 | productName = project.description 30 | } 31 | 32 | dependencies { 33 | compile project(":glimpse-framework-api") 34 | compile project(":glimpse-framework-materials") 35 | compile project(":glimpse-framework-jogl") 36 | 37 | compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" 38 | 39 | compile "org.jogamp.jogl:jogl-all-main:${jogl_version}" 40 | compile "org.jogamp.gluegen:gluegen-rt-main:${jogl_version}" 41 | 42 | testCompile "io.kotlintest:kotlintest:${kotlintest_version}" 43 | } 44 | -------------------------------------------------------------------------------- /preview/src/main/resources/glimpse/preview/ambient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glimpse-graphics/glimpse-framework/5069cc1c3f2d8cb344740f3d33bcf11848f94583/preview/src/main/resources/glimpse/preview/ambient.png -------------------------------------------------------------------------------- /preview/src/main/resources/glimpse/preview/diffuse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glimpse-graphics/glimpse-framework/5069cc1c3f2d8cb344740f3d33bcf11848f94583/preview/src/main/resources/glimpse/preview/diffuse.png -------------------------------------------------------------------------------- /preview/src/main/resources/glimpse/preview/specular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glimpse-graphics/glimpse-framework/5069cc1c3f2d8cb344740f3d33bcf11848f94583/preview/src/main/resources/glimpse/preview/specular.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'glimpse-framework' 2 | 3 | include ':glimpse-framework-api' 4 | include ':glimpse-framework-materials' 5 | include ':glimpse-framework-jogl' 6 | include ':glimpse-framework-preview' 7 | 8 | project(':glimpse-framework-api').projectDir = "$rootDir/api" as File 9 | project(':glimpse-framework-materials').projectDir = "$rootDir/materials" as File 10 | project(':glimpse-framework-jogl').projectDir = "$rootDir/jogl" as File 11 | project(':glimpse-framework-preview').projectDir = "$rootDir/preview" as File 12 | --------------------------------------------------------------------------------