Add gltf animation support to orx-dnk3
This commit is contained in:
BIN
demo-data/gltf-models/box-animated/BoxAnimated.glb
Normal file
BIN
demo-data/gltf-models/box-animated/BoxAnimated.glb
Normal file
Binary file not shown.
@@ -11,6 +11,7 @@ sourceSets {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation "com.google.code.gson:gson:$gsonVersion"
|
implementation "com.google.code.gson:gson:$gsonVersion"
|
||||||
implementation(project(":orx-fx"))
|
implementation(project(":orx-fx"))
|
||||||
|
implementation(project(":orx-keyframer"))
|
||||||
demoImplementation(project(":orx-camera"))
|
demoImplementation(project(":orx-camera"))
|
||||||
demoImplementation("org.openrndr:openrndr-core:$openrndrVersion")
|
demoImplementation("org.openrndr:openrndr-core:$openrndrVersion")
|
||||||
demoImplementation("org.openrndr:openrndr-extensions:$openrndrVersion")
|
demoImplementation("org.openrndr:openrndr-extensions:$openrndrVersion")
|
||||||
|
|||||||
61
orx-dnk3/src/demo/kotlin/DemoAnimations01.kt
Normal file
61
orx-dnk3/src/demo/kotlin/DemoAnimations01.kt
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import org.openrndr.application
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.extensions.SingleScreenshot
|
||||||
|
import org.openrndr.extra.dnk3.*
|
||||||
|
import org.openrndr.extra.dnk3.gltf.buildSceneNodes
|
||||||
|
import org.openrndr.extra.dnk3.gltf.loadGltfFromFile
|
||||||
|
import org.openrndr.extras.camera.Orbital
|
||||||
|
import org.openrndr.math.Matrix33
|
||||||
|
import org.openrndr.math.Quaternion
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
|
import org.openrndr.math.mod_
|
||||||
|
import org.openrndr.math.transforms.transform
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun main() = application {
|
||||||
|
configure {
|
||||||
|
width = 1280
|
||||||
|
height = 720
|
||||||
|
//multisample = WindowMultisample.SampleCount(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
program {
|
||||||
|
if (System.getProperty("takeScreenshot") == "true") {
|
||||||
|
extend(SingleScreenshot()) {
|
||||||
|
this.outputFile = System.getProperty("screenshotPath")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val gltf = loadGltfFromFile(File("demo-data/gltf-models/box-animated/BoxAnimated.glb"))
|
||||||
|
val scene = Scene(SceneNode())
|
||||||
|
|
||||||
|
|
||||||
|
// -- add some lights
|
||||||
|
val lightNode = SceneNode()
|
||||||
|
lightNode.transform = transform {
|
||||||
|
translate(0.0, 10.0, 0.0)
|
||||||
|
rotate(Vector3.UNIT_X, -65.0)
|
||||||
|
}
|
||||||
|
lightNode.entities.add(DirectionalLight())
|
||||||
|
scene.root.entities.add(HemisphereLight().apply {
|
||||||
|
upColor = ColorRGBa.BLUE.shade(0.4)
|
||||||
|
downColor = ColorRGBa.GRAY.shade(0.1)
|
||||||
|
})
|
||||||
|
scene.root.children.add(lightNode)
|
||||||
|
val sceneData = gltf.buildSceneNodes()
|
||||||
|
scene.root.children.addAll(sceneData.scenes.first())
|
||||||
|
|
||||||
|
// -- create a renderer
|
||||||
|
val renderer = dryRenderer()
|
||||||
|
extend(Orbital()) {
|
||||||
|
far = 50.0
|
||||||
|
eye = Vector3(1.5, 0.0, 3.0)
|
||||||
|
fov = 40.0
|
||||||
|
}
|
||||||
|
extend {
|
||||||
|
sceneData.animations[0].applyToTargets(seconds.mod_(4.0))
|
||||||
|
drawer.clear(ColorRGBa.PINK)
|
||||||
|
renderer.draw(drawer, scene)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ fun main() = application {
|
|||||||
downColor = ColorRGBa.GRAY.shade(0.1)
|
downColor = ColorRGBa.GRAY.shade(0.1)
|
||||||
})
|
})
|
||||||
scene.root.children.add(lightNode)
|
scene.root.children.add(lightNode)
|
||||||
scene.root.children.addAll(gltf.buildSceneNodes().first())
|
scene.root.children.addAll(gltf.buildSceneNodes().scenes.first())
|
||||||
|
|
||||||
// -- create a renderer
|
// -- create a renderer
|
||||||
val renderer = dryRenderer()
|
val renderer = dryRenderer()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.openrndr.extra.dnk3.*
|
|||||||
|
|
||||||
import org.openrndr.extra.dnk3.gltf.buildSceneNodes
|
import org.openrndr.extra.dnk3.gltf.buildSceneNodes
|
||||||
import org.openrndr.extra.dnk3.gltf.loadGltfFromFile
|
import org.openrndr.extra.dnk3.gltf.loadGltfFromFile
|
||||||
|
import org.openrndr.extra.dnk3.gltf.loadGltfFromGlbFile
|
||||||
import org.openrndr.extras.camera.Orbital
|
import org.openrndr.extras.camera.Orbital
|
||||||
import org.openrndr.math.Vector3
|
import org.openrndr.math.Vector3
|
||||||
import org.openrndr.math.transforms.transform
|
import org.openrndr.math.transforms.transform
|
||||||
@@ -24,30 +25,32 @@ fun main() = application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val gltf = loadGltfFromFile(File("demo-data/gltf-models/duck/Duck.gltf"))
|
val gltf = loadGltfFromFile(File("demo-data/gltf-models/complex02/scene.gltf"))
|
||||||
|
// val gltf = loadGltfFromGlbFile(File("demo-data/gltf-models/splash-sss.glb"))
|
||||||
val scene = Scene(SceneNode())
|
val scene = Scene(SceneNode())
|
||||||
|
|
||||||
// -- add some lights
|
// -- add some lights
|
||||||
val lightNode = SceneNode()
|
val lightNode = SceneNode()
|
||||||
lightNode.transform = transform {
|
lightNode.transform = transform {
|
||||||
translate(0.0, 10.0, 0.0)
|
translate(0.0, 10.0, 0.0)
|
||||||
rotate(Vector3.UNIT_X, -65.0)
|
rotate(Vector3.UNIT_X, -90.0)
|
||||||
}
|
}
|
||||||
lightNode.entities.add(DirectionalLight())
|
lightNode.entities.add(DirectionalLight())
|
||||||
scene.root.entities.add(HemisphereLight().apply {
|
scene.root.entities.add(HemisphereLight().apply {
|
||||||
upColor = ColorRGBa.WHITE.shade(0.4)
|
upColor = ColorRGBa.WHITE.shade(1.0)
|
||||||
downColor = ColorRGBa.WHITE.shade(0.1)
|
downColor = ColorRGBa.WHITE.shade(0.1)
|
||||||
})
|
})
|
||||||
scene.root.children.add(lightNode)
|
scene.root.children.add(lightNode)
|
||||||
scene.root.children.addAll(gltf.buildSceneNodes().first())
|
scene.root.children.addAll(gltf.buildSceneNodes().scenes.first())
|
||||||
|
|
||||||
|
|
||||||
// -- create a renderer
|
// -- create a renderer
|
||||||
val renderer = dryRenderer()
|
val renderer = dryRenderer()
|
||||||
extend(Orbital()) {
|
extend(Orbital()) {
|
||||||
far = 50.0
|
far = 500.0
|
||||||
lookAt = Vector3(0.0, 0.7, 0.0)
|
lookAt = Vector3(0.0, 0.7, 0.0)
|
||||||
eye = Vector3(3.0, 0.7, -2.0)
|
eye = Vector3(3.0, 0.7, -2.0)
|
||||||
fov = 40.0
|
fov = 30.0
|
||||||
}
|
}
|
||||||
extend {
|
extend {
|
||||||
drawer.clear(ColorRGBa.PINK)
|
drawer.clear(ColorRGBa.PINK)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ abstract class ColorBufferFacetCombiner(facets: Set<FacetType>,
|
|||||||
targetOutput: String,
|
targetOutput: String,
|
||||||
val format: ColorFormat,
|
val format: ColorFormat,
|
||||||
val type: ColorType,
|
val type: ColorType,
|
||||||
val blendMode: BlendMode = BlendMode.REPLACE) : FacetCombiner(facets, targetOutput)
|
val blendMode: BlendMode = BlendMode.BLEND) : FacetCombiner(facets, targetOutput)
|
||||||
|
|
||||||
class MomentsFacet : ColorBufferFacetCombiner(setOf(FacetType.WORLD_POSITION), "moments", ColorFormat.RG, ColorType.FLOAT16) {
|
class MomentsFacet : ColorBufferFacetCombiner(setOf(FacetType.WORLD_POSITION), "moments", ColorFormat.RG, ColorType.FLOAT16) {
|
||||||
override fun generateShader(): String {
|
override fun generateShader(): String {
|
||||||
@@ -114,7 +114,9 @@ class ClipPositionFacet : ColorBufferFacetCombiner(setOf(FacetType.CLIP_POSITION
|
|||||||
class LDRColorFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE, FacetType.SPECULAR), "color", ColorFormat.RGBa, ColorType.UINT8) {
|
class LDRColorFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE, FacetType.SPECULAR), "color", ColorFormat.RGBa, ColorType.UINT8) {
|
||||||
override fun generateShader() = """
|
override fun generateShader() = """
|
||||||
vec3 oofinalColor = (f_diffuse.rgb + f_specular.rgb + f_emission.rgb) * (1.0 - f_fog.a) + f_fog.rgb * f_fog.a;
|
vec3 oofinalColor = (f_diffuse.rgb + f_specular.rgb + f_emission.rgb) * (1.0 - f_fog.a) + f_fog.rgb * f_fog.a;
|
||||||
o_$targetOutput.rgba = pow(vec4(oofinalColor, 1.0), vec4(1.0/2.2));
|
o_$targetOutput = pow(vec4(oofinalColor.rgb, 1.0), vec4(1.0/2.2));
|
||||||
o_$targetOutput.a = f_alpha;
|
o_$targetOutput *= m_color.a;
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
@@ -57,12 +57,12 @@ private fun DirectionalLight.fs(index: Int) = """
|
|||||||
//| vec3 L = normalize(Lr);
|
//| vec3 L = normalize(Lr);
|
||||||
| ${shadows.fs(index)}
|
| ${shadows.fs(index)}
|
||||||
|
|
|
|
||||||
| f_diffuse += NoL * attenuation * Fd_Burley(m_roughness * m_roughness, NoV, NoL, LoH) * p_lightColor$index.rgb * m_color.rgb ;
|
| f_diffuse += NoL * attenuation * Fd_Burley(m_roughness * m_roughness, NoV, NoL, LoH) * p_lightColor$index.rgb * m_color.rgb * m_ambientOcclusion;;
|
||||||
| float Dg = D_GGX(m_roughness * m_roughness, NoH, H);
|
| float Dg = D_GGX(m_roughness * m_roughness, NoH, H);
|
||||||
| float Vs = V_SmithGGXCorrelated(m_roughness * m_roughness, NoV, NoL);
|
| float Vs = V_SmithGGXCorrelated(m_roughness * m_roughness, NoV, NoL);
|
||||||
| vec3 F = F_Schlick(m_color * (m_metalness) + 0.04 * (1.0-m_metalness), LoH);
|
| vec3 F = F_Schlick(m_color.rgb * (m_metalness) + 0.04 * (1.0-m_metalness), LoH);
|
||||||
| vec3 Fr = (Dg * Vs) * F;
|
| vec3 Fr = (Dg * Vs) * F;
|
||||||
| f_specular += NoL * attenuation * Fr * p_lightColor$index.rgb;
|
| f_specular += NoL * attenuation * Fr * p_lightColor$index.rgb * m_ambientOcclusion;;
|
||||||
|}
|
|}
|
||||||
""".trimMargin()
|
""".trimMargin()
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ private fun HemisphereLight.fs(index: Int): String = """
|
|||||||
|{
|
|{
|
||||||
| float f = dot(N, p_lightDirection$index) * 0.5 + 0.5;
|
| float f = dot(N, p_lightDirection$index) * 0.5 + 0.5;
|
||||||
| vec3 irr = ${irradianceMap?.let { "texture(p_lightIrradianceMap$index, N).rgb" } ?: "vec3(1.0)"};
|
| vec3 irr = ${irradianceMap?.let { "texture(p_lightIrradianceMap$index, N).rgb" } ?: "vec3(1.0)"};
|
||||||
| f_diffuse += mix(p_lightDownColor$index.rgb, p_lightUpColor$index.rgb, f) * irr * ((1.0 - m_metalness) * m_color.rgb);// * m_ambientOcclusion;
|
| f_diffuse += mix(p_lightDownColor$index.rgb, p_lightUpColor$index.rgb, f) * irr * ((1.0 - m_metalness) * m_color.rgb) * m_ambientOcclusion;
|
||||||
|}
|
|}
|
||||||
""".trimMargin()
|
""".trimMargin()
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ private fun SpotLight.fs(index: Int): String {
|
|||||||
| f_diffuse += NoL * (0.1+0.9*attenuation) * Fd_Burley(m_roughness * m_roughness, NoV, NoL, LoH) * p_lightColor$index.rgb * m_color.rgb ;
|
| f_diffuse += NoL * (0.1+0.9*attenuation) * Fd_Burley(m_roughness * m_roughness, NoV, NoL, LoH) * p_lightColor$index.rgb * m_color.rgb ;
|
||||||
| float Dg = D_GGX(m_roughness * m_roughness, NoH, H);
|
| float Dg = D_GGX(m_roughness * m_roughness, NoH, H);
|
||||||
| float Vs = V_SmithGGXCorrelated(m_roughness * m_roughness, NoV, NoL);
|
| float Vs = V_SmithGGXCorrelated(m_roughness * m_roughness, NoV, NoL);
|
||||||
| vec3 F = F_Schlick(m_color * (m_metalness) + 0.04 * (1.0-m_metalness), LoH);
|
| vec3 F = F_Schlick(m_color.rgb * (m_metalness) + 0.04 * (1.0-m_metalness), LoH);
|
||||||
| vec3 Fr = (Dg * Vs) * F;
|
| vec3 Fr = (Dg * Vs) * F;
|
||||||
| f_specular += NoL * attenuation * Fr * p_lightColor$index.rgb;
|
| f_specular += NoL * attenuation * Fr * p_lightColor$index.rgb;
|
||||||
| }
|
| }
|
||||||
@@ -252,7 +252,6 @@ class PBRMaterial : Material {
|
|||||||
var color = ColorRGBa.WHITE
|
var color = ColorRGBa.WHITE
|
||||||
var metalness = 0.5
|
var metalness = 0.5
|
||||||
var roughness = 1.0
|
var roughness = 1.0
|
||||||
var opacity = 1.0
|
|
||||||
var emission = ColorRGBa.BLACK
|
var emission = ColorRGBa.BLACK
|
||||||
|
|
||||||
var vertexPreamble: String? = null
|
var vertexPreamble: String? = null
|
||||||
@@ -281,15 +280,13 @@ class PBRMaterial : Material {
|
|||||||
val cached = shadeStyles.getOrPut(context) {
|
val cached = shadeStyles.getOrPut(context) {
|
||||||
val needLight = needLight(context)
|
val needLight = needLight(context)
|
||||||
val preambleFS = """
|
val preambleFS = """
|
||||||
vec3 m_color = p_color.rgb;
|
vec4 m_color = p_color;
|
||||||
float m_f0 = 0.5;
|
float m_f0 = 0.5;
|
||||||
float m_roughness = p_roughness;
|
float m_roughness = p_roughness;
|
||||||
float m_metalness = p_metalness;
|
float m_metalness = p_metalness;
|
||||||
float m_opacity = p_opacity;
|
|
||||||
float m_ambientOcclusion = 1.0;
|
float m_ambientOcclusion = 1.0;
|
||||||
vec3 m_emission = p_emission.rgb;
|
vec3 m_emission = p_emission.rgb;
|
||||||
vec3 m_normal = vec3(0.0, 0.0, 1.0);
|
vec3 m_normal = vec3(0.0, 0.0, 1.0);
|
||||||
float f_alpha = m_opacity;
|
|
||||||
vec4 f_fog = vec4(0.0, 0.0, 0.0, 0.0);
|
vec4 f_fog = vec4(0.0, 0.0, 0.0, 0.0);
|
||||||
vec3 f_worldNormal = v_worldNormal;
|
vec3 f_worldNormal = v_worldNormal;
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
@@ -306,7 +303,7 @@ class PBRMaterial : Material {
|
|||||||
} + textures.mapIndexed { index, texture ->
|
} + textures.mapIndexed { index, texture ->
|
||||||
when (texture.target) {
|
when (texture.target) {
|
||||||
TextureTarget.NONE -> ""
|
TextureTarget.NONE -> ""
|
||||||
TextureTarget.COLOR -> "m_color.rgb *= pow(tex$index.rgb, vec3(2.2));"
|
TextureTarget.COLOR -> "m_color.rgb *= pow(tex$index.rgb, vec3(2.2)); m_color.a *= tex$index.a;"
|
||||||
TextureTarget.METALNESS -> "m_metalness = tex$index.r;"
|
TextureTarget.METALNESS -> "m_metalness = tex$index.r;"
|
||||||
TextureTarget.ROUGHNESS -> "m_roughness = tex$index.r;"
|
TextureTarget.ROUGHNESS -> "m_roughness = tex$index.r;"
|
||||||
TextureTarget.METALNESS_ROUGHNESS -> "m_metalness = tex$index.r; m_roughness = tex$index.g;"
|
TextureTarget.METALNESS_ROUGHNESS -> "m_metalness = tex$index.r; m_roughness = tex$index.g;"
|
||||||
@@ -420,7 +417,6 @@ class PBRMaterial : Material {
|
|||||||
shadeStyle.parameter("color", color)
|
shadeStyle.parameter("color", color)
|
||||||
shadeStyle.parameter("metalness", metalness)
|
shadeStyle.parameter("metalness", metalness)
|
||||||
shadeStyle.parameter("roughness", roughness)
|
shadeStyle.parameter("roughness", roughness)
|
||||||
shadeStyle.parameter("opacity", opacity)
|
|
||||||
|
|
||||||
parameters.forEach { (k, v) ->
|
parameters.forEach { (k, v) ->
|
||||||
when (v) {
|
when (v) {
|
||||||
@@ -464,7 +460,6 @@ class PBRMaterial : Material {
|
|||||||
when (light) {
|
when (light) {
|
||||||
is AmbientLight -> {
|
is AmbientLight -> {
|
||||||
}
|
}
|
||||||
|
|
||||||
is PointLight -> {
|
is PointLight -> {
|
||||||
shadeStyle.parameter("lightPosition$index", (node.worldTransform * Vector4.UNIT_W).xyz)
|
shadeStyle.parameter("lightPosition$index", (node.worldTransform * Vector4.UNIT_W).xyz)
|
||||||
shadeStyle.parameter("lightConstantAttenuation$index", light.constantAttenuation)
|
shadeStyle.parameter("lightConstantAttenuation$index", light.constantAttenuation)
|
||||||
|
|||||||
@@ -5,9 +5,16 @@ import org.openrndr.draw.DepthFormat
|
|||||||
import org.openrndr.draw.RenderTarget
|
import org.openrndr.draw.RenderTarget
|
||||||
import org.openrndr.draw.renderTarget
|
import org.openrndr.draw.renderTarget
|
||||||
|
|
||||||
class RenderPass(val combiners: List<FacetCombiner>, val renderOpaque: Boolean = true, val renderTransparent: Boolean = false)
|
class RenderPass(val combiners: List<FacetCombiner>,
|
||||||
|
val renderOpaque: Boolean = true,
|
||||||
|
val renderTransparent: Boolean = false,
|
||||||
|
val depthWrite: Boolean = true
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
val DefaultPass = RenderPass(listOf(LDRColorFacet()))
|
val DefaultPass = RenderPass(listOf(LDRColorFacet()))
|
||||||
|
val DefaultOpaquePass = RenderPass(listOf(LDRColorFacet()), renderOpaque = true, renderTransparent = false)
|
||||||
|
val DefaultTransparentPass = RenderPass(listOf(LDRColorFacet()), renderOpaque = false, renderTransparent = true, depthWrite = false)
|
||||||
val LightPass = RenderPass(emptyList())
|
val LightPass = RenderPass(emptyList())
|
||||||
val VSMLightPass = RenderPass(listOf(MomentsFacet()))
|
val VSMLightPass = RenderPass(listOf(MomentsFacet()))
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package org.openrndr.extra.dnk3
|
package org.openrndr.extra.dnk3
|
||||||
|
|
||||||
|
import org.openrndr.Dispatcher
|
||||||
import org.openrndr.math.Matrix44
|
import org.openrndr.math.Matrix44
|
||||||
|
|
||||||
class Scene(val root: SceneNode = SceneNode(),
|
class Scene(val root: SceneNode = SceneNode(), val dispatcher: Dispatcher = Dispatcher())
|
||||||
val updateFunctions: MutableList<() -> Unit> = mutableListOf())
|
|
||||||
|
|
||||||
|
|
||||||
open class SceneNode(var entities: MutableList<Entity> = mutableListOf()) {
|
open class SceneNode() {
|
||||||
|
var entities: MutableList<Entity> = mutableListOf()
|
||||||
var parent: SceneNode? = null
|
var parent: SceneNode? = null
|
||||||
var transform = Matrix44.IDENTITY
|
open var transform = Matrix44.IDENTITY
|
||||||
var worldTransform = Matrix44.IDENTITY
|
var worldTransform = Matrix44.IDENTITY
|
||||||
val children = mutableListOf<SceneNode>()
|
val children = mutableListOf<SceneNode>()
|
||||||
var disposed = false
|
var disposed = false
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class SceneRenderer {
|
|||||||
|
|
||||||
var cubemapDepthBuffer = depthBuffer(256, 256, DepthFormat.DEPTH16, BufferMultisample.Disabled)
|
var cubemapDepthBuffer = depthBuffer(256, 256, DepthFormat.DEPTH16, BufferMultisample.Disabled)
|
||||||
|
|
||||||
var outputPasses = mutableListOf(DefaultPass)
|
var outputPasses = mutableListOf(DefaultOpaquePass, DefaultTransparentPass)
|
||||||
var outputPassTarget: RenderTarget? = null
|
var outputPassTarget: RenderTarget? = null
|
||||||
var outputPassTargetMS: RenderTarget? = null
|
var outputPassTargetMS: RenderTarget? = null
|
||||||
|
|
||||||
@@ -36,9 +36,9 @@ class SceneRenderer {
|
|||||||
drawer.depthTestPass = DepthTestPass.LESS_OR_EQUAL
|
drawer.depthTestPass = DepthTestPass.LESS_OR_EQUAL
|
||||||
|
|
||||||
drawer.cullTestPass = CullTestPass.FRONT
|
drawer.cullTestPass = CullTestPass.FRONT
|
||||||
scene.updateFunctions.forEach {
|
|
||||||
it()
|
scene.dispatcher.execute()
|
||||||
}
|
|
||||||
|
|
||||||
// update all the transforms
|
// update all the transforms
|
||||||
scene.root.scan(Matrix44.IDENTITY) { p ->
|
scene.root.scan(Matrix44.IDENTITY) { p ->
|
||||||
@@ -99,7 +99,10 @@ class SceneRenderer {
|
|||||||
for (pass in outputPasses) {
|
for (pass in outputPasses) {
|
||||||
val materialContext = MaterialContext(pass, lights, fogs, shadowLightTargets, meshCubemaps)
|
val materialContext = MaterialContext(pass, lights, fogs, shadowLightTargets, meshCubemaps)
|
||||||
|
|
||||||
if ((pass != DefaultPass || postSteps.isNotEmpty()) && outputPassTarget == null) {
|
|
||||||
|
val defaultPasses = setOf(DefaultTransparentPass, DefaultOpaquePass)
|
||||||
|
|
||||||
|
if ((pass !in defaultPasses || postSteps.isNotEmpty()) && outputPassTarget == null) {
|
||||||
outputPassTarget = pass.createPassTarget(RenderTarget.active.width, RenderTarget.active.height)
|
outputPassTarget = pass.createPassTarget(RenderTarget.active.width, RenderTarget.active.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +168,8 @@ class SceneRenderer {
|
|||||||
private fun drawPass(drawer: Drawer, pass: RenderPass, materialContext: MaterialContext,
|
private fun drawPass(drawer: Drawer, pass: RenderPass, materialContext: MaterialContext,
|
||||||
meshes: List<NodeContent<Mesh>>,
|
meshes: List<NodeContent<Mesh>>,
|
||||||
instancedMeshes: List<NodeContent<InstancedMesh>>) {
|
instancedMeshes: List<NodeContent<InstancedMesh>>) {
|
||||||
|
|
||||||
|
drawer.depthWrite = pass.depthWrite
|
||||||
val primitives = meshes.flatMap { mesh ->
|
val primitives = meshes.flatMap { mesh ->
|
||||||
mesh.content.primitives.map { primitive ->
|
mesh.content.primitives.map { primitive ->
|
||||||
NodeContent(mesh.node, primitive)
|
NodeContent(mesh.node, primitive)
|
||||||
@@ -230,6 +235,7 @@ class SceneRenderer {
|
|||||||
primitive.primitive.geometry.vertexCount)
|
primitive.primitive.geometry.vertexCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
drawer.depthWrite = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ class GltfPbrMetallicRoughness(val baseColorFactor: DoubleArray?,
|
|||||||
var metallicRoughnessTexture: GltfMaterialTexture?,
|
var metallicRoughnessTexture: GltfMaterialTexture?,
|
||||||
val roughnessFactor: Double?,
|
val roughnessFactor: Double?,
|
||||||
val metallicFactor: Double?)
|
val metallicFactor: Double?)
|
||||||
|
|
||||||
class GltfMaterialTexture(val index: Int, val scale: Double?, val texCoord: Int?)
|
class GltfMaterialTexture(val index: Int, val scale: Double?, val texCoord: Int?)
|
||||||
|
|
||||||
class GltfImage(val uri: String?, val bufferView: Int?)
|
class GltfImage(val uri: String?, val bufferView: Int?)
|
||||||
@@ -156,6 +157,7 @@ class GltfSampler(val magFilter: Int, val minFilter: Int, val wrapS: Int, val wr
|
|||||||
class GltfTexture(val sampler: Int, val source: Int)
|
class GltfTexture(val sampler: Int, val source: Int)
|
||||||
|
|
||||||
class GltfMaterial(val name: String,
|
class GltfMaterial(val name: String,
|
||||||
|
val alphaMode: String?,
|
||||||
val doubleSided: Boolean?,
|
val doubleSided: Boolean?,
|
||||||
val normalTexture: GltfMaterialTexture?,
|
val normalTexture: GltfMaterialTexture?,
|
||||||
val occlusionTexture: GltfMaterialTexture?,
|
val occlusionTexture: GltfMaterialTexture?,
|
||||||
@@ -163,7 +165,7 @@ class GltfMaterial(val name: String,
|
|||||||
val emissiveFactor: DoubleArray?,
|
val emissiveFactor: DoubleArray?,
|
||||||
val pbrMetallicRoughness: GltfPbrMetallicRoughness?,
|
val pbrMetallicRoughness: GltfPbrMetallicRoughness?,
|
||||||
val extensions: GltfMaterialExtensions?
|
val extensions: GltfMaterialExtensions?
|
||||||
)
|
)
|
||||||
|
|
||||||
class GltfMaterialExtensions(
|
class GltfMaterialExtensions(
|
||||||
val KHR_materials_pbrSpecularGlossiness: KhrMaterialsPbrSpecularGlossiness?
|
val KHR_materials_pbrSpecularGlossiness: KhrMaterialsPbrSpecularGlossiness?
|
||||||
@@ -214,6 +216,13 @@ class GltfAccessor(
|
|||||||
val type: String
|
val type: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class GltfAnimation(val name: String?, val channels: List<GltfChannel>, val samplers: List<GltfAnimationSampler>)
|
||||||
|
class GltfAnimationSampler(val input: Int, val interpolation: String, val output: Int)
|
||||||
|
|
||||||
|
class GltfChannelTarget(val node: Int?, val path: String?)
|
||||||
|
|
||||||
|
class GltfChannel(val sampler: Int, val target: GltfChannelTarget)
|
||||||
|
|
||||||
class GltfFile(
|
class GltfFile(
|
||||||
val asset: GltfAsset?,
|
val asset: GltfAsset?,
|
||||||
val scene: Int?,
|
val scene: Int?,
|
||||||
@@ -226,17 +235,26 @@ class GltfFile(
|
|||||||
val buffers: List<GltfBuffer>,
|
val buffers: List<GltfBuffer>,
|
||||||
val images: List<GltfImage>?,
|
val images: List<GltfImage>?,
|
||||||
val textures: List<GltfTexture>?,
|
val textures: List<GltfTexture>?,
|
||||||
val samplers: List<GltfSampler>?
|
val samplers: List<GltfSampler>?,
|
||||||
|
val animations: List<GltfAnimation>?) {
|
||||||
|
@Transient
|
||||||
|
lateinit var file: File
|
||||||
|
|
||||||
) {
|
@Transient
|
||||||
@Transient lateinit var file: File
|
var bufferBuffer: ByteBuffer? = null
|
||||||
@Transient var bufferBuffer : ByteBuffer? = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadGltfFromFile(file: File): GltfFile {
|
fun loadGltfFromFile(file: File): GltfFile = when (file.extension) {
|
||||||
val gson = Gson()
|
"gltf" -> {
|
||||||
val json = file.readText()
|
val gson = Gson()
|
||||||
return gson.fromJson(json, GltfFile::class.java).apply {
|
val json = file.readText()
|
||||||
this.file = file
|
gson.fromJson(json, GltfFile::class.java).apply {
|
||||||
|
this.file = file
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
"glb" -> {
|
||||||
|
loadGltfFromGlbFile(file)
|
||||||
|
}
|
||||||
|
else -> error("extension ${file.extension} not supported in ${file}")
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,60 @@ package org.openrndr.extra.dnk3.gltf
|
|||||||
import org.openrndr.color.ColorRGBa
|
import org.openrndr.color.ColorRGBa
|
||||||
import org.openrndr.draw.*
|
import org.openrndr.draw.*
|
||||||
import org.openrndr.extra.dnk3.*
|
import org.openrndr.extra.dnk3.*
|
||||||
|
import org.openrndr.extra.keyframer.KeyframerChannelQuaternion
|
||||||
|
import org.openrndr.extra.keyframer.KeyframerChannelVector3
|
||||||
import org.openrndr.math.Matrix44
|
import org.openrndr.math.Matrix44
|
||||||
import org.openrndr.math.Quaternion
|
import org.openrndr.math.Quaternion
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
import org.openrndr.math.transforms.transform
|
import org.openrndr.math.transforms.transform
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
import kotlin.reflect.KMutableProperty0
|
||||||
|
|
||||||
|
class SceneAnimation(var channels: List<AnimationChannel>) {
|
||||||
|
fun applyToTargets(input: Double) {
|
||||||
|
for (channel in channels) {
|
||||||
|
channel.applyToTarget(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class AnimationChannel {
|
||||||
|
abstract fun applyToTarget(input: Double)
|
||||||
|
}
|
||||||
|
|
||||||
|
class QuaternionChannel(val target: KMutableProperty0<Quaternion>,
|
||||||
|
val keyframer: KeyframerChannelQuaternion) : AnimationChannel() {
|
||||||
|
override fun applyToTarget(input: Double) {
|
||||||
|
target.set(keyframer.value(input) ?: Quaternion.IDENTITY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Vector3Channel(val target: KMutableProperty0<Vector3>,
|
||||||
|
val keyframer: KeyframerChannelVector3, val default: Vector3) : AnimationChannel() {
|
||||||
|
override fun applyToTarget(input: Double) {
|
||||||
|
target.set(keyframer.value(input) ?: default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GltfSceneNode : SceneNode() {
|
||||||
|
var translation = Vector3.ZERO
|
||||||
|
var scale = Vector3.ONE
|
||||||
|
var rotation = Quaternion.IDENTITY
|
||||||
|
|
||||||
|
override var transform: Matrix44 = Matrix44.IDENTITY
|
||||||
|
get() = transform {
|
||||||
|
translate(translation)
|
||||||
|
multiply(rotation.matrix.matrix44)
|
||||||
|
scale(scale)
|
||||||
|
} * field
|
||||||
|
}
|
||||||
|
|
||||||
|
class GltfSceneData(val scenes: List<List<SceneNode>>, val animations: List<SceneAnimation>)
|
||||||
|
|
||||||
|
|
||||||
/** Tools to convert GltfFile into a DNK3 scene */
|
/** Tools to convert GltfFile into a DNK3 scene */
|
||||||
|
fun GltfFile.buildSceneNodes(): GltfSceneData {
|
||||||
fun GltfFile.buildSceneNodes(): List<List<SceneNode>> {
|
|
||||||
|
|
||||||
val sceneImages = mutableMapOf<GltfImage, ColorBuffer>()
|
val sceneImages = mutableMapOf<GltfImage, ColorBuffer>()
|
||||||
fun GltfImage.createSceneImage(): ColorBuffer {
|
fun GltfImage.createSceneImage(): ColorBuffer {
|
||||||
return sceneImages.getOrPut(this) {
|
return sceneImages.getOrPut(this) {
|
||||||
@@ -43,6 +88,7 @@ fun GltfFile.buildSceneNodes(): List<List<SceneNode>> {
|
|||||||
val material = PBRMaterial()
|
val material = PBRMaterial()
|
||||||
|
|
||||||
material.doubleSided = this.doubleSided ?: false
|
material.doubleSided = this.doubleSided ?: false
|
||||||
|
material.transparent = this.alphaMode != null
|
||||||
|
|
||||||
pbrMetallicRoughness?.let { pbr ->
|
pbrMetallicRoughness?.let { pbr ->
|
||||||
material.roughness = pbr.roughnessFactor ?: 1.0
|
material.roughness = pbr.roughnessFactor ?: 1.0
|
||||||
@@ -63,8 +109,8 @@ fun GltfFile.buildSceneNodes(): List<List<SceneNode>> {
|
|||||||
val sceneTexture = Texture(ModelCoordinates(texture = cb, pre = "x_texCoord.y = 1.0-x_texCoord.y;"), TextureTarget.METALNESS_ROUGHNESS)
|
val sceneTexture = Texture(ModelCoordinates(texture = cb, pre = "x_texCoord.y = 1.0-x_texCoord.y;"), TextureTarget.METALNESS_ROUGHNESS)
|
||||||
material.textures.add(sceneTexture)
|
material.textures.add(sceneTexture)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
occlusionTexture?.let { texture ->
|
occlusionTexture?.let { texture ->
|
||||||
val cb = images!![textures!![texture.index].source].createSceneImage()
|
val cb = images!![textures!![texture.index].source].createSceneImage()
|
||||||
cb.filter(MinifyingFilter.LINEAR_MIPMAP_LINEAR, MagnifyingFilter.LINEAR)
|
cb.filter(MinifyingFilter.LINEAR_MIPMAP_LINEAR, MagnifyingFilter.LINEAR)
|
||||||
@@ -94,9 +140,7 @@ fun GltfFile.buildSceneNodes(): List<List<SceneNode>> {
|
|||||||
material.textures.add(sceneTexture)
|
material.textures.add(sceneTexture)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
extensions?.let { ext ->
|
extensions?.let { ext ->
|
||||||
|
|
||||||
ext.KHR_materials_pbrSpecularGlossiness?.let { sg ->
|
ext.KHR_materials_pbrSpecularGlossiness?.let { sg ->
|
||||||
sg.diffuseFactor?.let {
|
sg.diffuseFactor?.let {
|
||||||
material.color = ColorRGBa(it[0], it[1], it[2], it[3])
|
material.color = ColorRGBa(it[0], it[1], it[2], it[3])
|
||||||
@@ -109,13 +153,17 @@ fun GltfFile.buildSceneNodes(): List<List<SceneNode>> {
|
|||||||
val sceneTexture = Texture(ModelCoordinates(texture = cb, pre = "x_texCoord.y = 1.0-x_texCoord.y;"), TextureTarget.COLOR)
|
val sceneTexture = Texture(ModelCoordinates(texture = cb, pre = "x_texCoord.y = 1.0-x_texCoord.y;"), TextureTarget.COLOR)
|
||||||
material.textures.add(sceneTexture)
|
material.textures.add(sceneTexture)
|
||||||
}
|
}
|
||||||
|
occlusionTexture?.let { texture ->
|
||||||
|
val cb = images!![textures!![texture.index].source].createSceneImage()
|
||||||
|
cb.filter(MinifyingFilter.LINEAR_MIPMAP_LINEAR, MagnifyingFilter.LINEAR)
|
||||||
|
cb.wrapU = WrapMode.REPEAT
|
||||||
|
cb.wrapV = WrapMode.REPEAT
|
||||||
|
val sceneTexture = Texture(ModelCoordinates(texture = cb, pre = "x_texCoord.y = 1.0-x_texCoord.y;"), TextureTarget.AMBIENT_OCCLUSION)
|
||||||
|
material.textures.add(sceneTexture)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
emissiveFactor?.let {
|
emissiveFactor?.let {
|
||||||
material.emission = ColorRGBa(it[0], it[1], it[2], 1.0)
|
material.emission = ColorRGBa(it[0], it[1], it[2], 1.0)
|
||||||
}
|
}
|
||||||
@@ -140,41 +188,114 @@ fun GltfFile.buildSceneNodes(): List<List<SceneNode>> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun GltfNode.createSceneNode(): SceneNode {
|
val sceneNodes = mutableMapOf<GltfNode, SceneNode>()
|
||||||
val node = SceneNode()
|
fun GltfNode.createSceneNode(): SceneNode = sceneNodes.getOrPut(this) {
|
||||||
|
val node = GltfSceneNode()
|
||||||
mesh?.let {
|
mesh?.let {
|
||||||
node.entities.add(meshes[it].createSceneMesh())
|
node.entities.add(meshes[it].createSceneMesh())
|
||||||
}
|
}
|
||||||
|
// val localTransform = transform {
|
||||||
|
// translation?.let {
|
||||||
|
// translate(it[0], it[1], it[2])
|
||||||
|
// }
|
||||||
|
// rotation?.let {
|
||||||
|
// val q = Quaternion(it[0], it[1], it[2], it[3])
|
||||||
|
// multiply(q.matrix.matrix44)
|
||||||
|
// }
|
||||||
|
// scale?.let {
|
||||||
|
// scale(it[0], it[1], it[2])
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
val localTransform = transform {
|
node.translation = translation?.let { Vector3(it[0], it[1], it[2]) } ?: Vector3.ZERO
|
||||||
|
node.scale = scale?.let { Vector3(it[0], it[1], it[2]) } ?: Vector3.ONE
|
||||||
|
node.rotation = rotation?.let { Quaternion(it[0], it[1], it[2], it[3]) } ?: Quaternion.IDENTITY
|
||||||
|
|
||||||
translation?.let {
|
matrix?.let {
|
||||||
translate(it[0], it[1], it[2])
|
node.transform = Matrix44.fromDoubleArray(it).transposed
|
||||||
}
|
|
||||||
rotation?.let {
|
|
||||||
val q = Quaternion(it[0], it[1], it[2], it[3])
|
|
||||||
multiply(q.matrix.matrix44)
|
|
||||||
}
|
|
||||||
|
|
||||||
scale?.let {
|
|
||||||
scale(it[0], it[1], it[2])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node.transform = this.matrix?.let {
|
|
||||||
Matrix44.fromDoubleArray(it).transposed
|
// node.transform = this.matrix?.let {
|
||||||
} ?: localTransform
|
// Matrix44.fromDoubleArray(it).transposed
|
||||||
|
// } ?: localTransform
|
||||||
for (child in children.orEmpty) {
|
for (child in children.orEmpty) {
|
||||||
node.children.add(nodes[child].createSceneNode())
|
node.children.add(nodes[child].createSceneNode())
|
||||||
}
|
}
|
||||||
return node
|
node
|
||||||
}
|
}
|
||||||
|
|
||||||
return scenes.map { scene ->
|
val scenes = scenes.map { scene ->
|
||||||
scene.nodes.map { node ->
|
scene.nodes.map { node ->
|
||||||
nodes[node].createSceneNode()
|
nodes[node].createSceneNode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val sceneAnimations = animations?.map { animation ->
|
||||||
|
val animationChannels = animation.channels.mapNotNull { channel ->
|
||||||
|
val candidate = channel.target.node?.let { nodes[it] }?.createSceneNode() as? GltfSceneNode
|
||||||
|
candidate?.let { sceneNode ->
|
||||||
|
val sampler = animation.samplers[channel.sampler]
|
||||||
|
|
||||||
|
val inputAccessor = accessors[sampler.input]
|
||||||
|
val inputBufferView = bufferViews[inputAccessor.bufferView]
|
||||||
|
val inputData = buffers[inputBufferView.buffer].contents(this)
|
||||||
|
|
||||||
|
val outputAccessor = accessors[sampler.output]
|
||||||
|
val outputBufferView = bufferViews[outputAccessor.bufferView]
|
||||||
|
val outputData = buffers[outputBufferView.buffer].contents(this)
|
||||||
|
|
||||||
|
inputData.order(ByteOrder.nativeOrder())
|
||||||
|
outputData.order(ByteOrder.nativeOrder())
|
||||||
|
|
||||||
|
require(inputAccessor.count == outputAccessor.count)
|
||||||
|
when (channel.target.path) {
|
||||||
|
"scale", "translation" -> {
|
||||||
|
require(inputAccessor.type == "SCALAR")
|
||||||
|
require(outputAccessor.type == "VEC3")
|
||||||
|
val keyframer = KeyframerChannelVector3()
|
||||||
|
val inputOffset = (inputBufferView.byteOffset ?: 0) + (inputAccessor.byteOffset ?: 0)
|
||||||
|
val outputOffset = (outputBufferView.byteOffset ?: 0) + (outputAccessor.byteOffset ?: 0)
|
||||||
|
val inputStride = (inputBufferView.byteStride ?: 4)
|
||||||
|
val outputStride = (outputBufferView.byteStride ?: 12)
|
||||||
|
for (i in 0 until outputAccessor.count) {
|
||||||
|
val input = inputData.getFloat(inputOffset + i * inputStride).toDouble()
|
||||||
|
val outputX = outputData.getFloat(outputOffset + i * outputStride).toDouble()
|
||||||
|
val outputY = outputData.getFloat(outputOffset + i * outputStride + 4).toDouble()
|
||||||
|
val outputZ = outputData.getFloat(outputOffset + i * outputStride + 8).toDouble()
|
||||||
|
keyframer.add(input, Vector3(outputX, outputY, outputZ))
|
||||||
|
}
|
||||||
|
val target = if (channel.target.path == "translation") sceneNode::translation else sceneNode::scale
|
||||||
|
val default = if (channel.target.path == "translation") Vector3.ZERO else Vector3.ONE
|
||||||
|
Vector3Channel(target, keyframer, default)
|
||||||
|
}
|
||||||
|
"rotation" -> {
|
||||||
|
require(inputAccessor.type == "SCALAR")
|
||||||
|
require(outputAccessor.type == "VEC4") {
|
||||||
|
"${outputAccessor.type}"
|
||||||
|
}
|
||||||
|
val keyframer = KeyframerChannelQuaternion()
|
||||||
|
val inputOffset = (inputBufferView.byteOffset ?: 0) + (inputAccessor.byteOffset ?: 0)
|
||||||
|
val outputOffset = (outputBufferView.byteOffset ?: 0) + (inputAccessor.byteOffset ?: 0)
|
||||||
|
val inputStride = (inputBufferView.byteStride ?: 4)
|
||||||
|
val outputStride = (outputBufferView.byteStride ?: 16)
|
||||||
|
for (i in 0 until outputAccessor.count) {
|
||||||
|
val input = inputData.getFloat(inputOffset + i * inputStride).toDouble()
|
||||||
|
val outputX = outputData.getFloat(outputOffset + i * outputStride).toDouble()
|
||||||
|
val outputY = outputData.getFloat(outputOffset + i * outputStride + 4).toDouble()
|
||||||
|
val outputZ = outputData.getFloat(outputOffset + i * outputStride + 8).toDouble()
|
||||||
|
val outputW = outputData.getFloat(outputOffset + i * outputStride + 12).toDouble()
|
||||||
|
keyframer.add(input, Quaternion(outputX, outputY, outputZ, outputW))
|
||||||
|
}
|
||||||
|
QuaternionChannel(sceneNode::rotation, keyframer)
|
||||||
|
}
|
||||||
|
else -> error("unsupported path ${channel.target.path}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SceneAnimation(animationChannels)
|
||||||
|
}
|
||||||
|
return GltfSceneData(scenes, sceneAnimations.orEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
private val IntArray?.orEmpty: IntArray get() = this ?: IntArray(0)
|
private val IntArray?.orEmpty: IntArray get() = this ?: IntArray(0)
|
||||||
@@ -65,4 +65,5 @@ class KeyframerChannel {
|
|||||||
leftKey.value * (1.0 - e0) + rightKey.value * (e0)
|
leftKey.value * (1.0 - e0) + rightKey.value * (e0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
64
orx-keyframer/src/main/kotlin/KeyQuaternion.kt
Normal file
64
orx-keyframer/src/main/kotlin/KeyQuaternion.kt
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package org.openrndr.extra.keyframer
|
||||||
|
|
||||||
|
import org.openrndr.extras.easing.Easing
|
||||||
|
import org.openrndr.extras.easing.EasingFunction
|
||||||
|
import org.openrndr.math.Quaternion
|
||||||
|
import org.openrndr.math.slerp
|
||||||
|
|
||||||
|
class KeyQuaternion(val time: Double, val value: Quaternion, val easing: EasingFunction)
|
||||||
|
|
||||||
|
class KeyframerChannelQuaternion {
|
||||||
|
val keys = mutableListOf<KeyQuaternion>()
|
||||||
|
|
||||||
|
operator fun invoke() : Double {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(time: Double, value: Quaternion?, easing: EasingFunction = Easing.Linear.function, jump: Hold = Hold.HoldNone) {
|
||||||
|
if (jump == Hold.HoldAll || (jump == Hold.HoldSet && value != null)) {
|
||||||
|
lastValue()?.let {
|
||||||
|
keys.add(KeyQuaternion(time, it, Easing.Linear.function))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value?.let {
|
||||||
|
keys.add(KeyQuaternion(time, it, easing))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lastValue(): Quaternion? {
|
||||||
|
return keys.lastOrNull()?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun duration(): Double {
|
||||||
|
return keys.last().time
|
||||||
|
}
|
||||||
|
|
||||||
|
fun value(time: Double): Quaternion? {
|
||||||
|
if (keys.size == 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (keys.size == 1) {
|
||||||
|
return if (time < keys.first().time) {
|
||||||
|
keys[0].value.normalized
|
||||||
|
} else {
|
||||||
|
keys[0].value.normalized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time < keys.first().time) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val rightIndex = keys.indexOfFirst { it.time > time }
|
||||||
|
return if (rightIndex == -1) {
|
||||||
|
keys.last().value.normalized
|
||||||
|
} else {
|
||||||
|
val leftIndex = (rightIndex - 1).coerceAtLeast(0)
|
||||||
|
val rightKey = keys[rightIndex]
|
||||||
|
val leftKey = keys[leftIndex]
|
||||||
|
val t0 = (time - leftKey.time) / (rightKey.time - leftKey.time)
|
||||||
|
val e0 = rightKey.easing(t0, 0.0, 1.0, 1.0)
|
||||||
|
slerp(leftKey.value, rightKey.value, e0).normalized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
orx-keyframer/src/main/kotlin/KeyVector3.kt
Normal file
63
orx-keyframer/src/main/kotlin/KeyVector3.kt
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package org.openrndr.extra.keyframer
|
||||||
|
|
||||||
|
import org.openrndr.extras.easing.Easing
|
||||||
|
import org.openrndr.extras.easing.EasingFunction
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
|
|
||||||
|
class KeyVector3(val time: Double, val value: Vector3, val easing: EasingFunction)
|
||||||
|
|
||||||
|
class KeyframerChannelVector3 {
|
||||||
|
val keys = mutableListOf<KeyVector3>()
|
||||||
|
|
||||||
|
operator fun invoke() : Double {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(time: Double, value: Vector3?, easing: EasingFunction = Easing.Linear.function, jump: Hold = Hold.HoldNone) {
|
||||||
|
if (jump == Hold.HoldAll || (jump == Hold.HoldSet && value != null)) {
|
||||||
|
lastValue()?.let {
|
||||||
|
keys.add(KeyVector3(time, it, Easing.Linear.function))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value?.let {
|
||||||
|
keys.add(KeyVector3(time, it, easing))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lastValue(): Vector3? {
|
||||||
|
return keys.lastOrNull()?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun duration(): Double {
|
||||||
|
return keys.last().time
|
||||||
|
}
|
||||||
|
|
||||||
|
fun value(time: Double): Vector3? {
|
||||||
|
if (keys.size == 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (keys.size == 1) {
|
||||||
|
return if (time < keys.first().time) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
keys[0].value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time < keys.first().time) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val rightIndex = keys.indexOfFirst { it.time > time }
|
||||||
|
return if (rightIndex == -1) {
|
||||||
|
keys.last().value
|
||||||
|
} else {
|
||||||
|
val leftIndex = (rightIndex - 1).coerceAtLeast(0)
|
||||||
|
val rightKey = keys[rightIndex]
|
||||||
|
val leftKey = keys[leftIndex]
|
||||||
|
val t0 = (time - leftKey.time) / (rightKey.time - leftKey.time)
|
||||||
|
val e0 = rightKey.easing(t0, 0.0, 1.0, 1.0)
|
||||||
|
leftKey.value * (1.0 - e0) + rightKey.value * (e0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,8 +23,6 @@ enum class KeyframerFormat {
|
|||||||
FULL
|
FULL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
open class Keyframer {
|
open class Keyframer {
|
||||||
private var currentTime = 0.0
|
private var currentTime = 0.0
|
||||||
operator fun invoke(time: Double) {
|
operator fun invoke(time: Double) {
|
||||||
|
|||||||
Reference in New Issue
Block a user