Gouraud Shading in JavaFX - javafx

Each face has 3 colors (one for each vertex). I want to use Gouraud shading to blend these colors. So far, I have taken some inspiration from the FXyz library.
My current approach uses the setTextureModeVertices3D from the FXyz library. But this uses density maps, which does not work in my case because the colors don't come from a mathematical formula. My initial idea was to implement it as follows:
Calculate the color for each vertex in the mesh.
Extract a list of all the unique colors
Make a palette of the unique colors
val palette = object : ColorPalette {
override fun getNumColors() = colors.size
override fun getColor(i: Int) = colors.getOrNull(i)?: Color.BLACK
}
Make a map with as key the point value (x,y,z) and with as value the index of the color
Make a density function that returns the color index from the aforementioned map.
{ point3F ->
val key = Triple(point3F.x.toInt(), point3F.y.toInt(), point3F.z.toInt())
pointColorIndexMap[key]!!
}
I have a feeling my only option is to create one large image where I put in all shaded triangles, and then references those. But I am unsure what the best technique would be here. Any help is appreciated!
Edit:
This is the code I currently use (this is written in Kotlin):
override fun updateMesh() {
val definition = model.modelDefinition
val (colors1, colors2, colors3) = definition.calculateFaceColors()
val uniqueColorHSBValues = (colors1 + colors2 + colors3).toSet().toList()
val uniqueColors = uniqueColorHSBValues.map { ModelUtil.hsbToColor(it, null) }
val palette = object : ColorPalette {
override fun getNumColors() = uniqueColors.size
override fun getColor(i: Int) = uniqueColors.getOrNull(i)?: Color.BLACK
}
val pointColorIndexMap = HashMap<Triple<Int, Int, Int>, Int>()
for (face in 0 until definition.getFaceCount()) {
val type = definition.getFaceTypes()?.get(face)?.toInt()?.let { it and 3}?:0
val (p1, p2, p3) = definition.getPoints(face)
if (type == RENDER_SHADED_TRIANGLE) {
pointColorIndexMap[p1] = uniqueColorHSBValues.indexOf(colors1[face])
pointColorIndexMap[p2] = uniqueColorHSBValues.indexOf(colors2[face])
pointColorIndexMap[p3] = uniqueColorHSBValues.indexOf(colors3[face])
} else if (type == RENDER_FLAT_TRIANGLE) {
pointColorIndexMap[p1] = uniqueColorHSBValues.indexOf(colors1[face])
pointColorIndexMap[p2] = uniqueColorHSBValues.indexOf(colors1[face])
pointColorIndexMap[p3] = uniqueColorHSBValues.indexOf(colors1[face])
}
}
setTextureModeVertices3D(palette) { point3F ->
val key = Triple(point3F.x.toInt(), point3F.y.toInt(), point3F.z.toInt())
pointColorIndexMap[key]!!
}
val meshHelper = MeshHelper(atlas)
updateMesh(meshHelper)
}
This is how my current implementation of the shading looks like.
This is how my current implementation looks like without the shading.
This is how I want my implementation of the shading to roughly look like
This is how my palette looks like (9x10 pixels) (

Related

Invert Map<K, List<V>> to Map<V, K>

map = mapOf((2: [3,4,5]), (7: [22,33,44]))
need to convert this to
mapOf(3:2, 4:2, 5:2, 22:7, 33:7, 44:7)
tried using associate with forEach, not sure of the syntax
There might be some nicer syntax, but this should work well enough.
fun main() {
val map = mapOf(
2 to listOf(3, 4, 5),
7 to listOf(22, 33, 44)
)
val transformedMap = map.flatMap { entry ->
entry.value.map { it to entry.key }
}.toMap()
println(transformedMap)
}
Prints
{3=2, 4=2, 5=2, 22=7, 33=7, 44=7}
Note that the toMap function states
The returned map preserves the entry iteration order of the original collection. If any of two pairs would have the same key the last one gets added to the map.
So if you have the same value in two different lists, only the last one will be included in the map.
fun main() {
val map = mapOf(
2 to listOf(3, 4, 5),
7 to listOf(22, 33, 44),
8 to listOf(3)
)
val transformedMap = map.flatMap { entry ->
entry.value.map { it to entry.key }
}.toMap()
println(transformedMap)
}
Prints {3=8, 4=2, 5=2, 22=7, 33=7, 44=7}
Zymus' answer is correct, and is also what I would probably write.
However, if this is something that will be called often, you might want to extract it to a separate function that is more efficient.
fun <K, V> Map<K, Iterable<V>>.invert(): Map<V, K> {
val newMap = mutableMapOf<V, K>()
for ((key, iterable) in this) {
for (value in iterable) {
newMap[value] = key
}
}
return newMap
}
Usage:
fun main() {
val map = mapOf((2 to listOf(3, 4, 5)), (7 to listOf(22, 33, 44)))
val inverted = map.invert()
println(inverted)
}
Output:
{3=2, 4=2, 5=2, 22=7, 33=7, 44=7}
This is functionally equivalent to
map.flatMap { (key, values) -> values.map { it to key } }.toMap()
including the behaviour where if there are duplicate values in the original input, only the last one will be preserved as a new key. However, the flatMap version creates many temporary Lists (the number of original keys + 1) and many temporary Pairs (the number of original values), whereas this iterative version creates no extra objects.

Move and rotate object around pivot and resume from where it stopped

I'd like to move an object around another - just as if the one object was a child of the other. This is GDscript - Godot Engine 3.2, but the logic should be very similar to other game engines.
Whenever I hold spacebar the green cube follows the blue cubes rotation.
First GIF demonstrates the green cube starting at position Vector3(0, 4, 0) without any rotation applied. This works perfectly fine.
In the second GIF I'm holding and releasing spacebar repeatedly. I would expect the green cube to continue from where it left, but instead it "jumps" to a new position and continues from there.
Code below does not include the actual rotation of the blue cube (pivot point), but only the calculations needed to move/rotate the green cube. Rotation of the blue cube is not an issue. Also, rotation is just for the sake of demonstration - in real scenario the blue cube would be moving around as well.
Rotations are calculated using quaternion, but this is not a requirement.
extends Node
var _parent
var _child
var _subject
var _positionOffset: Vector3
var _rotationOffset: Quat
func _ready():
_parent = get_parent()
_child = $"/root/Main/Child"
func _input(event):
if event is InputEventKey:
if event.scancode == KEY_SPACE:
if event.is_action_pressed("ui_accept") and _child != null:
_subject = _child
_set_subject_offset(_parent.transform, _child.transform)
elif event.is_action_released("ui_accept"):
_subject = null
func _set_subject_offset(pivot: Transform, subject: Transform):
_positionOffset = (pivot.origin - subject.origin)
_rotationOffset = pivot.basis.get_rotation_quat().inverse() * subject.basis.get_rotation_quat()
func _rotate_around_pivot(subject_position: Vector3, pivot: Vector3, subject_rotation: Quat):
return pivot + (subject_rotation * (subject_position - pivot))
func _physics_process(_delta):
if _subject == null: return
var target_position = _parent.transform.origin - _positionOffset
var target_rotation = _parent.transform.basis.get_rotation_quat() * _rotationOffset
_subject.transform.origin = _rotate_around_pivot(target_position, _parent.transform.origin, target_rotation)
_subject.set_rotation(target_rotation.get_euler())
I feel like I'm missing something obvious.
You can easily achieve this by temporarily parenting the subject to the pivot point while preserving the global transform:
extends Spatial
onready var obj1: Spatial = $Object1
onready var obj2: Spatial = $Object2
func reparent(obj: Spatial, new_parent: Spatial):
# Preserve the global transform while reparenting
var old_trans := obj.global_transform
obj.get_parent().remove_child(obj)
new_parent.add_child(obj)
obj.global_transform = old_trans
func _physics_process(delta: float):
obj1.rotate_z(delta)
func _input(event: InputEvent):
if event.is_action_pressed("ui_accept"):
reparent(obj2, obj1)
elif event.is_action_released("ui_accept"):
reparent(obj2, self)
If reparenting isn't feasible, you could instead parent a RemoteTransform to the pivot, and have that push its transform to the object you want to rotate:
extends Spatial
onready var remote_trans: RemoteTransform = $RemoteTransform
func _process(delta):
rotate_z(delta)
func attach(n: Node):
# move the remote so the target maintains its transform
remote_trans.global_transform = n.global_transform
remote_trans.remote_path = n.get_path()
func detach():
remote_trans.remote_path = ""
repost, Thank You path9263, answered Jun 4, 2019 by path9263 (134 points)
func rotate_around(obj:Spatial, point:Vector3, axis:Vector3, phi:float):
# https://godotengine.org/qa/45609/how-do-you-rotate-spatial-node-around-axis-given-point-space?show=45970#a45970
obj.global_translate(-point)
obj.transform = obj.transform.rotated(axis, phi)
obj.global_translate(point)
the below functions do not work
func rotate_around_point(transform_me:Spatial, pivot_point:Vector3, axis:Vector3, phi:float):
# #4: busted, no rotation
# https://stackoverflow.com/a/59939086/1695680
# Preserve the global transform while reparenting
var pivot_spatial = Spatial.new()
pivot_spatial.translation = pivot_point
var parent_orig = transform_me.get_parent()
var transform_orig = transform_me.global_transform
parent_orig.remove_child(transform_me)
pivot_spatial.add_child(transform_me)
transform_me.global_transform = transform_orig
pivot_spatial.rotate(axis, phi)
var transform_rotated = transform_me.global_transform
pivot_spatial.remove_child(transform_me)
parent_orig.add_child(transform_me)
transform_me.global_transform = transform_orig
func rotate_around_point(transform_me:Spatial, pivot_point:Vector3, axis:Vector3, phi:float):
# busted, no rotate
# https://stackoverflow.com/a/59939086/1695680
var remote_trans : RemoteTransform = RemoteTransform.new()
remote_trans.global_transform = transform_me.global_transform
remote_trans.remote_path = transform_me.get_path()
remote_trans.rotate(axis, phi)
remote_trans.remote_path = ""
func rotate_around_point(transform_me:Spatial, pivot_point:Vector3, axis:Vector3, phi:float):
# busted, local rotate
# https://godotforums.org/discussion/comment/43335/#Comment_43335
var gto_orig = transform_me.global_transform.origin
transform_me.global_transform.origin = pivot_point
transform_me.rotate(axis, phi)
transform_me.global_transform.origin = gto_orig
func rotate_around_point(transform_me:Spatial, pivot_point:Vector3, axis:Vector3, phi:float):
# busted, no rotate
# https://godotengine.org/qa/34248/rotate-around-a-fixed-point-in-3d-space?show=38928#a38928
var start_position : Vector3 = transform_me.translation
var pivot_radius : Vector3 = start_position - pivot_point
var pivot_transform : Transform = Transform(transform.basis, pivot_point)
var transform = pivot_transform.rotated(axis, phi).translated(pivot_radius)
transform.xform(transform_me)

Using multiple constructors for R classes and subclasses

I would like to use multiple constructors in my R S4 class.
I have an object that has three slots. To make that object, sometimes I want to just give the values for the three slots outright. But sometimes I'd like to provide a matrix, and I have a function that can take a matrix and return what those three slots should be.
At first, it seems like I could write a function as a constructor. So I could write objectFromMatrix(matrix) --> object with three slots. The problem is that I also have sub-classes that inherit from that main class, and I want to be able to use that constructor with them as well.
So I could just write functions as extra constructors for each of the subclasses, but that would be a bit tedious and not super OO-like.
To make my problem a little more tangible, I'll try to write a minimal example below. I'll write it in Java, but I'm a bit rusty so let me know if it doesn't make sense.
Desired structure, in Java:
// An abode is a place where you live and it has a size
class Abode {
int size = 1;
// Main constructor that just assigns args to fields
Abode(int size) {
this.size = size;
}
// Alternative constructor that takes in a different datatype
// and computes args to assign to fields
Abode(string description) {
if(description eq "Large") {
this.size = 5;
}
if(description eq "Small") {
this.size = 1;
}
}
// To keep it simple, a house is just an abode with a name
class House extends Abode {
String name;
House(int size, String name) {
super(size);
this.name = name;
}
House(string size, String name) {
super(size);
this.name = name;
}
}
This implementation works nicely because I can call Abode("big") or House("big", "Casa de me"), and both of those get passed to the extra constructor I built in the Abode class.
Keeping up with the house analogy, this is the best I've been able to do in R:
# An abode is a place you live and it has a size
setClass("Abode",
slots =
list(size = "numeric")
)
# Alternative constructor that takes in a different datatype
# and computes args to assign to fields
abode_constructor_2 <- function(sizeString) {
if (sizeString == "big") {return new("Abode", size = 5)}
if (sizeString == "small") {return new("Abode", size = 1)}
}
# A house is an abode with a name
setClass("House",
slots =
list(name = "string"),
contains = "Abode"
)
# I already defined this constructor but I have to do it again
house_constructor_2 <- function(sizeString, name) {
if (sizeString == "big") {return new("House", size = 5, name = name)}
if (sizeString == "small") {return new("House", size = 1, name = name)}
}
In case it helps, here is a minimal example of the real context where this problem is coming up. I define an extra constructor for the Sensor class, sensor_constructor_2, as a function. But then, when I have a class that inherits from Sensor, I have to make that constructor over again.
# A sensor has three parameters
setClass("Sensor",
slots =
list(Rmin = "numeric", Rmax = "numeric", delta = "numeric")
)
# I also like to make sensors from a matrix
sensor_constructor_2 <- function(matrix) {
params <- matrix_to_params(matrix)
return (new("Sensor", Rmin = params[1], Rmax = params[2], delta = params[3]))
}
# A redoxSensor is just a sensor with an extra field
setClass("redoxSensor",
slots =
list(e0 = "numeric"),
contains = "Sensor"
)
# Goal: make this extra constructor unnecessary by making sensor_constructor_2 a property of the sensor class
extraConstructor_redox <- function(matrix, e0) {
params <- matrix_to_params(matrix)
return (new("redoxSensor", Rmin = params[1], Rmax = params[2], delta = params[3]), e0 = e0)
}
There is no reason why you can't do this with one S4 constructor by using default arguments and a little extra logic, along the lines of
setClass("Abode",
slots = list(size = "numeric")
) -> Abode
setClass("House",
slots = list(name = "character"),
contains = "Abode"
) -> House
createDwelling <- function(size=0,name,sizeString){
if(!missing(sizeString)){
if(sizeString == "Large") size <- 5
else if(sizeString == "Small") size <- 1
else stop("invalid sizeString")
}
if(missing(name)) return(Abode(size=size))
else return(House(size=size,name=name))
}
example usage:
> createDwelling(size=3)
An object of class "Abode"
Slot "size":
[1] 3
> createDwelling(sizeString="Small")
An object of class "Abode"
Slot "size":
[1] 1
> createDwelling(sizeString="Small",name="my house")
An object of class "House"
Slot "name":
[1] "my house"
Slot "size":
[1] 1

Orsoncharts chart3D surface rendering not working

I am using orson chart's Chart3D to make a surface plot, and for some reason the graph isn't properly coloring the gradient. The code is below:
#SuppressWarnings("serial")
Function3D function = new Function3D() {
#Override
public double getValue(double x, double z) {
double xKey = Math.round(x * 100) / 100;
double zKey = Math.round(z * 100) / 100;
if(plotValues.containsKey(new Point2D(xKey,zKey))) {
return plotValues.get(new Point2D(xKey,zKey));
} else {
return 0;
}
}
};
String xTitle = factorSweepComboBox.getSelectionModel().getSelectedItem();
String yTitle = outputComboBox.getSelectionModel().getSelectedItem();
String zTitle = factorSweep2ComboBox.getSelectionModel().getSelectedItem();
// Create surface plot
Chart3D chart = Chart3DFactory.createSurfaceChart(
"",
"",
function, xTitle, yTitle, zTitle);
XYZPlot xyzplot = (XYZPlot) chart.getPlot();
xyzplot.setDimensions(new Dimension3D(10, 10, 10));
ValueAxis3D xAxis = xyzplot.getXAxis();
xAxis.setRange(xLow, xUp);
ValueAxis3D zAxis = xyzplot.getZAxis();
zAxis.setRange(zLow, zUp);
ValueAxis3D yAxis = xyzplot.getYAxis();
yAxis.setRange(yLow, yUp);
SurfaceRenderer renderer = (SurfaceRenderer) xyzplot.getRenderer();
renderer.setColorScale(new GradientColorScale(new Range(yLow, yUp),
Color.BLUE, Color.YELLOW));
Chart3DViewer chartPanel = new Chart3DViewer(chart);
chartPane.getChildren().addAll(chartPanel);
plotValues is a hashmap mapping a (x,z) 2D point to a double y output value. xLow, xUp, etc. are range values and are all being set correctly. yLow and yUp are what I want them to be. However, when I run the code my surface is all one color, there is no gradient at all even though the key looks correct. I have also tried:
SurfaceRenderer renderer = new SurfaceRenderer(function);
renderer.setColorScale(new GradientColorScale(new Range(yLow, yUp),
Color.BLUE, Color.YELLOW));
xyzplot.setRenderer(renderer);
and the result is the same. Here is a link to a screenshot: http://i.imgur.com/F2iYFh1.png
I found the problem. It was due to my function only return real values on discrete x and z values, which works if the set of (x,z) matches the set in my own data hash. However, the surface renderer uses the mid-point between two "x" and "z"'s in the dataset when it plots the surface to determine the color, and because the middle point isn't in the hash it returns 0, making the whole surface one color.

Kinect skeleton Scaling strange behaviour

I am trying to scale a skeleton to match to the sizes of another skeleton.
My algoritm do the following:
Find the distance between two joints of the origin skeleton and the destiny skeleton using phytagorean teorem
divide this two distances to find a multiply factor.
Multiply each joint by this factor.
Here is my actual code:
public static Skeleton ScaleToMatch(this Skeleton skToBeScaled, Skeleton skDestiny)
{
Joint newJoint = new Joint();
double distanciaOrigem = 0;
double distanciaDestino = 0;
double fator = 1;
SkeletonPoint pos = new SkeletonPoint();
foreach (BoneOrientation bo in skToBeScaled.BoneOrientations)
{
distanciaOrigem = FisioKinectCalcs.Distance3DBetweenJoint(skToBeScaled.Joints[bo.StartJoint], skToBeScaled.Joints[bo.EndJoint]);
distanciaDestino = FisioKinectCalcs.Distance3DBetweenJoint(skDestiny.Joints[bo.StartJoint], skDestiny.Joints[bo.EndJoint]);
if (distanciaOrigem > 0 && distanciaDestino > 0)
{
fator = (distanciaDestino / distanciaOrigem);
newJoint = skToBeScaled.Joints[bo.EndJoint]; // escaling only the end joint as the BoneOrientatios starts from HipCenter, i am scaling from center to edges.
// applying the new values to the joint
pos = new SkeletonPoint()
{
X = (float)(newJoint.Position.X * fator),
Y = (float)(newJoint.Position.Y * fator),
Z = (float)(newJoint.Position.Z * fator)
};
newJoint.Position = pos;
skToBeScaled.Joints[bo.EndJoint] = newJoint;
}
}
return skToBeScaled;
}
Every seems to work fine except for the hands and foots
Look at this images
I have my own skeleton over me, and my skeleton scaled to the sizes of another person, but the hands and foots still crazy. (but code looks right)
Any suggestion?
It's hard to say without running the code, but it somewhat "looks good".
What I would validate though, is your
if (distanciaOrigem > 0 && distanciaDestino > 0)
If distanciaOrigem is very close to 0, but even just epsilon away from 0, it won't be picked up by the if, and then
fator = (distanciaDestino / distanciaOrigem);
Will result in a very large number!
I would suggest to smooth the factor so it generally fits the proper scale. Try this code:
private static Dictionary<JointType, double> jointFactors = null;
static CalibrationUtils()
{
InitJointFactors();
}
public static class EnumUtil
{
public static IEnumerable<T> GetValues<T>()
{
return Enum.GetValues(typeof(T)).Cast<T>();
}
}
private static void InitJointFactors()
{
var jointTypes = EnumUtil.GetValues<JointType>();
jointFactors = new Dictionary<JointType, double>();
foreach(JointType type in jointTypes)
{
jointFactors.Add(type, 0);
}
}
private static double SmoothenFactor(JointType jointType, double factor, int weight)
{
double currentValue = jointFactors[jointType];
double newValue = 0;
if(currentValue != 0)
newValue = (weight * currentValue + factor) / (weight + 1);
else
newValue = factor;
jointFactors[jointType] = newValue;
return newValue;
}
When it comes to factor usage just use the SmoothenFactor method first:
public static Skeleton ScaleToMatch(this Skeleton skToBeScaled, Skeleton skDestiny, double additionalFactor = 1)
{
Joint newJoint = new Joint();
double distanceToScale = 0;
double distanceDestiny = 0;
double factor = 1;
int weight = 500;
SkeletonPoint pos = new SkeletonPoint();
Skeleton newSkeleton = null;
KinectHelper.CopySkeleton(skToBeScaled, ref newSkeleton);
SkeletonPoint hipCenterPosition = newSkeleton.Joints[JointType.HipCenter].Position;
foreach(BoneOrientation bo in skToBeScaled.BoneOrientations)
{
distanceToScale = Distance3DBetweenJoints(skToBeScaled.Joints[bo.StartJoint], skToBeScaled.Joints[bo.EndJoint]);
distanceDestiny = Distance3DBetweenJoints(skDestiny.Joints[bo.StartJoint], skDestiny.Joints[bo.EndJoint]);
if(distanceToScale > 0 && distanceDestiny > 0)
{
factor = (distanceDestiny / distanceToScale) * additionalFactor;
newJoint = skToBeScaled.Joints[bo.EndJoint]; // escaling only the end joint as the BoneOrientatios starts from HipCenter, i am scaling from center to edges.
factor = SmoothenFactor(newJoint.JointType, factor, weight);
pos = new SkeletonPoint()
{
X = (float)((newJoint.Position.X - hipCenterPosition.X) * factor + hipCenterPosition.X),
Y = (float)((newJoint.Position.Y - hipCenterPosition.Y) * factor + hipCenterPosition.Y),
Z = (float)((newJoint.Position.Z - hipCenterPosition.Z) * factor + hipCenterPosition.Z)
};
newJoint.Position = pos;
newSkeleton.Joints[bo.EndJoint] = newJoint;
}
}
return newSkeleton;
}
I also modified your ScaleToMatch method as you see. There was a need to move joints in relation to HipCenter position. Also new positions are saved to a new Skeleton instance so they are not used in further vector calculations.
Experiment with the weight but since our bones length is constant you can use big numbers like 100 and more to be sure that wrong Kinect readings do not disturb the correct scale.
Here's an example of how it helped with scaling HandRight joint position:
The weight was set to 500. The resulting factor is supposed to be around 2 (because the base skeleton was purposely downscaled by a factor of 2).
I hope it helps!

Resources