├── project
├── build.properties
└── plugins.sbt
├── example
├── src
│ └── main
│ │ ├── resources
│ │ └── mesh
│ │ │ ├── texture
│ │ │ ├── DS1_tex_01.psd
│ │ │ ├── foil_n.png
│ │ │ ├── foil_gold_ramp.png
│ │ │ └── foil_silver_ramp.png
│ │ │ └── deep_space_1_11.mtl
│ │ └── scala
│ │ ├── CartonShader.scala
│ │ └── Main.scala
└── third_party
│ └── third_party_licenses.md
├── .gitattributes
├── .gitignore
├── shader
├── src
│ ├── test
│ │ └── scala
│ │ │ └── com
│ │ │ └── jsuereth
│ │ │ └── gl
│ │ │ └── shaders
│ │ │ ├── helpers.scala
│ │ │ ├── TestTextureUniformInShader.scala
│ │ │ ├── TestMathTranslations.scala
│ │ │ ├── TestStructUniformShader.scala
│ │ │ └── TestShader.scala
│ └── main
│ │ └── scala
│ │ └── com
│ │ └── jsuereth
│ │ └── gl
│ │ └── shaders
│ │ ├── Uniform.scala
│ │ ├── codegen
│ │ ├── ast.scala
│ │ ├── transform.scala
│ │ └── convertors.scala
│ │ ├── DslShaderProgram.scala
│ │ └── BasicShaderProgram.scala
└── third_party
│ └── third_party_licenses.md
├── .travis.yml
├── scene
├── third_party
│ └── third_party_licenses.md
└── src
│ ├── test
│ └── scala
│ │ └── com
│ │ └── jsuereth
│ │ └── gl
│ │ └── mesh
│ │ └── parser
│ │ ├── TestMtlParser.scala
│ │ └── TestObjParser.scala
│ └── main
│ └── scala
│ └── com
│ └── jsuereth
│ └── gl
│ ├── scene
│ ├── SceneObject.scala
│ ├── Scene.scala
│ └── Camera.scala
│ └── mesh
│ ├── Material.scala
│ ├── parser
│ ├── MtlParser.scala
│ └── ObjParser.scala
│ └── Mesh.scala
├── math
├── third_party
│ └── third_party_licenses.md
└── src
│ ├── main
│ └── scala
│ │ └── com
│ │ └── jsuereth
│ │ └── gl
│ │ └── math
│ │ ├── Comparable.scala
│ │ ├── ops.scala
│ │ ├── UInt.scala
│ │ ├── Vec2.scala
│ │ ├── SquareRootable.scala
│ │ ├── Trigonometry.scala
│ │ ├── Vec4.scala
│ │ ├── Matrix3.scala
│ │ ├── Vec3.scala
│ │ ├── Quaternion.scala
│ │ └── Matrix4.scala
│ └── test
│ └── scala
│ └── com
│ └── jsuereth
│ └── gl
│ └── math
│ └── math
│ └── TestVec3.scala
├── CONTRIBUTING.md
├── io
├── src
│ ├── test
│ │ └── scala
│ │ │ └── com
│ │ │ └── jsuereth
│ │ │ └── gl
│ │ │ └── io
│ │ │ ├── TestBufferLoadable.scala
│ │ │ ├── TestShaderUniformLoadable.scala
│ │ │ └── TestVaoAttribute.scala
│ └── main
│ │ └── scala
│ │ └── com
│ │ └── jsuereth
│ │ └── gl
│ │ ├── io
│ │ ├── GlBuffer.scala
│ │ ├── StackUtil.scala
│ │ ├── FrameBufferObject.scala
│ │ ├── ActiveTextures.scala
│ │ ├── VertexArrayObject.scala
│ │ ├── BufferLoadable.scala
│ │ ├── VaoAttribute.scala
│ │ └── ShaderUniformLoadable.scala
│ │ └── texture
│ │ ├── TextureParameter.scala
│ │ └── Texture.scala
└── third_party
│ └── third_party_licenses.md
├── README.md
├── CODE_OF_CONDUCT.md
├── types.md
└── LICENSE.md
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.7
2 |
--------------------------------------------------------------------------------
/example/src/main/resources/mesh/texture/DS1_tex_01.psd:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.scala text eolf=lf
2 | *.sh text eol=lf
3 | * text=auto
4 |
--------------------------------------------------------------------------------
/example/src/main/resources/mesh/texture/foil_n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsuereth/shady-side/HEAD/example/src/main/resources/mesh/texture/foil_n.png
--------------------------------------------------------------------------------
/example/src/main/resources/mesh/texture/foil_gold_ramp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsuereth/shady-side/HEAD/example/src/main/resources/mesh/texture/foil_gold_ramp.png
--------------------------------------------------------------------------------
/example/src/main/resources/mesh/texture/foil_silver_ramp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsuereth/shady-side/HEAD/example/src/main/resources/mesh/texture/foil_silver_ramp.png
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.2.0")
2 | addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.3.3")
3 | addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # eclipse
2 | bin
3 | *.launch
4 | .settings
5 | .metadata
6 | .classpath
7 | .metals
8 | .project
9 | .bloop
10 |
11 | # idea
12 | out
13 | *.ipr
14 | *.iws
15 | *.iml
16 | .idea
17 |
18 | # sbt
19 | target/
20 |
21 | # other
22 | eclipse
23 | run
24 |
--------------------------------------------------------------------------------
/shader/src/test/scala/com/jsuereth/gl/shaders/helpers.scala:
--------------------------------------------------------------------------------
1 | package com.jsuereth.gl
2 | package test
3 |
4 | import org.junit.Assert
5 |
6 | def cleanLineEndings(in: String): String =
7 | in.replaceAll("\r\n", "\n")
8 |
9 | def assertCleanEquals(expected: String, actual: String): Unit =
10 | Assert.assertEquals(cleanLineEndings(expected), cleanLineEndings(actual))
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 |
3 | script:
4 | - sbt compile
5 | - sbt test
6 |
7 | before_cache:
8 | - find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete
9 | - find $HOME/.ivy2 -name "ivydata-*.properties" -delete
10 | - find $HOME/.sbt -name "*.lock" -delete
11 |
12 | cache:
13 | directories:
14 | - $HOME/.cache/coursier/v1
15 | - $HOME/.ivy2/cache
16 | - $HOME/.sbt/boot
17 |
--------------------------------------------------------------------------------
/scene/third_party/third_party_licenses.md:
--------------------------------------------------------------------------------
1 | # third_party_licenses
2 |
3 | Category | License | Dependency | Notes
4 | --- | --- | --- | ---
5 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # io_0.16 # 0.1.0 |
6 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # math_0.16 # 0.1.0 |
7 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-library # 2.12.8 |
8 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl # 3.2.2 | Lightweight Java Game Library
9 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl-opengl # 3.2.2 | Lightweight Java Game Library
10 | BSD | [BSD New](https://github.com/lampepfl/dotty/blob/master/LICENSE.md) | ch.epfl.lamp # dotty-library_0.16 # 0.16.0-RC3 |
11 |
12 |
--------------------------------------------------------------------------------
/math/third_party/third_party_licenses.md:
--------------------------------------------------------------------------------
1 | # third_party_licenses
2 |
3 | Category | License | Dependency | Notes
4 | --- | --- | --- | ---
5 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-library # 2.12.8 |
6 | BSD | [BSD](https://github.com/sbt/test-interface/blob/master/LICENSE) | org.scala-sbt # test-interface # 1.0 |
7 | BSD | [BSD New](https://github.com/lampepfl/dotty/blob/master/LICENSE.md) | ch.epfl.lamp # dotty-library_0.16 # 0.16.0-RC3 |
8 | BSD | [New BSD License](http://www.opensource.org/licenses/bsd-license.php) | org.hamcrest # hamcrest-core # 1.3 |
9 | BSD | [Two-clause BSD-style license](http://github.com/sbt/junit-interface/blob/master/LICENSE.txt) | com.novocode # junit-interface # 0.11 | Used for testing
10 | Common Public License | [Common Public License Version 1.0](http://www.opensource.org/licenses/cpl1.0.txt) | junit # junit # 4.11 | Used for testing
11 |
12 |
--------------------------------------------------------------------------------
/shader/src/main/scala/com/jsuereth/gl/shaders/Uniform.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package shaders
19 |
20 | import io.ShaderLoadingEnvironment
21 |
22 | /** A marker trait for how we pass data in/out of an open-gl shader. */
23 | trait Uniform[T] {
24 | /** The name we use for the uniform. */
25 | def name: String
26 | /** Writes a value into a shader program. */
27 | def :=(x: T)(using ShaderLoadingEnvironment): Unit
28 | }
--------------------------------------------------------------------------------
/math/src/main/scala/com/jsuereth/gl/math/Comparable.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.math
18 |
19 | import scala.math.Ordering
20 |
21 | // This file defines helper methods to replace the use of java.lang.Math.
22 |
23 | /** Returns the maximum value of two options. */
24 | def max[T: Ordering](lhs: T, rhs: T): T = summon[Ordering[T]].max(lhs,rhs)
25 | /** Returns the minimum value of two options. */
26 | def min[T: Ordering](lhs: T, rhs: T): T = summon[Ordering[T]].min(lhs,rhs)
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows
28 | [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/).
--------------------------------------------------------------------------------
/io/src/test/scala/com/jsuereth/gl/io/TestBufferLoadable.scala:
--------------------------------------------------------------------------------
1 | package com.jsuereth.gl
2 | package io
3 | package testbufferlodable
4 |
5 | import math._
6 |
7 | import org.junit.Test
8 | import org.junit.Assert._
9 | import java.nio.ByteBuffer
10 |
11 | case class ExamplePod(x: Vec3[Float], y: Vec2[Int], z: Vec2[Boolean]) derives BufferLoadable
12 | class TestBufferLoadable {
13 |
14 | @Test def derivedLoadableWorks(): Unit = {
15 | val buf = ByteBuffer.allocate(sizeOf[ExamplePod])
16 | buf.load(ExamplePod(Vec3(1f,2f,3f), Vec2(4,5), Vec2(true, false)))
17 | buf.flip
18 | assertEquals(1f, buf.getFloat, 0.000001f)
19 | assertEquals(2f, buf.getFloat, 0.000001f)
20 | assertEquals(3f, buf.getFloat, 0.000001f)
21 | assertEquals(4, buf.getInt)
22 | assertEquals(5, buf.getInt)
23 | assertEquals(1.toByte, buf.get)
24 | assertEquals(0.toByte, buf.get)
25 | }
26 | @Test def primitiveWorks(): Unit = {
27 | val buf = ByteBuffer.allocate(12)
28 | buf.load(1f)
29 | buf.load(2)
30 | buf.load(false)
31 | buf.load(2.toByte)
32 | buf.flip
33 | assertEquals(1f, buf.getFloat, 0.00001f)
34 | assertEquals(2, buf.getInt)
35 | assertEquals(0.toByte, buf.get)
36 | assertEquals(2.toByte, buf.get)
37 | }
38 | }
--------------------------------------------------------------------------------
/math/src/main/scala/com/jsuereth/gl/math/ops.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.math
18 |
19 | import scala.math.Numeric
20 | import scala.math.Fractional
21 |
22 | extension ops on [T](x: T)(using n: Numeric[T]) {
23 | def +(y: T): T = n.plus(x,y)
24 | def -(y: T): T = n.minus(x,y)
25 | def *(y: T): T = n.times(x,y)
26 | // Open bug about this not working.
27 | def unary_- = n.negate(x)
28 | }
29 |
30 | def [T](x: T) unary_-(using n: Numeric[T]): T = n.negate(x)
31 |
32 | extension fractionalOps on [T](x: T)(using f: Fractional[T]) {
33 | def /(y: T) = f.div(x, y)
34 | }
35 |
36 | def zero[T: Numeric]: T = summon[Numeric[T]].zero
37 | def one[T: Numeric]: T = summon[Numeric[T]].one
38 | def two[T: Numeric]: T = one + one
39 |
--------------------------------------------------------------------------------
/example/third_party/third_party_licenses.md:
--------------------------------------------------------------------------------
1 | # third_party_licenses
2 |
3 | Category | License | Dependency | Notes
4 | --- | --- | --- | ---
5 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # io_0.16 # 0.1.0 |
6 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # math_0.16 # 0.1.0 |
7 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # scene_0.16 # 0.1.0 |
8 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # shader_0.16 # 0.1.0 |
9 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-library # 2.12.8 |
10 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl # 3.2.2 | Lightweight Java Game Library
11 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl-glfw # 3.2.2 | Lightweight Java Game Library
12 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl-opengl # 3.2.2 | Lightweight Java Game Library
13 | BSD | [BSD New](https://github.com/lampepfl/dotty/blob/master/LICENSE.md) | ch.epfl.lamp # dotty-library_0.16 # 0.16.0-RC3 |
14 |
15 |
--------------------------------------------------------------------------------
/io/src/main/scala/com/jsuereth/gl/io/GlBuffer.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.io
18 |
19 | import org.lwjgl.system.MemoryStack
20 | import org.lwjgl.opengl.GL30.{GL_FRAMEBUFFER,glBindFramebuffer,glGenFramebuffers}
21 |
22 |
23 | /** A helper to represent a buffer in OpenGL. We can load things into this buffer. */
24 | trait GLBuffer extends AutoCloseable {
25 | /* The ID of the buffer object. This makes no sense to use unless you know the type of buffer (FBO/VBO). */
26 | def id: Int
27 | // all instances should have an inline "with"
28 | inline def withBound[A](f: => A): A = {
29 | bind()
30 | try f
31 | finally unbind()
32 | }
33 | /** Binds the buffer to be used. */
34 | def bind(): Unit
35 | /** Unbinds the buffer. */
36 | def unbind(): Unit
37 | }
--------------------------------------------------------------------------------
/io/third_party/third_party_licenses.md:
--------------------------------------------------------------------------------
1 | # third_party_licenses
2 |
3 | Category | License | Dependency | Notes
4 | --- | --- | --- | ---
5 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # math_0.16 # 0.1.0 |
6 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-library # 2.12.8 |
7 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl # 3.2.2 | Lightweight Java Game Library
8 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl-opengl # 3.2.2 | Lightweight Java Game Library
9 | BSD | [BSD](https://github.com/sbt/test-interface/blob/master/LICENSE) | org.scala-sbt # test-interface # 1.0 |
10 | BSD | [BSD New](https://github.com/lampepfl/dotty/blob/master/LICENSE.md) | ch.epfl.lamp # dotty-library_0.16 # 0.16.0-RC3 |
11 | BSD | [New BSD License](http://www.opensource.org/licenses/bsd-license.php) | org.hamcrest # hamcrest-core # 1.3 |
12 | BSD | [Two-clause BSD-style license](http://github.com/sbt/junit-interface/blob/master/LICENSE.txt) | com.novocode # junit-interface # 0.11 | Used for testing
13 | Common Public License | [Common Public License Version 1.0](http://www.opensource.org/licenses/cpl1.0.txt) | junit # junit # 4.11 | Used for testing
14 |
15 |
--------------------------------------------------------------------------------
/math/src/main/scala/com/jsuereth/gl/math/UInt.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package math
19 |
20 | // TODO - move this somewhere it'll work.
21 | opaque type UInt = Int
22 |
23 | def (x: UInt) toLong: Long = x & 0xffffffffL
24 | def (x: UInt) + (y: UInt): UInt = x + y
25 | def (x: UInt) - (y: UInt): UInt = x - y
26 | def (x: UInt) * (y: UInt): UInt = x * y
27 | def (x: UInt) / (y: UInt): UInt = (x.toLong / y.toLong).toInt
28 | def (x: UInt) < (y: UInt): Boolean = x.toLong < y.toLong
29 | def (x: UInt) <= (y: UInt): Boolean = x.toLong <= y.toLong
30 | def (x: UInt) > (y: UInt): Boolean = x.toLong > y.toLong
31 | def (x: UInt) >= (y: UInt): Boolean = x.toLong >= y.toLong
32 | def (x: UInt) == (y: UInt): Boolean = x == y
33 | def (x: UInt) != (y: UInt): Boolean = x != y
34 |
35 | object UInt {
36 | def apply(x: Int): UInt = x
37 | def apply(x: Long): UInt = x.toInt
38 | }
--------------------------------------------------------------------------------
/io/src/test/scala/com/jsuereth/gl/io/TestShaderUniformLoadable.scala:
--------------------------------------------------------------------------------
1 | package com.jsuereth.gl
2 | package io
3 |
4 | import org.junit.Test
5 | import org.junit.Assert._
6 | import org.lwjgl.opengl.GL11.{GL_FLOAT, GL_INT, GL_SHORT, GL_BYTE,GL_DOUBLE}
7 |
8 | case class UniformExamplePod(one: math.Vec2[Int], two: math.Vec3[Float])
9 | case class UniformExamplePod2(one: math.Vec2[Int], two: math.Vec3[Float], three: math.Vec2[Float], four: math.Vec2[Float])
10 | case class UniformExamplePod3(one: math.Vec2[Int], two: UniformExamplePod)
11 |
12 | class TestUniformSize {
13 | import ShaderUniformLoadable.uniformSize
14 |
15 | @Test def sizeOfPrimitive(): Unit = {
16 | assertEquals(1, uniformSize[math.Vec2[Int]])
17 | assertEquals(1, uniformSize[math.Vec3[Float]])
18 | assertEquals(1, uniformSize[math.Matrix4x4[Float]])
19 | }
20 | @Test def sizeOfStruct(): Unit = {
21 | assertEquals(2, uniformSize[UniformExamplePod])
22 | // Recursive size test.
23 | assertEquals(3, uniformSize[UniformExamplePod3])
24 | assertEquals(4, uniformSize[UniformExamplePod2])
25 | }
26 |
27 | // The test here is if we compile. Calling OpenGL at this point will cause an error, since we have no gl context.
28 | @Test def deriveUnfiromCompiles(): Unit = {
29 | given testPod as ShaderUniformLoadable[UniformExamplePod] = ShaderUniformLoadable.derived[UniformExamplePod]
30 | // Test nested derivation.
31 | given testPod2 as ShaderUniformLoadable[UniformExamplePod3] = ShaderUniformLoadable.derived[UniformExamplePod3]
32 | }
33 | }
--------------------------------------------------------------------------------
/shader/third_party/third_party_licenses.md:
--------------------------------------------------------------------------------
1 | # third_party_licenses
2 |
3 | Category | License | Dependency | Notes
4 | --- | --- | --- | ---
5 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # io_0.16 # 0.1.0 |
6 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | com.jsuereth.shadyside # math_0.16 # 0.1.0 |
7 | Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-library # 2.12.8 |
8 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl # 3.2.2 | Lightweight Java Game Library
9 | BSD | [BSD](https://www.lwjgl.org/license) | org.lwjgl # lwjgl-opengl # 3.2.2 | Lightweight Java Game Library
10 | BSD | [BSD](https://github.com/sbt/test-interface/blob/master/LICENSE) | org.scala-sbt # test-interface # 1.0 |
11 | BSD | [BSD New](https://github.com/lampepfl/dotty/blob/master/LICENSE.md) | ch.epfl.lamp # dotty-library_0.16 # 0.16.0-RC3 |
12 | BSD | [New BSD License](http://www.opensource.org/licenses/bsd-license.php) | org.hamcrest # hamcrest-core # 1.3 |
13 | BSD | [Two-clause BSD-style license](http://github.com/sbt/junit-interface/blob/master/LICENSE.txt) | com.novocode # junit-interface # 0.11 | Used for testing
14 | Common Public License | [Common Public License Version 1.0](http://www.opensource.org/licenses/cpl1.0.txt) | junit # junit # 4.11 | Used for testing
15 |
16 |
--------------------------------------------------------------------------------
/shader/src/test/scala/com/jsuereth/gl/shaders/TestTextureUniformInShader.scala:
--------------------------------------------------------------------------------
1 | package com.jsuereth.gl
2 | package shaders
3 | package testtexture
4 |
5 | import org.junit.Test
6 | import org.junit.Assert._
7 |
8 | import com.jsuereth.gl.shaders._
9 | import com.jsuereth.gl.math._
10 | import com.jsuereth.gl.test.assertCleanEquals
11 | import com.jsuereth.gl.texture.Texture2D
12 |
13 | /** This is our target syntax. */
14 | // Attempt at cel-shading
15 | import com.jsuereth.gl.math.{given _, _}
16 |
17 | object TextureShader extends DslShaderProgram {
18 |
19 | // User for pixel shader / lighting model
20 | val diffuseTexture = Uniform[Texture2D]()
21 |
22 | val (vertexShaderCode, fragmentShaderCode) = defineShaders {
23 | val inPosition = Input[Vec3[Float]](location=0)
24 | glPosition(Vec4(inPosition, 1))
25 | // Fragment shader
26 | fragmentShader {
27 | Output("color", 0, diffuseTexture().texture(Vec2(0f,0f)))
28 | }
29 | }
30 | }
31 |
32 | class TestTextureShaders {
33 | @Test def createsCorrectVertexShader(): Unit = {
34 | assertCleanEquals(
35 | """#version 300 es
36 |
37 | precision highp float;
38 | precision highp int;
39 |
40 | layout (location = 0) in vec3 inPosition;
41 | void main() {
42 | gl_Position = vec4(inPosition,1.0);
43 | }""", TextureShader.vertexShaderCode)
44 | }
45 | @Test def createsCorrectFragmentShader(): Unit = {
46 | assertCleanEquals(
47 | """#version 300 es
48 |
49 | precision highp float;
50 | precision highp int;
51 |
52 | layout (location = 0) out vec4 color;
53 | uniform sampler2D diffuseTexture;
54 | void main() {
55 | color = texture(diffuseTexture,vec2(0.0,0.0));
56 | }""", TextureShader.fragmentShaderCode)
57 | }
58 | }
--------------------------------------------------------------------------------
/shader/src/test/scala/com/jsuereth/gl/shaders/TestMathTranslations.scala:
--------------------------------------------------------------------------------
1 | package com.jsuereth.gl
2 | package shaders
3 | package testmathtranslation
4 |
5 | import org.junit.Test
6 | import org.junit.Assert._
7 | import com.jsuereth.gl.math._
8 | import com.jsuereth.gl.test.assertCleanEquals
9 |
10 | /** This is our target syntax. */
11 | // Attempt at cel-shading
12 | import com.jsuereth.gl.math.{given _, _}
13 | object ExampleShader extends DslShaderProgram {
14 | // Used for vertex shader
15 | val lightPosition = Uniform[Vec3[Float]]()
16 | val eyePosition = Uniform[Vec3[Float]]()
17 |
18 | val (vertexShaderCode, fragmentShaderCode) = defineShaders {
19 | val inPosition = Input[Vec3[Float]](location=0)
20 | glPosition(Vec4(inPosition, 1))
21 | // Fragment shader
22 | fragmentShader {
23 | val L = pow(1f, max(min(0f, lightPosition().x), lightPosition().y))
24 | Output("color", 0, Vec4(L, L, L, 1f))
25 | }
26 | }
27 | }
28 |
29 | class Test1 {
30 | @Test def extractVertexShaderProgramString(): Unit = {
31 | assertCleanEquals(
32 | """#version 300 es
33 |
34 | precision highp float;
35 | precision highp int;
36 |
37 | layout (location = 0) in vec3 inPosition;
38 | void main() {
39 | gl_Position = vec4(inPosition,1.0);
40 | }""", ExampleShader.vertexShaderCode)
41 | }
42 | @Test def extractFragmentShaderProgramString(): Unit = {
43 | assertCleanEquals(
44 | """#version 300 es
45 |
46 | precision highp float;
47 | precision highp int;
48 |
49 | uniform vec3 lightPosition;
50 | layout (location = 0) out vec4 color;
51 | void main() {
52 | float L = pow(1.0,max(min(0.0,(lightPosition).x),(lightPosition).y));
53 | color = vec4(L,L,L,1.0);
54 | }""", ExampleShader.fragmentShaderCode)
55 | }
56 | }
--------------------------------------------------------------------------------
/scene/src/test/scala/com/jsuereth/gl/mesh/parser/TestMtlParser.scala:
--------------------------------------------------------------------------------
1 | package com.jsuereth.gl
2 | package mesh
3 | package parser
4 |
5 | import org.junit.Test
6 | import org.junit.Assert._
7 | import math._
8 |
9 | class TestMtlParser {
10 | def stream(in: String): java.io.InputStream =
11 | java.io.ByteArrayInputStream(in.getBytes)
12 |
13 | @Test def parseSimpleMaterial(): Unit = {
14 | val materials = MtlFileParser.parse(stream("""|
15 | |newmtl default
16 | |Ns 96.0
17 | |Ka 0.000000 0.000000 1.000000
18 | |Kd 0.640000 0.640000 0.640000
19 | |Ks 0.500000 0.500000 0.500000
20 | |Ni 1.000000
21 | |d 1.000000
22 | |illum 2
23 | |map_Kd blue-sky.jpg
24 | |""".stripMargin('|')))
25 |
26 | assertEquals(1, materials.size)
27 | val mtl = materials("default")
28 | assertEquals("Parse ambient color", Vec3(0f,0f,1f), mtl.base.ka)
29 | assertEquals("Parse diffuse color", Vec3(0.64f,0.64f,0.64f), mtl.base.kd)
30 | assertEquals("Parse specular color", Vec3(0.5f,0.5f,0.5f), mtl.base.ks)
31 | assertEquals("Parse specular component", 96.0f, mtl.base.ns, 0.0000001f)
32 | assertEquals("Parse diffuse texture file", Some("blue-sky.jpg"), mtl.textures.kd.map(_.filename))
33 | assertEquals("Not Parse specular texture file", None, mtl.textures.ks.map(_.filename))
34 | assertEquals("Not Parse ambient texture file", None, mtl.textures.ka.map(_.filename))
35 | assertEquals("Not Parse specular component texture file", None, mtl.textures.ns.map(_.filename))
36 | assertEquals("Not Parse d texture file", None, mtl.textures.d.map(_.filename))
37 | assertEquals("Not Parse bump map texture file", None, mtl.textures.bump.map(_.filename))
38 | }
39 | }
--------------------------------------------------------------------------------
/shader/src/test/scala/com/jsuereth/gl/shaders/TestStructUniformShader.scala:
--------------------------------------------------------------------------------
1 | import org.junit.Test
2 | import org.junit.Assert._
3 |
4 | import com.jsuereth.gl.shaders._
5 | import com.jsuereth.gl.math._
6 | import com.jsuereth.gl.io._
7 | import com.jsuereth.gl.test.assertCleanEquals
8 |
9 | /** This is our target syntax. */
10 | import com.jsuereth.gl.math.{given _, _}
11 |
12 | case class Material(kd: Float, ks: Float, color: Vec3[Float]) derives ShaderUniformLoadable
13 | object ExampleStructShader extends DslShaderProgram {
14 | val material = Uniform[Material]()
15 | val (vertexShaderCode, fragmentShaderCode) = defineShaders {
16 | val inPosition = Input[Vec3[Float]](location=0)
17 | glPosition(Vec4(inPosition, 1f))
18 | fragmentShader {
19 | Output("color", 0, Vec4(material().color, 1f))
20 | }
21 | }
22 | }
23 |
24 | class TestStructs {
25 | @Test def extractVertexShader(): Unit = {
26 | assertCleanEquals(
27 | """#version 300 es
28 |
29 | precision highp float;
30 | precision highp int;
31 |
32 | layout (location = 0) in vec3 inPosition;
33 | void main() {
34 | gl_Position = vec4(inPosition,1.0);
35 | }""", ExampleStructShader.vertexShaderCode)
36 | }
37 | @Test def extractPixelShader(): Unit = {
38 | assertCleanEquals(
39 | """#version 300 es
40 |
41 | precision highp float;
42 | precision highp int;
43 |
44 | struct Material {
45 | float kd;
46 | float ks;
47 | vec3 color;
48 | };
49 | layout (location = 0) out vec4 color;
50 | uniform Material material;
51 | void main() {
52 | color = vec4((material).color,1.0);
53 | }""", ExampleStructShader.fragmentShaderCode)
54 | // We should find the first value of the structure here,
55 | // since we do offset-based loading.
56 | assertEquals("material.kd", ExampleStructShader.material.name)
57 | }
58 | }
--------------------------------------------------------------------------------
/scene/src/main/scala/com/jsuereth/gl/scene/SceneObject.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package scene
19 |
20 | import mesh.Mesh3d
21 | import math.{given _, _}
22 | /**
23 | * Represents an object in the scene.
24 | * TODO - Define how to interact w/ objects, and how we render them.
25 | *
26 | * TODO - define as enum?
27 | */
28 | trait SceneObject {
29 | /** The mesh representing this object. */
30 | def mesh: Mesh3d
31 | /** The current position of hte object. */
32 | def pos: Vec3[Float]
33 | /** The orientation of the object. We use a quaternion to save space on rotational axes. */
34 | def orientation: Quaternion[Float]
35 | /** Scale of the mesh. */
36 | def scale: Vec3[Float]
37 | /** Returns the current model-matrix we can use to render this scene object. */
38 | def modelMatrix: Matrix4[Float] =
39 | Matrix4.translate[Float](pos.x, pos.y, pos.z) * orientation.toMatrix * Matrix4.scale(scale.x, scale.y, scale.z)
40 | }
41 | /**
42 | * A static, unomving object within the scene.
43 | */
44 | case class StaticSceneObject(mesh: Mesh3d, pos: Vec3[Float], orientation: Quaternion[Float] = Quaternion.identity, scale: Vec3[Float]) extends SceneObject
--------------------------------------------------------------------------------
/math/src/main/scala/com/jsuereth/gl/math/Vec2.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package math
19 |
20 | import scala.math.Numeric
21 | import scala.reflect.ClassTag
22 |
23 | class Vec2[T : ClassTag](private[this] val values: Array[T]) {
24 | def x: T = values(0)
25 | def y: T = values(1)
26 | def rg: Vec2[T] = this
27 | def xy: Vec2[T] = this
28 |
29 | def +(other: Vec2[T])(using Numeric[T]): Vec2[T] = Vec2(x+other.x, y+other.y)
30 | def -(other: Vec2[T])(using Numeric[T]): Vec2[T] = Vec2(x-other.x, y-other.y)
31 | // behaves as GLSL would, just multiplies pairs...
32 | def *(other: Vec2[T])(using Numeric[T]): Vec2[T] = Vec2(x*other.x, y*other.y)
33 |
34 | /** The dot product of this vector and another. */
35 | def dot(other: Vec2[T])(using Numeric[T]): T = x*other.x + y*other.y
36 |
37 | /** the square of the length of this vector. */
38 | def lengthSquared(using Numeric[T]): T = this dot this
39 | /** Returns the length of this vector. Requires a SQRT function. */
40 | def length(using Rootable[T], Numeric[T]): T = sqrt(lengthSquared)
41 |
42 | /** Normalizes this vector (setting distance to 1). Note: This requires a valid SQRT function. */
43 | def normalize(using Fractional[T], Rootable[T]): Vec2[T] =
44 | new Vec2[T](Array((x / length), (y / length)))
45 |
46 | override def toString: String = s"($x,$y)"
47 | }
48 | object Vec2 {
49 | def apply[T: ClassTag](x: T, y: T): Vec2[T] = new Vec2(Array(x,y))
50 | }
--------------------------------------------------------------------------------
/io/src/main/scala/com/jsuereth/gl/io/StackUtil.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.io
18 |
19 | import org.lwjgl.system.{
20 | MemoryStack,
21 | MemoryUtil
22 | }
23 | import java.nio.{
24 | ByteBuffer,
25 | FloatBuffer,
26 | IntBuffer
27 | }
28 |
29 | /** Ensures there is a memory stack available for an operation, popping the stack when complete. */
30 | inline def withMemoryStack[A](f: MemoryStack ?=> A): A = {
31 | val stack = org.lwjgl.system.MemoryStack.stackPush()
32 | try f(using stack)
33 | finally stack.pop()
34 | }
35 | /** Allocates a byte buffer. Will allocate on the stack, if small enough, otherwise in off-heap memory. */
36 | inline def withByteBuffer[A](size: Int)(f: ByteBuffer => A)(using stack: MemoryStack): A =
37 | if (size < 4096) f(stack.calloc(size))
38 | else {
39 | val buf = MemoryUtil.memAlloc(size)
40 | try f(buf) finally MemoryUtil.memFree(buf)
41 | }
42 | /** Allocates a float buffer. Will allocate on the stack, if small enough, otherwise in off-heap memory. */
43 | inline def withFloatBuffer[A](size: Int)(f: FloatBuffer => A)(using stack: MemoryStack): A =
44 | if (size < 1024) f(stack.callocFloat(size))
45 | else {
46 | val buf = MemoryUtil.memAllocFloat(size)
47 | try f(buf) finally MemoryUtil.memFree(buf)
48 | }
49 | /** Allocates an int buffer. Will allocate on the stack, if small enough, otherwise in off-heap memory. */
50 | inline def withIntBuffer[A](size: Int)(f: IntBuffer => A)(using stack: MemoryStack): A =
51 | if (size < 1024) f(stack.callocInt(size))
52 | else {
53 | val buf = MemoryUtil.memAllocInt(size)
54 | try f(buf) finally MemoryUtil.memFree(buf)
55 | }
--------------------------------------------------------------------------------
/example/src/main/resources/mesh/deep_space_1_11.mtl:
--------------------------------------------------------------------------------
1 | # Blender MTL File: 'Deep_Space_1_11.blend'
2 | # Provided from https://github.com/nasa/NASA-3D-Resources
3 | # Material Count: 8
4 |
5 | newmtl DS1_tex_01
6 | Ns 96.078431
7 | Ka 1.000000 1.000000 1.000000
8 | Kd 0.640000 0.640000 0.640000
9 | Ks 0.500000 0.500000 0.500000
10 | Ke 0.000000 0.000000 0.000000
11 | Ni 1.000000
12 | d 1.000000
13 | illum 2
14 | map_Kd texture/DS1_tex_01.psd
15 |
16 | newmtl None
17 | Ns 0
18 | Ka 0.000000 0.000000 0.000000
19 | Kd 0.8 0.8 0.8
20 | Ks 0.8 0.8 0.8
21 | d 1
22 | illum 2
23 |
24 | newmtl black_krinkle
25 | Ns 100.000000
26 | Ka 1.000000 1.000000 1.000000
27 | Kd 0.003917 0.003917 0.003917
28 | Ks 0.192187 0.192187 0.192187
29 | Ke 0.000000 0.000000 0.000000
30 | Ni 1.000000
31 | d 1.000000
32 | illum 2
33 | map_Kd foil_n.png
34 | map_Bump texture/foil_n.png
35 |
36 | newmtl foil_gold
37 | Ns 96.078431
38 | Ka 1.000000 1.000000 1.000000
39 | Kd 0.557647 0.412941 0.056471
40 | Ks 0.866667 0.850980 0.658824
41 | Ke 0.000000 0.000000 0.000000
42 | Ni 1.000000
43 | d 1.000000
44 | illum 2
45 | map_Kd foil_n.png
46 | map_Bump -bm 0.450000 texture/foil_n.png
47 | refl texture/foil_gold_ramp.png
48 |
49 | newmtl foil_gold_NONE
50 | Ns 96.078431
51 | Ka 1.000000 1.000000 1.000000
52 | Kd 0.557647 0.412941 0.056471
53 | Ks 0.866667 0.850980 0.658824
54 | Ke 0.000000 0.000000 0.000000
55 | Ni 1.000000
56 | d 1.000000
57 | illum 2
58 | map_Bump -bm 0.450000 texture/foil_n.png
59 | refl texture/foil_gold_ramp.png
60 |
61 | newmtl foil_silver
62 | Ns 96.078431
63 | Ka 1.000000 1.000000 1.000000
64 | Kd 0.578934 0.578934 0.578934
65 | Ks 1.000000 1.000000 1.000000
66 | Ke 0.000000 0.000000 0.000000
67 | Ni 1.000000
68 | d 1.000000
69 | illum 2
70 | map_Kd foil_n.png
71 | map_Bump -bm 0.450000 texture/foil_n.png
72 | refl texture/foil_silver_ramp.png
73 |
74 | newmtl foil_silver_NONE
75 | Ns 96.078431
76 | Ka 1.000000 1.000000 1.000000
77 | Kd 0.578934 0.578934 0.578934
78 | Ks 1.000000 1.000000 1.000000
79 | Ke 0.000000 0.000000 0.000000
80 | Ni 1.000000
81 | d 1.000000
82 | illum 2
83 | map_Bump -bm 0.450000 texture/foil_n.png
84 | refl texture/foil_silver_ramp.png
85 |
86 | newmtl shiny_panel
87 | Ns 96.078431
88 | Ka 1.000000 1.000000 1.000000
89 | Kd 0.640000 0.640000 0.640000
90 | Ks 0.500000 0.500000 0.500000
91 | Ke 0.000000 0.000000 0.000000
92 | Ni 1.000000
93 | d 1.000000
94 | illum 2
95 | map_Kd texture/DS1_tex_01.psd
96 |
--------------------------------------------------------------------------------
/math/src/main/scala/com/jsuereth/gl/math/SquareRootable.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.math
18 |
19 | /** Denotes that you can take "roots" for a type T.
20 | *
21 | * Effectively, this means we have the following functions:
22 | * - root(value, n)
23 | * - sqrt(value) // root(value,2)
24 | * - pow(n, value) // n^value
25 | */
26 | trait Rootable[T] {
27 | /** Computes the arbitrary root of a value. pow(root(value, base), base) == value */
28 | def root(value: T, base: T): T
29 | /** Computes the square root of a value. */
30 | def sqrt(value: T): T
31 | /** Exponential multiplication. base^exp. */
32 | def pow(base: T, exp: T): T
33 | }
34 |
35 | object Rootable
36 | given Rootable[Float] {
37 | // Approximation for arbitrary roots.
38 | def root(value: Float, base: Float): Float = pow(value, 1f / base)
39 | def sqrt(value: Float): Float = Math.sqrt(value.toDouble).toFloat
40 | def pow(base: Float, exp: Float): Float = Math.pow(base.toDouble, exp.toDouble).toFloat
41 | }
42 | given Rootable[Double] {
43 | // Approximation for arbitrary roots.
44 | def root(value: Double, base: Double): Double = Math.pow(Math.E, Math.log(base) / value.toDouble)
45 | def sqrt(value: Double): Double = Math.sqrt(value)
46 | def pow(base: Double, exp: Double): Double = Math.pow(base, exp)
47 | }
48 |
49 |
50 | /** package-level method to compute sqrt on any type that's Rootable. */
51 | def sqrt[T](value: T)(using r: Rootable[T]): T = r.sqrt(value)
52 | /** package-level method to compute a root on any type that's Rootable. */
53 | def root[T](value: T, base: T)(using r: Rootable[T]): T = r.root(value, base)
54 | /** package-level method to compute the power on any type that's Rootable. */
55 | def pow[T](base: T, exp: T)(using r: Rootable[T]): T = r.pow(base, exp)
--------------------------------------------------------------------------------
/math/src/test/scala/com/jsuereth/gl/math/math/TestVec3.scala:
--------------------------------------------------------------------------------
1 | package com.jsuereth.gl.math
2 |
3 | import org.junit.Test
4 | import org.junit.Assert._
5 |
6 | // TODO - test this w/ scalacheck.
7 | class TestVec3 {
8 |
9 | @Test
10 | def boolOperations(): Unit = {
11 | assertEquals(true, Vec3(true,false,false).x)
12 | assertEquals(true, Vec3(false,true,false).y)
13 | assertEquals(true, Vec3(false,false,true).z)
14 | }
15 |
16 | @Test
17 | def intOperations(): Unit = {
18 | assertEquals("length of vectors",3, Vec3(1, 1, 1).lengthSquared)
19 | assertEquals("add vectors", Vec3(-3, 1, 1), Vec3(-1, 0, 1) + Vec3(-2, 1, 0))
20 | assertEquals("Subtract vectors", Vec3(2, 1, 0), Vec3(3, 5, -1) - Vec3(1, 4, -1))
21 | assertEquals("Multiply vectors", Vec3(8, 16, 4), Vec3[Int](4, 8, 2) * 2)
22 | assertEquals("Dot product", 3, Vec3(1,3,-5) dot Vec3(4,-2,-1))
23 | assertEquals("Cross product", Vec3(-3,6,-3), Vec3(2,3,4) cross Vec3(5,6,7))
24 | assertEquals("negate", Vec3(-1,-2,-3), Vec3(1,2,3).negate)
25 | //assertEquals("normalize", Vec3(1,0,0), Vec3(8, 0, 0).normalize)
26 | // TODO - as/conversions.
27 | }
28 |
29 | @Test
30 | def floatOperations(): Unit = {
31 | val err = 0.0001f
32 | assertEquals("length of vectors",1.0f, Vec3(1.0f, 0.0f, 0.0f).lengthSquared, err)
33 | assertEquals("add vectors", Vec3(1.0f, 1.0f, 1.0f), Vec3(0.2f, 1.0f, 0.6f) + Vec3(0.8f, 0.0f, 0.4f))
34 | // TODO - delta comparison.
35 | assertEquals("Subtract vectors", Vec3(1.0f, 1.0f, 1.0f), Vec3(1.2f, 1.35f, 0.6f) - Vec3(0.2f, 0.35f, -0.4f))
36 | assertEquals("Multiply vectors", Vec3(2.0f, 4.0f, 1.0f), Vec3[Float](4.0f, 8.0f, 2.0f) * 0.5f)
37 | // TODO - dot
38 | // TODO - cross
39 | // TODO - negate
40 | assertEquals("normalize", Vec3(1.0f,0.0f,0.0f), Vec3(8.0f, 0.0f, 0.0f).normalize)
41 | // TODO - as/conversions.
42 | }
43 |
44 | @Test
45 | def doubleOperations(): Unit = {
46 | assertEquals("length of vectors",1.0, Vec3(1.0, 0.0, 0.0).lengthSquared, 0.01)
47 | assertEquals("add vectors", Vec3(1.0, 1.0, 1.0), Vec3(0.2, 1.0, 0.6) + Vec3(0.8, 0.0, 0.4))
48 | assertEquals("Subtract vectors", Vec3(1.0, 1.0, 1.0), Vec3(1.2, 1.3, 0.6) - Vec3(0.2, 0.3, -0.4))
49 | assertEquals("Multiply vectors", Vec3(2.0, 4.0, 1.0), Vec3[Double](4.0, 8.0, 2.0) * 0.5)
50 | // TODO - dot
51 | // TODO - cross
52 | // TODO - negate
53 | // TODO - normalize
54 | // TODO - as/conversions.
55 | }
56 |
57 | // TODO - Boolean vec.
58 | }
59 |
--------------------------------------------------------------------------------
/io/src/main/scala/com/jsuereth/gl/io/FrameBufferObject.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.io
18 |
19 | import org.lwjgl.opengl.GL30.{GL_FRAMEBUFFER,glBindFramebuffer,glGenFramebuffers,glDeleteFramebuffers}
20 |
21 | /** A frame buffer object that has been allocated in OpenGL.
22 | * You can load/store textures memory in FBOs.
23 | * Shader pipelines push data into these.
24 | */
25 | class FrameBufferObject private (override val id: Int) extends GLBuffer {
26 | def close(): Unit = glDeleteFramebuffers(id)
27 |
28 | // TODO - Allocation of frame-objects on this buffer, specifically Samplers/Textures.
29 |
30 | def bind(): Unit = glBindFramebuffer(GL_FRAMEBUFFER, id)
31 | def unbind(): Unit = glBindFramebuffer(GL_FRAMEBUFFER, 0)
32 |
33 | // TODO - make public
34 | // TODO - common error message.
35 | /** Validates whether or not this frame bufffer is in a consistent/usable state. */
36 | private def validate(): Unit = withBound {
37 | import org.lwjgl.opengl.GL30._
38 | glCheckFramebufferStatus(GL_FRAMEBUFFER) match {
39 | case GL_FRAMEBUFFER_COMPLETE => ()
40 | case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT => sys.error("Frame buffer incomplete attachment")
41 | case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER => sys.error("Frame buffer incomplete draw buffer")
42 | case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT => sys.error("Frame buffer incomplete missing attachment")
43 | case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER => sys.error("Frame buffer incomplete read buffer")
44 | case GL_FRAMEBUFFER_UNSUPPORTED => sys.error("Frame buffers are unsupported on this platform.")
45 | case id => sys.error(s"Unkown issue with framebuffer: $id")
46 | }
47 | }
48 |
49 | override def toString: String = s"VBO($id)"
50 | }
51 | object FrameBufferObject {
52 | /** Creates a new framebuffer we can use to allocate texture/frame objects. */
53 | def apply(): FrameBufferObject = new FrameBufferObject(glGenFramebuffers)
54 | }
--------------------------------------------------------------------------------
/math/src/main/scala/com/jsuereth/gl/math/Trigonometry.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.math
18 |
19 |
20 | /** Accepts a value in radians and returns the sine. */
21 | def sin[T](value: T)(using t: Trigonometry[T]): T = t.sin(value)
22 | /** Accepts a value in radians and returns the cosine. */
23 | def cos[T](value: T)(using t: Trigonometry[T]): T = t.cos(value)
24 | /** Accepts a value in radians and returns the tangent. */
25 | def tan[T](value: T)(using t: Trigonometry[T]): T = t.tan(value)
26 | /** Converts from Degrees to Radians for a given type. */
27 | def angleToRadians[T](value: T)(using t: Trigonometry[T]): T = t.angleToRadians(value)
28 |
29 |
30 | /** An implementation of trigonometry functions for a type. */
31 | trait Trigonometry[T] {
32 | /** Accepts a value in radians and returns the sine. */
33 | def sin(value: T): T
34 | /** Accepts a value in radians and returns the cosine. */
35 | def cos(value: T): T
36 | /** Accepts a value in radians and returns the tangent. */
37 | def tan(value: T): T
38 | // TODO - asin,acos,atan,atan2
39 | /** Converts from Degrees to Radians for a given type. */
40 | def angleToRadians(value: T): T
41 | }
42 |
43 | object Trigonometry
44 | // TODO - Fast lookup tables
45 | // For now we just provide java's implementation of trig.
46 | given JavaFloatTrig as Trigonometry[Float] {
47 | def sin(value: Float): Float = Math.sin(value.toDouble).toFloat
48 | def cos(value: Float): Float = Math.cos(value.toDouble).toFloat
49 | def tan(value: Float): Float = Math.tan(value.toDouble).toFloat
50 | private val conversionFactor = (scala.math.Pi / 180.0f).toFloat
51 | def angleToRadians(value: Float): Float = value * conversionFactor
52 | }
53 | given JavaDoubleTrig as Trigonometry[Double] {
54 | def sin(value: Double): Double = Math.sin(value)
55 | def cos(value: Double): Double = Math.cos(value)
56 | def tan(value: Double): Double = Math.tan(value)
57 | private val conversionFactor = (scala.math.Pi / 180.0f)
58 | def angleToRadians(value: Double): Double = value * conversionFactor
59 | }
--------------------------------------------------------------------------------
/scene/src/main/scala/com/jsuereth/gl/mesh/Material.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package mesh
19 |
20 | import math._
21 | import io._
22 |
23 | /** Defines the baseline material we will parse out of .obj/.mtl files. We will need to manipualte this before sending into OpenGL. */
24 | case class RawMaterial(name: String, base: BaseMaterial, textures: MaterialTextures)
25 |
26 | /** Definition of material that only includes color definitions, no texture maps. */
27 | case class BaseMaterial(
28 | /** Ambient Color */
29 | ka: Vec3[Float] = Vec3(0f,0f,0f),
30 | /** Diffuse Color */
31 | kd: Vec3[Float] = Vec3(0f,0f,0f),
32 | /** Specular Color */
33 | ks: Vec3[Float] = Vec3(0f,0f,0f),
34 | /** Specular exponent (shininess) */
35 | ns: Float = 1f
36 | // Ignoring transparency and illumination modes.
37 | // TODO - texture maps?
38 | ) derives BufferLoadable /*, ShaderUniformLoadable */
39 |
40 |
41 | // Defines all possible material textures we could parse.
42 | case class MaterialTextures(
43 | ka: Option[TextureReference] = None,
44 | kd: Option[TextureReference] = None,
45 | ks: Option[TextureReference] = None,
46 | ns: Option[TextureReference] = None,
47 | d: Option[TextureReference] = None,
48 | bump: Option[TextureReference] = None
49 | )
50 |
51 |
52 | /** A referernce to a texture, including texture options. */
53 | case class TextureReference(filename: String, options: TextureOptions)
54 | /** All the options .mtl allows for textures. We ignore almost all of this. */
55 | case class TextureOptions(
56 | horizontalBlend: Boolean = true,
57 | verticalBlend: Boolean = true,
58 | /** Boost mip-map sharpness. */
59 | boost: Option[Float] = None,
60 | /** Modify texture map values: base_value = brightness, gain_value = contrast. */
61 | modifyMap: (Float,Float) = (0f, 1f),
62 | originOffset: Vec3[Float] = Vec3(0f,0f,0f),
63 | scale: Vec3[Float] = Vec3(1f,1f,1f),
64 | turbulence: Vec3[Float] = Vec3(0f,0f,0f),
65 | resolution: Option[(Int,Int)] = None,
66 | clamp: Boolean = false,
67 | bumpMultiplier: Float = 1f
68 | // TODO image channel for bump-maps.
69 |
70 | )
--------------------------------------------------------------------------------
/math/src/main/scala/com/jsuereth/gl/math/Vec4.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package math
19 |
20 | import scala.math.Numeric
21 | import scala.reflect.ClassTag
22 |
23 | final class Vec4[T : ClassTag](private[this] val values: Array[T]) {
24 | def x: T = values(0)
25 | def y: T = values(1)
26 | def z: T = values(2)
27 | def w: T = values(3)
28 | def xy: Vec2[T] = new Vec2[T](values)
29 | def rg: Vec2[T] = new Vec2[T](values)
30 | def xyz: Vec3[T] = new Vec3[T](values)
31 | def rgb: Vec3[T] = new Vec3[T](values)
32 |
33 |
34 | def +(other: Vec4[T])(using Numeric[T]): Vec4[T] = Vec4(x+other.x, y+other.y, z+other.z, w+other.w)
35 | def -(other: Vec4[T])(using Numeric[T]): Vec4[T] = Vec4(x-other.x, y-other.y, z-other.z, w-other.w)
36 | // behaves as GLSL would, just multiplies pairs...
37 | def *(other: Vec4[T])(using Numeric[T]): Vec4[T] = Vec4(x*other.x, y*other.y, z*other.z, w*other.w)
38 | // TODO - we may need to disambiguate this from the above overload.
39 | def *(quant: T)(using Numeric[T]): Vec4[T] = Vec4(x*quant, y*quant, z*quant, w*quant)
40 | /** Computes the dot product of this vector with another. */
41 | def dot(other: Vec4[T])(using Numeric[T]): T =
42 | (x*other.x) + (y*other.y) + (z*other.z) + (w*other.w)
43 | // TODO: cross
44 |
45 | /** the square of the length of this vector. */
46 | def lengthSquared(using Numeric[T]): T = this dot this
47 | /** Returns the length of this vector. Requires a SQRT function. */
48 | def length(using Rootable[T], Numeric[T]): T = sqrt(lengthSquared)
49 |
50 | /** Normalizes this vector (setting distance to 1). Note: This requires a valid SQRT function. */
51 | def normalize(using Fractional[T], Rootable[T]): Vec4[T] = {
52 | val l = length
53 | new Vec4[T](Array((x / l), (y / l), (y / l), (w / l)))
54 | }
55 | override def toString: String = s"($x, $y, $z, $w)"
56 | }
57 | object Vec4 {
58 | def apply[T: ClassTag](x: T, y: T, z: T, w: T): Vec4[T] = new Vec4[T](Array(x,y,z, w))
59 | def apply[T: ClassTag](xy: Vec2[T], z: T, w: T): Vec4[T] = new Vec4[T](Array(xy.x, xy.y, z, w))
60 | def apply[T: ClassTag](xyz: Vec3[T], w: T): Vec4[T] = new Vec4[T](Array(xyz.x, xyz.y, xyz.z, w))
61 | }
62 |
--------------------------------------------------------------------------------
/scene/src/test/scala/com/jsuereth/gl/mesh/parser/TestObjParser.scala:
--------------------------------------------------------------------------------
1 | package com.jsuereth.gl
2 | package mesh
3 | package parser
4 |
5 | import org.junit.Test
6 | import org.junit.Assert._
7 | import math._
8 |
9 | class TestObjParser {
10 | def stream(in: String): java.io.InputStream =
11 | java.io.ByteArrayInputStream(in.getBytes)
12 |
13 | @Test def parseSimpleMesh(): Unit = {
14 | val meshes = ObjFileParser.parse(stream("""|
15 | |o test
16 | |v 1.0 0.0 1.0
17 | |v 0.0 0.0 0.0
18 | |v 1.0 0.0 0.0
19 | |f 1 2 3
20 | |""".stripMargin('|')))
21 |
22 | assertEquals(1, meshes.size)
23 | assertTrue(meshes.contains("test"))
24 | val mesh: Mesh3d = meshes("test")
25 | assertEquals(1, mesh.faces.size)
26 | // Texture coordinates are 0, normal is synthesized
27 | assertEquals(TriangleFace(FaceIndex(1,0,1), FaceIndex(2,0,2), FaceIndex(3,0,3)), mesh.faces.head)
28 | assertEquals(3, mesh.vertices.size)
29 | assertEquals("Parse first point", Vec3(1f,0f,1f), mesh.vertices(0))
30 | assertEquals("Parse second point", Vec3(0f,0f,0f), mesh.vertices(1))
31 | assertEquals("Parses third point", Vec3(1f,0f,0f), mesh.vertices(2))
32 | assertEquals(3, mesh.normals.size)
33 | assertEquals("Calculate first normal", Vec3(0f,-1f,0f), mesh.normals(0))
34 | assertEquals("Calculate second normal", Vec3(0f,-1f,0f), mesh.normals(1))
35 | assertEquals("Calculate third normal", Vec3(0f,-1f,0f), mesh.normals(2))
36 | }
37 | @Test def parseMultiObjectMesh(): Unit = {
38 | val meshes = ObjFileParser.parse(stream("""|
39 | |o test
40 | |o test2
41 | |""".stripMargin('|')))
42 |
43 | assertEquals(2, meshes.size)
44 | assertTrue(meshes.contains("test"))
45 | assertTrue(meshes.contains("test2"))
46 | }
47 | @Test def useGivenNormals(): Unit = {
48 | val meshes = ObjFileParser.parse(stream("""|
49 | |o test
50 | |v 1.0 0.0 1.0
51 | |v 0.0 0.0 1.0
52 | |v 1.0 0.0 1.0
53 | |vn 0.0 1.0 0.0
54 | |f 1//1 2//1 3//1
55 | |""".stripMargin('|')))
56 | assertEquals(1, meshes.size)
57 | assertTrue(meshes.contains("test"))
58 | val mesh: Mesh3d = meshes("test")
59 | assertEquals(TriangleFace(FaceIndex(1,0,1), FaceIndex(2,0,1), FaceIndex(3,0,1)), mesh.faces.head)
60 | assertEquals(3, mesh.vertices.size)
61 | assertEquals("Parse first point", Vec3(1f,0f,1f), mesh.vertices(0))
62 | assertEquals("Parse second point", Vec3(0f,0f,1f), mesh.vertices(1))
63 | assertEquals("Parses third point", Vec3(1f,0f,1f), mesh.vertices(2))
64 | assertEquals(1, mesh.normals.size)
65 | assertEquals("Parse only normal", Vec3(0f,1f,0f), mesh.normals(0))
66 | }
67 | }
--------------------------------------------------------------------------------
/math/src/main/scala/com/jsuereth/gl/math/Matrix3.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package math
19 |
20 | import scala.reflect.ClassTag
21 |
22 | // Aliases
23 | type Matrix3[T] = Matrix3x3[T]
24 | val Matrix3 = Matrix3x3
25 |
26 | /** A matrix library for floating point values, meant to be relatively efficient and the analog of GLSL matrix. */
27 | class Matrix3x3[T : ClassTag](val values: Array[T]) {
28 | // row-column formats
29 | def m11: T = values(0)
30 | def m21: T = values(1)
31 | def m31: T = values(2)
32 | def m12: T = values(3)
33 | def m22: T = values(4)
34 | def m32: T = values(5)
35 | def m13: T = values(6)
36 | def m23: T = values(7)
37 | def m33: T = values(8)
38 |
39 |
40 | def apply(row: Int)(col: Int): T =
41 | values(row + (col*3))
42 |
43 | def *(scale: T)(using Numeric[T]): Matrix3x3[T] =
44 | Matrix3x3(values map (_ * scale))
45 |
46 | def *(vec: Vec3[T])(using Numeric[T]): Vec3[T] = {
47 | import vec.{x,y,z}
48 | val newX = m11*x + m12*y + m13*z
49 | val newY = m21*x + m22*y + m23*z
50 | val newZ = m31*x + m32*y + m33*z
51 | Vec3(newX, newY, newZ)
52 | }
53 | def *(other: Matrix3x3[T])(using Numeric[T]): Matrix3x3[T] = {
54 | // TODO - attempt to use Coppersmith–Winograd algorithm?
55 | // For now we do this naively, which is possibly more efficeint.
56 | val next = new Array[T](9)
57 | def idx(row: Int, col: Int): Int = row + (col*3)
58 | // TODO - Should we unroll this?
59 | for {
60 | i <- 0 until 3
61 | j <- 0 until 3
62 | } next(idx(i,j)) = (for {
63 | k <- 0 until 3
64 | } yield apply(i)(k) * other(k)(j)).sum
65 | Matrix3x3(next)
66 | }
67 | // TODO - Derive "show" or "print" across all numbers/formats once we have a shapeless release.
68 | override def toString: String = {
69 | def f(t: T): String = t.toString // TODO - use fixed-format number width...
70 | s""":|${f(m11)}|${f(m12)}|${f(m13)}|
71 | :|${f(m21)}|${f(m22)}|${f(m23)}|
72 | :|${f(m31)}|${f(m32)}|${f(m33)}|""".stripMargin(':')
73 | }
74 | }
75 | object Matrix3x3 {
76 | def identity[T: ClassTag : Numeric]: Matrix3x3[T] =
77 | new Matrix3x3(Array(
78 | one, zero, zero,
79 | zero, one, zero,
80 | zero, zero, one,
81 | ))
82 | }
--------------------------------------------------------------------------------
/io/src/test/scala/com/jsuereth/gl/io/TestVaoAttribute.scala:
--------------------------------------------------------------------------------
1 | package com.jsuereth.gl
2 | package io
3 |
4 | import org.junit.Test
5 | import org.junit.Assert._
6 | import org.lwjgl.opengl.GL11.{GL_FLOAT, GL_INT, GL_SHORT, GL_BYTE,GL_DOUBLE}
7 |
8 | case class ExamplePod(one: math.Vec2[Int], two: math.Vec3[Float])
9 | case class ExamplePod2(one: math.Vec2[Int], two: math.Vec3[Float], three: math.Vec2[Float], four: math.Vec2[Float])
10 |
11 | class TestSize {
12 | @Test def sizeOfPrimitive(): Unit = {
13 | assertEquals(2, sizeOf[Short])
14 | assertEquals(4, sizeOf[Float])
15 | assertEquals(4, sizeOf[Int])
16 | assertEquals(8, sizeOf[Double])
17 | assertEquals(8, sizeOf[Long])
18 | }
19 | @Test def sizeOfGlTypes(): Unit = {
20 | assertEquals(3, sizeOf[math.Vec3[Boolean]])
21 | assertEquals(12, sizeOf[math.Vec3[Float]])
22 | assertEquals(36, sizeOf[math.Matrix3x3[Int]])
23 | assertEquals(16, sizeOf[math.Vec2[Double]])
24 | assertEquals(8*16, sizeOf[math.Matrix4x4[Long]])
25 | assertEquals(16, sizeOf[math.Vec4[Int]])
26 | }
27 | @Test def sizeofPod(): Unit = {
28 | assertEquals(20, sizeOf[ExamplePod])
29 | assertEquals(36, sizeOf[ExamplePod2])
30 | }
31 | }
32 |
33 | class TestGlType {
34 | @Test def calculateTypeOfPrimative(): Unit = {
35 | assertEquals(GL_FLOAT, glType[Float])
36 | assertEquals(GL_INT, glType[Int])
37 | assertEquals(GL_SHORT, glType[Short])
38 | assertEquals(GL_BYTE, glType[Byte])
39 | assertEquals(GL_BYTE, glType[Boolean])
40 | assertEquals(GL_DOUBLE, glType[Double])
41 | }
42 | @Test def calculateTypeOfGlSlTypes(): Unit = {
43 | assertEquals(GL_FLOAT, glType[math.Vec3[Float]])
44 | assertEquals(GL_INT, glType[math.Matrix4[Int]])
45 | }
46 | }
47 |
48 | class TestGlSize {
49 | @Test def calculateGlSizeOfPrimative(): Unit = {
50 | assertEquals(1, glSize[Float])
51 | assertEquals(1, glSize[Int])
52 | }
53 | @Test def calculateGlSizeOfGlSlTypes(): Unit = {
54 | assertEquals(2, glSize[math.Vec2[Boolean]])
55 | assertEquals(3, glSize[math.Vec3[Float]])
56 | assertEquals(4, glSize[math.Vec4[Int]])
57 | assertEquals(9, glSize[math.Matrix3[Float]])
58 | assertEquals(16, glSize[math.Matrix4[Int]])
59 | }
60 | }
61 |
62 | class TestVaoAttribute {
63 | @Test def calculateVaoAttributes(): Unit = {
64 | val Array(one,two) = vaoAttributes[ExamplePod]
65 | assertEquals(one, VaoAttribute(0, 2, GL_INT, 20, 0))
66 | assertEquals(two, VaoAttribute(1, 3, GL_FLOAT, 20, 8))
67 | }
68 | @Test def calculateVaoAttributes2(): Unit = {
69 | val Array(one,two,three,four) = vaoAttributes[ExamplePod2]
70 | assertEquals(one, VaoAttribute(0, 2, GL_INT, 36, 0))
71 | assertEquals(two, VaoAttribute(1, 3, GL_FLOAT, 36, 8))
72 | assertEquals(three, VaoAttribute(2, 2, GL_FLOAT, 36, 20))
73 | assertEquals(four, VaoAttribute(3, 2, GL_FLOAT, 36, 28))
74 | }
75 | }
--------------------------------------------------------------------------------
/scene/src/main/scala/com/jsuereth/gl/scene/Scene.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package scene
19 |
20 | import math.{Vec3, Quaternion}
21 | import collection.mutable.ArrayBuffer
22 | import mesh.Mesh3d
23 | /**
24 | * Defines the base of a renderable scene.
25 | *
26 | * A scene can be more complicated than this base trait, but here are the absolute minimal characteristics.
27 | */
28 | trait Scene {
29 | /** The camera to use for this scene. */
30 | def camera: Camera
31 | // TODO - figure out what kind of light abstraction we want to show off different shaders...
32 | /** Position of all lights in the scene (if multiple). */
33 | def lights: Iterator[Vec3[Float]]
34 | /** Objects to render in the scene. */
35 | def objectsInRenderOrder: Iterator[SceneObject]
36 | // TODO - def updateSimulation, etc....
37 | }
38 |
39 | /** A simple static scene that contains a few objects. */
40 | final class SimpleStaticScene(
41 | override val camera: Camera,
42 | val light: Vec3[Float],
43 | val objects: Seq[SceneObject]
44 | ) extends Scene {
45 | override def lights: Iterator[Vec3[Float]] = Iterator(light)
46 | override def objectsInRenderOrder: Iterator[SceneObject] = objects.iterator
47 | }
48 |
49 | /** Scene builder syntax. */
50 | class SimpleStaticSceneBuilder {
51 | private var c = Camera()
52 | private var l = Vec3(0f, 10f, 0f)
53 | private val os = ArrayBuffer[SceneObject]()
54 |
55 | def light(location: Vec3[Float]): this.type = {
56 | this.l = location
57 | this
58 | }
59 |
60 | def camera(c: Camera): this.type = {
61 | this.c = c
62 | this
63 | }
64 |
65 | def add(obj: SceneObject): this.type = {
66 | this.os += obj
67 | this
68 | }
69 | def add(mesh: Mesh3d): ObjBuilder = ObjBuilder(mesh)
70 |
71 | class ObjBuilder(mesh: Mesh3d) {
72 | private[this] var p = Vec3(0f,0f,0f)
73 | private[this] var o = Quaternion.identity[Float]
74 | private[this] var s = Vec3(1f,1f,1f)
75 | def pos(pos: Vec3[Float]): this.type = {
76 | this.p = pos
77 | this
78 | }
79 | def orientation(o: Quaternion[Float]): this.type = {
80 | this.o = o
81 | this
82 | }
83 | def scale(x: Float, y: Float, z: Float): this.type = {
84 | this.s = Vec3(x,y,z)
85 | this
86 | }
87 | def done(): SimpleStaticSceneBuilder = {
88 | add(StaticSceneObject(mesh, p, o, s))
89 | }
90 | }
91 |
92 | def done(): Scene = SimpleStaticScene(c, l, os.toSeq)
93 | }
--------------------------------------------------------------------------------
/io/src/main/scala/com/jsuereth/gl/texture/TextureParameter.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package texture
19 |
20 | import org.lwjgl.opengl.GL11.{
21 | glTexParameteri,
22 | GL_TEXTURE_2D,
23 | GL_CLAMP,
24 | GL_REPEAT,
25 | GL_NEAREST,
26 | GL_LINEAR,
27 | GL_TEXTURE_WRAP_S,
28 | GL_TEXTURE_WRAP_T,
29 | GL_TEXTURE_MAG_FILTER,
30 | GL_TEXTURE_MIN_FILTER,
31 | GL_NEAREST_MIPMAP_NEAREST,
32 | GL_NEAREST_MIPMAP_LINEAR,
33 | GL_LINEAR_MIPMAP_LINEAR,
34 | GL_LINEAR_MIPMAP_NEAREST
35 | }
36 | import org.lwjgl.opengl.GL14.{
37 | GL_MIRRORED_REPEAT
38 | }
39 |
40 | /** Controls how textures wrap in OpenGL. */
41 | enum TextureWrapType {
42 | case ClampToEdge
43 | case MirroredRepeat
44 | case Repeat
45 |
46 | def toGL: Int = this match {
47 | case ClampToEdge => GL_CLAMP
48 | case MirroredRepeat => GL_MIRRORED_REPEAT
49 | case Repeat => GL_REPEAT
50 | }
51 | }
52 |
53 | /** Controls how we grab pixel values from textures when we don't have 1:1 mapping from point-to-texture coord. */
54 | enum TextureFilterType {
55 | case Nearest, Linear
56 |
57 | def toGL: Int = this match {
58 | case Nearest => GL_NEAREST
59 | case Linear => GL_LINEAR
60 | }
61 | }
62 | /** Defines a parameter for an OpenGL texture, as well as the ability to set that parameter. */
63 | enum TextureParameter {
64 | case WrapS(tpe: TextureWrapType)
65 | case WrapT(tpe: TextureWrapType)
66 | case MagFilter(tpe: TextureFilterType)
67 | case MinFilter(tpe: TextureFilterType, mipMap: Option[TextureFilterType] = None)
68 |
69 | import TextureFilterType.{Nearest,Linear}
70 | private def toMipMapTpe(tpe: TextureFilterType, mipMap: TextureFilterType): Int = (tpe, mipMap) match {
71 | case (Nearest, Nearest) => GL_NEAREST_MIPMAP_NEAREST
72 | case (Nearest, Linear) => GL_NEAREST_MIPMAP_LINEAR
73 | case (Linear, Linear) => GL_LINEAR_MIPMAP_LINEAR
74 | case (Linear, Nearest) => GL_LINEAR_MIPMAP_NEAREST
75 | }
76 |
77 | /** Call the appropriate glTextParameteri() function for this texture parameter. */
78 | def set(target: Int = GL_TEXTURE_2D): Unit = this match {
79 | case WrapS(tpe) => glTexParameteri(target, GL_TEXTURE_WRAP_S, tpe.toGL)
80 | case WrapT(tpe) => glTexParameteri(target, GL_TEXTURE_WRAP_T, tpe.toGL)
81 | case MagFilter(tpe) => glTexParameteri(target, GL_TEXTURE_MAG_FILTER, tpe.toGL)
82 | case MinFilter(tpe, None) => glTexParameteri(target, GL_TEXTURE_MAG_FILTER, tpe.toGL)
83 | case MinFilter(tpe, Some(m)) => glTexParameteri(target, GL_TEXTURE_MIN_FILTER, toMipMapTpe(tpe, m))
84 | }
85 | }
--------------------------------------------------------------------------------
/scene/src/main/scala/com/jsuereth/gl/scene/Camera.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package scene
19 |
20 | import math.{given _, _}
21 |
22 | /**
23 | * Defines a camera for a scene.
24 | *
25 | * This class allows simple "camera" operations as if you are a first-person viewer, like "turnRight", "moveForward".
26 | * It defines itself according to three basic vectors: eye position, up direction and focus position.
27 | * Using this when rendering requires converting to a ViewMatrix and loading into a shader.
28 | */
29 | class Camera {
30 | // TODO - better defaults...
31 | private var eye = Vec3[Float](0.0f, 0.0f, -10f) // Location of the camera
32 | private var up = Vec3[Float](0.0f, 1.0f, 0.0f) // The up vector.
33 | private var focus = Vec3[Float](0.0f, 0.0f, 0.0f) // Center of the camera foucs
34 | /** Returns the current position of the eye. */
35 | def eyePosition: Vec3[Float] = eye
36 |
37 | // The direction we're looking...
38 | private def lookDirection = (focus - eye).normalize
39 | // The direction to the right.
40 | private def rightDirection = (lookDirection cross up).normalize
41 | // TODO - Calculate the right/left direction
42 |
43 | def reset( eye: Vec3[Float] = Vec3(-0.5f, 5.0f, -0.5f),
44 | up: Vec3[Float] = Vec3(0.0f, 1.0f, 0.0f),
45 | focus: Vec3[Float] = Vec3(0.0f, 0.0f, 0.0f)): Unit = {
46 | this.eye = eye
47 | this.up = up
48 | this.focus = focus
49 | }
50 |
51 | def moveRight(amount: Float): Unit = {
52 | val mover = rightDirection * amount
53 | eye += mover
54 | focus += mover
55 | println(this)
56 | }
57 |
58 | def turnRight(amount: Float): Unit = {
59 | val rot = Quaternion.rotation(-amount, up)
60 | focus = eye + (rot * (focus-eye))
61 | println(this)
62 | }
63 |
64 | def turnUp(amount: Float): Unit = {
65 | val rot = Quaternion.rotation(amount, rightDirection)
66 | focus = eye + (rot * (focus-eye))
67 | println(this)
68 | }
69 |
70 |
71 | def moveForward(amount: Float): Unit = {
72 | val mover = lookDirection * amount
73 | focus += mover
74 | eye += mover
75 | println(this)
76 | }
77 |
78 | def moveUp(amount: Float): Unit = {
79 | val mover = up * amount
80 | eye += mover
81 | focus += mover
82 | println(this)
83 | }
84 |
85 | def zoom(amount: Float): Unit = {
86 | // TODO - Scale this differently...
87 | focus = focus + (lookDirection * amount)
88 | }
89 |
90 | def viewMatrix: Matrix4x4[Float] = Matrix4.lookAt(focus, eye, up)
91 |
92 | override def toString: String =
93 | s"""|Camera Positions
94 | |- eye: $eye
95 | |- focus: $focus
96 | |- up: $up""".stripMargin
97 | }
--------------------------------------------------------------------------------
/example/src/main/scala/CartonShader.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import com.jsuereth.gl.shaders._
18 | import com.jsuereth.gl.math.{given _, _}
19 | import com.jsuereth.gl.texture.Texture2D
20 | import com.jsuereth.gl.io.ShaderUniformLoadable
21 |
22 | case class WorldData(light: Vec3[Float], eye: Vec3[Float], view: Matrix4[Float], projection: Matrix4[Float]) derives ShaderUniformLoadable
23 |
24 | object CartoonShader extends DslShaderProgram {
25 | // Hack to get structures to work. OpenGL spec + reality may not line up on how to look up structure ids. Need to rethink
26 | // ShaderUniformLoadable class...
27 | val world = Uniform[WorldData]()
28 | val modelMatrix = Uniform[Matrix4[Float]]()
29 | val materialShininess = Uniform[Float]()
30 | val materialKd = Uniform[Float]()
31 | val materialKs = Uniform[Float]()
32 | // Textures in lighting model.
33 | val materialKdTexture = Uniform[Texture2D]()
34 |
35 | val (vertexShaderCode, fragmentShaderCode) = defineShaders {
36 | val inPosition = Input[Vec3[Float]](location=0)
37 | val inNormal = Input[Vec3[Float]](location=1)
38 | val texPosition = Input[Vec2[Float]](location=2)
39 | // TODO - convert to matrix3...
40 | val worldPos = (modelMatrix() * Vec4(inPosition, 1)).xyz
41 | val worldNormal = (modelMatrix() * Vec4(inNormal, 0)).xyz
42 | // Note: We have to do this dance to feed this into fragment shader.
43 | val texCoord: Vec2[Float] = texPosition
44 | glPosition(world().projection * world().view * modelMatrix() * Vec4(inPosition, 1))
45 | // Fragment shader
46 | fragmentShader {
47 | val L = (world().light - worldPos).normalize
48 | val N = worldNormal.normalize
49 | val lambertian = Math.max(L.dot(N), 0.0f).toFloat
50 | val V = (world().eye - worldPos).normalize
51 | val H = (L + V).normalize
52 | val diffuse = materialKd() * lambertian
53 | val specular =
54 | if (lambertian > 0.0f) materialKs() * Math.pow(Math.max(0, H.dot(N)).toFloat, materialShininess()).toFloat
55 | else 0.0f
56 | //Black color if dot product is smaller than 0.3
57 | //else keep the same colors
58 | val edgeDetection = if (V.dot(worldNormal) > 0.3f) 1f else 0.7f
59 | val texColor = materialKdTexture().texture(texCoord).xyz
60 | // TODO - add ambient light?
61 | val light = (((texColor*diffuse) + (texColor*specular)) * edgeDetection)
62 | Output("color", 0, Vec4(light, 1f))
63 | }
64 | }
65 | }
66 |
67 |
68 | object SimpleShader extends DslShaderProgram {
69 | val (vertexShaderCode, fragmentShaderCode) = defineShaders {
70 | val position = Input[Vec3[Float]](location = 0)
71 | glPosition(Vec4(position, 1.0f))
72 | fragmentShader {
73 | Output("color", 0, Vec4(0.0f, 0.5f, 0.5f, 1.0f))
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/io/src/main/scala/com/jsuereth/gl/io/ActiveTextures.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package io
19 |
20 | import org.lwjgl.opengl.GL11.glGetInteger
21 | import org.lwjgl.opengl.GL13.{
22 | GL_TEXTURE0,
23 | glActiveTexture
24 | }
25 | import org.lwjgl.opengl.GL20.{
26 | glUniform1i,
27 | GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
28 | }
29 | import scala.collection.mutable.ArrayBuffer
30 |
31 | /** A type representing an ActiveTextureIndex. Normally in opengl these are GL_TEXTURE* enums. */
32 | opaque type ActiveTextureIndex = Int
33 |
34 | /** Convenience method to turn an index into a GL_TEXTUREN enuma nd call glActiveTexture. */
35 | def activate(idx: ActiveTextureIndex): Unit = glActiveTexture(GL_TEXTURE0 + idx)
36 | /** Convenience method to bind the index of a texture to a uniform variable. */
37 | def loadTextureUniform(location: Int, idx: ActiveTextureIndex): Unit = glUniform1i(location, unwrap(idx))
38 | /** The maximum value of active indicies. */
39 | def MaxActiveIndex: ActiveTextureIndex = glGetInteger(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)
40 |
41 | // Internal helper to lift things into/out of opque type.
42 | private def unwrap(idx: ActiveTextureIndex): Int = idx
43 | private def wrap(idx: Int): ActiveTextureIndex = idx
44 |
45 | /** This interfaces describes which textures are bound, and gives access to binding IDs
46 | * that can be used to bind more textures.
47 | *
48 | * This is a mutable interface, as we expect it to be passe dthrough shader loadable context.
49 | */
50 | trait ActiveTextures {
51 | /** Grabs the next available texture index. */
52 | def acquire(): ActiveTextureIndex
53 | /** Returns a texture index in this call. */
54 | def release(idx: ActiveTextureIndex): Unit
55 |
56 | /** Acquires an active texture for a task,t hen releases it. */
57 | inline def withNextActiveIndex[A](f: ActiveTextureIndex => A): A = {
58 | val idx = acquire()
59 | try f(idx)
60 | finally release(idx)
61 | }
62 | /** Create a new "record" of what textures were used. */
63 | def push(): Unit
64 | /** Automatically release all textures used since last push. */
65 | def pop(): Unit
66 | }
67 | object ActiveTextures {
68 | def apply(maxIndex: Int = unwrap(MaxActiveIndex)): ActiveTextures = SimpleActiveTextures(maxIndex)
69 | }
70 |
71 | private class SimpleActiveTextures(maxIndex: Int) extends ActiveTextures {
72 | private var depth: Int = 0
73 | private val MaxDepth = 100
74 | private val used: ArrayBuffer[Int] = (0 until maxIndex).map(_ => 100).to(ArrayBuffer)
75 | /** Create a new "record" of what textures were used. */
76 | def push(): Unit = depth = Math.max(MaxDepth, depth+1)
77 | /** Open up all textures for usage from the last "pop" */
78 | def pop(): Unit = depth = Math.max(0, depth-1)
79 | def release(idx: ActiveTextureIndex): Unit = used(unwrap(idx)) = MaxDepth
80 | // TODO - ring buffer impl, something faster?
81 | def acquire(): ActiveTextureIndex =
82 | used.iterator.zipWithIndex.find((value, idx) => value > depth) match {
83 | case Some((_, idx)) =>
84 | used(idx) = depth
85 | wrap(idx)
86 | case None => throw RuntimeException("No more textures available!")
87 | }
88 | }
--------------------------------------------------------------------------------
/math/src/main/scala/com/jsuereth/gl/math/Vec3.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package math
19 |
20 | import scala.math.Numeric
21 | import scala.reflect.ClassTag
22 |
23 | // TODO - look into making this an opaque type w/ wrappers. We give up == + toString...
24 | // TODO - we probably need to provide our own set of numeric classes for this use case.
25 | final class Vec3[T : ClassTag](private[this] val values: Array[T]) {
26 | /** The x coordinate value. */
27 | def x: T = values(0)
28 | /** The y coordinate value. */
29 | def y: T = values(1)
30 | /** The z coordinate value. */
31 | def z: T = values(2)
32 |
33 | /** Piecewise sum this vector with another. */
34 | def +(other: Vec3[T])(using Numeric[T]): Vec3[T] = Vec3(x+other.x, y+other.y, z+other.z)
35 | /** Piecewise subtract this vector with another. */
36 | def -(other: Vec3[T])(using Numeric[T]): Vec3[T] = Vec3(x-other.x, y-other.y, z-other.z)
37 | /** Piecewise multiple this vector with another. */
38 | def *(other: Vec3[T])(using Numeric[T]): Vec3[T] = Vec3[T](x*other.x, y*other.y, z*other.z)
39 | /** Multiple a given value against each element of the vector. */
40 | def *(quant: T)(using Numeric[T]): Vec3[T] = Vec3(x*quant, y*quant, z*quant)
41 |
42 |
43 | /** Negates the value of this vector. */
44 | def negate(using Numeric[T]): Vec3[T] = Vec3(-x,-y,-z)
45 |
46 | /** Computes the dot product of this vector with another. */
47 | def dot(other: Vec3[T])(using Numeric[T]): T =
48 | (x*other.x) + (y*other.y) + (z*other.z)
49 |
50 | /** Cross product with another vector. */
51 | def cross(other: Vec3[T])(using Numeric[T]): Vec3[T] =
52 | Vec3(
53 | (y*other.z) - (z*other.y),
54 | (z*other.x) - (x*other.z),
55 | (x*other.y) - (y*other.x)
56 | )
57 |
58 | /** Returns the square of the length of this vector. More efficient to compute than length. */
59 | def lengthSquared(using Numeric[T]): T = this dot this
60 | /** Returns the length of this vector. Requires a SQRT function. */
61 | def length(using Rootable[T], Numeric[T]): T = sqrt(lengthSquared)
62 |
63 | /** Normalizes this vector. Note: This requires a valid SQRT function. */
64 | def normalize(using Fractional[T], Rootable[T]): Vec3[T] =
65 | new Vec3[T](Array((x / length), (y / length), (z / length)))
66 |
67 |
68 | // GLSL compat
69 | def xy: Vec2[T] = new Vec2[T](values)
70 | def xyz: Vec3[T] = this
71 | def rg: Vec2[T] = new Vec2[T](values)
72 | def rgb: Vec3[T] = this
73 |
74 |
75 | // TODO - Convert/cast between types. Should piece-wise convert.
76 | def as[U : ClassTag]: Vec3[U] = ???
77 |
78 | override def toString: String = s"($x,$y,$z)"
79 | override def equals(o: Any): Boolean =
80 | o match {
81 | case other: Vec3[T] => (x == other.x) && (y == other.y) && (z == other.z)
82 | case _ => false
83 | }
84 |
85 | // TODO - hashcode the contents of the array.
86 | override def hashCode(): Int = super.hashCode()
87 | }
88 | object Vec3 {
89 | def apply[T: ClassTag](x: T, y: T, z: T): Vec3[T] = new Vec3[T](Array(x,y,z))
90 | def apply[T: ClassTag](xy: Vec2[T], z: T): Vec3[T] = new Vec3[T](Array(xy.x, xy.y, z))
91 | // TODO - type-generic versions?
92 | def zero = Vec3(0,0,0)
93 | def up = Vec3(0,1,0)
94 | def down = Vec3(0,-1,0)
95 | def right = Vec3(1,0,0)
96 | def left = Vec3(-1,0,0)
97 | def forward = Vec3(0,0,-1)
98 | def back = Vec3(0,0,1)
99 | }
--------------------------------------------------------------------------------
/shader/src/test/scala/com/jsuereth/gl/shaders/TestShader.scala:
--------------------------------------------------------------------------------
1 | import org.junit.Test
2 | import org.junit.Assert._
3 |
4 | import com.jsuereth.gl.shaders._
5 | import com.jsuereth.gl.math._
6 | import com.jsuereth.gl.io._
7 | import com.jsuereth.gl.test.assertCleanEquals
8 |
9 | /** This is our target syntax. */
10 | // Attempt at cel-shading
11 | import com.jsuereth.gl.math.{given _, _}
12 | object ExampleCartoonShader extends DslShaderProgram {
13 | // Used for vertex shader
14 | val modelMatrix = Uniform[Matrix4[Float]]()
15 | val viewMatrix = Uniform[Matrix4[Float]]()
16 | val projectionMatrix = Uniform[Matrix4[Float]]()
17 |
18 | // User for pixel shader / lighting model
19 | val lightPosition = Uniform[Vec3[Float]]()
20 | val eyePosition = Uniform[Vec3[Float]]()
21 | val materialShininess = Uniform[Int]()
22 | val materialKd = Uniform[Float]()
23 | val materialKs = Uniform[Float]()
24 |
25 | val (vertexShaderCode, fragmentShaderCode) = defineShaders {
26 | val inPosition = Input[Vec3[Float]](location=0)
27 | val inNormal = Input[Vec3[Float]](location=1)
28 | // TODO - convert to matrix3...
29 | val worldPos = (modelMatrix() * Vec4(inPosition, 1)).xyz
30 | val worldNormal = (modelMatrix() * Vec4(inNormal, 0)).xyz
31 |
32 | glPosition(projectionMatrix() * viewMatrix() * modelMatrix() * Vec4(inPosition, 1))
33 | // Fragment shader
34 | fragmentShader {
35 | val L = (lightPosition() - worldPos).normalize
36 | val V = (eyePosition() - worldPos).normalize
37 | val H = (L + V).normalize
38 | val diffuse = materialKd() * Math.max(0, L.dot(worldNormal))
39 | val specular =
40 | if (L.dot(worldNormal) > 0.0f) materialKs() * Math.pow(Math.max(0, H.dot(worldNormal)).toFloat, materialShininess()).toFloat
41 | else 0.0f
42 | //Black color if dot product is smaller than 0.3
43 | //else keep the same colors
44 | val edgeDetection = if (V.dot(worldNormal) > 0.3f) 1f else 0f
45 | val light = edgeDetection * (diffuse + specular)
46 | Output("color", 0, Vec4(light, light, light, 1f))
47 | }
48 | }
49 | }
50 |
51 |
52 | class TestSimpleShaderGeneration {
53 | @Test def extractCartoonVertexShader(): Unit = {
54 | assertCleanEquals(
55 | """#version 300 es
56 |
57 | precision highp float;
58 | precision highp int;
59 |
60 | out vec3 worldPos;
61 | out vec3 worldNormal;
62 | layout (location = 0) in vec3 inPosition;
63 | layout (location = 1) in vec3 inNormal;
64 | uniform mat4 modelMatrix;
65 | uniform mat4 projectionMatrix;
66 | uniform mat4 viewMatrix;
67 | void main() {
68 | worldPos = ((modelMatrix * vec4(inPosition,1.0))).xyz;
69 | worldNormal = ((modelMatrix * vec4(inNormal,0.0))).xyz;
70 | gl_Position = (((projectionMatrix * viewMatrix) * modelMatrix) * vec4(inPosition,1.0));
71 | }""", ExampleCartoonShader.vertexShaderCode)
72 | }
73 | @Test def extractCartoonFragmentShader(): Unit = {
74 | assertCleanEquals(
75 | """#version 300 es
76 |
77 | precision highp float;
78 | precision highp int;
79 |
80 | in vec3 worldPos;
81 | in vec3 worldNormal;
82 | uniform vec3 lightPosition;
83 | uniform vec3 eyePosition;
84 | uniform float materialKd;
85 | uniform float materialKs;
86 | uniform int materialShininess;
87 | layout (location = 0) out vec4 color;
88 | void main() {
89 | vec3 L = normalize((lightPosition - worldPos));
90 | vec3 V = normalize((eyePosition - worldPos));
91 | vec3 H = normalize((L + V));
92 | float diffuse = (materialKd * max(0.0,dot(L,worldNormal)));
93 | float specular = ((dot(L,worldNormal) > 0.0)) ? ((materialKs * pow(max(0.0,dot(H,worldNormal)),materialShininess))) : (0.0);
94 | float edgeDetection = ((dot(V,worldNormal) > 0.3)) ? (1.0) : (0.0);
95 | float light = (edgeDetection * (diffuse + specular));
96 | color = vec4(light,light,light,1.0);
97 | }""", ExampleCartoonShader.fragmentShaderCode)
98 | }
99 |
100 | @Test def extractsUniformNames(): Unit = {
101 | assertEquals("modelMatrix", ExampleCartoonShader.modelMatrix.name)
102 | assertEquals("lightPosition", ExampleCartoonShader.lightPosition.name)
103 | }
104 | }
--------------------------------------------------------------------------------
/shader/src/main/scala/com/jsuereth/gl/shaders/codegen/ast.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.shaders.codegen
18 |
19 | case class Ast(decls: Seq[Declaration], version: String = "300 es") {
20 | // TODO - which version do we target?
21 | // TODO - Allow specifying default precision for opengl ES
22 | def toProgramString: String =
23 |
24 | s"""|#version $version
25 | |
26 | |precision highp float;
27 | |precision highp int;
28 | |
29 | |${decls.map(_.toProgramString).mkString("\n")}""".stripMargin('|')
30 | }
31 |
32 | // TODO - a better encoding of variables from GLSL
33 | /** Declarations available in GLSL at the top-level. */
34 | enum Declaration {
35 | case Uniform(name: String, tpe: String)
36 | // TODO - better layout controls
37 | case Input(name: String, tpe: String, location: Option[Int] = None)
38 | case Output(name: String, tpe: String, location: Option[Int] = None)
39 | // TODO - method with args...
40 | case Method(name: String, tpe: String, block: Seq[Statement])
41 | /** The definitiion of a struct. */
42 | case Struct(name: String, members: Seq[StructMember])
43 |
44 | def toProgramString: String = this match {
45 | case Uniform(name, tpe) => s"uniform $tpe $name;"
46 | case Input(name, tpe, Some(location)) => s"layout (location = $location) in $tpe $name;"
47 | case Input(name, tpe, None) => s"in $tpe $name;"
48 | case Output(name, tpe, Some(location)) => s"layout (location = $location) out $tpe $name;"
49 | case Output(name, tpe, None) => s"out $tpe $name;"
50 | case Method(name, tpe, block) => s"$tpe $name() {\n ${block.map(_.toProgramString).mkString("\n ")}\n}"
51 | case Struct(name, members) => s"struct $name {\n ${members.map(_.toProgramString).mkString("\n ")}\n};"
52 | }
53 | }
54 |
55 | /** A member of a structure. */
56 | case class StructMember(name: String, tpe: String) {
57 | def toProgramString: String = s"$tpe $name;"
58 | }
59 |
60 | /** Statements in the GLSL language. */
61 | enum Statement {
62 | case LocalVariable(name: String, tpe: String, initialValue: Option[Expr])
63 | case Effect(expr: Expr)
64 | case Assign(ref: String, value: Expr)
65 |
66 | def toProgramString: String = this match {
67 | // Here is a hack to remove empty statements we use as placehoders....
68 | case Effect(expr) => s"${expr.toProgramString};"
69 | case LocalVariable(name, tpe, None) => s"$tpe $name;"
70 | case LocalVariable(name, tpe, Some(expr)) => s"$tpe $name = ${expr.toProgramString};"
71 | case Assign(ref, value) => s"$ref = ${value.toProgramString};"
72 | }
73 | }
74 |
75 | // TODO - encode the valid range of expressions in GLSL
76 | /** Valid expressions (rvalues) in GLSL */
77 | enum Expr {
78 | case MethodCall(name: String, args: Seq[Expr])
79 | case Id(name: String)
80 | case Terenary(cond: Expr, lhs: Expr, rhs: Expr)
81 | case Operator(name: String, lhs: Expr, rhs: Expr)
82 | case Select(lhs: Expr, property: String)
83 | case Negate(expr: Expr)
84 |
85 |
86 | def toProgramString: String = this match {
87 | case MethodCall(name, args) => s"$name(${args.map(_.toProgramString).mkString("", ",", "")})"
88 | case Id(name) => name
89 | case Operator(name, lhs, rhs) => s"(${lhs.toProgramString} $name ${rhs.toProgramString})"
90 | case Negate(expr) => s"-(${expr.toProgramString})"
91 | case Terenary(cond, lhs, rhs) => s"(${cond.toProgramString}) ? (${lhs.toProgramString}) : (${rhs.toProgramString})"
92 | case Select(lhs, rhs) => s"(${lhs.toProgramString}).$rhs"
93 | }
94 | }
--------------------------------------------------------------------------------
/scene/src/main/scala/com/jsuereth/gl/mesh/parser/MtlParser.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package mesh
19 | package parser
20 |
21 |
22 | import java.io.{File, InputStream}
23 | import math.{Vec2,Vec3}
24 |
25 | object MtlFileParser {
26 | def parse(in: File): Map[String,RawMaterial] =
27 | parse(java.io.FileInputStream(in))
28 | def parse(in: InputStream): Map[String,RawMaterial] = {
29 | val tmp = MtlFileParser()
30 | tmp.parse(in)
31 | }
32 | }
33 | class MtlFileParser {
34 | private var currentParser: Option[(String, MtlParser)] = None
35 | private var materials = collection.mutable.Map[String, RawMaterial]()
36 |
37 |
38 | def parse(in: InputStream): Map[String,RawMaterial] = {
39 | try readStream(in)
40 | finally in.close()
41 | materials.toMap
42 | }
43 | private def readStream(in: java.io.InputStream): Unit = {
44 | val buffer = java.io.BufferedReader(java.io.InputStreamReader(in))
45 | def read(): Unit =
46 | buffer.readLine match {
47 | case null => ()
48 | case line =>
49 | readLine(line)
50 | read()
51 | }
52 | read()
53 | cleanSubParser()
54 | }
55 | private def readLine(line: String): Unit =
56 | line match {
57 | case ObjLine("#", _) | "" => // Ignore comments.
58 | case ObjLine("newmtl", name +: Nil) =>
59 | cleanSubParser()
60 | currentParser = Some((name, MtlParser(name)))
61 | case msg =>
62 | currentParser match {
63 | case Some((_, parser)) => parser.readLine(msg)
64 | case None => System.err.println(s"Could not read line in mtllib: $msg") // TODO - better errors.
65 | }
66 | }
67 | private def cleanSubParser(): Unit =
68 | currentParser match {
69 | case Some((name, parser)) => materials.put(name, parser.mtl)
70 | case _ => None
71 | }
72 | }
73 |
74 | class MtlParser(name: String) {
75 | private var base = BaseMaterial()
76 | private var textures = MaterialTextures()
77 | def readLine(line: String): Unit =
78 | line match {
79 | case ObjLine("Ka", P3f(x,y,z)) => base = base.copy(ka = Vec3(x,y,z))
80 | case ObjLine("Kd", P3f(x,y,z)) => base = base.copy(kd = Vec3(x,y,z))
81 | case ObjLine("Ks", P3f(x,y,z)) => base = base.copy(ks = Vec3(x,y,z))
82 | case ObjLine("Ns", Seq(Fl(value))) => base = base.copy(ns = value)
83 | case ObjLine("map_Ka", TextureRef(ref)) => textures = textures.copy(ka = Some(ref))
84 | case ObjLine("map_Kd", TextureRef(ref)) => textures = textures.copy(kd = Some(ref))
85 | case ObjLine("map_Ks", TextureRef(ref)) => textures = textures.copy(ks = Some(ref))
86 | case ObjLine("map_Ns", TextureRef(ref)) => textures = textures.copy(ns = Some(ref))
87 | case ObjLine("map_d", TextureRef(ref)) => textures = textures.copy(d = Some(ref))
88 | case ObjLine("map_bump", TextureRef(ref)) => textures = textures.copy(bump = Some(ref))
89 | case ObjLine("bump", TextureRef(ref)) => textures = textures.copy(bump = Some(ref))
90 | case msg => System.err.println(s"Could not read line in mtl: $msg") // TODO - better errors.
91 | }
92 |
93 | def mtl: RawMaterial = RawMaterial(name, base, textures)
94 | }
95 |
96 | object TextureRef {
97 | def unapply(values: Seq[String]): Option[TextureReference] =
98 | values match {
99 | case Seq(f) => Some(TextureReference(f, TextureOptions()))
100 | // TODO - handle texture options.
101 | case _ => None
102 | }
103 | }
--------------------------------------------------------------------------------
/io/src/main/scala/com/jsuereth/gl/texture/Texture.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package texture
19 |
20 | import io._
21 |
22 | import org.lwjgl.opengl.GL11.{
23 | glEnable,
24 | GL_FLOAT,
25 | glBindTexture,
26 | glGenTextures,
27 | glDeleteTextures,
28 | GL_TEXTURE_2D,
29 | GL_TEXTURE_1D,
30 | glPixelStorei,
31 | GL_UNPACK_ALIGNMENT,
32 | GL_RGBA,
33 | GL_RGBA8,
34 | GL_UNSIGNED_BYTE,
35 | glTexImage2D
36 | }
37 | import org.lwjgl.opengl.GL12.{
38 | GL_TEXTURE_3D
39 | }
40 | import org.lwjgl.opengl.GL30.{
41 | glGenerateMipmap
42 | }
43 | /** Represents a texture loaded into OpenGL. */
44 | sealed trait Texture extends io.GLBuffer {
45 | /** The id of the texture in OpenGL. */
46 | def id: Int
47 |
48 | override def close(): Unit = glDeleteTextures(id)
49 | }
50 | object Texture {
51 | // TODO - loading methods for various formats.
52 | // TODO - mechanism to load into/out of frame buffers...
53 | import TextureParameter._
54 | import TextureFilterType.{Linear}
55 | // Give up on generic texture loading, an,d just do one for PNGs.
56 | def loadImage(source: java.io.InputStream,
57 | params: Seq[TextureParameter] = Seq(MinFilter(Linear), MagFilter(Linear)),
58 | genMipMap: Boolean = true): Texture2D = {
59 | // Double give-up, we use AWT for now...
60 | val img = javax.imageio.ImageIO.read(source)
61 | val pixels = new Array[Int](img.getHeight*img.getWidth)
62 | img.getRGB(0, 0, img.getWidth, img.getHeight, pixels, 0, img.getWidth)
63 | // Load RGBA format
64 | val buf = java.nio.ByteBuffer.allocateDirect(img.getWidth*img.getHeight*4)
65 | // We invert the y axis for OpenGL.
66 | for {
67 | y <- (0 until img.getHeight).reverse
68 | x <- 0 until img.getWidth
69 | pixel = pixels(y*img.getWidth + x)
70 | } {
71 | // RGBA
72 | buf.put(((pixel >> 16) & 0xFF).toByte)
73 | buf.put(((pixel >> 8) & 0xFF).toByte)
74 | buf.put((pixel & 0xFF).toByte)
75 | buf.put(((pixel >> 24) & 0xFF).toByte)
76 | }
77 | buf.flip()
78 | val id = glGenTextures()
79 | glBindTexture(GL_TEXTURE_2D, id)
80 | import org.lwjgl.opengl.GL11._
81 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
82 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
83 | // TODO - params are not doing the right thing.
84 | //params.foreach(_.set(GL_TEXTURE_2D))
85 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, img.getWidth, img.getHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf)
86 | glBindTexture(GL_TEXTURE_2D, 0);
87 | Texture2D(id)
88 | }
89 |
90 | // TODO - is this useful?
91 | inline def textureType[T]: Int = inline compiletime.erasedValue[T] match {
92 | case _: Texture1D => GL_TEXTURE_1D
93 | case _: Texture2D => GL_TEXTURE_2D
94 | case _: Texture3D => GL_TEXTURE_3D
95 | case _ => compiletime.error("Not a valid texture type!")
96 | }
97 | }
98 |
99 |
100 | /** A reference to a one-dimensional texture. */
101 | case class Texture1D(id: Int) extends Texture {
102 | def bind(): Unit = glBindTexture(GL_TEXTURE_1D, id)
103 | def unbind(): Unit = glBindTexture(GL_TEXTURE_1D, 0)
104 | }
105 |
106 | /** A reference to a two dimensional texture. */
107 | case class Texture2D(id: Int) extends Texture {
108 | def bind(): Unit = glBindTexture(GL_TEXTURE_2D, id)
109 | def unbind(): Unit = glBindTexture(GL_TEXTURE_2D, 0)
110 | }
111 |
112 | /** A reference to a three dimensional texture. */
113 | case class Texture3D(id: Int) extends Texture {
114 | def bind(): Unit = glBindTexture(GL_TEXTURE_3D, id)
115 | def unbind(): Unit = glBindTexture(GL_TEXTURE_3D, 0)
116 | }
117 |
118 |
--------------------------------------------------------------------------------
/shader/src/main/scala/com/jsuereth/gl/shaders/DslShaderProgram.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.shaders
18 |
19 | import com.jsuereth.gl.math._
20 | import com.jsuereth.gl.texture.{Texture2D}
21 |
22 | import scala.quoted._
23 | import scala.annotation.compileTimeOnly
24 |
25 | import com.jsuereth.gl.io.ShaderUniformLoadable
26 | import org.lwjgl.system.MemoryStack
27 |
28 | /** this object contains our helper macros for this DSL. */
29 | object DslShaderProgram {
30 | def defineShadersImpl[T](f: Expr[T])(using ctx: QuoteContext): Expr[(String,String)] = {
31 | import ctx.tasty._
32 | val helpers = codegen.Convertors[ctx.tasty.type](ctx.tasty)
33 | val (vert,frag) = helpers.convert(f.unseal)
34 | // TODO - we need to detect cross/references between shaders and lift into input/output variables.
35 | '{Tuple2(${Expr(vert.toProgramString)}, ${Expr(frag.toProgramString)})}
36 | }
37 |
38 | def valNameImpl(using ctx: QuoteContext): Expr[String] = {
39 | import ctx.tasty._
40 | // QUick hack to look up the owner hierarhcy for a legit name...
41 | def findNonMacroSymbol(o: Symbol): Symbol =
42 | if (o.name == "macro") findNonMacroSymbol(o.owner)
43 | else o
44 | // TODO - Detect a structure and look up its first uniform member as the location, i.e. make its name be
45 | // that member, which is hacky, but an "ok" workaround.
46 | findNonMacroSymbol(ctx.tasty.rootContext.owner) match {
47 | case o: Symbol if o.isValDef => Expr(o.name)
48 | case _ =>
49 | ctx.error("Uniform() must be directly assigned to a val")
50 | Expr("")
51 | }
52 | }
53 | inline def valName: String = ${valNameImpl}
54 | def valNameOrFirstStructNameImpl[T](using ctx: QuoteContext, tpe: Type[T]): Expr[String] = {
55 | import ctx.tasty._
56 | val helpers = codegen.Convertors[ctx.tasty.type](ctx.tasty)
57 | if (helpers.isStructType(tpe.unseal.tpe)) {
58 | '{${valNameImpl} + "." + ${Expr(helpers.firstStructMemberName(tpe.unseal.tpe).get)}}
59 | } else valNameImpl
60 | }
61 | inline def valOrStructName[T]: String = ${valNameOrFirstStructNameImpl[T]}
62 |
63 | def testStructDefImpl[T](using ctx: QuoteContext, tpe: Type[T]): Expr[String] = {
64 | import ctx.tasty._
65 | val helpers = codegen.Convertors[ctx.tasty.type](ctx.tasty)
66 | Expr(helpers.toStructDefinition(tpe.unseal.tpe).map(_.toProgramString).toString)
67 | }
68 | inline def testStructDef[T] = ${testStructDefImpl[T]}
69 | }
70 | abstract class DslShaderProgram extends BasicShaderProgram {
71 |
72 | // TODO - we'd also like to implicit-match either ShaderUniformLoadable *or* OpaqueGlslType.
73 | inline def Uniform[T : ShaderUniformLoadable](): Uniform[T] =
74 | MyUniform[T](DslShaderProgram.valOrStructName[T])
75 |
76 | // API for defining shaders...
77 |
78 | inline def defineShaders[T](inline f: => T): (String, String) = ${DslShaderProgram.defineShadersImpl('f)}
79 | inline def fragmentShader[T](inline f: => T): Unit = f
80 |
81 |
82 | // COMPILE-TIME ONLY METHODS. TODO - move this into some kind of API file...
83 | // Allow DslShaders to access uniform values, but force this call to be within DSL.
84 | @compileTimeOnly("Can only access a uniform within a Shader.")
85 | inline def [T](c: Uniform[T]) apply(): T = ???
86 | @compileTimeOnly("Can only define input within a shader.")
87 | inline def Input[T](location: Int): T = ???
88 | @compileTimeOnly("Can only define output within a shader.")
89 | inline def Output[T](name: String, location: Int, value: T): Unit = ???
90 | @compileTimeOnly("Can only define glPosition within a shader.")
91 | inline def glPosition[T](vec: Vec4[T]): Unit = ???
92 | /** Samples a texture at a coordinate, pulling the color back. */
93 | @compileTimeOnly("Textures can only be sampled within a fragment shader.")
94 | inline def (c: Texture2D) texture(coord: Vec2[Float]): Vec4[Float] = ???
95 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ShadySide - OpenGL hackery in Scala 3.0
2 |
3 | This project aims to run Scala 3.0 through the ringer via producing
4 | an OpenGL DSL on top of LWJGL. The API should allow the following:
5 |
6 | - GLSL shaders defined fully within Scala, staged and commpiled on GPU.
7 | - "Fast" vector/matrix library for canonical graphics/gpu applications to
8 | pair w/ the GLSL shader API.
9 | - Easy support for stack-allocation hook of LWJGL
10 | - No attempt, at ALL, to be a "game engine".
11 | - Modest attempt at a linear algebra library, to be hacked on
12 | when it seems fun.
13 |
14 | This is a direct re-write of a personal project from 2013 where I was first learning OpenGL in Scala. As such, it retains many mistakes
15 | and poor assumptions.
16 |
17 | This project is split into several components:
18 |
19 | ## Linear Algebra
20 | A linear algebra library that provides missing types required in OpenGL shader language, specifically:
21 |
22 | * UInt
23 | * Vec2[T]
24 | * Vec3[T]
25 | * Vec4[T]
26 | * Matrix3x3[T]
27 | * Matrix4x4[T]
28 |
29 | In addition to baseline types, this library also provides some abstractions around types to be usable for numbers, specfically:
30 |
31 | * Trigonometry[T]
32 | * Rootable[T]
33 |
34 | An other types useful in implementing scene graphs/engines.
35 |
36 | ## OpenGL I/O
37 | A library that helps get data into/out of graphics memory. This consists of a few core pieces:
38 |
39 | * BufferLoadable[T] - load data into java.nio.ByteBuffers
40 | * VertexArrayObject[T] - load POD-type case classes into VAO. Can load raw vertices, or vertex+index arrays.
41 |
42 | ## OpenGL Shader DSL
43 | A library that allows specifying OpenGL shaders completely in scala. Relies on Linear Algebra types, and the I/O library for sending
44 | data into/out of graphics memory.
45 |
46 | ## Scene Graph
47 | A library to help simplify setting up a Scene in OpenGL and rendering it. This is used for the example application.
48 |
49 | # TODOs
50 |
51 | Here's a set of TODOs for this project. These are just the ones we find interesting enough to list out. There are probably more, if there's one you're interested in working on, list it out and work on it.
52 |
53 | ## Linear Algebra library
54 | - [ ] Add Matrix2x2
55 | - [ ] Add (optimal) Matrix multiplication
56 | - [ ] Create fuzzy equals method (floating point friendly)
57 | - [ ] Optimise matrix inverses
58 | - [ ] Create a legitimate set of tests
59 | - [ ] Derive "Show" typeclass for pretty-printing
60 | - [ ] Matrix +/- operations.
61 |
62 | ## OpenGL I/O library
63 | - [ ] Encode Textures and "opaque" types
64 | - [ ] Write tests for buffer loading
65 |
66 | ## Shader DSL
67 | - [ ] use compiler error messages instead of exceptions.
68 | - [ ] support helper/nested methods
69 | - [ ] support opaque type: Sampler2D
70 | - [ ] support unsigned ints
71 | - [ ] Add check for outside references that are not uniforms
72 | - [ ] Figure out a way to enforce vertex input in GLSL aligns with vertex buffer input from CPU.
73 |
74 | ## Scene Graph
75 | - [ ] Material support
76 | - [ ] non-static scenes?
77 |
78 | ## Example
79 | - [ ] load/use a texture
80 | - [ ] add a post-process shader (anti-aliasing?)
81 |
82 | ## Bugs to be reported to Dotty
83 |
84 | - [ ] Investigate using float literals and open types leading to stack-overflow
85 | - [ ] Investigate having open-type + constrained type in overloaded method causing
86 | issues in type inference/method discovery
87 | - [ ] Investigate anonymous delegate for multiple fully qualified type constructor
88 | having name collisions.
89 | - [ ] Report inconsistency of companion-object vs. free-floating delegates/givens.
90 |
91 | # Assets
92 |
93 | Mesh assets are graciously provided from NASA, with others.
94 | See [https://github.com/nasa/NASA-3D-Resources](https://github.com/nasa/NASA-3D-Resources) for more options.
95 |
96 | # Community Guidelines
97 |
98 | See [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
99 |
100 | # License
101 |
102 | Copyright 2019 Google LLC
103 |
104 | Licensed under the Apache License, Version 2.0 (the "License");
105 | you may not use this file except in compliance with the License.
106 | You may obtain a copy of the License at
107 |
108 | https://www.apache.org/licenses/LICENSE-2.0
109 |
110 | Unless required by applicable law or agreed to in writing, software
111 | distributed under the License is distributed on an "AS IS" BASIS,
112 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
113 | See the License for the specific language governing permissions and
114 | limitations under the License.
115 |
116 | See LICENSE.md for more info.
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of
9 | experience, education, socio-economic status, nationality, personal appearance,
10 | race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or reject
41 | comments, commits, code, wiki edits, issues, and other contributions that are
42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any
43 | contributor for other behaviors that they deem inappropriate, threatening,
44 | offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | This Code of Conduct also applies outside the project spaces when the Project
56 | Steward has a reasonable belief that an individual's behavior may have a
57 | negative impact on the project or its community.
58 |
59 | ## Conflict Resolution
60 |
61 | We do not believe that all conflict is bad; healthy debate and disagreement
62 | often yield positive results. However, it is never okay to be disrespectful or
63 | to engage in behavior that violates the project’s code of conduct.
64 |
65 | If you see someone violating the code of conduct, you are encouraged to address
66 | the behavior directly with those involved. Many issues can be resolved quickly
67 | and easily, and this gives people more control over the outcome of their
68 | dispute. If you are unable to resolve the matter for any reason, or if the
69 | behavior is threatening or harassing, report it. We are dedicated to providing
70 | an environment where participants feel welcome and safe.
71 |
72 | Reports should be directed to *[PROJECT STEWARD NAME(s) AND EMAIL(s)]*, the
73 | Project Steward(s) for *[PROJECT NAME]*. It is the Project Steward’s duty to
74 | receive and address reported violations of the code of conduct. They will then
75 | work with a committee consisting of representatives from the Open Source
76 | Programs Office and the Google Open Source Strategy team. If for any reason you
77 | are uncomfortable reaching out the Project Steward, please email
78 | opensource@google.com.
79 |
80 | We will investigate every complaint, but you may not receive a direct response.
81 | We will use our discretion in determining when and how to follow up on reported
82 | incidents, which may range from not taking action to permanent expulsion from
83 | the project and project-sponsored spaces. We will notify the accused of the
84 | report and provide them an opportunity to discuss it before any action is taken.
85 | The identity of the reporter will be omitted from the details of the report
86 | supplied to the accused. In potentially harmful situations, such as ongoing
87 | harassment or threats to anyone's safety, we may take action without notice.
88 |
89 | ## Attribution
90 |
91 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
92 | available at
93 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
94 |
--------------------------------------------------------------------------------
/shader/src/main/scala/com/jsuereth/gl/shaders/BasicShaderProgram.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.shaders
18 |
19 | import org.lwjgl.opengl.{
20 | GL11,
21 | GL20
22 | }
23 | import com.jsuereth.gl.io.{
24 | ShaderUniformLoadable,
25 | ShaderLoadingEnvironment
26 | }
27 | import org.lwjgl.system.MemoryStack
28 |
29 | enum Shader(val id: Int) {
30 | case Vertex extends Shader(GL20.GL_VERTEX_SHADER)
31 | case Fragment extends Shader(GL20.GL_FRAGMENT_SHADER)
32 | }
33 |
34 | class ShaderException(msg: String, source: String, cause: Throwable) extends Exception(s"$msg\n\n$source", cause) {
35 | def this(msg: String, source: String) = this(msg, source: String, null)
36 | }
37 |
38 | /** Compiles a shader and returns the id of the shader. */
39 | def compileShader(shaderType: Shader, source: String): Int = {
40 | val shader = GL20.glCreateShader(shaderType.id)
41 | try {
42 | if(shader == 0) throw ShaderException("Unable to construct shader!", source)
43 | GL20.glShaderSource(shader, source)
44 | GL20.glCompileShader(shader)
45 | if(GL20.glGetShaderi(shader, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
46 | throw ShaderException(GL20.glGetShaderInfoLog(shader, 2000), source)
47 | }
48 | } catch {
49 | case e: Exception =>
50 | GL20.glDeleteShader(shader)
51 | throw e
52 | }
53 | shader
54 | }
55 | /** Creates a shader program and linkes in a set of shaders together. */
56 | def linkShaders(compiledShaders: Int*): Int = {
57 | val program = GL20.glCreateProgram()
58 | try {
59 | for (shader <- compiledShaders) GL20.glAttachShader(program, shader)
60 |
61 | GL20.glLinkProgram(program)
62 | if (GL20.glGetProgrami(program, GL20.GL_LINK_STATUS) == GL11.GL_FALSE) {
63 | throw ShaderException(GL20.glGetProgramInfoLog(program, 2000), "")
64 | }
65 | // VALIDATION!
66 | GL20.glValidateProgram(program)
67 | if(GL20.glGetProgrami(program, GL20.GL_VALIDATE_STATUS) == GL11.GL_FALSE) {
68 | throw ShaderException(GL20.glGetProgramInfoLog(program, 2000), "")
69 | }
70 | } catch {
71 | // Make sure we clean up if we run into issues.
72 | case t => GL20.glDeleteProgram(program); throw t
73 | }
74 | program
75 | }
76 | /** looks up the "location" to a uniform in a shader program. */
77 | def lookupUniform(program: Int, name: String): Int =
78 | GL20.glGetUniformLocation(program, name)
79 |
80 |
81 | abstract class BasicShaderProgram {
82 | /** Returns the vertex shader for this program. */
83 | def vertexShaderCode: String
84 | /** Returns the fragment shader for this program. */
85 | def fragmentShaderCode: String
86 |
87 | private var programId: Int = 0
88 | /** Loas the configured shader into the graphics card. */
89 | def load() : Unit = {
90 | unload()
91 | // TODO - remember shader ids for unloading?
92 | programId = linkShaders(compileShader(Shader.Vertex, vertexShaderCode),
93 | compileShader(Shader.Fragment, fragmentShaderCode))
94 | }
95 | /** Removes this shader from OpenGL/Graphics card memory. */
96 | def unload(): Unit = if (programId != 0) {
97 | // TODO - unlink shaders?
98 | // GL20.glDetachShader(program, shader)
99 | // GL20.glDeleteShader(shader)
100 | GL20.glDeleteProgram(programId)
101 | programId = 0
102 | }
103 | /** Configures the OpenGL pipeline to use this program. */
104 | def bind(): Unit = GL20.glUseProgram(programId)
105 | /** Configures OpenGL pipeline to not use any shader. */
106 | def unbind(): Unit = GL20.glUseProgram(0)
107 |
108 |
109 | /** Our version of uniform which binds to the shader being compiled by this program. */
110 | protected class MyUniform[T : ShaderUniformLoadable](override val name: String) extends Uniform[T] {
111 | var location: Int = 0
112 | // TODO - does this need to be threadsafe?
113 | def :=(value: T)(using ShaderLoadingEnvironment): Unit = {
114 | if (location == 0) {
115 | location = GL20.glGetUniformLocation(programId, name)
116 | }
117 | summon[ShaderUniformLoadable[T]].loadUniform(location, value)
118 | }
119 |
120 | }
121 |
122 | def makeUniform[T : ShaderUniformLoadable](name: String): Uniform[T] = {
123 | MyUniform[T](name)
124 | }
125 |
126 | // Temporary for debugging purposes only.
127 | def debugUniform(name: String): Int = GL20.glGetUniformLocation(programId, name)
128 | }
--------------------------------------------------------------------------------
/scene/src/main/scala/com/jsuereth/gl/mesh/Mesh.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package mesh
19 |
20 | import math.{given _, _}
21 | import org.lwjgl.system.MemoryStack
22 | import io.{VertexArrayObject, BufferLoadable, vaoAttributes}
23 |
24 | /** Represents a single point in a mesh, and all data we can/could send down. */
25 | case class MeshPoint(
26 | vert: Vec3[Float],
27 | normal: Vec3[Float],
28 | tex: Vec2[Float]) derives BufferLoadable
29 | object MeshPoint
30 |
31 | // TODO - make this an enum
32 | sealed trait Face {
33 | def vertices: Seq[FaceIndex]
34 | }
35 | /** Represents a triangle defined as an index-reference into a Mesh3d. */
36 | final case class TriangleFace(one: FaceIndex, two: FaceIndex, three: FaceIndex) extends Face {
37 | override def vertices: Seq[FaceIndex] = Seq(one,two,three)
38 | }
39 | final case class QuadFace(one: FaceIndex, two: FaceIndex, three: FaceIndex, four: FaceIndex) extends Face {
40 | override def vertices: Seq[FaceIndex] = Seq(one,two,three,four)
41 | }
42 | /** Index reference into a Mesh3d for a face-definition. */
43 | final case class FaceIndex(vertix: Int, texture: Int, normal: Int)
44 |
45 |
46 | /**
47 | * A rendderable 3d Mesh.
48 | * - This needs a LOT of cleanup. We should not blindly take in OBJ format and use it, we should clean it before
49 | * creating this data structure.
50 | */
51 | trait Mesh3d {
52 | /** The verticies on this mesh. */
53 | def vertices: Seq[Vec3[Float]]
54 | /** The normals. */
55 | def normals: Seq[Vec3[Float]]
56 | /** Texture coordinates. */
57 | def textureCoords: Seq[Vec2[Float]]
58 |
59 | /** The faces for this mesh. Generally an index-reference to the other members. */
60 | def faces: Seq[Face]
61 | /** A flattening of indicies to those of the vertex. */
62 | private def allIndicies: Seq[FaceIndex] = faces.flatMap(_.vertices).distinct.toSeq
63 | /** Expensive to compute mechanism of lining up shared vertex/normal/texel coordinates. */
64 | private def points: Seq[MeshPoint] = {
65 | for(faceIdx <- allIndicies) yield {
66 | val vertex = vertices(faceIdx.vertix - 1)
67 | val normal =
68 | if(faceIdx.normal == 0) Vec3(0.0f,0.0f,0.0f)
69 | else normals(faceIdx.normal - 1)
70 | val texture =
71 | if (faceIdx.texture == 0) Vec2(0f,0f)
72 | else textureCoords(faceIdx.texture - 1)
73 | MeshPoint(vertex,normal,texture)
74 | }
75 | }
76 | // TODO - don't keep indicies in such a wierd format...
77 | private def idxes: Seq[Int] = {
78 | val indexMap = allIndicies.zipWithIndex.toMap
79 | for {
80 | face <- faces
81 | v <- face.vertices
82 | idx <- indexMap get v
83 | } yield idx
84 | }
85 |
86 | // TODO - cache/store of which VAOs are loaded and ability to dynamically unload them?
87 | /** Loads this mesh into a VAO that can be rendered. */
88 | def loadVao(using MemoryStack): VertexArrayObject[MeshPoint] = {
89 | // TODO - Figure out if triangles or quads.
90 | VertexArrayObject.loadWithIndex(points, idxes)
91 | }
92 | }
93 |
94 | object Mesh3d {
95 | private val oneThird = 1.0f / 3.0f
96 | /** Calculate the centroid (assuming equal weight on points). */
97 | def centroid(mesh: Mesh3d): Vec3[Float] = {
98 | // Algorithm
99 | // C = Centroid , A = (area of a face * 2)
100 | // R = face centroid = average of vertices making the face
101 | // C = [sum of all (A*R)] / [sum of all R]
102 | var sumAreaTimesFaceCentroidX = 0f
103 | var sumAreaTimesFaceCentroidY = 0f
104 | var sumAreaTimesFaceCentroidZ = 9f
105 | var sumArea = 0.0f
106 | for (face <- mesh.faces) {
107 | val TriangleFace(fone, ftwo, fthree) = face
108 | val one = mesh.vertices(fone.vertix-1)
109 | val two = mesh.vertices(ftwo.vertix-1)
110 | val three = mesh.vertices(fthree.vertix-1)
111 | val u = two - one
112 | val v = three - one
113 | val crossed = u cross v
114 | val area = crossed.length.toFloat
115 | sumArea += area
116 | val x = (one.x + two.x + three.x) * oneThird
117 | sumAreaTimesFaceCentroidX += (x * area)
118 | val y = (one.y + two.y + three.y) * oneThird
119 | sumAreaTimesFaceCentroidY += (y * area)
120 | val z = (one.z + two.z + three.z) * oneThird
121 | sumAreaTimesFaceCentroidZ += (z * area)
122 | }
123 | val x = sumAreaTimesFaceCentroidX / sumArea
124 | val y = sumAreaTimesFaceCentroidY / sumArea
125 | val z = sumAreaTimesFaceCentroidZ / sumArea
126 | Vec3(x, y, z)
127 | }
128 | // TODO - Normalize on centroid...
129 | }
130 |
131 |
--------------------------------------------------------------------------------
/io/src/main/scala/com/jsuereth/gl/io/VertexArrayObject.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.io
18 |
19 | import org.lwjgl.system.MemoryStack
20 | import org.lwjgl.opengl.GL11.{
21 | GL_TRIANGLES,
22 | GL_UNSIGNED_INT,
23 | glDrawArrays,
24 | glDrawElements
25 | }
26 | import org.lwjgl.opengl.GL15.{
27 | GL_ARRAY_BUFFER,
28 | GL_ELEMENT_ARRAY_BUFFER,
29 | GL_STATIC_DRAW,
30 | glGenBuffers,
31 | glBindBuffer,
32 | glBufferData
33 | }
34 | import org.lwjgl.opengl.GL20.{
35 | glEnableVertexAttribArray
36 | }
37 | import org.lwjgl.opengl.GL30.{
38 | glGenVertexArrays,
39 | glDeleteVertexArrays,
40 | glBindVertexArray
41 | }
42 | import java.nio.ByteBuffer
43 |
44 | /**
45 | * Represents a vertex array object stored in graphics memory.
46 | *
47 | * The object stores vertex data of type VertexType. The layout
48 | * is determined via the {{vaoAttributes[VertexType]}} method.
49 | */
50 | trait VertexArrayObject[VertexType] extends GLBuffer {
51 | /** Binds this VAO for usage. */
52 | def bind(): Unit = glBindVertexArray(id)
53 | /** Removes this VAO from being used. */
54 | def unbind(): Unit = glBindVertexArray(0)
55 | /** Renders the VAO through a shader pipeline. */
56 | def draw(): Unit
57 |
58 | // TODO - Clean VBOs?
59 | override def close(): Unit = glDeleteVertexArrays(id)
60 | }
61 | object VertexArrayObject {
62 | /** Loads a VAO whose elements will align with the passed in type.
63 | * @tparam T the plain-old-data type (case class) where each member becomes one of the vertex attribute pointers in this VAO.
64 | * @param vertices A sequence of all the verticies to load into graphics memory.
65 | * @param indicies A set of indicies to refereence in the vertex array when drawing geometry.
66 | *
67 | * Note: This assumes TRIANGLES are being sent for drawing.
68 | */
69 | inline def loadWithIndex[T : BufferLoadable](vertices: Seq[T], indices: Seq[Int])(using MemoryStack): VertexArrayObject[T] = {
70 | val id = glGenVertexArrays()
71 | glBindVertexArray(id)
72 | val attrs: Array[VaoAttribute] = vaoAttributes[T]
73 | // Create the Vertex data
74 | val vboVertex = glGenBuffers()
75 | glBindBuffer(GL_ARRAY_BUFFER, vboVertex)
76 | withLoadedBuf(vertices)(glBufferData(GL_ARRAY_BUFFER, _, GL_STATIC_DRAW))
77 | attrs.foreach(_.define())
78 | // Now create the index buffer.
79 | val vboIndex = glGenBuffers()
80 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndex)
81 | withLoadedBuf(indices)(glBufferData(GL_ELEMENT_ARRAY_BUFFER, _, GL_STATIC_DRAW))
82 | IndexedVertexArrayObject(id, attrs, indices.size)
83 | }
84 | /** Loads a VAO whose elements will align with the passed in type.
85 | * @tparam T the plain-old-data type (case class) where each member becomes one of the vertex attribute pointers in this VAO.
86 | * @param vertices A sequence of all the verticies to load into graphics memory.
87 | *
88 | * Note: This assumes TRIANGLES are being sent for drawing.
89 | */
90 | inline def loadRaw[T : BufferLoadable](vertices: Seq[T])(using MemoryStack): VertexArrayObject[T] = {
91 | val id = glGenVertexArrays()
92 | val vertByteSize = sizeOf[T]*vertices.size
93 | val attrs = vaoAttributes[T]
94 | glBindVertexArray(id)
95 | val vboVertex = glGenBuffers()
96 | glBindBuffer(GL_ARRAY_BUFFER, vboVertex)
97 | withLoadedBuf(vertices)(glBufferData(GL_ARRAY_BUFFER, _, GL_STATIC_DRAW))
98 | attrs.foreach(_.define())
99 | RawVertexArrayObject(id, attrs, vertices.size)
100 | }
101 | }
102 | /** This class can render a VAO where vertex data is rendered in order. */
103 | class RawVertexArrayObject[VertexType](override val id: Int,
104 | attrs: Array[VaoAttribute],
105 | vertexCount: Int,
106 | drawMode: Int = GL_TRIANGLES) extends VertexArrayObject[VertexType] {
107 | def draw(): Unit = withBound {
108 | attrs.iterator.map(_.idx).foreach(glEnableVertexAttribArray(_))
109 | glDrawArrays(drawMode, 0, vertexCount)
110 | }
111 | override def close(): Unit = glDeleteVertexArrays(id)
112 | }
113 |
114 | /** This class can render a VAO where you load vertex data separately from index data. */
115 | class IndexedVertexArrayObject[VertexType](override val id: Int,
116 | attrs: Array[VaoAttribute],
117 | indexCount: Int,
118 | drawMode: Int = GL_TRIANGLES) extends VertexArrayObject[VertexType] {
119 | def draw(): Unit = withBound {
120 | attrs.iterator.map(_.idx).foreach(glEnableVertexAttribArray(_))
121 | // TODO openGL index type...
122 | glDrawElements(drawMode, indexCount, GL_UNSIGNED_INT, 0)
123 | }
124 | }
--------------------------------------------------------------------------------
/shader/src/main/scala/com/jsuereth/gl/shaders/codegen/transform.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.shaders
18 | package codegen
19 |
20 | def Refs(ast: Ast | Declaration | Statement | Expr): Set[String] = ast match {
21 | case x: Ast => x.decls.iterator.flatMap(Refs).toSet
22 | case Declaration.Method(_, _, block) => block.flatMap(Refs).toSet
23 | case Statement.Effect(expr) => Refs(expr)
24 | case Statement.Assign(_, expr) => Refs(expr)
25 | case Statement.LocalVariable(_, _, Some(expr)) => Refs(expr)
26 | case Expr.MethodCall(name, args) => args.flatMap(Refs).toSet
27 | case Expr.Id(name) => Set(name)
28 | case Expr.Operator(name, lhs, rhs) => Refs(lhs) ++ Refs(rhs)
29 | case Expr.Negate(expr) => Refs(expr)
30 | case Expr.Terenary(cond, lhs, rhs) => Refs(cond) ++ Refs(lhs) ++ Refs(rhs)
31 | case Expr.Select(lhs, rhs) => Refs(lhs)
32 | case _ => Set()
33 | }
34 |
35 | /** An overridable transformer that provides basic "transform everything" impl. */
36 | class AstTransformer {
37 | def transform(ast: Ast): Ast = Ast(ast.decls.map(transform))
38 | def transform(decl: Declaration): Declaration = decl match {
39 | case Declaration.Method(name, tpe, block) => Declaration.Method(name, tpe, block.map(transform))
40 | case _ => decl
41 | }
42 | def transform(stmt: Statement): Statement = stmt match {
43 | case Statement.Effect(expr) => Statement.Effect(transform(expr))
44 | case Statement.Assign(name, expr) => Statement.Assign(name, transform(expr))
45 | case Statement.LocalVariable(name, tpe, optExpr) => Statement.LocalVariable(name, tpe, optExpr.map(transform))
46 | }
47 | def transform(expr: Expr): Expr = expr match {
48 | case Expr.MethodCall(name, args) => Expr.MethodCall(name, args.map(transform))
49 | case Expr.Id(_) => expr
50 | case Expr.Operator(name, lhs, rhs) => Expr.Operator(name, transform(lhs), transform(rhs))
51 | case Expr.Negate(expr) => Expr.Negate(transform(expr))
52 | case Expr.Terenary(cond, lhs, rhs) => Expr.Terenary(transform(cond), transform(lhs), transform(rhs))
53 | case Expr.Select(lhs, rhs) => Expr.Select(transform(lhs), rhs)
54 | }
55 | }
56 |
57 | object PlaceholderRemover extends AstTransformer {
58 | def isPlaceholder(e: Expr | Statement): Boolean = e match {
59 | case Statement.Effect(e) => isPlaceholder(e)
60 | case Expr.Id("") => true
61 | case _ => false
62 | }
63 |
64 | override def transform(decl: Declaration): Declaration = decl match {
65 | case Declaration.Method(name, tpe, block) => Declaration.Method(name, tpe, block.filterNot(isPlaceholder))
66 | case _ => decl
67 | }
68 | }
69 |
70 | class OutputVariableTransformer(vars: Set[(String,String)]) extends AstTransformer {
71 | override def transform(ast: Ast): Ast = {
72 | val outs = for((name, tpe) <- vars) yield Declaration.Output(name, tpe, None)
73 | Ast(outs.toSeq ++ ast.decls.map(transform))
74 | }
75 | override def transform(stmt: Statement): Statement = stmt match {
76 | case Statement.LocalVariable(name, tpe, Some(expr)) if vars(name -> tpe) => Statement.Assign(name, expr)
77 | case Statement.LocalVariable(name, tpe, None) if vars(name -> tpe) => Statement.Effect(Expr.Id("")) // TODO - alternative way to get rid of statements.
78 | case _ => super.transform(stmt)
79 | }
80 | }
81 |
82 | class InputVariableTransformer(vars: Set[(String,String)]) extends AstTransformer {
83 | override def transform(ast: Ast): Ast = {
84 | val ins = for((name, tpe) <- vars) yield Declaration.Input(name, tpe, None)
85 | Ast(ins.toSeq ++ ast.decls.map(transform))
86 | }
87 | override def transform(stmt: Statement): Statement = stmt match {
88 | case Statement.LocalVariable(name, tpe, Some(expr)) if vars(name -> tpe) => Statement.Assign(name, expr)
89 | case Statement.LocalVariable(name, tpe, None) if vars(name -> tpe) => Statement.Effect(Expr.Id("")) // TODO - alternative way to get rid of statements.
90 | case _ => super.transform(stmt)
91 | }
92 | }
93 |
94 | /** Autocorrects shared reference variables between vertex + pixel to be output/input appropriately. */
95 | def TransformAst(vertex: Ast, pixel: Ast): (Ast, Ast) = {
96 | // TODO - this is a really horrible implementation, not very safe/good.
97 | val localVariables =
98 | (for {
99 | Declaration.Method(_,_, block) <- vertex.decls.iterator.filter(_.isInstanceOf[Declaration.Method])
100 | Statement.LocalVariable(name, tpe, _) <- block.iterator.filter(_.isInstanceOf[Statement.LocalVariable])
101 | } yield name -> tpe).toMap
102 | val sharedVariables =
103 | for {
104 | name <- Refs(pixel)
105 | if localVariables.contains(name)
106 | } yield name -> localVariables(name)
107 | val vTransform = new OutputVariableTransformer(sharedVariables)
108 | val fTransform = new InputVariableTransformer(sharedVariables)
109 | //throw new Exception(s"Found shared references: ${sharedVariables.mkString(", ")}")
110 | (PlaceholderRemover.transform(vTransform.transform(vertex)),PlaceholderRemover.transform(fTransform.transform(pixel)))
111 | }
--------------------------------------------------------------------------------
/io/src/main/scala/com/jsuereth/gl/io/BufferLoadable.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package io
19 |
20 |
21 | import java.nio.ByteBuffer
22 | import scala.compiletime.{erasedValue,summonFrom}
23 |
24 | /**
25 | * Loads a value into a byte buffer.
26 | * This can be derived for most final case classes (i.e. PODs).
27 | */
28 | trait BufferLoadable[T] {
29 | /** Loads a value into a byte buffer. */
30 | def load(value: T, buf: ByteBuffer): Unit
31 | }
32 | object BufferLoadable {
33 | import deriving._
34 |
35 | /* A quick and dirty mechanism to derive loading product-types into a buffer. */
36 | inline def derived[A](using m: Mirror.Of[A]): BufferLoadable[A] = new BufferLoadable[A] {
37 | def load(v: A, buf: ByteBuffer): Unit = {
38 | inline m match {
39 | case m: Mirror.ProductOf[A] => writeElems[m.MirroredElemTypes](buf, v, 0)
40 | case _ => compiletime.error("Cannot derive buffer loading for non-product classes")
41 | }
42 | }
43 | }
44 | // peels off a layer of the tuple and writes it into the buffer.
45 | inline def writeElems[Elems <: Tuple](buf: ByteBuffer, v: Any, idx: Int): Unit =
46 | inline erasedValue[Elems] match {
47 | case _: (elem *: elems1) =>
48 | loadInl[elem](productElement[elem](v, idx), buf)
49 | writeElems[elems1](buf, v, idx+1)
50 | case _: Unit =>
51 | }
52 | // Looks up the implicit a buffer loadable for a given type.
53 | inline def loadInl[A](value: A, buf: ByteBuffer): Unit =
54 | summonFrom {
55 | case loader: BufferLoadable[A] => loader.load(value,buf)
56 | }
57 |
58 | // Defines all the buffer loading for primitives.
59 | // TODO - figure out if we can autogenerate this or clean it up in otherways.
60 | given BufferLoadable[Float] {
61 | def load(value: Float, buf: ByteBuffer): Unit =
62 | buf.putFloat(value)
63 | }
64 | given BufferLoadable[Double] {
65 | def load(value: Double, buf: ByteBuffer): Unit =
66 | buf.putDouble(value)
67 | }
68 | given BufferLoadable[Boolean] {
69 | def load(value: Boolean, buf: ByteBuffer): Unit =
70 | buf.put(if(value) 1.toByte else 0.toByte)
71 | }
72 | given BufferLoadable[Byte] {
73 | def load(value: Byte, buf: ByteBuffer): Unit =
74 | buf.put(value)
75 | }
76 | given BufferLoadable[Short] {
77 | def load(value: Short, buf: ByteBuffer): Unit =
78 | buf.putShort(value)
79 | }
80 | given BufferLoadable[Int] {
81 | def load(value: Int, buf: ByteBuffer): Unit =
82 | buf.putInt(value)
83 | }
84 | given BufferLoadable[Long] {
85 | def load(value: Long, buf: ByteBuffer): Unit =
86 | buf.putLong(value)
87 | }
88 |
89 | given [T](using BufferLoadable[T]) as BufferLoadable[Seq[T]] {
90 | def load(value: Seq[T], buf: ByteBuffer): Unit = {
91 | val i = value.iterator
92 | while(i.hasNext) {
93 | buf.load(i.next)
94 | }
95 | }
96 | }
97 |
98 | import com.jsuereth.gl.math._
99 | given [T](using io.BufferLoadable[T]) as BufferLoadable[Matrix3x3[T]] {
100 | def load(value: Matrix3x3[T], buf: java.nio.ByteBuffer): Unit = {
101 | buf.loadArray(value.values, 9, 0)
102 | }
103 | }
104 | given [T](using io.BufferLoadable[T]) as BufferLoadable[Matrix4x4[T]] {
105 | def load(value: Matrix4x4[T], buf: java.nio.ByteBuffer): Unit = {
106 | buf.loadArray(value.values, 16, 0)
107 | }
108 | }
109 | given [T](using io.BufferLoadable[T]) as BufferLoadable[Vec2[T]] {
110 | def load(value: Vec2[T], buf: java.nio.ByteBuffer): Unit = {
111 | buf.load(value.x)
112 | buf.load(value.y)
113 | }
114 | }
115 | given [T](using io.BufferLoadable[T]) as BufferLoadable[Vec4[T]] {
116 | def load(value: Vec4[T], buf: java.nio.ByteBuffer): Unit = {
117 | buf.load(value.x)
118 | buf.load(value.y)
119 | buf.load(value.z)
120 | buf.load(value.w)
121 | }
122 | }
123 | given [T](using io.BufferLoadable[T]) as BufferLoadable[Vec3[T]] {
124 | def load(value: Vec3[T], buf: java.nio.ByteBuffer): Unit = {
125 | buf.load(value.x)
126 | buf.load(value.y)
127 | buf.load(value.z)
128 | }
129 | }
130 | }
131 | /**
132 | * Extension method for ByteBuffer.
133 | * Allows calling 'load' on non-primitive types.
134 | */
135 | def (buf: ByteBuffer) load[T](value: T)(using loader: BufferLoadable[T]): Unit = loader.load(value, buf)
136 |
137 | /**
138 | * Extension method for ByteBuffer.
139 | * Allows calling 'load' for partial array values.
140 | */
141 | def (buf: ByteBuffer) loadArray[T](value: Array[T], length: Int, offset: Int)(using loader: BufferLoadable[T]): Unit = {
142 | for (i <- offset until (length+offset) if i < value.length)
143 | loader.load(value(i), buf)
144 | }
145 |
146 | import org.lwjgl.system.MemoryStack
147 | /**
148 | * Loads a buffer with the given data, flips it for reading and passes it to the lambda.
149 | * Cleans up memory when done.
150 | */
151 | inline def withLoadedBuf[T : BufferLoadable, A](value: Seq[T])(f: ByteBuffer => A)(using MemoryStack): A = {
152 | val size = sizeOf[T]*value.length
153 | withByteBuffer(size) { buf =>
154 | buf.clear()
155 | buf.load(value)
156 | buf.flip()
157 | f(buf)
158 | }
159 | }
--------------------------------------------------------------------------------
/math/src/main/scala/com/jsuereth/gl/math/Quaternion.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.math
18 |
19 | import scala.reflect.ClassTag
20 |
21 | object Quaternion {
22 | /** Computes 1/2 in a given numeric type. */
23 | def half[T : Fractional]: T = one / two
24 |
25 | def identity[T : Fractional]: Quaternion[T] = Quaternion(one, zero, zero, zero)
26 | /** Generate a quaternion that represents rotation about a given vector. */
27 | def rotation[T: Fractional : Trigonometry](angle: T, vector: Vec3[T]): Quaternion[T] =
28 | rotationRad(angleToRadians(angle), vector)
29 | /** Generate a quaternion that represents rotation about a given vector. */
30 | def rotationRad[T : Fractional : Trigonometry](angle: T, vector: Vec3[T]): Quaternion[T] = {
31 | val halfAngle = angle * half[T]
32 | val sinHalfAngle = sin(halfAngle)
33 | val w = cos(halfAngle)
34 | val x = (sinHalfAngle * vector.x)
35 | val y = (sinHalfAngle * vector.y)
36 | val z = (sinHalfAngle * vector.z)
37 | Quaternion(w,x,y,z)
38 | }
39 | /** Converts a Vec3 into the equivalent quaternion. */
40 | def fromVector[T : Rootable : Fractional](v: Vec3[T]): Quaternion[T] = {
41 | val vn = v.normalize
42 | Quaternion(zero, vn.x, vn.y, vn.z)
43 | }
44 |
45 | // Convert from angular acceleration into Quaternion.
46 | def fromAngularVector[T : Fractional : Rootable : Trigonometry](w: Vec3[T]): Quaternion[T] =
47 | rotationRad(w.length, w.normalize)
48 |
49 | /** takes euler pitch/yaw/roll and turns it into a quaternion. Angles in degrees. */
50 | def fromEuler[T : Fractional : Rootable : Trigonometry](pitch: T, yaw: T, roll: T): Quaternion[T] = {
51 | // Basically we create 3 Quaternions, one for pitch, one for yaw, one for roll
52 | // and multiply those together.
53 | // the calculation below does the same, just shorter
54 | val p = angleToRadians(pitch) * half[T]
55 | val y = angleToRadians(yaw) * half[T]
56 | val r = angleToRadians(roll) * half[T]
57 | val sinp = sin(p)
58 | val siny = sin(y)
59 | val sinr = sin(r)
60 | val cosp = cos(p)
61 | val cosy = cos(y)
62 | val cosr = cos(r)
63 | val qx = sinr * cosp * cosy - cosr * sinp * siny
64 | val qy = cosr * sinp * cosy + sinr * cosp * siny
65 | val qz = cosr * cosp * siny - sinr * sinp * cosy
66 | val qw = cosr * cosp * cosy + sinr * sinp * siny
67 | Quaternion(qw,qx,qy,qz).normalize
68 | }
69 | }
70 | /**
71 | * A way of representing numbers that is ideal for preserving rotational precision and generating rotation matrices as needed.
72 | *
73 | * w + x*i + y*j + z*k
74 | *
75 | * Note: While this can be representing with any type, some operations will require various aspects of those types, e.g.
76 | * normalization requries having roots.
77 | */
78 | case class Quaternion[T](w: T, x: T, y: T, z: T) {
79 |
80 | def +(other: Quaternion[T])(using Numeric[T]): Quaternion[T] =
81 | Quaternion(w+other.w, other.x+x, other.y+y, other.z+z)
82 |
83 | def -(other: Quaternion[T])(using Numeric[T]): Quaternion[T] =
84 | Quaternion(w-other.w, x-other.x, y-other.y, z-other.z)
85 |
86 | def *(scalar: T)(using Numeric[T]): Quaternion[T] =
87 | Quaternion(w*scalar, x*scalar, y*scalar, z*scalar)
88 |
89 | /** Hamilton product. This is similar to multiplying two rotation matrices together. */
90 | def *(other:Quaternion[T])(using Numeric[T]): Quaternion[T] = {
91 | val newW =
92 | w*other.w - x*other.x - y*other.y - z*other.z
93 | val newX =
94 | w*other.x + x*other.w + y*other.z - z*other.y
95 | val newY =
96 | w*other.y - x*other.z + y*other.w + z*other.x
97 | val newZ =
98 | w*other.z + x*other.y - y*other.x + z*other.w
99 | Quaternion(newW, newX, newY, newZ)
100 | }
101 |
102 | // Defined as rotating the vector by this quaternion.
103 | def *(other: Vec3[T])(using ClassTag[T], Fractional[T], Rootable[T]): Vec3[T] = {
104 | val result =
105 | this * Quaternion.fromVector(other) * this.conjugate
106 | Vec3(result.x, result.y, result.z)
107 | }
108 |
109 | def conjugate(using Numeric[T]): Quaternion[T] =
110 | Quaternion(w, -x, -y, -z);
111 |
112 | def normSquared(using Numeric[T]): T = w*w + x*x + y*y + z*z
113 |
114 | def normalize(using Rootable[T], Fractional[T]): Quaternion[T] = {
115 | val ns = normSquared
116 | // Here is a danger mechanism to avoid crazy divisions
117 | // What we really want is a better "near" function for floating point numbers.
118 | //import scala.math.abs
119 | //if(abs(ns) > 0.001f && abs(ns - f.one) > 0.001f) {
120 | val factor = (one / sqrt(ns))
121 | Quaternion(w * factor, x * factor, y * factor, z * factor)
122 | //} else this
123 | }
124 |
125 | def toMatrix(using ClassTag[T], Rootable[T], Ordering[T], Fractional[T]): Matrix4[T] = normalize.toMatrixFromNorm
126 |
127 | // This did not translate correctly...
128 | private def toMatrixFromNorm(using ClassTag[T], Ordering[T], Fractional[T]): Matrix4x4[T] = {
129 | val Nq = normSquared
130 | val s = if(summon[Ordering[T]].gt(Nq, zero)) two else zero
131 | val X = x*s; val Y = y*s; val Z = z*s
132 | val wX = w*X; val wY = w*Y; val wZ = w*Z
133 | val xX = x*X; val xY = x*Y; val xZ = x*Z
134 | val yY = y*Y; val yZ = y*Z; val zZ = z*Z
135 |
136 | Matrix4x4(Array(
137 | one-(yY+zZ), xY+wZ, xZ-wY, zero,
138 | xY-wZ, one-(xX+zZ), yZ+wX, zero,
139 | xZ+wY, yZ-wX, zero-(xX+yY), zero,
140 | zero, zero, zero, one
141 | ))
142 | }
143 | override def toString: String = s"$w + ${x}i + ${y}j + ${z}j"
144 | }
--------------------------------------------------------------------------------
/io/src/main/scala/com/jsuereth/gl/io/VaoAttribute.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package io
19 |
20 | import math._
21 |
22 | import scala.compiletime.{erasedValue, summonFrom}
23 | import deriving.Mirror
24 |
25 | /**
26 | * Returns the bytes required to store a given value in GLSL.
27 | * Note: This does NOT work on dynamic data. It returns a staticly known size for a type,
28 | * assuming it is packed w/ a BufferLoadable typeclass.
29 | */
30 | inline def sizeOf[T]: Int = inline erasedValue[T] match {
31 | case _: Boolean => 1
32 | case _: Byte => 1
33 | case _: Short => 2
34 | case _: Int => 4
35 | case _: Long => 8
36 | case _: Float => 4
37 | case _: Double => 8
38 | case _: Vec2[t] => 2*sizeOf[t]
39 | case _: Vec3[t] => 3*sizeOf[t]
40 | case _: Vec4[t] => 4*sizeOf[t]
41 | case _: Matrix4x4[t] => 16*sizeOf[t]
42 | case _: Matrix3x3[t] => 9*sizeOf[t]
43 | // TODO - SizedArray opaque type...
44 | case _: (a *: b) => sizeOf[a]+sizeOf[b]
45 | case _: Product => summonFrom {
46 | case given m: Mirror.ProductOf[T] => sizeOf[m.MirroredElemTypes]
47 | case _ => compiletime.error("Product is not plain-old-data, cannot compute the type.")
48 | }
49 | // Note: Unit shows up when we decompose tuples with *:
50 | case _: Unit => 0
51 | case _ => compiletime.error("Cannot discover the binary size of type T.")
52 | }
53 |
54 | import org.lwjgl.opengl.GL11.{
55 | GL_FLOAT,
56 | GL_INT,
57 | GL_UNSIGNED_INT, // TODO
58 | GL_SHORT,
59 | GL_UNSIGNED_SHORT, // TODO
60 | GL_BYTE,
61 | GL_UNSIGNED_BYTE, // TODO
62 | GL_DOUBLE
63 | }
64 | /**
65 | * Calculates the OpenGL enum for a type we'll pass into a VAO.
66 | * Note: For types like vec2 or mat4x4, this will return the primitive type,
67 | * as OpenGL specifies these with primitive + size.
68 | */
69 | inline def glType[T]: Int = inline erasedValue[T] match {
70 | // TODO - Modern OpenGL types: GL_FIXED, GL_HALF_FLOAT,GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_10F_11F_11F_REV
71 | case _: Boolean => GL_BYTE
72 | case _: Byte => GL_BYTE
73 | case _: Short => GL_SHORT
74 | case _: Int => GL_INT
75 | case _: Float => GL_FLOAT
76 | case _: Double => GL_DOUBLE
77 | case _: Vec2[t] => glType[t]
78 | case _: Vec3[t] => glType[t]
79 | case _: Vec4[t] => glType[t]
80 | case _: Matrix4x4[t] => glType[t]
81 | case _: Matrix3x3[t] => glType[t]
82 | case _ => compiletime.error("This type is not a supported OpenGL type")
83 | }
84 |
85 | /**
86 | * Calculates the size for OpenGL vertex attribute calls.
87 | * Essentially just how many of a give primitive are stored in a type.
88 | */
89 | inline def glSize[T]: Int = inline erasedValue[T] match {
90 | case _: Vec2[t] => 2
91 | case _: Vec3[t] => 3
92 | case _: Vec4[t] => 4
93 | case _: Matrix3x3[t] => 9
94 | case _: Matrix4x4[t] => 16
95 | case _: Boolean => 1
96 | case _: Byte => 1
97 | case _: Short => 1
98 | case _: Int => 1
99 | case _: Float => 1
100 | case _: Double => 1
101 | case _ => compiletime.error("This type is not a supported OpenGL type")
102 | }
103 |
104 | /** Grab the VAO attributes for a member of a case class. */
105 | inline def attr[T](idx: Int, stride: Int, offset: Int): VaoAttribute =
106 | VaoAttribute(idx, glSize[T], glType[T], stride, offset)
107 |
108 | /** Defines the vertex attribute pointer configuration we need for a VAO. */
109 | case class VaoAttribute(idx: Int, size: Int, tpe: Int, stride: Int, offset: Int) {
110 | import org.lwjgl.opengl.GL20.{glVertexAttribPointer,glEnableVertexAttribArray}
111 | /** Sends open-gl the VAO attribute pointer information given the index of this attribute. */
112 | def define(): Unit =
113 | glVertexAttribPointer(idx, size, tpe, false, stride, offset)
114 | def enable(): Unit =
115 | glEnableVertexAttribArray(idx)
116 |
117 | override def toString: String = {
118 | val tpeString = tpe match {
119 | case GL_FLOAT => "float"
120 | case _ => "unknown"
121 | }
122 | s"$idx(type: $tpeString size: $size, stride: $stride, offset: $offset)"
123 | }
124 | }
125 |
126 | /**
127 | * Attempts to construct an array of VAO attribute definitions for a loaded VAO.
128 | *
129 | * - Calculates stride size via total byte-size of T
130 | * - Caclulates offsets via incrementally adding each member of T
131 | * - Automatically determines GL types of members using glType[m]
132 | * - Automatically determines GL size information using glSize[m]
133 | */
134 | inline def vaoAttributes[T]: Array[VaoAttribute] = {
135 | val stride = sizeOf[T]
136 | inline erasedValue[T] match {
137 | case _: Tuple1[a] => Array(attr[a](0,stride, 0))
138 | // TODO - use a continuation approach to tease apart tuples, like we do in sizeOf.
139 | case _: (a, b) => Array(attr[a](0, stride, 0),
140 | attr[b](1, stride, sizeOf[a]))
141 | case _: (a, b, c) => Array(attr[a](0, stride, 0),
142 | attr[b](1, stride, sizeOf[a]),
143 | attr[c](2, stride, sizeOf[a]+sizeOf[b]))
144 | case _: (a, b, c, d) => Array(attr[a](0, stride, 0),
145 | attr[b](1, stride, sizeOf[a]),
146 | attr[c](2, stride, sizeOf[a]+sizeOf[b]),
147 | attr[d](3, stride, sizeOf[a]+sizeOf[b]+sizeOf[c]))
148 | case _: Product =>
149 | summonFrom {
150 | case given m: Mirror.ProductOf[T] => vaoAttributes[m.MirroredElemTypes]
151 | }
152 | case _ => compiletime.error("Cannot compute the VAO attributes of this type.")
153 | }
154 | }
--------------------------------------------------------------------------------
/types.md:
--------------------------------------------------------------------------------
1 | # Mappings from OpenGL GLSL types to Scala types.
2 |
3 | Taken from [OpenGL ES GLSL 3.00 Spec (29 Jan 2016)](https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf)
4 |
5 |
6 | ## Basic types
7 |
8 | | GLSL | Scala | Meaning |
9 | |-------|----------------------------------|---------|
10 | |void |scala.Unit |for functions that do not return a value|
11 | |bool |scala.Boolean |a conditional type, taking on values of true or false|
12 | |int |scala.Int |a signed integer|
13 | |uint |TBD |an unsigned integer|
14 | |float |scala.Float |a single floating-point scalar|
15 | |vec2 |com.jsuereth.gl.math.Vec2[Float] |a two-component floating-point vector|
16 | |vec3 |com.jsuereth.gl.math.Vec3[Float] |a three-component floating-point vector|
17 | |vec4 |com.jsuereth.gl.math.Vec4[Float] |a four-component floating-point vector|
18 | |bvec2 |com.jsuereth.gl.math.Vec2[Boolean]|a two-component Boolean vector|
19 | |bvec3 |com.jsuereth.gl.math.Vec3[Boolean]|a three-component Boolean vector|
20 | |bvec4 |com.jsuereth.gl.math.Vec4[Boolean]|a four-component Boolean vector|
21 | |ivec2 |com.jsuereth.gl.math.Vec2[Int] |a two-component signed integer vector|
22 | |ivec3 |com.jsuereth.gl.math.Vec3[Int] | a three-component signed integer vector|
23 | |ivec4 |com.jsuereth.gl.math.Vec4[Int] |a four-component signed integer vector|
24 | |uvec2 |com.jsuereth.gl.math.Vec2[Uint] |a two-component unsigned integer vector|
25 | |uvec3 |com.jsuereth.gl.math.Vec3[Uint] |a three-component unsigned integer vector|
26 | |uvec4 |com.jsuereth.gl.math.Vec4[UInt] |a four-component unsigned integer vector|
27 | |mat2 |TBD |a 2×2 floating-point matrix|
28 | |mat3 |TBD |a 3×3 floating-point matrix|
29 | |mat4 |com.jsuereth.gl.math.Matrix4[Float]|a 4×4 floating-point matrix|
30 | |mat2x2 |TBD|same as a mat2
31 | |mat2x3 |TBD| a floating-point matrix with 2 columns and 3 rows
32 | |mat2x4 |TBD| a floating-point matrix with 2 columns and 4 rows
33 | |mat3x2 |TBD| a floating-point matrix with 3 columns and 2 rows
34 | |mat3x3 |TBD| same as a mat3
35 | |mat3x4 |TBD| a floating-point matrix with 3 columns and 4 rows
36 | |mat4x2 |TBD| a floating-point matrix with 4 columns and 2 rows
37 | |mat4x3 |TBD| a floating-point matrix with 4 columns and 3 rows
38 | |mat4x4 |com.jsuereth.gl.math.Matrix4x4[Float]| same as a mat4
39 |
40 | ## Sampler opaque types
41 |
42 | | GLSL | Scala | Meaning |
43 | |----------------------|--------------------------|---------|
44 | |sampler2D |TBD| a handle for accessing a 2D texture
45 | |sampler3D |TBD| a handle for accessing a 3D texture
46 | |samplerCube |TBD| a handle for accessing a cube mapped texture
47 | |samplerCubeShadow |TBD| a handle for accessing a cube map depth texture with comparison
48 | |sampler2DArray |TBD| a handle for accessing an 2D array texture
49 | |sampler2DArrayShadow |TBD| a handle for accessing an 2D array texture
50 | |isampler2D |TBD| a handle for accessing a signed integer 2D texture
51 | |isampler3D |TBD| a handle for accessing a signed integer 3D texture
52 | |isamplerCube |TBD| a handle for accessing a signed integer 2D cube mapped texture
53 | |isampler2DArray |TBD| a handle for accessing a signed integer 2D array texture
54 | |usampler2D |TBD| a handle for accessing an unsigned integer 2D texture
55 | |usampler3D |TBD| a handle for accessing an unsigned integer 3D texture
56 | |usamplerCube |TBD| a handle for accessing an unsigned integer 2D cube mapped texture
57 | |usampler2DArray |TBD| a handle for accessing an unsigned integer 2D array texture
58 |
59 |
60 | ## Built in operators
61 |
62 | | GLSL | Scala |
63 | |-------|--------|
64 | |x + y | x + y |
65 | |x - y | x - y |
66 | |x * y | x * y |
67 | |x / y | x / y |
68 | |x % y |TBD |
69 | |x++ |TBD |
70 | |x-- |TBD |
71 | |-x | -x |
72 | |~x |TBD |
73 | |!x |TBD |
74 | |x << y |TBD |
75 | |x >> y |TBD |
76 | |x < y |x < y |
77 | |x <= y |x <= y |
78 | |x > y |x > y |
79 | |x >= y |x >= y |
80 | |&&|TBD|
81 | |`||`|TBD|
82 |
83 | ## Built-in trigonometry functions
84 | | GLSL | Scala |
85 | |------|-------|
86 | |radians(degrees)|TBD|
87 | |degress(radians)|TBD|
88 | |sin(angle)|TBD|
89 | |cos(angle)|TBD|
90 | |tan(angle)|TBD|
91 | |asin(x)|TBD|
92 | |acos(x)|TBD|
93 | |atan(x,y)|TBD|
94 | |atan(y_over_x)|TBD|
95 | |sinh(x)|TBD|
96 | |cosh(x)|TBD|
97 | |tanh(x)|TBD|
98 | |asinh(x)|TBD|
99 | |acosh(x)|TBD|
100 | |atanh(x)|TBD|
101 |
102 | # Built-in Exponential Functions
103 |
104 | | GLSL | Scala |
105 | |--------|-------|
106 | |pow(x,y)|java.lang.Math.pow(x,y)|
107 | |exp(x)|TBD|
108 | |log(x)|TBD|
109 | |exp2(x)|TBD|
110 | |log2(x)|TBD|
111 | |sqrt(x)|TBD|
112 | |inversesqrt(x)|TBD|
113 |
114 | ## Built-in Common Functions
115 |
116 | | GLSL | Scala |
117 | |--------|-------|
118 | |abs(x,y)|TBD|
119 | |sign(x)|TBD|
120 | |floor(x)|TBD|
121 | |trunc(x)|TBD|
122 | |round(x)|TBD|
123 | |roundEven(x)|TBD|
124 | |ceil(x)|TBD|
125 | |fract(x)|TBD|
126 | |mod(x,y)|TBD|
127 | |modf(x,y)|TBD|
128 | |min(x,y)|java.lang.Math.min(x,y)|
129 | |max(x,y)|java.lang.Math.max(x,y)|
130 | |clamp(x,minVal, maxVal)|TBD|
131 | |mix(x,y,a)|TBD|
132 | |step(edge,x)|TBD|
133 | |smoothstep(edge1,edge2,x)|TBD|
134 | |isnan(x)|TBD|
135 | |isinf(x)|TBD|
136 | |floatBitsToInt(x)|TBD|
137 | |floatBitsToUint(x)|TBD|
138 | |intBitsToFloat(x)|TBD|
139 | |uintBitsToFloat(x)|TBD|
140 |
141 |
142 | ## TODO - floating point packing ops
143 |
144 |
145 | ## Built-in Geometric Functions
146 |
147 | | GLSL | Scala |
148 | |---------------|----------|
149 | |length(x) |x.length |
150 | |distance(p0,p1)|TBD |
151 | |dot(x,y) |x.dot(y) |
152 | |cross(x,y) |x.cross(y)|
153 | |normalize(x) |x.normalize|
154 | |faceforward(N,I,Nref)|TBD|
155 | |reflect(I,N)|TBD|
156 | |refract(I,N,eta)|TBD|
157 |
158 |
159 | ## Built-in Matrix Functions
160 | | GLSL | Scala |
161 | |---------------|----------|
162 | |matrixCompMulti(x,y)|TBD|
163 | |outerProduct(x,y)|TBD|
164 | |transpose(x)|TBD|
165 | |determinant(x)|x.determinant|
166 | |inverse(x)|x.inverse|
167 |
168 | ## TODO translations for section 8.7 and below
169 |
--------------------------------------------------------------------------------
/scene/src/main/scala/com/jsuereth/gl/mesh/parser/ObjParser.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package mesh
19 | package parser
20 |
21 |
22 | import java.io.{File, InputStream}
23 | import math.{Vec2,Vec3, given _}
24 |
25 | /** The results of parsing an obj file. */
26 | class ObjParsedMesh(override val vertices: Seq[Vec3[Float]],
27 | override val normals: Seq[Vec3[Float]],
28 | override val textureCoords: Seq[Vec2[Float]],
29 | override val faces: Seq[Face]) extends Mesh3d {
30 | override def toString: String = s"ObjFileMesh(faces=${faces.size})"
31 | }
32 | object ObjFileParser {
33 | def parse(in: File): Map[String,Mesh3d] =
34 | parse(java.io.FileInputStream(in))
35 | def parse(in: InputStream): Map[String,Mesh3d] = {
36 | val tmp = ObjFileParser()
37 | tmp.parse(in)
38 | }
39 | }
40 | /** A parser for .obj files. Only really handles the ones I've used, not robust.
41 | * Also, ignores material files.
42 | */
43 | class ObjFileParser {
44 | private var currentObjectParser: Option[(String, ObjMeshParser)] = None
45 | private var meshes = collection.mutable.Map[String, ObjParsedMesh]()
46 |
47 | def parse(in: File): Map[String,ObjParsedMesh] = {
48 | parse(java.io.FileInputStream(in))
49 | }
50 |
51 | def parse(in: InputStream): Map[String,ObjParsedMesh] = {
52 | try readStream(in)
53 | finally in.close()
54 | meshes.toMap
55 | }
56 |
57 | private def readStream(in: java.io.InputStream): Unit = {
58 | val buffer = java.io.BufferedReader(java.io.InputStreamReader(in))
59 | def read(): Unit =
60 | buffer.readLine match {
61 | case null => ()
62 | case line =>
63 | readLine(line)
64 | read()
65 | }
66 | read()
67 | cleanSubParser()
68 | }
69 |
70 | private def cleanSubParser(): Unit =
71 | currentObjectParser match {
72 | case Some((name, parser)) => meshes.put(name, parser.obj)
73 | case _ => None
74 | }
75 |
76 | private def readLine(line: String): Unit =
77 | line match {
78 | case ObjLine("#", _) | "" => // Ignore comments.
79 | case ObjLine("usemtl", mtl +: Nil) => System.err.println("Ignoring material: " + mtl) // TODO - figure out what to do w/ materials.
80 | case ObjLine("o", name +: Nil) =>
81 | cleanSubParser()
82 | currentObjectParser = Some((name, ObjMeshParser()))
83 | case msg =>
84 | currentObjectParser match {
85 | case Some((_, parser)) => parser.readLine(msg)
86 | case None =>
87 | // Handle unnamed objects
88 | currentObjectParser = Some(("unnamed", ObjMeshParser()))
89 | currentObjectParser.get._2.readLine(msg)
90 | }
91 | }
92 | }
93 |
94 | /** stateful parser we use to look for objects within a .obj file. */
95 | class ObjMeshParser() {
96 | private var vertices = collection.mutable.ArrayBuffer.empty[Vec3[Float]]
97 | private var normals = collection.mutable.ArrayBuffer.empty[Vec3[Float]]
98 | private var textureCoords = collection.mutable.ArrayBuffer.empty[Vec2[Float]]
99 | private var faces = collection.mutable.ArrayBuffer.empty[Face]
100 | /** Read a line of input for a named object. */
101 | def readLine(line: String): Unit =
102 | line match {
103 | case ObjLine("g", group +: Nil) => System.err.println(s"Ignoring group: $group")
104 | case ObjLine("s", config +: Nil) => System.err.println(s"Ignoring smooth shading config: $config")
105 | case ObjLine("usemtl", mtl +: Nil) => System.err.println("Ignoring material: " + mtl)
106 | case ObjLine("v", P3f(x,y,z)) => vertices.append(Vec3(x, y, z)) // verticies
107 | case ObjLine("vt", P2f(u,v)) => textureCoords.append(Vec2(u,v)) // texture coords
108 | case ObjLine("vt", P3f(u,v,_)) => textureCoords.append(Vec2(u,v)) // ignore 3d texture coords
109 | case ObjLine("vn", P3f(x,y,z)) => normals.append(Vec3(x,y,z).normalize) // vertex normals
110 | case ObjLine("f", F(face)) => faces.append(face)
111 | // TODO - support polylines ("l")
112 | case msg => System.err.println(s"Could not read line in object: $msg") // TODO - better errors.
113 | }
114 |
115 | def obj: ObjParsedMesh = {
116 | // TODO - Clean up imported objects.
117 | // Convert all quad faces into triangles
118 | val fixedFaces: Seq[TriangleFace] = faces.flatMap {
119 | case x: TriangleFace => Seq(x)
120 | case QuadFace(one,two,three,four) => Seq(TriangleFace(one,two,three), TriangleFace(three, four, one))
121 | }.toSeq
122 | // Generate normals if needed, update faces.
123 | val faces2 = if (normals.isEmpty) generateNormals(fixedFaces) else fixedFaces
124 | ObjParsedMesh(vertices.toSeq, normals.toSeq, textureCoords.toSeq, faces2.toSeq)
125 | }
126 |
127 | private def generateNormals(faces: Seq[TriangleFace]): Seq[TriangleFace] = {
128 | for (v <- vertices) normals += Vec3(0f,0f,0f)
129 | for (TriangleFace(one,two,three) <- faces) {
130 | val A = vertices(one.vertix-1)
131 | val B = vertices(two.vertix-1)
132 | val C = vertices(three.vertix-1)
133 | val p = (B-A).cross(C-A)
134 | normals(one.vertix-1) += p
135 | normals(two.vertix-1) += p
136 | normals(three.vertix-1) += p
137 | }
138 | for (i <- 0 until normals.size) {
139 | normals(i) = normals(i).normalize
140 | }
141 | for {
142 | TriangleFace(one,two,three) <- faces
143 | } yield TriangleFace(one.copy(normal=one.vertix), two.copy(normal=two.vertix), three.copy(normal=three.vertix))
144 | }
145 | }
146 |
147 | private[mesh] object F {
148 | def unapply(face: Seq[String]): Option[Face] =
149 | face match {
150 | case Seq(FI(one), FI(two), FI(three)) => Some(TriangleFace(one,two,three))
151 | case Seq(FI(one), FI(two), FI(three), FI(four)) => Some(QuadFace(one,two,three,four))
152 | case _ => None
153 | }
154 | }
155 |
156 | private[mesh] object FI {
157 | def unapply(face: String): Option[FaceIndex] =
158 | face split "/" match {
159 | case Array(It(one)) => Some(FaceIndex(one, 0, 0))
160 | case Array(It(one), It(two)) => Some(FaceIndex(one, two, 0))
161 | case Array(It(one), It(two), It(three)) => Some(FaceIndex(one, two, three))
162 | case Array(It(one), "", It(three)) => Some(FaceIndex(one, 0, three))
163 | case _ => None
164 | }
165 | }
166 |
167 | private[parser] object P3f {
168 | def unapply(in: Seq[String]): Option[(Float, Float, Float)] =
169 | in match {
170 | case Seq(Fl(x), Fl(y), Fl(z)) => Some((x,y,z))
171 | case _ => None
172 | }
173 | }
174 |
175 | private[parser] object P2f {
176 | def unapply(in: Seq[String]): Option[(Float, Float)] =
177 | in match {
178 | case Seq(Fl(x), Fl(y)) => Some(x -> y)
179 | case _ => None
180 | }
181 | }
182 | private[parser] object ObjLine {
183 | def unapply(in: String): Option[(String, Seq[String])] = {
184 | val split = in split "\\s+"
185 | if(split.isEmpty) None
186 | else {
187 | Some(split.head -> split.tail)
188 | }
189 | }
190 |
191 | }
192 |
193 | private[parser] object It {
194 | def unapply(in: String): Option[Int] =
195 | try Some(in.toInt)
196 | catch {
197 | case _: NumberFormatException => None
198 | }
199 | }
200 | private[parser] object Fl {
201 | def unapply(in: String): Option[Float] =
202 | try Some(in.toFloat)
203 | catch {
204 | case _: NumberFormatException => None
205 | }
206 | }
--------------------------------------------------------------------------------
/io/src/main/scala/com/jsuereth/gl/io/ShaderUniformLoadable.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.io
18 |
19 |
20 | import org.lwjgl.system.MemoryStack
21 | import scala.compiletime.summonFrom
22 |
23 | /** Denotes that a variable can be loaded into an OpenGL shader as a uniform. */
24 | trait ShaderUniformLoadable[T] {
25 | def loadUniform(location: Int, value: T)(using ShaderLoadingEnvironment): Unit
26 | }
27 |
28 | /** The environment we make use of when loading a shader. */
29 | trait ShaderLoadingEnvironment {
30 | /** Memory we can use for this shader. */
31 | def stack: MemoryStack
32 | /** A mechanism we can that lets us dynamically select an available texture slot. */
33 | def textures: ActiveTextures
34 |
35 | def push(): Unit = textures.push()
36 | def pop(): Unit = textures.pop()
37 | }
38 |
39 | import org.lwjgl.opengl.GL20.{glUniform1f, glUniform1i}
40 | object ShaderUniformLoadable {
41 | /** Helper to pull the stack off the shader loading environment. */
42 | def stack(using ShaderLoadingEnvironment): MemoryStack = summon[ShaderLoadingEnvironment].stack
43 | /** Helper to grab active texutres off the shader loading environment. */
44 | def textures(using ShaderLoadingEnvironment): ActiveTextures = summon[ShaderLoadingEnvironment].textures
45 |
46 |
47 | given ShaderUniformLoadable[Float] {
48 | def loadUniform(location: Int, value: Float)(using ShaderLoadingEnvironment): Unit =
49 | glUniform1f(location, value)
50 | }
51 |
52 | given ShaderUniformLoadable[Int] {
53 | def loadUniform(location: Int, value: Int)(using ShaderLoadingEnvironment): Unit =
54 | glUniform1i(location, value)
55 | }
56 | import com.jsuereth.gl.math._
57 | given Matrix3x3FloatLoader as ShaderUniformLoadable[Matrix3x3[Float]] {
58 | import org.lwjgl.opengl.GL20.glUniformMatrix3fv
59 | def loadUniform(location: Int, value: Matrix3x3[Float])(using ShaderLoadingEnvironment): Unit = {
60 | val buf: java.nio.FloatBuffer = {
61 | // Here we're allocating a stack-buffer. We need to ensure we're in a context that has a GL-transition stack.
62 | val buf = stack.callocFloat(9)
63 | buf.clear()
64 | buf.put(value.values)
65 | buf.flip()
66 | buf
67 | }
68 | glUniformMatrix3fv(location, false, buf)
69 | }
70 | }
71 | given Matrix4x4FloatUniformLoader as ShaderUniformLoadable[Matrix4x4[Float]] {
72 | import org.lwjgl.system.MemoryStack
73 | import org.lwjgl.opengl.GL20.glUniformMatrix4fv
74 | def loadUniform(location: Int, value: Matrix4x4[Float])(using ShaderLoadingEnvironment): Unit = {
75 | val buf: java.nio.FloatBuffer = {
76 | // Here we're allocating a stack-buffer. We need to ensure we're in a context that has a GL-transition stack.
77 | val buf = stack.calloc(sizeOf[Matrix4x4[Float]])
78 | buf.clear()
79 | buf.load(value)
80 | buf.flip()
81 | buf.asFloatBuffer
82 | }
83 | glUniformMatrix4fv(location, false, buf)
84 | }
85 | }
86 |
87 | given loadVec2Float as ShaderUniformLoadable[Vec2[Float]] {
88 | import org.lwjgl.opengl.GL20.glUniform2f
89 | import org.lwjgl.system.MemoryStack
90 | def loadUniform(location: Int, value: Vec2[Float])(using ShaderLoadingEnvironment): Unit =
91 | glUniform2f(location, value.x, value.y)
92 | }
93 |
94 | given loadVec2Int as ShaderUniformLoadable[Vec2[Int]] {
95 | import org.lwjgl.opengl.GL20.glUniform2i
96 | import org.lwjgl.system.MemoryStack
97 | def loadUniform(location: Int, value: Vec2[Int])(using ShaderLoadingEnvironment): Unit =
98 | glUniform2i(location, value.x, value.y)
99 | }
100 |
101 | given loadVec3Float as ShaderUniformLoadable[Vec3[Float]] {
102 | import org.lwjgl.opengl.GL20.glUniform3f
103 | import org.lwjgl.opengl.GL20.glUniform4f
104 | import org.lwjgl.system.MemoryStack
105 | def loadUniform(location: Int, value: Vec3[Float])(using ShaderLoadingEnvironment): Unit =
106 | glUniform3f(location, value.x, value.y, value.z)
107 | }
108 |
109 | given loadVec3Int as ShaderUniformLoadable[Vec3[Int]] {
110 | import org.lwjgl.opengl.GL20.glUniform3i
111 | import org.lwjgl.system.MemoryStack
112 | def loadUniform(location: Int, value: Vec3[Int])(using ShaderLoadingEnvironment): Unit =
113 | glUniform3i(location, value.x, value.y, value.z)
114 | }
115 |
116 | given loadVec4Float as ShaderUniformLoadable[Vec4[Float]] {
117 | import org.lwjgl.opengl.GL20.glUniform4f
118 | import org.lwjgl.system.MemoryStack
119 | def loadUniform(location: Int, value: Vec4[Float])(using ShaderLoadingEnvironment): Unit =
120 | glUniform4f(location, value.x, value.y, value.z, value.w)
121 | }
122 |
123 | given loadVec4Int as ShaderUniformLoadable[Vec4[Int]] {
124 | import org.lwjgl.opengl.GL20.glUniform4i
125 | import org.lwjgl.system.MemoryStack
126 | def loadUniform(location: Int, value: Vec4[Int])(using ShaderLoadingEnvironment): Unit =
127 | glUniform4i(location, value.x, value.y, value.z, value.w)
128 | }
129 |
130 | // Now opaque types, i.e. textures + buffers.
131 | import com.jsuereth.gl.texture._
132 | given ShaderUniformLoadable[Texture2D] {
133 | def loadUniform(location: Int, value: Texture2D)(using ShaderLoadingEnvironment): Unit = {
134 | // TODO - figure out how to free acquired textures in a nice way
135 | val id = textures.acquire()
136 | // TOOD - is this needed?
137 | // glEnable(GL_TEXTURE_2D)
138 | activate(id)
139 | value.bind()
140 | // Bind the texture
141 | // Tell OpenGL which id we used.
142 | loadTextureUniform(location, id)
143 | }
144 | }
145 | import deriving._
146 | inline def uniformSize[T]: Int =
147 | inline compiletime.erasedValue[T] match {
148 | case _: (head *: tail) => uniformSize[head] + uniformSize[tail]
149 | case _: Unit => 0
150 | case _ => summonFrom {
151 | case m: Mirror.ProductOf[T] => uniformSize[m.MirroredElemTypes]
152 | case primitive: ShaderUniformLoadable[T] => 1
153 | case _ => compiletime.error("Type is not a valid uniform value!")
154 | }
155 | }
156 |
157 | /** Derives uniform loading for struct-like case classes. These MUST have statically known sized. */
158 | inline def derived[T](using m: Mirror.ProductOf[T]): ShaderUniformLoadable[T] =
159 | new ShaderUniformLoadable[T] {
160 | def loadUniform(location: Int, value: T)(using ShaderLoadingEnvironment): Unit =
161 | loadStructAtIdx[m.MirroredElemTypes](location, 0, value.asInstanceOf[Product])
162 | }
163 |
164 | /** Inline helper to load a value into a location, performing the implicit lookup INLINE. */
165 | inline def loadOne[T](location: Int, value: T)(using ShaderLoadingEnvironment): Unit =
166 | summonFrom {
167 | case loader: ShaderUniformLoadable[T] =>
168 | loader.loadUniform(location, value)
169 | case _ =>
170 | compiletime.error("Could not find a uniform serializer for all types!")
171 | }
172 |
173 | /** Peels off a level of case-class property and writes it to a uniform, before continuing to call itself on the next uniform. */
174 | inline def loadStructAtIdx[RemainingElems](location: Int, idx: Int, value: Product)(using ShaderLoadingEnvironment): Unit = {
175 | inline compiletime.erasedValue[RemainingElems] match {
176 | case _: Unit => () // Base case, no elements left.
177 | case _: Tuple1[head] => loadOne[head](location, value.productElement(idx).asInstanceOf)
178 | case _: (head *: tail) =>
179 | // Peel off and load this element.
180 | loadOne[head](location, value.productElement(idx).asInstanceOf)
181 | // Chain the rest of the elements, allowing for nested structures.
182 | val nextLoc = location + uniformSize[head]
183 | loadStructAtIdx[tail](nextLoc, idx+1, value)
184 | }
185 | }
186 | }
187 |
188 |
--------------------------------------------------------------------------------
/example/src/main/scala/Main.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import org.lwjgl.Version
18 | import org.lwjgl.glfw.Callbacks._
19 | import org.lwjgl.opengl._
20 | import org.lwjgl.glfw._
21 | import org.lwjgl.glfw.GLFW._
22 | import org.lwjgl.opengl.GL
23 | import org.lwjgl.glfw.GLFW._
24 | import org.lwjgl.opengl.GL11._
25 | import org.lwjgl.system.MemoryUtil._
26 | import com.jsuereth.gl.math.{given _, _}
27 | import com.jsuereth.gl.io.{
28 | withMemoryStack,
29 | VertexArrayObject,
30 | ShaderLoadingEnvironment,
31 | ActiveTextures
32 | }
33 | import org.lwjgl.system.MemoryStack
34 | import com.jsuereth.gl.mesh.parser.ObjFileParser
35 | import com.jsuereth.gl.scene._
36 | import com.jsuereth.gl.texture.{
37 | Texture,
38 | Texture2D
39 | }
40 | import com.jsuereth.gl.math._
41 |
42 | object Main {
43 | val NULL=0L
44 | val WIDTH = 1920
45 | val HEIGHT = 1080
46 | var window: Long = 0
47 | var scene: Scene = null
48 |
49 |
50 | def run(): Unit = {
51 | System.out.println("Hello LWJGL " + Version.getVersion() + "!");
52 | System.out.println("Example shaders")
53 | System.out.println("--- Vertex Shader ---")
54 | System.out.println(CartoonShader.vertexShaderCode)
55 | System.out.println("--- Fragment Shader ---")
56 | System.out.println(CartoonShader.fragmentShaderCode)
57 | System.out.println("--- ---")
58 | try {
59 | init()
60 | loop()
61 |
62 | // Release window and window callbacks
63 | glfwFreeCallbacks(window)
64 | glfwDestroyWindow(window)
65 | } finally {
66 | // Terminate GLFW and release the GLFWerrorfun
67 | glfwTerminate()
68 | glfwSetErrorCallback(null).free()
69 | }
70 | }
71 |
72 | private def init(): Unit = {
73 | // Setup an error callback. The default implementation
74 | // will print the error message in System.err.
75 | GLFWErrorCallback.createPrint(System.err).set()
76 |
77 | // Initialize GLFW. Most GLFW functions will not work before doing this.
78 | if (!glfwInit()) {
79 | throw new IllegalStateException("Unable to initialize GLFW");
80 | }
81 |
82 | // Configure our window
83 | glfwDefaultWindowHints() // optional, the current window hints are already the default
84 | glfwWindowHint(GLFW_VISIBLE, GL_FALSE) // the window will stay hidden after creation
85 | glfwWindowHint(GLFW_RESIZABLE, GL_TRUE) // the window will be resizable
86 |
87 |
88 |
89 | // Create the window
90 | window = glfwCreateWindow(WIDTH, HEIGHT, "Hello World!", NULL, NULL)
91 | if (window == NULL) {
92 | throw new RuntimeException("Failed to create the GLFW window")
93 | }
94 | val cameraAmount = 0.5f
95 | // Setup a key callback. It will be called every time a key is pressed, repeated or released.
96 | glfwSetKeyCallback(window, (window, key, scancode, action, mods) => {
97 | if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
98 | glfwSetWindowShouldClose(window, true) // We will detect this in the rendering loop
99 | } else if (key == GLFW_KEY_W && action == GLFW_PRESS) {
100 | scene.camera.moveForward(cameraAmount)
101 | } else if (key == GLFW_KEY_S && action == GLFW_PRESS) {
102 | scene.camera.moveForward(-cameraAmount)
103 | } else if (key == GLFW_KEY_A && action == GLFW_PRESS) {
104 | scene.camera.moveRight(-cameraAmount)
105 | } else if (key == GLFW_KEY_D && action == GLFW_PRESS) {
106 | scene.camera.moveRight(cameraAmount)
107 | } else if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
108 | scene.camera.moveUp(-cameraAmount)
109 | } else if (key == GLFW_KEY_X && action == GLFW_PRESS) {
110 | scene.camera.moveUp(cameraAmount)
111 | } else if (key == GLFW_KEY_UP && action == GLFW_PRESS) {
112 | scene.camera.turnUp(cameraAmount)
113 | } else if (key == GLFW_KEY_DOWN && action == GLFW_PRESS) {
114 | scene.camera.turnUp(-cameraAmount)
115 | } else if (key == GLFW_KEY_LEFT && action == GLFW_PRESS) {
116 | scene.camera.turnRight(-cameraAmount)
117 | } else if (key == GLFW_KEY_RIGHT && action == GLFW_PRESS) {
118 | scene.camera.turnRight(cameraAmount)
119 | }
120 | });
121 |
122 | // Get the resolution of the primary monitor
123 | val vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor())
124 | // Center our window
125 | glfwSetWindowPos(
126 | window,
127 | (vidmode.width() - WIDTH) / 2,
128 | (vidmode.height() - HEIGHT) / 2
129 | )
130 |
131 | // Make the OpenGL context current
132 | glfwMakeContextCurrent(window)
133 | // Enable v-sync
134 | glfwSwapInterval(1)
135 |
136 | // Make the window visible
137 | glfwShowWindow(window)
138 | }
139 |
140 | private def loop(): Unit = {
141 | // This line is critical for LWJGL's interoperation with GLFW's
142 | // OpenGL context, or any context that is managed externally.
143 | // LWJGL detects the context that is current in the current thread,
144 | // creates the ContextCapabilities instance and makes the OpenGL
145 | // bindings available for use.
146 | GL.createCapabilities()
147 |
148 | // Set the clear color
149 | glClearColor(0.1f, 0.1f, 0.1f, 0.1f)
150 | glViewport(0,0,WIDTH, HEIGHT)
151 | glEnable(GL_TEXTURE)
152 |
153 |
154 | // Load models and shader.
155 | val models = ObjFileParser.parse(getClass.getClassLoader.getResourceAsStream("mesh/deep_space_1_11.obj"))
156 | System.out.println("Done loading models!")
157 | for ((name, mesh) <- models) {
158 | System.err.println(s" - Loaded model [$name] w/ ${mesh.vertices.size} vertices, ${mesh.normals.size} normals, ${mesh.textureCoords.size} texcoords, ${mesh.faces.size} faces")
159 | }
160 | val mesh =
161 | models.iterator.next._2
162 | val uglyTexture = Texture.loadImage(getClass.getClassLoader.getResourceAsStream("mesh/texture/foil_silver_ramp.png"))
163 |
164 | val scaleFactor = 1f
165 | // TODO - start rendering using the scene...
166 | scene = SimpleStaticSceneBuilder().
167 | add(mesh).scale(scaleFactor,scaleFactor,scaleFactor).orientation(Quaternion.fromEuler(0f,90f,90f)).done().
168 | add(mesh).scale(scaleFactor,scaleFactor,scaleFactor).pos(Vec3(5f,2f,4f)).orientation(Quaternion.fromEuler(90f,0f,0f)).done().
169 | add(mesh).scale(scaleFactor,scaleFactor,scaleFactor).pos(Vec3(-5f,4f,4f)).orientation(Quaternion.fromEuler(0f,0f,90f)).done().
170 | light(Vec3(20f,100f,-5f)).
171 | done()
172 | val projectionMatrix =
173 | Matrix4.perspective(45f, WIDTH.toFloat/HEIGHT.toFloat, 1f, 200f)
174 | val vao = withMemoryStack(mesh.loadVao)
175 | CartoonShader.load()
176 |
177 | System.out.println("-- Shader struct debug --")
178 | System.out.println(s" world: ${CartoonShader.debugUniform("world")}")
179 | System.out.println(s" world.light: ${CartoonShader.debugUniform("world.light")}")
180 | System.out.println(s" world.eye: ${CartoonShader.debugUniform("world.eye")}")
181 | System.out.println(s" world.view: ${CartoonShader.debugUniform("world.view")}")
182 | System.out.println(s" world.projection: ${CartoonShader.debugUniform("world.projection")}")
183 |
184 | // Render a scene using cartoon shader.
185 | def render(): Unit = {
186 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) // clear the framebuffer
187 | glEnable(GL_DEPTH_TEST)
188 | glEnable(GL_CULL_FACE)
189 | glCullFace(GL_BACK)
190 | glEnable(GL_TEXTURE)
191 | glEnable(GL_TEXTURE_2D)
192 |
193 | CartoonShader.bind()
194 | withMemoryStack {
195 | given env as ShaderLoadingEnvironment {
196 | val stack = summon[MemoryStack]
197 | val textures = ActiveTextures()
198 | }
199 | CartoonShader.world := WorldData(light = scene.lights.next,
200 | eye = scene.camera.eyePosition,
201 | view = scene.camera.viewMatrix,
202 | projection = projectionMatrix)
203 | CartoonShader.materialKdTexture := uglyTexture
204 | for (o <- scene.objectsInRenderOrder) {
205 | env.push()
206 | // TODO - pull material from objects.
207 | CartoonShader.materialShininess := 1.3f
208 | CartoonShader.materialKd := 0.5f
209 | CartoonShader.materialKs := 0.4f
210 | CartoonShader.modelMatrix := o.modelMatrix
211 | // TODO - pull the VAO for the model.
212 | vao.draw()
213 | env.pop()
214 | }
215 | }
216 | }
217 |
218 |
219 |
220 | // Run the rendering loop until the user has attempted to close
221 | // the window or has pressed the ESCAPE key.
222 | while (!glfwWindowShouldClose(window)) {
223 | render()
224 | glfwSwapBuffers(window)
225 | // Poll for window events. The key callback above will only be
226 | // invoked during this call.
227 | glfwPollEvents()
228 | }
229 | }
230 |
231 | def main(args: Array[String]): Unit = {
232 | run()
233 | }
234 | }
--------------------------------------------------------------------------------
/math/src/main/scala/com/jsuereth/gl/math/Matrix4.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl
18 | package math
19 |
20 | import scala.reflect.ClassTag
21 |
22 | // Aliases
23 | type Matrix4[T] = Matrix4x4[T]
24 | val Matrix4 = Matrix4x4
25 |
26 | /** A matrix library for floating point values, meant to be relatively efficient and the analog of GLSL matrix. */
27 | class Matrix4x4[T : ClassTag](val values: Array[T]) {
28 | // row-column formats
29 | def m11: T = values(0)
30 | def m21: T = values(1)
31 | def m31: T = values(2)
32 | def m41: T = values(3)
33 | def m12: T = values(4)
34 | def m22: T = values(5)
35 | def m32: T = values(6)
36 | def m42: T = values(7)
37 | def m13: T = values(8)
38 | def m23: T = values(9)
39 | def m33: T = values(10)
40 | def m43: T = values(11)
41 | def m14: T = values(12)
42 | def m24: T = values(13)
43 | def m34: T = values(14)
44 | def m44: T = values(15)
45 |
46 |
47 | def apply(row: Int)(col: Int): T =
48 | values(row + (col*4))
49 |
50 | def *(scale: T)(using Numeric[T]): Matrix4x4[T] =
51 | Matrix4x4(values map (_ * scale))
52 |
53 | def *(point: Vec4[T])(using Numeric[T]): Vec4[T] = {
54 | import point.{x,y,z,w}
55 | val newX = m11*x + m12*y + m13*z + m14*w
56 | val newY = m21*x + m22*y + m23*z + m24*w
57 | val newZ = m31*x + m32*y + m33*z + m34*w
58 | val newW = m41*x + m42*y + m43*z + m44*w
59 | Vec4(newX, newY, newZ, newW)
60 | }
61 |
62 | def *(other: Matrix4x4[T])(using Numeric[T]): Matrix4x4[T] = {
63 | // TODO - attempt to use Coppersmith–Winograd algorithm?
64 | // For now we do this naively, which is possibly more efficeint.
65 | val next = new Array[T](16)
66 | def idx(row: Int, col: Int): Int = row + (col*4)
67 | // TODO - Should we unroll this?
68 | for {
69 | i <- 0 until 4
70 | j <- 0 until 4
71 | } next(idx(i,j)) = (for {
72 | k <- 0 until 4
73 | } yield apply(i)(k) * other(k)(j)).sum
74 | Matrix4x4(next)
75 | }
76 |
77 | def determinant(using Numeric[T]): T = {
78 | // TODO - Use a better decomposition to compute the inverse!
79 | (m14*m23*m32*m41) - (m13*m24*m32*m41) - (m14*m22*m33*m41) + (m12*m24*m33*m41) +
80 | (m13*m22*m34*m41) - (m12*m23*m34*m41) - (m14*m23*m31*m42) + (m13*m24*m31*m42) +
81 | (m14*m21*m33*m41) - (m11*m24*m33*m42) - (m13*m21*m34*m42) + (m11*m23*m34*m42) +
82 | (m14*m22*m31*m43) - (m12*m24*m31*m43) - (m14*m21*m32*m43) + (m11*m24*m32*m43) +
83 | (m12*m21*m34*m43) - (m11*m22*m34*m43) - (m13*m22*m31*m44) + (m12*m23*m31*m44) +
84 | (m13*m21*m32*m44) - (m11*m23*m32*m44) - (m12*m21*m33*m44) + (m11*m22*m33*m44)
85 | }
86 | def transpose: Matrix4x4[T] = {
87 | new Matrix4x4[T](Array(
88 | m11, m12, m13, m14,
89 | m21, m22, m23, m24,
90 | m31, m32, m33, m34,
91 | m41, m42, m43, m44
92 | ))
93 | }
94 | def inverse(using Fractional[T]): Matrix4x4[T] = {
95 | // Stack overflow when we had 1.0f / determinant.
96 | val scale: T = summon[Fractional[T]].one / determinant
97 | // TODO - use a better decomposition to compute the inverse!
98 | val n11 = (m23*m34*m42) - (m24*m33*m42) + (m24*m32*m43) - (m22*m34*m43) - (m23*m32*m44) + (m22*m33*m44)
99 | val n12 = (m14*m33*m42) - (m13*m34*m42) - (m14*m32*m43) + (m12*m34*m43) + (m13*m32*m44) - (m12*m33*m44)
100 | val n13 = (m13*m24*m42) - (m14*m23*m42) + (m14*m22*m43) - (m12*m24*m43) - (m13*m22*m44) + (m12*m23*m44)
101 | val n14 = (m14*m23*m32) - (m13*m24*m32) - (m14*m22*m33) + (m12*m24*m33) + (m13*m22*m34) - (m12*m23*m34)
102 | val n21 = (m24*m33*m41) - (m23*m34*m41) - (m24*m31*m43) + (m21*m34*m43) + (m23*m31*m44) - (m21*m33*m44)
103 | val n22 = (m13*m34*m41) - (m14*m33*m41) + (m14*m31*m43) - (m11*m34*m43) - (m13*m31*m44) + (m11*m33*m44)
104 | val n23 = (m14*m23*m41) - (m13*m24*m41) - (m14*m21*m43) + (m11*m24*m43) + (m13*m21*m44) - (m11*m23*m44)
105 | val n24 = (m13*m24*m31) - (m14*m23*m31) + (m14*m21*m33) - (m11*m24*m33) - (m13*m21*m34) + (m11*m23*m34)
106 | val n31 = (m22*m34*m41) - (m24*m32*m41) + (m24*m31*m42) - (m21*m34*m42) - (m22*m31*m44) + (m21*m32*m44)
107 | val n32 = (m14*m32*m41) - (m12*m34*m41) - (m14*m31*m42) + (m11*m34*m42) + (m12*m31*m44) - (m11*m32*m44)
108 | val n33 = (m12*m24*m41) - (m14*m22*m41) + (m14*m21*m42) - (m11*m24*m42) - (m12*m21*m44) + (m11*m22*m44)
109 | val n34 = m14*m22*m31 - m12*m24*m31 - m14*m21*m32 + m11*m24*m32 + m12*m21*m34 - m11*m22*m34
110 | val n41 = m23*m32*m41 - m22*m33*m41 - m23*m31*m42 + m21*m33*m42 + m22*m31*m43 - m21*m32*m43
111 | val n42 = m12*m33*m41 - m13*m32*m41 + m13*m31*m42 - m11*m33*m42 - m12*m31*m43 + m11*m32*m43
112 | val n43 = m13*m22*m41 - m12*m23*m41 - m13*m21*m42 + m11*m23*m42 + m12*m21*m43 - m11*m22*m43
113 | val n44 = m12*m23*m31 - m13*m22*m31 + m13*m21*m32 - m11*m23*m32 - m12*m21*m33 + m11*m22*m33
114 |
115 | new Matrix4x4[T](Array[T](
116 | n11*scale, n21*scale, n31*scale, n41*scale,
117 | n12*scale, n22*scale, n32*scale, n42*scale,
118 | n13*scale, n23*scale, n33*scale, n43*scale,
119 | n14*scale, n24*scale, n34*scale, n44*scale
120 | ))
121 | }
122 |
123 | def toMat3x3: Matrix3x3[T] =
124 | Matrix3x3(Array(m11, m21, m31,
125 | m12, m22, m32,
126 | m13, m23, m33))
127 |
128 | override def toString: String = {
129 | def f(t: T): String = t.toString // TODO - use fixed-format number width...
130 | s""":|${f(m11)}|${f(m12)}|${f(m13)}|${f(m14)}|
131 | :|${f(m21)}|${f(m22)}|${f(m23)}|${f(m24)}|
132 | :|${f(m31)}|${f(m32)}|${f(m33)}|${f(m34)}|
133 | :|${f(m41)}|${f(m42)}|${f(m43)}|${f(m44)}|""".stripMargin(':')
134 | }
135 | }
136 | object Matrix4x4 {
137 |
138 | def identity[T: ClassTag : Numeric]: Matrix4x4[T] =
139 | new Matrix4x4(Array(
140 | one, zero, zero, zero,
141 | zero, one, zero, zero,
142 | zero, zero, one, zero,
143 | zero, zero, zero, one
144 | ))
145 |
146 | /** Creates a scaling matrix with the given factors:
147 | * @param x the scale factor for the x dimension
148 | * @param y the scale factor for the y dimension
149 | * @param z the scale factory for the z dimension.
150 | */
151 | def scale[T : ClassTag : Numeric](x: T, y: T, z: T): Matrix4x4[T] =
152 | new Matrix4x4(Array(
153 | x, zero, zero, zero,
154 | zero, y, zero, zero,
155 | zero, zero, z, zero,
156 | zero, zero, zero, one
157 | ))
158 |
159 | /**
160 | * Creates an affine transform matrix with transformations in x/y/z space.
161 | */
162 | def translate[T : ClassTag : Numeric](x: T, y: T, z: T): Matrix4x4[T] =
163 | new Matrix4x4(Array(
164 | one, zero, zero, zero,
165 | zero, one, zero, zero,
166 | zero, zero, one, zero,
167 | x, y, z, one
168 | ))
169 | /**
170 | * Creates a rotation matrix around the x axis
171 | */
172 | def rotateX[T: ClassTag : Trigonometry : Numeric](angle: T): Matrix4[T] = {
173 | val r = angleToRadians(angle)
174 | Matrix4(Array(
175 | one, zero, zero, zero,
176 | zero, cos(r), -sin(r), zero,
177 | zero, sin(r), cos(r), zero,
178 | zero, zero, zero, one
179 | ))
180 | }
181 |
182 | /** Creates a rotation matrix around the y axis */
183 | def rotateY[T: ClassTag : Trigonometry : Numeric](angle: T): Matrix4[T] = {
184 | val r = angleToRadians(angle)
185 | Matrix4(Array(
186 | cos(r), zero, sin(r), zero,
187 | zero, one, zero, zero,
188 | -sin(r), zero, cos(r), zero,
189 | zero, zero, zero, one
190 | ))
191 | }
192 | /** Creates a rotations matrix around the z axis */
193 | def rotateZ[T: ClassTag : Trigonometry : Numeric](angle: T): Matrix4[T] = {
194 | val r = angleToRadians(angle)
195 | Matrix4(Array(
196 | cos(r), -sin(r), zero, zero,
197 | sin(r), cos(r), zero, zero,
198 | zero, zero, one, zero,
199 | zero, zero, zero, one
200 | ))
201 | }
202 |
203 | /**
204 | * Creates a 'view' matrix for a given eye.
205 | *
206 | * @param at The location being looked at.
207 | * @param eye The location of the eye.
208 | * @param up The direction of up according to the eye.
209 | */
210 | def lookAt[T: ClassTag : Rootable : Fractional](at: Vec3[T], eye: Vec3[T], up: Vec3[T]): Matrix4[T] = {
211 | val zaxis = (at - eye).normalize // L = C - E
212 | val xaxis = (zaxis cross up).normalize // S = L x U
213 | val yaxis = (xaxis cross zaxis).normalize // U' = S x L
214 | // TODO - can we auto-multiple the camera/eye matrix?
215 | Matrix4[T](Array(
216 | xaxis.x, yaxis.x, -zaxis.x, zero, // (S, 0)
217 | xaxis.y, yaxis.y, -zaxis.y, zero, // (U', 0)
218 | xaxis.z, yaxis.z, -zaxis.z, zero, // (-L, 0)
219 | zero, zero, zero, one // (-E, 1)
220 | )) * Matrix4x4.translate[T](-eye.x, -eye.y, -eye.z)
221 | }
222 | /**
223 | * Creates a perspective projection matrix.
224 | * Based on gluPerspective.
225 | *
226 | * @param fovy - field of view for y (angle in degrees)
227 | * @param aspect - aspect ratio of the screen
228 | * @param zNear - distance from viewer to near clipping plane
229 | * @param zFar - distance from viewer to far clipping plane.
230 | */
231 | def perspective[T : ClassTag : Trigonometry : Fractional](fovy: T, aspect: T, zNear: T, zFar: T): Matrix4x4[T] = {
232 | val f = (one / tan(angleToRadians(fovy / two)))
233 | val z = (zFar + zNear) / (zNear - zFar)
234 | val zFixer = (two*zFar*zNear)/(zNear - zFar)
235 | Matrix4(Array(
236 | f/aspect, zero, zero, zero,
237 | zero, f, zero, zero,
238 | zero, zero, z, zFixer,
239 | zero, zero, -one, zero
240 | ))
241 | }
242 |
243 | /**
244 | * Creates an orthographic projection matrix.
245 | *
246 | * @param left The left clipping plane (x)
247 | * @param right The right clipping plane (x)
248 | * @param top The top clipping plane (y)
249 | * @param bottom The bottom clipping plane (y)
250 | * @param near The near clipping plane (z)
251 | * @param far The far clipping plane (z)
252 | */
253 | def ortho[T : ClassTag : Fractional](left: T, right: T, bottom: T, top: T, near: T, far: T): Matrix4[T] = {
254 | val a = two / (right - left)
255 | val b = two / (top - bottom)
256 | val c = -two / (far - near)
257 |
258 | val tx = -(right + left) / (right - left)
259 | val ty = -(top + bottom) / (top - bottom)
260 | val tz = -(far + near) / (far - near)
261 |
262 | Matrix4(Array(
263 | a, zero, zero, tx,
264 | zero, b, zero, ty,
265 | zero, zero, c, tz,
266 | zero, zero, zero, one))
267 | }
268 | }
269 |
270 |
271 |
272 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/shader/src/main/scala/com/jsuereth/gl/shaders/codegen/convertors.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jsuereth.gl.shaders
18 | package codegen
19 |
20 | import quoted.QuoteContext
21 | import com.jsuereth.gl.math._
22 | import com.jsuereth.gl.texture.{Texture2D}
23 |
24 |
25 | // Symbols for casts between scala number types that we can ignore in GLSL.
26 | val autoCasts = Set("scala.Float$.float2double","scala.Int$.int2double")
27 |
28 | // Arithmetic types we will do operator conversions for. I.e. we take scala method calls that are operator-like and turn them into GLSL operators.
29 | val arithmeticTypes = Set(
30 | "scala.Float",
31 | "scala.Int",
32 | "scala.Double",
33 | "com.jsuereth.gl.math.Vec2",
34 | "com.jsuereth.gl.math.Vec3",
35 | "com.jsuereth.gl.math.Vec4",
36 | "com.jsuereth.gl.math.Matrix4x4",
37 | "com.jsuereth.gl.math.Matrix3x3")
38 | // TODO - not all of these cross correctly...
39 | val arithmeticOps = Set("+", "-", "*", "/", ">", "<")
40 | val operatorTransforms: Map[String, String] =
41 | (for {
42 | tpe <- arithmeticTypes
43 | op <- arithmeticOps
44 | } yield s"$tpe.$op" -> op).toMap
45 |
46 | // Constructors used to create GLSL types.
47 | val typeApplyMethods = Map[String, String](
48 | "com.jsuereth.gl.math.Vec4$.apply" -> "vec4",
49 | "com.jsuereth.gl.math.Vec3$.apply" -> "vec3",
50 | "com.jsuereth.gl.math.Vec2$.apply" -> "vec2",
51 | "com.jsuereth.gl.math.Matrix4x4$.apply" -> "mat4",
52 | "com.jsuereth.gl.math.Matrix3x3$.apply" -> "mat3"
53 | )
54 |
55 | // Operations on java.lang.Math that are built-in on GLSL.
56 | val javaMathOperations = Set("max", "min", "pow")
57 | // Operations we've built into our math library that can be translated into GLSL.
58 | val ourMathOperations = Set("max", "min", "pow")
59 |
60 | trait StmtConvertorEnv {
61 | def recordUniform(name: String, tpe: String): Unit
62 | def recordOutput(name: String, tpe: String, location: Int): Unit
63 | def recordInput(name: String, tpe: String, location: Int): Unit
64 | def recordStruct(struct: codegen.Declaration.Struct): Unit
65 | }
66 | trait ShaderConvertorEnv extends StmtConvertorEnv {
67 | def addStatement(stmt: Statement): Unit
68 | def ast: Ast
69 | }
70 | trait VertexShaderConvertorEnv extends ShaderConvertorEnv {
71 | def fragmentEnv: ShaderConvertorEnv
72 | }
73 |
74 | class DefaultShaderConvertEnv extends ShaderConvertorEnv {
75 | private var stmts = collection.mutable.ArrayBuffer[codegen.Statement]()
76 | private var globals = collection.mutable.ArrayBuffer[codegen.Declaration]()
77 | private var names = Set[String]()
78 | private var types = Set[String]()
79 | override def addStatement(stmt: codegen.Statement): Unit = stmts.append(stmt)
80 | // TODO - record location
81 | override def recordInput(name: String, tpe: String, location: Int): Unit =
82 | if (!names(name)) {
83 | globals.append(codegen.Declaration.Input(name, tpe, Some(location)))
84 | names += name
85 | }
86 | override def recordUniform(name: String, tpe: String): Unit =
87 | if (!names(name)) {
88 | globals.append(codegen.Declaration.Uniform(name, tpe))
89 | names += name
90 | }
91 | override def recordStruct(struct: codegen.Declaration.Struct): Unit = {
92 | if (!types(struct.name)) {
93 | globals.prepend(struct)
94 | types += struct.name
95 | }
96 | }
97 | override def recordOutput(name: String, tpe: String, location: Int): Unit =
98 | if (!names(name)) {
99 | globals.append(codegen.Declaration.Output(name, tpe, Some(location)))
100 | names += name
101 | }
102 | override def ast: codegen.Ast = {
103 | codegen.Ast(globals.toSeq :+ codegen.Declaration.Method("main", "void", stmts.toSeq))
104 | }
105 | }
106 |
107 | /** Utilities for translating Scala into GLSL. */
108 | class Convertors[R <: tasty.Reflection](val r: R)(using QuoteContext) {
109 | // TODO - given new API we may be able to use Type/TypeTree directly without
110 | // hiding it behind tasty.Reflection instance.
111 | import r._
112 |
113 | def toGlslTypeFromTree(tpe: r.TypeTree): String = toGlslType(tpe.tpe)
114 | def toGlslType(tpe: r.Type): String = {
115 | import r._
116 | // TODO - more principled version of this...
117 | // TODO - test to ensure this lines up with sizeof, vaoattribute, uniform loader, etc.
118 | if (tpe <:< typeOf[Vec2[Float]]) "vec2"
119 | else if (tpe <:< typeOf[Vec2[Int]]) "ivec2"
120 | else if (tpe <:< typeOf[Vec3[Float]]) "vec3"
121 | else if (tpe <:< typeOf[Vec3[Int]]) "ivec3"
122 | else if(tpe <:< typeOf[Vec4[Float]]) "vec4"
123 | else if(tpe <:< typeOf[Vec4[Int]]) "ivec4"
124 | else if(tpe <:< typeOf[Matrix4x4[Float]]) "mat4"
125 | else if(tpe <:< typeOf[Matrix3x3[Float]]) "mat3"
126 | else if(tpe <:< typeOf[Float]) "float"
127 | else if(tpe <:< typeOf[Boolean]) "bool"
128 | else if(tpe <:< typeOf[Int]) "int"
129 | else if(tpe <:< typeOf[Double]) "double"
130 | else if(tpe <:< typeOf[Unit]) "void"
131 | else if(tpe <:< typeOf[Texture2D]) "sampler2D"
132 | // TODO - a real compiler errors, not a runtime exception.
133 | else throw new RuntimeException(s"Unknown GLSL type: ${tpe}")
134 | }
135 |
136 | // TODO - can we make type->glsl type a straight inline method we use in macros?
137 | def toStructMemberDef(sym: r.Symbol): StructMember =
138 | StructMember(sym.name, toGlslType(sym.tree.asInstanceOf[r.ValDef].tpt.tpe))
139 |
140 | /** Converts a given type into a GLSL struct definition. */
141 | def toStructDefinition(tpe: r.Type): Option[Declaration.Struct] =
142 | tpe.classSymbol map { clsSym =>
143 | new Declaration.Struct(clsSym.name, clsSym.caseFields.map(toStructMemberDef).toSeq)
144 | }
145 | /** Checks whether a given type is an GLSL "opaque" type. */
146 | def isOpaqueType(tpe: r.Type): Boolean = tpe <:< typeOf[Texture2D]
147 | /** Returns true if a given type is a "structure-like" type for the purposes of GLSL. */
148 | def isStructType(tpe: r.Type): Boolean = tpe.classSymbol match {
149 | // TODO - more robust definition. E.g. check if ALL fields have a GLSL type.
150 | case Some(clsSym) => !clsSym.caseFields.isEmpty && !isOpaqueType(tpe)
151 | case None => false
152 | }
153 | /** Returns the first member name of a structure type, if this is a structure type. Note: This is a workaround. */
154 | def firstStructMemberName(tpe: r.Type): Option[String] =
155 | for {
156 | struct <- toStructDefinition(tpe)
157 | member <- struct.members.headOption
158 | } yield member.name
159 |
160 |
161 | /** Extractor for detecting a "fragmentShader {}" block. */
162 | object FragmentShader {
163 | def unapply(tree: r.Statement): Option[r.Statement] = tree match {
164 | case Inlined(Some(Apply(mth, args)), statements, expansion) if mth.symbol.fullName == "com.jsuereth.gl.shaders.DslShaderProgram.fragmentShader" => Some(expansion)
165 | case _ => None
166 | }
167 | }
168 | object Erased {
169 | def unapply(tree: r.Statement): Option[r.Tree] =
170 | tree match {
171 | case Inlined(Some(erased), stmts, blck) => Some(erased)
172 | case _ => None
173 | }
174 | }
175 |
176 | /** Extractor for val x = Input(...) */
177 | object Input {
178 | /** Returns: name, type, location */
179 | def unapply(tree: r.Statement): Option[(String, String, Int)] = tree match {
180 | // defined in object
181 | case ValDef(sym, tpe, Some(Erased(
182 | Apply(TypeApply(Ident("Input"), _), List(NamedArg("location", Literal(Constant(location: Int))))))
183 | )) =>
184 | Some(sym, toGlslTypeFromTree(tpe), location)
185 | // Defined in Inline....
186 | case _ => None
187 | }
188 | }
189 | /** Extractor for: Output("name", position, value) */
190 | object Output {
191 | /** Returns: name, value (todo - tpe + location) */
192 | def unapply(tree: r.Statement): Option[(String, String, r.Statement)] = tree match {
193 | case Erased(Apply(TypeApply(Ident("Output"), _), List(Literal(Constant(name: String)), position, value))) =>
194 | Some(name, toGlslType(value.tpe), value)
195 | case _ => None
196 | }
197 | }
198 | /** Extractor for: () */
199 | object Uniform {
200 | /** Returns: name, type. */
201 | def unapply(tree: r.Statement): Option[(String, String)] = tree match {
202 | case Erased(Apply(Apply(TypeApply(Ident("apply"), List(tpe)), List(a @ Ident(ref))), List())) if a.tpe <:< typeOf[Uniform[_]] =>
203 | // TODO - handle uniform-style structs.
204 | if (isStructType(tpe.tpe)) None
205 | else Some(ref.toString, toGlslType(tpe.tpe))
206 | case _ => None
207 | }
208 | }
209 | /** Extractor for: (), where we are accessing a struct type. */
210 | object UniformStruct {
211 | /** Returns: name, type and structure definition. */
212 | def unapply(tree: r.Statement): Option[(String, Declaration.Struct)] = tree match {
213 | case Erased(Apply(Apply(TypeApply(Ident("apply"), List(tpe)), List(a @ Ident(ref))), List())) if a.tpe <:< typeOf[Uniform[_]] && isStructType(tpe.tpe) =>
214 | // TODO - handle uniform-style structs.
215 | toStructDefinition(tpe.tpe) match {
216 | case Some(struct) => Some(ref.toString, struct)
217 | case None => None
218 | }
219 | case _ => None
220 | }
221 | }
222 | /** Extractor for: gl_Position =. */
223 | object GlPosition {
224 | // TODO - limit glPosition to calling against the appropriate type/symbol...
225 | def unapply(tree: r.Statement): Option[r.Statement] = tree match {
226 | // Called by Object
227 | case Erased(Apply(TypeApply(Ident("glPosition"), _), List(value))) => Some(value)
228 | // called within Class
229 | // TODO - update for inlined/erasure.
230 | case Apply(TypeApply(Select(_, "glPosition"), _), List(value)) => Some(value)
231 | case _ => None
232 | }
233 | }
234 | /** Detect if scala is converting between int->float->double, etc. where GLSL automatically does this. */
235 | object SafeAutoCast {
236 | def unapply(tree: r.Statement): Option[r.Statement] = tree match {
237 | case Apply(mthd, List(arg)) if autoCasts(mthd.symbol.fullName) => Some(arg)
238 | case _ => None
239 | }
240 | }
241 | /** Detect a GLSL operator encoded in Scala. */
242 | object Operator {
243 | /** Returns: operator, lhs, rhs. */
244 | def unapply(tree: r.Statement): Option[(String, r.Statement, r.Statement)] = tree match {
245 | // Handle operators that have implicit params... [foo.op(bar)(implicits)]
246 | case Apply(Apply(mthd @ Select(lhs, _), List(rhs)), _) if operatorTransforms.contains(mthd.symbol.fullName) =>
247 | Some((operatorTransforms(mthd.symbol.fullName), lhs, rhs))
248 | // Handle operators without implicit params....
249 | case Apply(mthd @ Select(lhs, _), List(rhs)) if operatorTransforms.contains(mthd.symbol.fullName) =>
250 | Some((operatorTransforms(mthd.symbol.fullName), lhs, rhs))
251 | case _ => None
252 | }
253 | }
254 | /** Detect a GLSL constructor in Scala. */
255 | object Constructor {
256 | /** Returns: type, args */
257 | def unapply(tree: r.Statement): Option[(String, List[r.Statement])] = tree match {
258 | case Apply(term @ Apply(_, args), _) if typeApplyMethods.contains(term.symbol.fullName) =>
259 | Some((typeApplyMethods(term.symbol.fullName), args))
260 | case _ => None
261 | }
262 | }
263 | /** Detect GLSL built-in functions. */
264 | object BuiltinFunction {
265 | /** Returns: operation, args */
266 | def unapply(tree: r.Statement): Option[(String, List[r.Statement])] = tree match {
267 | // Handle java.lang.Math methods (todo - figure a better way to handle all built-in methods)
268 | case Apply(Select(ref, mthd), List(lhs,rhs)) if ref.symbol.fullName == "java.lang.Math" && javaMathOperations(mthd) =>
269 | Some((mthd, List(lhs, rhs)))
270 | // TODO - lookup for all normalize methods...
271 | case Apply(term @ Select(ref, "normalize"), List(_,_)) if term.symbol.fullName == "com.jsuereth.gl.math.Vec3.normalize" =>
272 | Some(("normalize", List(ref)))
273 | // Handle dot products on vectors
274 | // TODO - check the symbol of the method.
275 | case Apply(Apply(mthd @ Select(lhs, "dot"), List(rhs)), _) =>
276 | Some(("dot", List(lhs,rhs)))
277 | // Handle Sampler2D methods
278 | case Erased(Apply(Apply(Ident("texture"), List(ref)), List(arg))) if ref.tpe <:< typeOf[Texture2D] =>
279 | Some("texture", List(ref, arg))
280 | // Handle our built-int math operations. TODO - lock this down to NOT be so flexible...
281 | case Apply(Apply(TypeApply(Ident(mathOp), _), args), /* Implicit witnesses */_) if ourMathOperations(mathOp) =>
282 | Some((mathOp, args))
283 | case _ => None
284 | }
285 | }
286 |
287 | def constantToString(c: r.Constant): String = {
288 | import r._
289 | c match {
290 | case Constant(null) => "null"
291 | case Constant(_: Unit) => ""
292 | case Constant(n) => n.toString
293 | }
294 | }
295 |
296 | def convertExpr(tree: r.Statement)(using env: StmtConvertorEnv): Expr = tree match {
297 | case Uniform(name, tpe) =>
298 | env.recordUniform(name, tpe)
299 | Expr.Id(name)
300 | case UniformStruct(name, st) =>
301 | env.recordStruct(st)
302 | env.recordUniform(name, st.name)
303 | Expr.Id(name)
304 | case SafeAutoCast(arg) => convertExpr(arg)
305 | case Operator(name, lhs, rhs) => Expr.Operator(name, convertExpr(lhs), convertExpr(rhs))
306 | case Constructor(name, args) => Expr.MethodCall(name, args.map(convertExpr))
307 | case BuiltinFunction(name, args) => Expr.MethodCall(name, args.map(convertExpr))
308 | case Ident(localRef) => Expr.Id(localRef)
309 | case If(cond, lhs, rhs) => Expr.Terenary(convertExpr(cond), convertExpr(lhs), convertExpr(rhs))
310 | // Erased methods...
311 | case Select(expr, "toFloat") => convertExpr(expr)
312 | case SafeAutoCast(arg) => convertExpr(arg)
313 | case Select(term, mthd) => Expr.Select(convertExpr(term), mthd)
314 | // TODO - actual encode literals in AST?
315 | case Literal(constant) => Expr.Id(constantToString(constant))
316 | // TODO - error message for everything else.
317 | //case Inlined(_, _, blck) => convertExpr(blck)
318 | case _ =>
319 | summon[QuoteContext].error(s"Unable to convert to expr: ${tree.show}\n\nfull tree: $tree\n\nPlease file a bug with this context.")
320 | Expr.Id("")
321 | }
322 | def convertStmt(tree: r.Statement)(using env: StmtConvertorEnv): codegen.Statement = tree match {
323 | // assign
324 | case Output(name, tpe, exp) =>
325 | env.recordOutput(name, tpe, 0) // TODO - location
326 | codegen.Statement.Assign(name, convertExpr(exp))
327 | case Input(name, tpe, location) =>
328 | env.recordInput(name, tpe, location)
329 | codegen.Statement.Effect(Expr.Id("")) // TODO - don't make fake statements....
330 | case GlPosition(value) => Statement.Assign("gl_Position", convertExpr(value))
331 | // local variable
332 | case ValDef(sym, tpe, optExpr) => codegen.Statement.LocalVariable(sym.toString, toGlslTypeFromTree(tpe), optExpr.map(convertExpr))
333 | //case VarDef(sym, tpe, optExpr) => Statement.LocalVariable(sym.toString, toGlslTypeFromTree(tpe), optExpr.map(convertExpr))
334 | // effect
335 | case _ => codegen.Statement.Effect(convertExpr(tree))
336 | }
337 | // TODO - handle helper methods and extract them separately as definitions.
338 | def walkFragmentShader(tree: r.Statement)(using env: ShaderConvertorEnv): Unit = tree match {
339 | case Inlined(None, stmts, exp) => walkFragmentShader(exp)
340 | case Block(statements, last) =>
341 | for(s <- (statements :+ last)) {
342 | walkFragmentShader(s)
343 | }
344 | // Ignore when we type a block as "Unit"
345 | case Typed(block, tpe) if tpe.tpe <:< typeOf[Unit] => walkFragmentShader(block)
346 | case other => env.addStatement(convertStmt(other))
347 | }
348 |
349 | // TODO - share some of the "walk" code with walkFragmentShader.
350 | def walkVertexShader(tree: r.Statement)(using env: VertexShaderConvertorEnv): Unit = tree match {
351 | // Ignore when we type a block as "Unit"
352 | case Typed(block, tpe) if tpe.tpe <:< typeOf[Unit] => walkVertexShader(block)
353 | case FragmentShader(exp) =>
354 | // TODO - make a new environment, then unify the shaders. after walking.
355 | walkFragmentShader(exp)(using env.fragmentEnv)
356 | case Inlined(None, stmts, exp) => walkVertexShader(exp)
357 | case Block(statements, last) =>
358 | for(s <- (statements :+ last)) {
359 | walkVertexShader(s)
360 | }
361 | case stmt => env.addStatement(convertStmt(stmt))
362 | }
363 |
364 | // TODO - we need to return separate ASTs for vertex/fragment shaders.
365 | def convert(tree: r.Statement): (codegen.Ast, codegen.Ast) = {
366 | object env extends DefaultShaderConvertEnv with VertexShaderConvertorEnv {
367 | override val fragmentEnv = new DefaultShaderConvertEnv()
368 | }
369 | //summon[QuoteContext].warning(s"Found shader: $tree");
370 | walkVertexShader(tree)(using env)
371 | TransformAst(env.ast, env.fragmentEnv.ast)
372 | }
373 | }
--------------------------------------------------------------------------------