From 05b3e66dc03452c2d04518f8ffcf0ba318b36393 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sat, 28 May 2016 20:30:12 +0200 Subject: [PATCH] Start of multi bone path constraints. --- .../src/com/esotericsoftware/spine/Bone.java | 9 + .../esotericsoftware/spine/IkConstraint.java | 14 +- .../spine/PathConstraint.java | 118 +++++-- .../spine/PathConstraintData.java | 12 +- .../com/esotericsoftware/spine/Skeleton.java | 3 +- .../spine/SkeletonBinary.java | 3 +- .../esotericsoftware/spine/SkeletonJson.java | 9 +- .../spine/attachments/PathAttachment.java | 299 +++++++++--------- 8 files changed, 268 insertions(+), 199 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java index 67e505a4f..c454e1177 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -233,6 +233,15 @@ public class Bone implements Updatable { appliedScaleY = scaleY; } + public void rotateWorld (float degrees) { + float a = this.a, b = this.b, c = this.c, d = this.d; + float cos = cosDeg(degrees), sin = sinDeg(degrees); + this.a = cos * a - sin * c; + this.b = cos * b - sin * d; + this.c = sin * a + cos * c; + this.d = sin * b + cos * d; + } + public void setToSetupPose () { BoneData data = this.data; x = data.x; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java index 51d9644d4..d3710d08f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java @@ -56,14 +56,14 @@ public class IkConstraint implements Updatable { } /** Copy constructor. */ - public IkConstraint (IkConstraint ikConstraint, Skeleton skeleton) { - data = ikConstraint.data; - bones = new Array(ikConstraint.bones.size); - for (Bone bone : ikConstraint.bones) + public IkConstraint (IkConstraint constraint, Skeleton skeleton) { + data = constraint.data; + bones = new Array(constraint.bones.size); + for (Bone bone : constraint.bones) bones.add(skeleton.bones.get(bone.data.index)); - target = skeleton.bones.get(ikConstraint.target.data.index); - mix = ikConstraint.mix; - bendDirection = ikConstraint.bendDirection; + target = skeleton.bones.get(constraint.target.data.index); + mix = constraint.mix; + bendDirection = constraint.bendDirection; } public void apply () { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index da60beb3a..6eecc75f0 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -4,29 +4,38 @@ package com.esotericsoftware.spine; import static com.badlogic.gdx.math.MathUtils.*; import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.FloatArray; import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.PathAttachment; public class PathConstraint implements Updatable { final PathConstraintData data; - Bone bone; + final Array bones; Slot target; float position, rotateMix, translateMix; - final Vector2 worldPosition = new Vector2(), tangent = new Vector2(); + final FloatArray lengths = new FloatArray(), positions = new FloatArray(); + final Vector2 temp = new Vector2(); public PathConstraint (PathConstraintData data, Skeleton skeleton) { this.data = data; position = data.position; rotateMix = data.rotateMix; translateMix = data.translateMix; - bone = skeleton.findBone(data.bone.name); + + bones = new Array(data.bones.size); + for (BoneData boneData : data.bones) + bones.add(skeleton.findBone(boneData.name)); + target = skeleton.findSlot(data.target.name); } /** Copy constructor. */ public PathConstraint (PathConstraint constraint, Skeleton skeleton) { data = constraint.data; - bone = skeleton.bones.get(constraint.bone.data.index); + bones = new Array(constraint.bones.size); + for (Bone bone : constraint.bones) + bones.add(skeleton.bones.get(bone.data.index)); target = skeleton.slots.get(constraint.target.data.index); position = constraint.position; rotateMix = constraint.rotateMix; @@ -40,31 +49,82 @@ public class PathConstraint implements Updatable { public void update () { Attachment attachment = target.getAttachment(); if (!(attachment instanceof PathAttachment)) return; - PathAttachment path = (PathAttachment)attachment; - - Vector2 worldPosition = this.worldPosition; - Bone bone = this.bone; float translateMix = this.translateMix, rotateMix = this.rotateMix; - if (translateMix > 0) { - path.computeWorldPosition(target, position, worldPosition, rotateMix > 0 ? tangent : null); - bone.worldX += (worldPosition.x - bone.worldX) * translateMix; - bone.worldY += (worldPosition.y - bone.worldY) * translateMix; + boolean translate = translateMix > 0, rotate = rotateMix > 0; + if (!translate && !rotate) return; + + PathAttachment path = (PathAttachment)attachment; + FloatArray lengths = this.lengths, positions = this.positions; + lengths.clear(); + lengths.add(0); + positions.clear(); + + Array bones = this.bones; + int boneCount = bones.size; + if (boneCount == 1) { + path.computeWorldPositions(target, position, lengths, positions, rotate); + Bone bone = bones.first(); + bone.worldX += (positions.first() - bone.worldX) * translateMix; + bone.worldY += (positions.get(1) - bone.worldY) * translateMix; + if (rotate) { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = positions.get(2) - atan2(c, a) + data.offsetRotation * degRad; + if (r > PI) + r -= PI2; + else if (r < -PI) r += PI2; + r *= rotateMix; + float cos = cos(r), sin = sin(r); + bone.a = cos * a - sin * c; + bone.b = cos * b - sin * d; + bone.c = sin * a + cos * c; + bone.d = sin * b + cos * d; + } + return; } - if (rotateMix > 0) { - if (translateMix == 0) path.computeWorldPosition(target, position, worldPosition, tangent); - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = atan2(worldPosition.y - tangent.y, worldPosition.x - tangent.x) - atan2(c, a) + data.offsetRotation * degRad; - if (r > PI) - r -= PI2; - else if (r < -PI) r += PI2; - r *= rotateMix; - float cos = cos(r), sin = sin(r); - bone.a = cos * a - sin * c; - bone.b = cos * b - sin * d; - bone.c = sin * a + cos * c; - bone.d = sin * b + cos * d; + for (int i = 0; i < boneCount; i++) + lengths.add(bones.get(i).data.length); + path.computeWorldPositions(target, position, lengths, positions, false); + + Vector2 temp = this.temp; + float boneX = positions.first(), boneY = positions.get(1); + for (int i = 0, p = 2; i < boneCount; i++, p += 2) { + Bone bone = bones.get(i); + bone.worldX += (boneX - bone.worldX) * translateMix; + bone.worldY += (boneY - bone.worldY) * translateMix; + + float x = positions.get(p), y = positions.get(p + 1); + if (rotate) { + // Scale. + // float dist = (float)Math.sqrt((boneX - x) * (boneX - x) + (boneY - y) * (boneY - y)); + // bone.scaleX = dist / bone.data.length; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = atan2(y - boneY, x - boneX) - atan2(c, a) + data.offsetRotation * degRad; + if (r > PI) + r -= PI2; + else if (r < -PI) r += PI2; + r *= rotateMix; + float cos = cos(r), sin = sin(r); + bone.a = cos * a - sin * c; + bone.b = cos * b - sin * d; + bone.c = sin * a + cos * c; + bone.d = sin * b + cos * d; + if (data.offsetRotation == 0 && rotateMix == 1) { + // Place at tip. + bone.localToWorld(temp.set(bone.data.length, 0)); + boneX = temp.x; + boneY = temp.y; + } else { + // Place on path. + boneX = x; + boneY = y; + } + } else { + // Place on path. + boneX = x; + boneY = y; + } } } @@ -92,12 +152,8 @@ public class PathConstraint implements Updatable { this.translateMix = translateMix; } - public Bone getBone () { - return bone; - } - - public void setBone (Bone bone) { - this.bone = bone; + public Array getBones () { + return bones; } public Slot getTarget () { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java index f3a276896..4df2952ea 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java @@ -1,9 +1,11 @@ package com.esotericsoftware.spine; +import com.badlogic.gdx.utils.Array; + public class PathConstraintData { final String name; - BoneData bone; + final Array bones = new Array(); SlotData target; float position, rotateMix, translateMix; float offsetRotation; @@ -12,12 +14,8 @@ public class PathConstraintData { this.name = name; } - public BoneData getBone () { - return bone; - } - - public void setBone (BoneData bone) { - this.bone = bone; + public Array getBones () { + return bones; } public SlotData getTarget () { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java index 2aac860a1..823da165d 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -162,7 +162,8 @@ public class Skeleton { for (int i = 0, n = pathConstraints.size; i < n; i++) { PathConstraint pathConstraint = pathConstraints.get(i); - Bone bone = pathConstraint.bone; + // BOZO! - Fix update order for multiple bones. + Bone bone = pathConstraint.bones.peek(); for (int ii = updateCache.size - 1; ii >= 0; ii--) { if (updateCache.get(ii) == bone) { updateCache.insert(ii + 1, pathConstraint); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java index 8c6b4c3d7..becad6226 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -210,7 +210,8 @@ public class SkeletonBinary { // Path constraints. for (int i = 0, n = input.readInt(true); i < n; i++) { PathConstraintData data = new PathConstraintData(input.readString()); - data.bone = skeletonData.bones.get(input.readInt(true)); + for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) + data.bones.add(skeletonData.bones.get(input.readInt(true))); data.target = skeletonData.slots.get(input.readInt(true)); data.offsetRotation = input.readFloat(); data.position = input.readFloat(); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index 72afbc720..ed6d1f0b9 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -184,9 +184,12 @@ public class SkeletonJson { for (JsonValue constraintMap = root.getChild("path"); constraintMap != null; constraintMap = constraintMap.next) { PathConstraintData data = new PathConstraintData(constraintMap.getString("name")); - String boneName = constraintMap.getString("bone"); - data.bone = skeletonData.findBone(boneName); - if (data.bone == null) throw new SerializationException("Bone not found: " + boneName); + for (JsonValue boneMap = constraintMap.getChild("bones"); boneMap != null; boneMap = boneMap.next) { + String boneName = boneMap.asString(); + BoneData bone = skeletonData.findBone(boneName); + if (bone == null) throw new SerializationException("Path bone not found: " + boneName); + data.bones.add(bone); + } String targetName = constraintMap.getString("target"); data.target = skeletonData.findSlot(targetName); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java index c50f5f7d3..4c4ef8786 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -31,16 +31,17 @@ package com.esotericsoftware.spine.attachments; +import static com.badlogic.gdx.math.MathUtils.*; + import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.math.MathUtils; -import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.FloatArray; import com.esotericsoftware.spine.Slot; public class PathAttachment extends VertexAttachment { // Nonessential. final Color color = new Color(1, 0.5f, 0, 1); - float[] worldVertices, lengths; + float[] worldVertices, temp; boolean closed, constantSpeed; public PathAttachment (String name) { @@ -51,144 +52,140 @@ public class PathAttachment extends VertexAttachment { super.computeWorldVertices(slot, worldVertices); } - public void computeWorldPosition (Slot slot, float position, Vector2 worldPosition, Vector2 tangent) { - float x1, y1, cx1, cy1, cx2, cy2, x2, y2; + public void computeWorldPositions (Slot slot, float position, FloatArray lengths, FloatArray out, boolean tangents) { + float[] vertices = this.worldVertices; + int verticesLength = worldVerticesLength; + int curves = verticesLength / 6; + if (!constantSpeed) { - int curves = worldVerticesLength / 6; - if (closed) { - position = position % 1; - if (position < 0) position += 1; - } else { - position = MathUtils.clamp(position, 0, 1); - curves--; - } - int curve = position < 1 ? (int)(curves * position) : curves - 1; - position = (position - curve / (float)curves) * curves; - - float[] worldVertices = this.worldVertices; - if (closed && curve == curves - 1) { - super.computeWorldVertices(slot, curves * 6 - 4, 4, worldVertices, 0); - super.computeWorldVertices(slot, 0, 4, worldVertices, 4); - } else - super.computeWorldVertices(slot, curve * 6 + 2, 8, worldVertices, 0); - - x1 = worldVertices[0]; - y1 = worldVertices[1]; - cx1 = worldVertices[2]; - cy1 = worldVertices[3]; - cx2 = worldVertices[4]; - cy2 = worldVertices[5]; - x2 = worldVertices[6]; - y2 = worldVertices[7]; - } else { - float[] worldVertices = this.worldVertices; - int verticesLength; - if (closed) { - verticesLength = worldVerticesLength; - super.computeWorldVertices(slot, 2, verticesLength - 2, worldVertices, 0); - super.computeWorldVertices(slot, 0, 2, worldVertices, verticesLength - 2); - worldVertices[verticesLength] = worldVertices[0]; - worldVertices[verticesLength + 1] = worldVertices[1]; - verticesLength += 2; - } else { - verticesLength = worldVerticesLength - 4; - super.computeWorldVertices(slot, 2, verticesLength, worldVertices, 0); - } - - // Curve lengths. - float[] lengths = this.lengths; - float length = 0; - x1 = worldVertices[0]; - y1 = worldVertices[1]; - float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; - for (int i = 0, w = 2; w < verticesLength; i++, w += 6) { - cx1 = worldVertices[w]; - cy1 = worldVertices[w + 1]; - cx2 = worldVertices[w + 2]; - cy2 = worldVertices[w + 3]; - x2 = worldVertices[w + 4]; - y2 = worldVertices[w + 5]; - tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f; - tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f; - dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f; - dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f; - ddfx = tmpx * 2 + dddfx; - ddfy = tmpy * 2 + dddfy; - dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f; - dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[i] = length; - x1 = x2; - y1 = y2; - } - position *= length; - - if (closed) { - position = position % length; - if (position < 0) position += length; - } else if (position < 0 || position > length) { - // Outside curve. - if (position < 0) { - x1 = worldVertices[0]; - y1 = worldVertices[1]; - cx1 = worldVertices[2] - x1; - cy1 = worldVertices[3] - y1; + for (int i = 0, n = lengths.size; i < n; i++) { + // BOZO - Wrong. Use path length property to give !constantSpeed paths support for oob and multiple bones? + position += lengths.get(i); + if (closed) { + position %= 1; + if (position < 0) position += 1; } else { - x1 = worldVertices[verticesLength - 2]; - y1 = worldVertices[verticesLength - 1]; - cx1 = x1 - worldVertices[verticesLength - 4]; - cy1 = y1 - worldVertices[verticesLength - 3]; - position -= length; + position = clamp(position, 0, 1); + curves--; } - float r = MathUtils.atan2(cy1, cx1); - float cos = MathUtils.cos(r), sin = MathUtils.sin(r); - worldPosition.x = x1 + position * cos; - worldPosition.y = y1 + position * sin; - if (tangent != null) { - tangent.x = worldPosition.x - cos; - tangent.y = worldPosition.y - sin; + int curve = position < 1 ? (int)(curves * position) : curves - 1; + position = (position - curve / (float)curves) * curves; + + if (closed && curve == curves - 1) { + super.computeWorldVertices(slot, curves * 6 - 4, 4, vertices, 0); + super.computeWorldVertices(slot, 0, 4, vertices, 4); + } else + super.computeWorldVertices(slot, curve * 6 + 2, 8, vertices, 0); + + addPoint(vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5], vertices[6], vertices[7], + position, tangents, out); + } + return; + } + + if (closed) { + super.computeWorldVertices(slot, 2, verticesLength - 2, vertices, 0); + super.computeWorldVertices(slot, 0, 2, vertices, verticesLength - 2); + vertices[verticesLength] = vertices[0]; + vertices[verticesLength + 1] = vertices[1]; + verticesLength += 2; + } else { + verticesLength -= 4; + super.computeWorldVertices(slot, 2, verticesLength, vertices, 0); + } + + // Curve lengths. + float[] temp = this.temp; + float pathLength = 0, x1 = vertices[0], y1 = vertices[1], cx1, cy1, cx2, cy2, x2, y2; + float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; + for (int i = 10, w = 2; w < verticesLength; i++, w += 6) { + cx1 = vertices[w]; + cy1 = vertices[w + 1]; + cx2 = vertices[w + 2]; + cy2 = vertices[w + 3]; + x2 = vertices[w + 4]; + y2 = vertices[w + 5]; + tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f; + tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f; + dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f; + dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f; + ddfx = tmpx * 2 + dddfx; + ddfy = tmpy * 2 + dddfy; + dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f; + dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f; + pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); + temp[i] = pathLength; + x1 = x2; + y1 = y2; + } + position *= pathLength; + + for (int i = 0, n = lengths.size; i < n; i++) { + position += lengths.get(i); + float p = position; + + if (closed) { + p %= pathLength; + if (p < 0) p += pathLength; + } else if (p < 0 || p > pathLength) { + // Outside path. + if (p < 0) { + x1 = vertices[0]; + y1 = vertices[1]; + cx1 = vertices[2] - x1; + cy1 = vertices[3] - y1; + } else { + x1 = vertices[verticesLength - 2]; + y1 = vertices[verticesLength - 1]; + cx1 = x1 - vertices[verticesLength - 4]; + cy1 = y1 - vertices[verticesLength - 3]; + p -= pathLength; } - return; + float r = atan2(cy1, cx1); + out.add(x1 + p * cos(r)); + out.add(y1 + p * sin(r)); + if (tangents) out.add(r + PI); + continue; } // Determine curve containing position. int curve; - length = lengths[0]; - if (position <= length) { + float length = temp[10]; + if (p <= length) { curve = 0; - position /= length; + p /= length; } else { - for (curve = 1;; curve++) { - length = lengths[curve]; - if (position <= length) { - float prev = lengths[curve - 1]; - position = (position - prev) / (length - prev); + for (curve = 11;; curve++) { + length = temp[curve]; + if (p <= length) { + float prev = temp[curve - 1]; + p = (p - prev) / (length - prev); break; } } - curve *= 6; + curve = (curve - 10) * 6; } // Curve segment lengths. - x1 = worldVertices[curve]; - y1 = worldVertices[curve + 1]; - cx1 = worldVertices[curve + 2]; - cy1 = worldVertices[curve + 3]; - cx2 = worldVertices[curve + 4]; - cy2 = worldVertices[curve + 5]; - x2 = worldVertices[curve + 6]; - y2 = worldVertices[curve + 7]; + x1 = vertices[curve]; + y1 = vertices[curve + 1]; + cx1 = vertices[curve + 2]; + cy1 = vertices[curve + 3]; + cx2 = vertices[curve + 4]; + cy2 = vertices[curve + 5]; + x2 = vertices[curve + 6]; + y2 = vertices[curve + 7]; tmpx = (x1 - cx1 * 2 + cx2) * 0.03f; tmpy = (y1 - cy1 * 2 + cy2) * 0.03f; dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f; @@ -198,51 +195,54 @@ public class PathAttachment extends VertexAttachment { dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f; dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f; length = (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[0] = length; - for (int i = 1; i < 8; i++) { + // BOZO! - Don't overwrite curve lengths with segment lengths. + temp[0] = length; + for (int ii = 1; ii < 8; ii++) { dfx += ddfx; dfy += ddfy; ddfx += dddfx; ddfy += dddfy; length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[i] = length; + temp[ii] = length; } dfx += ddfx; dfy += ddfy; length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[8] = length; + temp[8] = length; dfx += ddfx + dddfx; dfy += ddfy + dddfy; length += (float)Math.sqrt(dfx * dfx + dfy * dfy); - lengths[9] = length; + temp[9] = length; // Weight by segment length. - position *= length; - length = lengths[0]; - if (position <= length) - position = 0.1f * position / length; + p *= length; + length = temp[0]; + if (p <= length) + p = 0.1f * p / length; else { - for (int i = 1;; i++) { - length = lengths[i]; - if (position <= length) { - float prev = lengths[i - 1]; - position = 0.1f * (i + (position - prev) / (length - prev)); + for (int ii = 1;; ii++) { + length = temp[ii]; + if (p <= length) { + float prev = temp[ii - 1]; + p = 0.1f * (ii + (p - prev) / (length - prev)); break; } } } - } - // Calculate point and tangent. - position += 0.0001f; + addPoint(x1, y1, cx1, cy1, cx2, cy2, x2, y2, p, tangents, out); + } + } + + private void addPoint (float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, float position, + boolean tangents, FloatArray out) { + if (position == 0) position = 0.0001f; float tt = position * position, ttt = tt * position, u = 1 - position, uu = u * u, uuu = uu * u; float ut = u * position, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * position; - worldPosition.x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt; - worldPosition.y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; - if (tangent != null) { - tangent.x = x1 * uu + cx1 * ut * 2 + cx2 * tt; - tangent.y = y1 * uu + cy1 * ut * 2 + cy2 * tt; - } + float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; + out.add(x); + out.add(y); + if (tangents) out.add(atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt))); } public Color getColor () { @@ -267,7 +267,8 @@ public class PathAttachment extends VertexAttachment { public void setWorldVerticesLength (int worldVerticesLength) { super.setWorldVerticesLength(worldVerticesLength); + // BOZO! - Share working memory? worldVertices = new float[Math.max(2, worldVerticesLength + 4)]; - lengths = new float[Math.max(10, worldVerticesLength / 6)]; + temp = new float[10 + worldVerticesLength / 6]; } }