Commit 50435b77 authored by Benedikt Jensen's avatar Benedikt Jensen
Browse files

added bezier curve demo

parent 51e0378a
PVector a = new PVector(400, 100);
PVector b = new PVector(700, 500);
PVector ab = new PVector(a.x-300, b.y);
PVector[] points = new PVector[]{a, b, ab};
int iterations = 20;
PVector lastMousePos = new PVector(0, 0);
float selectRadius = 25;
void keyPressed() {
if (keyCode==UP) {
iterations++;
}
if (keyCode==DOWN) {
iterations--;
}
}
void setup() {
size(800, 600);
}
void draw() {
background(255);
noStroke();
for (var v : points) {
fill(0);
circle(v.x, v.y, 20);
if (dist(mouseX, mouseY, v.x, v.y) < selectRadius) {
fill(0, 100);
circle(v.x, v.y, selectRadius*2);
}
}
stroke(0);
strokeWeight(1);
line(a.x, a.y, ab.x, ab.y);
line(ab.x, ab.y, b.x, b.y);
var step = 1f / (iterations-1);
for (int i = 0; i<iterations; i++) {
var x = i * step;
var v1 = getPointBetween(a, ab, x);
var v2 = getPointBetween(ab, b, x);
stroke(0);
strokeWeight(1);
line(v1.x, v1.y, v2.x, v2.y);
}
for (int i = 0; i<iterations; i++) {
var x = i * step;
var v1 = getPointBetween(a, ab, x);
var v2 = getPointBetween(ab, b, x);
noStroke();
fill(255, 0, 0);
var v = getPointBetween(v1, v2, x);
circle(v.x, v.y, 10);
}
dragPoint();
lastMousePos.x = mouseX;
lastMousePos.y = mouseY;
}
void dragPoint() {
if (mousePressed) {
for (var p : points) {
if (dist(lastMousePos.x, lastMousePos.y, p.x,p.y) < selectRadius) {
p.x += mouseX - lastMousePos.x;
p.y += mouseY - lastMousePos.y;
}
}
}
}
PVector getPointBetween(PVector a, PVector b, float x) {
return a.copy().mult(1-x).add(b.copy().mult(x));
}
class Animation {
boolean isPlaying = true;
int passedFrames = 0;
int keyframeLength = 120;
ArrayList<Keyframe> keyframes = new ArrayList<>();
Keyframe interpolatedFrame = new Keyframe(skinnedSkeleton.skeleton.bones);
void addKeyframe() {
keyframes.add(new Keyframe(skinnedSkeleton.skeleton.bones));
}
void interpolate() {
if (!isPlaying || keyframes.size()==0) return;
interpolatedFrame = new Keyframe(skinnedSkeleton.skeleton.bones);
passedFrames = (passedFrames + 1) % (keyframeLength*keyframes.size());
int keyframeAIndex = passedFrames / keyframeLength;
var keyframeA = keyframes.get(keyframeAIndex);
var keyframeB = keyframes.get((keyframeAIndex + 1) % keyframes.size());
float x = 1 - (float(passedFrames % keyframeLength) / keyframeLength);
for (var bone : skinnedSkeleton.skeleton.bones) {
// Interpolate position
var portionA = keyframeA.boneToTransformMap.get(bone).pos.copy().mult(x);
var portionB = keyframeB.boneToTransformMap.get(bone).pos.copy().mult(1-x);
interpolatedFrame.boneToTransformMap.get(bone).pos = portionA.add(portionB);
// Interpolate rotation
var rotA = keyframeA.boneToTransformMap.get(bone).rotation * x;
var rotB = keyframeB.boneToTransformMap.get(bone).rotation * (1-x);
interpolatedFrame.boneToTransformMap.get(bone).rotation = rotA + rotB;
// Interpolate length
var lengthA = keyframeA.boneToTransformMap.get(bone).length * x;
var lengthB = keyframeB.boneToTransformMap.get(bone).length * (1-x);
interpolatedFrame.boneToTransformMap.get(bone).length = lengthA + lengthB;
}
}
Keyframe getInterpolatedFrame() {
interpolate();
return interpolatedFrame;
}
}
class CombinedAnimation {
HashMap<Animation, Float> weightedAnimation;
}
class Keyframe {
HashMap<Bone, BoneTransform> boneToTransformMap = new HashMap<>();
Keyframe() {}
Keyframe(ArrayList<Bone> bones) {
for (var bone : bones) {
boneToTransformMap.put(bone, bone.copyTransform());
}
}
}
void applyTransform(Keyframe frame, float weight) {
for(var bone : frame.boneToTransformMap.keySet()) {
var transform = frame.boneToTransformMap.get(bone);
bone.pos = transform.pos.copy().mult(weight);
bone.rotation = transform.rotation * weight;
bone.length = transform.length * weight;
}
}
void applyTransformAdditive(Keyframe frame, float weight) {
for(var bone : frame.boneToTransformMap.keySet()) {
var transform = frame.boneToTransformMap.get(bone);
bone.pos = transform.pos.copy().mult(weight).add(bone.pos);
bone.rotation += transform.rotation * weight;
bone.length += transform.length * weight;
}
}
......@@ -46,7 +46,7 @@ void editorAction() {
void select() {
Bone closestBone = null;
float shortestDistance = 9999f;
for (Bone bone : skeleton.bones) {
for (Bone bone : skinnedSkeleton.skeleton.bones) {
if (action != Action.NONE) return;
if (keyIsDown(CONTROL)) {
bone.unselect();
......@@ -84,11 +84,8 @@ Bone createBone(Bone parent) {
new PVector(0, 0),
angle,
endOfBone.copy().sub(mousePos).mag());
println("global_rot: " + bone.getGlobalRot());
println("end: " + parent.getEndOfBone());
println("rotation: " + parent.getGlobalRot());
}
skeleton.addBone(bone);
skinnedSkeleton.skeleton.addBone(bone);
selectedBones.clear();
bone.select();
return bone;
......@@ -115,3 +112,33 @@ float getAngle(PVector vector) {
return PVector.angleBetween(new PVector(0, 1), vector) + PI;
}
}
float getDistanceToSegment( float x1, float y1, float x2, float y2, float x, float y ){
PVector result = new PVector();
float dx = x2 - x1;
float dy = y2 - y1;
float d = sqrt( dx*dx + dy*dy );
float ca = dx/d; // cosine
float sa = dy/d; // sine
float mX = (-x1+x)*ca + (-y1+y)*sa;
if( mX <= 0 ){
result.x = x1;
result.y = y1;
}
else if( mX >= d ){
result.x = x2;
result.y = y2;
}
else{
result.x = x1 + mX*ca;
result.y = y1 + mX*sa;
}
dx = x - result.x;
dy = y - result.y;
result.z = sqrt( dx*dx + dy*dy );
return result.z;
}
......@@ -24,7 +24,7 @@ void mousePressed() {
dragFrom = new PVector(mouseX, mouseY);
}
void mouseDragged() {
if (keyIsDown(CONTROL)) {
if (keyIsDown(CONTROL) || keyIsDown(ALT)) {
return;
}
if (action == Action.NONE) {
......@@ -38,7 +38,7 @@ void mouseReleased() {
action = Action.NONE;
} else if (dragSelect) {
dragSelect = false;
for (Bone bone : skeleton.bones) {
for (Bone bone : skinnedSkeleton.skeleton.bones) {
// If SHIFT isn't down unselect all
if (!keyIsDown(SHIFT)) {
bone.unselect();
......@@ -57,13 +57,16 @@ void mouseReleased() {
null;
Bone newBone = createBone(parent);
// Make only new bone selected
for (Bone bone : skeleton.bones) {
for (Bone bone : skinnedSkeleton.skeleton.bones) {
if (bone == newBone) {
bone.select();
} else {
bone.unselect();
}
}
// If ALT is down create screenPoint
} else if (keyIsDown(ALT)) {
skinnedSkeleton.screenPositions.add(new PVector(mouseX,mouseY));
} else {
select();
}
......@@ -71,6 +74,7 @@ void mouseReleased() {
}
void setSelectionCenterAnchor() {
if (selectedBones.size()==0) return;
PVector topLeft = selectedBones.get(0).getGlobalPos().copy();
PVector bottomRight = topLeft.copy();
for (Bone bone : selectedBones) {
......@@ -85,10 +89,9 @@ void setSelectionCenterAnchor() {
void keyPressed() {
setSelectionCenterAnchor();
//add keys to list of pressed keys
if (keyCode==SHIFT) pressedKeys.add(new Integer(SHIFT));
if (keyCode==CONTROL) pressedKeys.add(new Integer(CONTROL));
pressedKeys.add(new Integer(keyCode));
//move
if (key=='g') {
......@@ -120,19 +123,64 @@ void keyPressed() {
//delete
if (key=='x') {
skeleton.bones.removeAll(selectedBones);
skinnedSkeleton.skeleton.bones.removeAll(selectedBones);
selectedBones.clear();
}
//add keyframe
// add keyframe
if (key=='k') {
animations.get(selectedAnimation-1).addKeyframe();
}
// play/stop animation
if (key=='p') {
isPlaying = !isPlaying;
}
// generate skin
if (key=='m') {
skinnedSkeleton.generateSkin();
}
// create lines
if (key=='l') {
if (lines.size()==0) {
for (var vertex : skinnedSkeleton.skin.vertexSpritePosMap.keySet()) {
var pos1 = vertex.getPositionOnScreen();
for (var vertex2 : skinnedSkeleton.skin.vertexSpritePosMap.keySet()) {
var pos2 = vertex2.getPositionOnScreen();
if (dist(pos1.x, pos1.y, pos2.x, pos2.y) <= 30f) {
lines.add(new SkinVertex[] {vertex, vertex2});
}
}
}
}
}
if (key=='0' || key=='1' || key=='2' || key=='3' || key=='4' || key=='5' || key=='6' || key=='7' || key=='8' || key=='9') {
var number = key - '0';
if (number<=animationCount) {
selectedAnimation = number;
}
}
// add animation
if (key=='n') {
if (animationCount<10) {
animationCount++;
animations.add(new Animation());
}
}
// add animation
if (key=='a') {
additiveLayering = !additiveLayering;
}
}
void keyReleased() {
//remove keys from list of pressed keys
if (keyCode==SHIFT) pressedKeys.remove(new Integer(SHIFT));
if (keyCode==CONTROL) pressedKeys.remove(new Integer(CONTROL));
if (pressedKeys.contains(new Integer(keyCode))) pressedKeys.remove(new Integer(keyCode));
}
boolean keyIsDown(int pressedKey) {
......
import java.util.*;
import java.lang.Cloneable;
color colorSelected = color(255, 255, 255);
color colorDefault = color(252, 186, 3);
Skeleton skeleton;
SkinnedSkeleton skinnedSkeleton = new SkinnedSkeleton();
ArrayList<SkinVertex[]> lines = new ArrayList<>();
boolean isPlaying = false;
ArrayList<Animation> animations = new ArrayList<>();
CombinedAnimation combinedAnimation = new CombinedAnimation();
int selectedAnimation = 1;
int animationCount = 1;
boolean additiveLayering = false;
void setup() {
size(800, 600);
skeleton = new Skeleton();
skeleton
animations.add(new Animation());
skinnedSkeleton.skeleton
.addBone(new Bone(null, new PVector(400, 300), 0, 100));
}
void draw() {
background(100);
// draw animation list
for (int i = 1; i<=animationCount; i++) {
float x = 10 + 30*(i-1);
float y = 10;
stroke(0);
fill(i==selectedAnimation ? 255 : 100);
rect(x, y, 20, 20);
fill(0);
textAlign(CENTER, CENTER);
text(i, x+10, y+10);
}
if (isPlaying) {
if (additiveLayering) {
// Only applies with animationCount == 4;
if (animationCount==4) {
boolean first = true;
float fullDistance = 200f;
float upWeight = constrain(((height/2+fullDistance) - mouseY) / (fullDistance*2),0,1);
float downWeight = 1 - upWeight;
float leftWeight = constrain(((width/2+fullDistance) - mouseX) / (fullDistance*2),0,1);
float rightWeight = 1 - leftWeight;
float weightInfluence = constrain(dist(mouseX,mouseY,width/2,height/2)/fullDistance,0,1);
float verticalness = (max(upWeight, downWeight)-0.5f) * 2f;
float horizontalness = (max(leftWeight, rightWeight)-0.5f) * 2f;
println("v" + verticalness);
println("h" + horizontalness);
upWeight = upWeight * verticalness;
downWeight = downWeight * verticalness;
leftWeight = leftWeight * horizontalness;
rightWeight = rightWeight * horizontalness;
float totalWeight = upWeight + downWeight + leftWeight + rightWeight;
if (totalWeight!=0) {
upWeight /= totalWeight;
downWeight /= totalWeight;
leftWeight /= totalWeight;
rightWeight /= totalWeight;
}
println("up: " + upWeight + ", down: " + downWeight + ", left: " + leftWeight + ", right: " + rightWeight);
for (int i = 0; i<4; i++) {
float weight = 0;
// choose correct weight
if (i==0) weight = upWeight;
else if (i==1) weight = downWeight;
else if (i==2) weight = leftWeight;
else weight = rightWeight;
if (first) applyTransform(animations.get(i).getInterpolatedFrame(), weight);
else applyTransformAdditive(animations.get(i).getInterpolatedFrame(), weight);
first = false;
}
}
} else {
applyTransform(animations.get(selectedAnimation-1).getInterpolatedFrame(), 1f);
}
}
mouseMovement();
editorAction();
skeleton.display();
skinnedSkeleton.skeleton.display();
// draw mesh links
for (var line : lines) {
var pos1 = line[0].getPositionOnScreen();
var pos2 = line[1].getPositionOnScreen();
line(pos1.x, pos1.y, pos2.x, pos2.y);
}
// draw unassigned points
for (var pos : skinnedSkeleton.screenPositions) {
circle(pos.x, pos.y, 5);
}
// draw vertices
skinnedSkeleton.skin.display();
}
class SkinnedSkeleton {
Skeleton skeleton = new Skeleton();
Skin skin = new Skin();
ArrayList<PVector> screenPositions = new ArrayList<>();
void generateSkin() {
//screenPositions.clear();
if (skeleton.bones.size()==0) return;
PVector min = null;
PVector max = null;
for (var bone : skeleton.bones) {
var globalPos = bone.getGlobalPos();
var currentMin = new PVector(globalPos.x - bone.length*2f, globalPos.y - bone.length*2f);
var currentMax = new PVector(globalPos.x + bone.length*2f, globalPos.y + bone.length*2f);
if (min==null) {
min = currentMin;
} else {
if (currentMin.x < min.x) min.x = currentMin.x;
if (currentMin.y < min.y) min.y = currentMin.y;
}
if (max==null) {
max = currentMax;
} else {
if (currentMax.x > max.x) max.x = currentMax.x;
if (currentMax.y > max.y) max.y = currentMax.y;
}
}
float meshSize = 20f;
float maxDist = 50f;
//for (float i = min.x; i<=max.x; i+=meshSize) {
// for (float j = min.y; j<=max.y; j+=meshSize) {
// for (var bone : skeleton.bones) {
// if (bone.distance(i, j) <= maxDist) {
// screenPositions.add(new PVector(i, j));
// break;
// }
// }
// }
//}
float maxWeightDist = maxDist*2;
for (var point : screenPositions) {
float summedDistance = 0f;
ArrayList<Bone> closeBones = new ArrayList<>();
float shortestDistance = 200f;
for (var bone : skeleton.bones) {
var distance = bone.distance(point.x, point.y);
if (distance > maxWeightDist) continue;
shortestDistance = distance;
closeBones.add(bone);
summedDistance += distance;
}
//println(closeBones.size());
var newVertex = new SkinVertex();
skin.vertexSpritePosMap.put(newVertex, null);
for (var bone : closeBones) {
var distance = bone.distance(point.x, point.y);
var weight = pow(maxWeightDist - distance, 10);
var weightVector = new WeightVector(bone.screenPosToRelPos(point), weight);
newVertex.boneWeightMap.put(bone, weightVector);
}
}
screenPositions.clear();
skin.normalizeWeights();
}
}
class Skeleton {
......@@ -32,6 +178,17 @@ class Skeleton {
}
}
class BoneTransform {
PVector pos;
float rotation;
float length;
BoneTransform(Bone bone) {
this.pos = bone.pos.copy();
this.rotation = bone.rotation;
this.length = bone.length;
}
}
class Bone {
Bone parent;
PVector pos;
......@@ -40,11 +197,16 @@ class Bone {
boolean isSelected = false;
PVector posBeforeTransformation;
float lengthBeforeTransformation;
float influenceRadius;
Bone(Bone parent, PVector pos, float rotation, float length) {
this.parent = parent;
this.pos = pos;
this.rotation = rotation;
this.length = length;
this.influenceRadius = 50f;
}
BoneTransform copyTransform() {
return new BoneTransform(this);
}
void display() {
fill(isSelected ? colorSelected : colorDefault);
......@@ -80,27 +242,90 @@ class Bone {
}
}
PVector getEndOfBone() {
PVector v = new PVector(0,-length).rotate(getGlobalRot());
PVector v = new PVector(0, -length).rotate(getGlobalRot());
return getGlobalPos().add(v);
}
float getGlobalRot() {
if (parent==null) return rotation;
else return rotation + parent.getGlobalRot();
}
PVector relPosToScreenPos(PVector relativePosition) {
return getGlobalPos().add(relativePosition.copy().rotate(getGlobalRot()));
}
PVector screenPosToRelPos(PVector screenPosition) {
var diff = screenPosition.copy().sub(getGlobalPos());
return diff.rotate(-getGlobalRot());
}
float distance(float x, float y) {
PVector la = getGlobalPos();
PVector lb = getEndOfBone();
return getDistanceToSegment(la.x, la.y, lb.x, lb.y, x, y);
}
}
class Animation {
HashMap<Bone, ArrayList<KeyFrame>> boneAnimations = new HashMap<>();
class Skin {
HashMap<SkinVertex, PVector> vertexSpritePosMap = new HashMap<>();
void addVertex(SkinVertex vertex, PVector posOnSprite) {
vertexSpritePosMap.put(vertex, posOnSprite);
}
void normalizeWeights() {
for (var vertex : vertexSpritePosMap.keySet()) {
vertex.normalizeWeights();
}
}
void display() {
for (var vertex : vertexSpritePosMap.keySet()) {
var pos = vertex.getPositionOnScreen();
fill(0);
stroke(255);
if (selectedBones.size()==1) {
noStroke();