Tungsten: normal mapping capabilities (#274)
* Ability to add code prior to prepareMaterial * Add normal input on shading node * Add constant float node * Add multiply and subtract nodes * Split math nodes out into MathNodes.kt
This commit is contained in:
@@ -56,9 +56,11 @@ open class Expression(
|
||||
}
|
||||
}
|
||||
|
||||
fun floatTypeName(dimensions: Int) = if (dimensions > 1) "float$dimensions" else "float"
|
||||
|
||||
private fun createFloatLiteral(dimensions: Int): String {
|
||||
val zeroes = "0.0, ".repeat(maxOf(dimensions - 1, 0)) + "0.0"
|
||||
val typeName = if (dimensions > 1) "float$dimensions" else "float"
|
||||
val typeName = floatTypeName(dimensions)
|
||||
return "$typeName($zeroes)"
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,19 @@ import org.jetbrains.annotations.Nullable;
|
||||
*/
|
||||
public final class GraphCompiler {
|
||||
|
||||
public enum CodeSection {
|
||||
AFTER_PREPARE_MATERIAL,
|
||||
BEFORE_PREPARE_MATERIAL
|
||||
}
|
||||
|
||||
// Material function source text before the "prepareMaterial" call
|
||||
private StringBuilder mMaterialFunctionPrologue = new StringBuilder();
|
||||
|
||||
// Material function source text after the "prepareMaterial" call
|
||||
private StringBuilder mMaterialFunctionBodyBuilder = new StringBuilder();
|
||||
|
||||
private CodeSection mCurrentCodeSection = CodeSection.AFTER_PREPARE_MATERIAL;
|
||||
|
||||
private final List<String> mRequiredAttributes = new ArrayList<>();
|
||||
private String mShadingModel = "unlit";
|
||||
private final List<Parameter> mParameters = new ArrayList<>();
|
||||
@@ -73,7 +85,7 @@ public final class GraphCompiler {
|
||||
}
|
||||
|
||||
String fragmentSection = GraphFormatter.formatFragmentSection(mGlobalFunctions.values(),
|
||||
mMaterialFunctionBodyBuilder.toString());
|
||||
mMaterialFunctionPrologue.toString(), mMaterialFunctionBodyBuilder.toString());
|
||||
String materialDefinition =
|
||||
GraphFormatter.formatMaterialSection(mRequiredAttributes, mParameters,
|
||||
mShadingModel) + fragmentSection;
|
||||
@@ -181,7 +193,20 @@ public final class GraphCompiler {
|
||||
if (!mShouldAppendCode) {
|
||||
return;
|
||||
}
|
||||
mMaterialFunctionBodyBuilder.append(code);
|
||||
if (mCurrentCodeSection == CodeSection.AFTER_PREPARE_MATERIAL) {
|
||||
mMaterialFunctionBodyBuilder.append(code);
|
||||
} else if (mCurrentCodeSection == CodeSection.BEFORE_PREPARE_MATERIAL) {
|
||||
mMaterialFunctionPrologue.append(code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state of this GraphCompiler to output code to a specific section of the material
|
||||
* function body: either before the call to prepareMaterial(), or after.
|
||||
* The state persists until setCurrentCodeSection is called again.
|
||||
*/
|
||||
public void setCurrentCodeSection(CodeSection section) {
|
||||
mCurrentCodeSection = section;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,7 +30,7 @@ final class GraphFormatter {
|
||||
private GraphFormatter() { }
|
||||
|
||||
static String formatFragmentSection(Collection<String> globalFunctions,
|
||||
String materialFunctionBody) {
|
||||
String materialFunctionBodyPrologue, String materialFunctionBody) {
|
||||
StringBuilder functions = new StringBuilder();
|
||||
for (String functionBody : globalFunctions) {
|
||||
functions.append(indent(functionBody, FUNCTION_INDENT_AMOUNT));
|
||||
@@ -39,7 +39,7 @@ final class GraphFormatter {
|
||||
return "fragment {\n"
|
||||
+ functions.toString()
|
||||
+ " void material(inout MaterialInputs material) {\n"
|
||||
+ formatMaterialFunctionBody(materialFunctionBody)
|
||||
+ formatMaterialFunctionBody(materialFunctionBodyPrologue ,materialFunctionBody)
|
||||
+ " }\n"
|
||||
+ "}";
|
||||
}
|
||||
@@ -75,9 +75,10 @@ final class GraphFormatter {
|
||||
return t.replaceAll("\n(?!$)", "\n" + spaces); // indent remaining non-empty lines
|
||||
}
|
||||
|
||||
private static String formatMaterialFunctionBody(String code) {
|
||||
return indent("prepareMaterial(material);\n", BODY_CODE_INDENT_AMOUNT)
|
||||
+ indent(code, BODY_CODE_INDENT_AMOUNT);
|
||||
private static String formatMaterialFunctionBody(String prologue, String epilogue) {
|
||||
return indent(prologue, BODY_CODE_INDENT_AMOUNT)
|
||||
+ indent("prepareMaterial(material);\n", BODY_CODE_INDENT_AMOUNT)
|
||||
+ indent(epilogue, BODY_CODE_INDENT_AMOUNT);
|
||||
}
|
||||
|
||||
private static String formatParameters(Collection<Parameter> parameters) {
|
||||
|
||||
@@ -18,11 +18,15 @@ package com.google.android.filament.tungsten.compiler
|
||||
|
||||
import com.google.android.filament.tungsten.model.Node
|
||||
import com.google.android.filament.tungsten.model.NodeId
|
||||
import com.google.android.filament.tungsten.model.createAdderNode
|
||||
import com.google.android.filament.tungsten.model.createAddNode
|
||||
import com.google.android.filament.tungsten.model.createDivideNode
|
||||
import com.google.android.filament.tungsten.model.createFloat2ConstantNode
|
||||
import com.google.android.filament.tungsten.model.createFloat3ConstantNode
|
||||
import com.google.android.filament.tungsten.model.createFloat3ParameterNode
|
||||
import com.google.android.filament.tungsten.model.createFloatConstantNode
|
||||
import com.google.android.filament.tungsten.model.createMultiplyNode
|
||||
import com.google.android.filament.tungsten.model.createShaderNode
|
||||
import com.google.android.filament.tungsten.model.createSubtractNode
|
||||
import com.google.android.filament.tungsten.model.createTextureSampleNode
|
||||
import com.google.android.filament.tungsten.model.serialization.INodeFactory
|
||||
|
||||
@@ -44,7 +48,11 @@ class NodeRegistry : INodeFactory {
|
||||
|
||||
init {
|
||||
mNodes = listOf(
|
||||
NodeEntry("Add", "adder", createAdderNode),
|
||||
NodeEntry("Add", "add", createAddNode),
|
||||
NodeEntry("Subtract", "subtract", createSubtractNode),
|
||||
NodeEntry("Multiply", "multiply", createMultiplyNode),
|
||||
NodeEntry("Divide", "divide", createDivideNode),
|
||||
NodeEntry("Constant float", "floatConstant", createFloatConstantNode),
|
||||
NodeEntry("Constant float2", "float2Constant", createFloat2ConstantNode),
|
||||
NodeEntry("Constant float3", "float3Constant", createFloat3ConstantNode),
|
||||
NodeEntry("Float3 parameter", "float3Parameter", createFloat3ParameterNode),
|
||||
|
||||
@@ -28,6 +28,7 @@ data class Connection(
|
||||
abstract class Slot
|
||||
|
||||
typealias NodeId = Int
|
||||
typealias CompileFunction = (Node, GraphCompiler) -> Node
|
||||
|
||||
/**
|
||||
* An immutable Node in the graph.
|
||||
@@ -42,7 +43,7 @@ data class Node(
|
||||
|
||||
// A node's compile function generates code by calling methods on a GraphCompiler. The function
|
||||
// can return a new node if compilation has changed properties of the node itself.
|
||||
val compileFunction: (Node, GraphCompiler) -> Node = { n, _ -> n },
|
||||
val compileFunction: CompileFunction = { n, _ -> n },
|
||||
|
||||
// Input and output slots of a node are represented by a String name.
|
||||
val outputSlots: List<String> = emptyList(),
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.model
|
||||
|
||||
import com.google.android.filament.tungsten.compiler.Expression
|
||||
import com.google.android.filament.tungsten.compiler.Literal
|
||||
import com.google.android.filament.tungsten.compiler.floatTypeName
|
||||
import com.google.android.filament.tungsten.compiler.trim
|
||||
|
||||
data class ResolvedExpressions(
|
||||
val a: Expression,
|
||||
val b: Expression,
|
||||
val resultDimensionality: Int
|
||||
)
|
||||
|
||||
// A function that takes two expressions and prepares them for a math operation.
|
||||
typealias ExpressionResolver = (Expression?, Expression?) -> ResolvedExpressions
|
||||
|
||||
typealias Operation = (Expression, Expression) -> String
|
||||
|
||||
/**
|
||||
* Create a math node compile function with a given operation.
|
||||
* Assumes two input slots, "a" and "b", and a single output, "out".
|
||||
*/
|
||||
private fun createMathCompileFunction(
|
||||
expressionResolver: ExpressionResolver,
|
||||
operation: Operation,
|
||||
temporaryVariable: String
|
||||
): CompileFunction = { node, compiler ->
|
||||
val a = compiler.compileAndRetrieveExpression(node.getInputSlot("a"))
|
||||
val b = compiler.compileAndRetrieveExpression(node.getInputSlot("b"))
|
||||
|
||||
val (aExpr, bExpr, resultDimensionality) = expressionResolver(a, b)
|
||||
|
||||
compiler.setExpressionForSlot(node.getInputSlot("a"), aExpr)
|
||||
compiler.setExpressionForSlot(node.getInputSlot("b"), bExpr)
|
||||
|
||||
val temp = compiler.getNewTemporaryVariableName(temporaryVariable)
|
||||
compiler.addCodeToMaterialFunctionBody(
|
||||
"${floatTypeName(resultDimensionality)} $temp = ${operation(aExpr, bExpr)};\n")
|
||||
|
||||
val output = node.getOutputSlot("out")
|
||||
compiler.setExpressionForSlot(output, Expression(temp, resultDimensionality))
|
||||
|
||||
node
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims two expressions to be the same dimensionality, preferring the one with fewer dimensions.
|
||||
* If one of the inputs is a single float, neither input is modified, allowing for a scalar
|
||||
* operations.
|
||||
*/
|
||||
internal fun resolveInputExpressions(a: Expression?, b: Expression?): ResolvedExpressions {
|
||||
val aExpr = a ?: Literal(4)
|
||||
val bExpr = b ?: Literal(4)
|
||||
val (trimmedA, trimmedB) = if (aExpr.dimensions > 1 && bExpr.dimensions > 1) {
|
||||
trim(aExpr, bExpr)
|
||||
} else {
|
||||
Pair(aExpr, bExpr)
|
||||
}
|
||||
return ResolvedExpressions(trimmedA, trimmedB, maxOf(trimmedA.dimensions, trimmedB.dimensions))
|
||||
}
|
||||
|
||||
private val subtractNodeCompile = createMathCompileFunction(
|
||||
::resolveInputExpressions,
|
||||
{ a, b -> "$a - $b" },
|
||||
"subtract"
|
||||
)
|
||||
|
||||
private val addNodeCompile = createMathCompileFunction(
|
||||
::resolveInputExpressions,
|
||||
{ a, b -> "$a + $b" },
|
||||
"add"
|
||||
)
|
||||
|
||||
private val multiplyNodeCompile = createMathCompileFunction(
|
||||
::resolveInputExpressions,
|
||||
{ a, b -> "$a * $b" },
|
||||
"multiply"
|
||||
)
|
||||
|
||||
private val divideNodeCompile = createMathCompileFunction(
|
||||
::resolveInputExpressions,
|
||||
{ a, b -> "$a / $b" },
|
||||
"divide"
|
||||
)
|
||||
|
||||
val createAddNode = fun(id: NodeId) =
|
||||
Node(
|
||||
id = id,
|
||||
type = "add",
|
||||
compileFunction = addNodeCompile,
|
||||
inputSlots = listOf("a", "b"),
|
||||
outputSlots = listOf("out")
|
||||
)
|
||||
|
||||
val createSubtractNode = fun(id: NodeId) =
|
||||
Node(
|
||||
id = id,
|
||||
type = "subtract",
|
||||
compileFunction = subtractNodeCompile,
|
||||
inputSlots = listOf("a", "b"),
|
||||
outputSlots = listOf("out")
|
||||
)
|
||||
|
||||
val createMultiplyNode = fun(id: NodeId) =
|
||||
Node(
|
||||
id = id,
|
||||
type = "multiply",
|
||||
compileFunction = multiplyNodeCompile,
|
||||
inputSlots = listOf("a", "b"),
|
||||
outputSlots = listOf("out")
|
||||
)
|
||||
|
||||
val createDivideNode = fun(id: NodeId) =
|
||||
Node(
|
||||
id = id,
|
||||
type = "divide",
|
||||
compileFunction = divideNodeCompile,
|
||||
inputSlots = listOf("a", "b"),
|
||||
outputSlots = listOf("out")
|
||||
)
|
||||
@@ -19,36 +19,15 @@ package com.google.android.filament.tungsten.model
|
||||
import com.google.android.filament.tungsten.compiler.GraphCompiler
|
||||
import com.google.android.filament.tungsten.compiler.Expression
|
||||
import com.google.android.filament.tungsten.compiler.Literal
|
||||
import com.google.android.filament.tungsten.compiler.trim
|
||||
import com.google.android.filament.tungsten.properties.ColorChooser
|
||||
import com.google.android.filament.tungsten.properties.FloatSlider
|
||||
import com.google.android.filament.tungsten.properties.MultipleChoice
|
||||
import com.google.android.filament.tungsten.properties.TextureFileChooser
|
||||
|
||||
private val adderNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
val outputSlot = node.getOutputSlot("result")
|
||||
|
||||
val a = compiler.compileAndRetrieveExpression(node.getInputSlot("a"))
|
||||
val b = compiler.compileAndRetrieveExpression(node.getInputSlot("b"))
|
||||
|
||||
// Trim the expressions so that they're the same dimension.
|
||||
val (aExpression, bExpression) = trim(a ?: Literal(4), b ?: Literal(4))
|
||||
|
||||
compiler.setExpressionForSlot(node.getInputSlot("a"), aExpression)
|
||||
compiler.setExpressionForSlot(node.getInputSlot("b"), bExpression)
|
||||
|
||||
val temp = compiler.getNewTemporaryVariableName("adder")
|
||||
val resultDimensions = aExpression.dimensions
|
||||
compiler.addCodeToMaterialFunctionBody(
|
||||
"float$resultDimensions $temp = $aExpression + $bExpression;\n")
|
||||
|
||||
compiler.setExpressionForSlot(outputSlot, Expression(temp, resultDimensions))
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
private val UNLIT_INPUTS = listOf("baseColor", "emissive")
|
||||
private val LIT_INPUTS = listOf("baseColor", "metallic", "roughness", "reflectance", "clearCoat",
|
||||
"clearCoatRoughness", "anisotropy", "anisotropyDirection", "ambientOcclusion", "emissive")
|
||||
private val LIT_INPUTS = listOf("normal", "baseColor", "metallic", "roughness", "reflectance",
|
||||
"clearCoat", "clearCoatRoughness", "anisotropy", "anisotropyDirection", "ambientOcclusion",
|
||||
"emissive")
|
||||
|
||||
private val inputSlotsForShadingModel = { shadingModel: String ->
|
||||
when (shadingModel) {
|
||||
@@ -63,6 +42,11 @@ private val shaderNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
compiler.setShadingModel(shadingModel)
|
||||
|
||||
val compileMaterialInput = { name: String, dimensions: Int ->
|
||||
// The "normal" input is special, and its code must go before the call to prepareMaterial().
|
||||
if (name == "normal") {
|
||||
compiler.setCurrentCodeSection(GraphCompiler.CodeSection.BEFORE_PREPARE_MATERIAL)
|
||||
}
|
||||
|
||||
val inputSlot = node.getInputSlot(name)
|
||||
val connectedExpression = compiler.compileAndRetrieveExpression(inputSlot)
|
||||
val isConnected = connectedExpression != null
|
||||
@@ -75,6 +59,8 @@ private val shaderNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
compiler.addCodeToMaterialFunctionBody("material.$name = $conformedExpression;\n")
|
||||
}
|
||||
compiler.setExpressionForSlot(inputSlot, conformedExpression)
|
||||
|
||||
compiler.setCurrentCodeSection(GraphCompiler.CodeSection.AFTER_PREPARE_MATERIAL)
|
||||
}
|
||||
|
||||
// Compile inputs common to all shading models.
|
||||
@@ -95,6 +81,11 @@ private val shaderNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
compileMaterialInput("ambientOcclusion", 1)
|
||||
}
|
||||
|
||||
if (shadingModel == "lit") {
|
||||
// "normal" must be compiled first. If it has any dependencies that are connected to other
|
||||
// inputs, we need to ensure their code comes before prepareMaterial()
|
||||
compileMaterialInput("normal", 3)
|
||||
}
|
||||
compileCommonInputs()
|
||||
when (shadingModel) {
|
||||
"lit" -> compileLitInputs()
|
||||
@@ -127,6 +118,18 @@ private val float3ParameterNodeCompile = fun(node: Node, compiler: GraphCompiler
|
||||
return node
|
||||
}
|
||||
|
||||
private val constantFloatNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
val value = (node.properties[0].value as FloatValue)
|
||||
|
||||
val outputVariable = compiler.getNewTemporaryVariableName("floatConstant")
|
||||
compiler.addCodeToMaterialFunctionBody("float $outputVariable = ${value.v};\n")
|
||||
|
||||
val outputSlot = node.getOutputSlot("out")
|
||||
compiler.setExpressionForSlot(outputSlot, Expression(outputVariable, 1))
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
private val constantFloat2NodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
val outputSlot = node.getOutputSlot("result")
|
||||
|
||||
@@ -175,16 +178,6 @@ val createTextureSampleNode = fun(id: NodeId) =
|
||||
))
|
||||
)
|
||||
|
||||
val createAdderNode = fun(id: NodeId): Node {
|
||||
return Node(
|
||||
id = id,
|
||||
type = "adder",
|
||||
compileFunction = adderNodeCompile,
|
||||
inputSlots = listOf("a", "b"),
|
||||
outputSlots = listOf("result")
|
||||
)
|
||||
}
|
||||
|
||||
val createFloat3ConstantNode = fun(id: NodeId): Node {
|
||||
return Node(
|
||||
id = id,
|
||||
@@ -212,6 +205,19 @@ val createFloat3ParameterNode = fun(id: NodeId): Node {
|
||||
)
|
||||
}
|
||||
|
||||
val createFloatConstantNode = fun(id: NodeId) =
|
||||
Node(
|
||||
id = id,
|
||||
type = "floatConstant",
|
||||
compileFunction = constantFloatNodeCompile,
|
||||
outputSlots = listOf("out"),
|
||||
properties = listOf(Property(
|
||||
name = "value",
|
||||
value = FloatValue(),
|
||||
editorFactory = ::FloatSlider
|
||||
))
|
||||
)
|
||||
|
||||
val createFloat2ConstantNode = fun(id: NodeId): Node {
|
||||
return Node(
|
||||
id = id,
|
||||
|
||||
@@ -39,6 +39,20 @@ sealed class PropertyValue {
|
||||
abstract fun deserialize(value: Any): PropertyValue
|
||||
}
|
||||
|
||||
data class FloatValue(val v: Float = 0.0f) : PropertyValue() {
|
||||
|
||||
override fun applyToMaterialInstance(materialInstance: MaterialInstance, name: String) {
|
||||
materialInstance.setParameter(name, v)
|
||||
}
|
||||
|
||||
override fun serialize() = v
|
||||
|
||||
override fun deserialize(value: Any): PropertyValue {
|
||||
if (value !is Number) return this
|
||||
return FloatValue(value.toFloat())
|
||||
}
|
||||
}
|
||||
|
||||
data class Float3(val x: Float = 0.0f, val y: Float = 0.0f, val z: Float = 0.0f) : PropertyValue() {
|
||||
|
||||
override fun applyToMaterialInstance(materialInstance: MaterialInstance, name: String) {
|
||||
|
||||
@@ -17,17 +17,21 @@
|
||||
package com.google.android.filament.tungsten.properties
|
||||
|
||||
import com.google.android.filament.tungsten.model.Float3
|
||||
import com.google.android.filament.tungsten.model.FloatValue
|
||||
import com.google.android.filament.tungsten.model.PropertyValue
|
||||
import com.google.android.filament.tungsten.model.StringValue
|
||||
import com.google.android.filament.tungsten.model.TextureFile
|
||||
import com.google.android.filament.tungsten.texture.TextureUtils
|
||||
import java.awt.Color
|
||||
import java.text.NumberFormat
|
||||
import javax.swing.JButton
|
||||
import javax.swing.JColorChooser
|
||||
import javax.swing.JComboBox
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.JFormattedTextField
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.JSlider
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
abstract class PropertyEditor {
|
||||
@@ -38,6 +42,71 @@ abstract class PropertyEditor {
|
||||
abstract fun setValue(v: PropertyValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a JSlider without causing any changeListeners to trigger.
|
||||
*/
|
||||
fun JSlider.quietlySetValue(v: Int) {
|
||||
val changeListeners = this.changeListeners
|
||||
for (changeListener in changeListeners) {
|
||||
this.removeChangeListener(changeListener)
|
||||
}
|
||||
this.value = v
|
||||
for (changeListener in changeListeners) {
|
||||
this.addChangeListener(changeListener)
|
||||
}
|
||||
}
|
||||
|
||||
internal class FloatSlider(initialValue: FloatValue) : PropertyEditor() {
|
||||
|
||||
private val scaleFactor = 1000
|
||||
|
||||
override val component: JPanel = JPanel()
|
||||
private val slider: JSlider
|
||||
private val field: JFormattedTextField
|
||||
|
||||
private var currentValue: Float = initialValue.v
|
||||
|
||||
override fun setValue(v: PropertyValue) {
|
||||
val value = v as FloatValue
|
||||
currentValue = value.v
|
||||
field.value = currentValue
|
||||
slider.quietlySetValue((currentValue * scaleFactor).roundToInt())
|
||||
}
|
||||
|
||||
init {
|
||||
slider = JSlider(0, scaleFactor, (initialValue.v * scaleFactor).roundToInt())
|
||||
slider.addChangeListener {
|
||||
if (!slider.valueIsAdjusting) {
|
||||
currentValue = slider.value.toFloat() / scaleFactor
|
||||
updateValue()
|
||||
}
|
||||
}
|
||||
|
||||
val format = NumberFormat.getNumberInstance()
|
||||
format.maximumFractionDigits = 5
|
||||
field = JFormattedTextField(format)
|
||||
field.columns = 10
|
||||
field.value = initialValue.v
|
||||
|
||||
field.addActionListener {
|
||||
val newValue = field.value as? Number
|
||||
newValue?.let { v ->
|
||||
currentValue = v.toFloat()
|
||||
updateValue()
|
||||
}
|
||||
}
|
||||
|
||||
component.add(slider)
|
||||
component.add(field)
|
||||
}
|
||||
|
||||
private fun updateValue() {
|
||||
slider.quietlySetValue((currentValue * scaleFactor).roundToInt())
|
||||
field.value = currentValue
|
||||
valueChanged(FloatValue(currentValue))
|
||||
}
|
||||
}
|
||||
|
||||
internal class ColorChooser(value: Float3) : PropertyEditor() {
|
||||
|
||||
override val component: JColorChooser
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.model
|
||||
|
||||
import com.google.android.filament.tungsten.compiler.Literal
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class NodeTest {
|
||||
|
||||
@Test
|
||||
fun `resolveInputExpressions keeps two float4s the same length`() {
|
||||
val (a, b) = resolveInputExpressions(Literal(4), Literal(4))
|
||||
assertEquals(4, a.dimensions)
|
||||
assertEquals(4, b.dimensions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveInputExpressions keeps two floats the same length`() {
|
||||
val (a, b) = resolveInputExpressions(Literal(1), Literal(1))
|
||||
assertEquals(1, a.dimensions)
|
||||
assertEquals(1, b.dimensions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveInputExpressions trims both a float3 and a float2 to a float2`() {
|
||||
val (a, b) = resolveInputExpressions(Literal(3), Literal(2))
|
||||
assertEquals(2, a.dimensions)
|
||||
assertEquals(2, b.dimensions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveInputExpressions allows a float as input for scalar operations`() {
|
||||
val (a, b) = resolveInputExpressions(Literal(3), Literal(1))
|
||||
assertEquals(3, a.dimensions)
|
||||
assertEquals(1, b.dimensions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveInputExpressions defaults to float4 when a single input is not present`() {
|
||||
val (a, b) = resolveInputExpressions(null, Literal(1))
|
||||
assertEquals(4, a.dimensions)
|
||||
assertEquals(1, b.dimensions)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user