/** * Creates a KeyFrameInterpolator, with {@code frame} as associated {@link #frame()}. * * <p>The {@link #frame()} can be set or changed using {@link #setFrame(Frame)}. * * <p>{@link #interpolationTime()}, {@link #interpolationSpeed()} and {@link * #interpolationPeriod()} are set to their default values. */ public KeyFrameInterpolator(AbstractScene scn, Frame frame) { gScene = scn; keyFrameList = new ArrayList<KeyFrame>(); path = new ArrayList<Frame>(); mainFrame = null; period = 40; interpolationTm = 0.0f; interpolationSpd = 1.0f; interpolationStrt = false; lpInterpolation = false; pathIsValid = false; valuesAreValid = true; currentFrmValid = false; setFrame(frame); currentFrame0 = keyFrameList.listIterator(); currentFrame1 = keyFrameList.listIterator(); currentFrame2 = keyFrameList.listIterator(); currentFrame3 = keyFrameList.listIterator(); interpolationTimerTask = new TimingTask() { public void execute() { update(); } }; gScene.registerTimingTask(interpolationTimerTask); }
/** * Interpolate {@link #frame()} at time {@code time} (expressed in seconds). {@link * #interpolationTime()} is set to {@code time} and {@link #frame()} is set accordingly. * * <p>If you simply want to change {@link #interpolationTime()} but not the {@link #frame()} * state, use {@link #setInterpolationTime(float)} instead. */ public void interpolateAtTime(float time) { this.checkValidity(); setInterpolationTime(time); if ((keyFrameList.isEmpty()) || (frame() == null)) return; if (!valuesAreValid) updateModifiedFrameValues(); updateCurrentKeyFrameForTime(time); if (!splineCacheIsValid) updateSplineCache(); float alpha; float dt = keyFrameList.get(currentFrame2.nextIndex()).time() - keyFrameList.get(currentFrame1.nextIndex()).time(); if (Util.zero(dt)) alpha = 0.0f; else alpha = (time - keyFrameList.get(currentFrame1.nextIndex()).time()) / dt; Vec pos = Vec.add( keyFrameList.get(currentFrame1.nextIndex()).position(), Vec.multiply( Vec.add( keyFrameList.get(currentFrame1.nextIndex()).tgP(), Vec.multiply(Vec.add(pv1, Vec.multiply(pv2, alpha)), alpha)), alpha)); float mag = Util.lerp( keyFrameList.get(currentFrame1.nextIndex()).magnitude(), keyFrameList.get(currentFrame2.nextIndex()).magnitude(), alpha); Rotation q; if (gScene.is3D()) { q = Quat.squad( (Quat) keyFrameList.get(currentFrame1.nextIndex()).orientation(), ((KeyFrame3D) keyFrameList.get(currentFrame1.nextIndex())).tgQ(), ((KeyFrame3D) keyFrameList.get(currentFrame2.nextIndex())).tgQ(), (Quat) keyFrameList.get(currentFrame2.nextIndex()).orientation(), alpha); } else { q = new Rot( Util.lerp( keyFrameList.get(currentFrame1.nextIndex()).orientation().angle(), keyFrameList.get(currentFrame2.nextIndex()).orientation().angle(), (alpha))); } frame().setPositionWithConstraint(pos); frame().setRotationWithConstraint(q); frame().setMagnitude(mag); }
/** * Remove KeyFrame according to {@code index} in the list and {@link #stopInterpolation()} if * {@link #interpolationStarted()}. If {@code index < 0 || index >= keyFr.size()} the call is * silently ignored. */ public void removeKeyFrame(int index) { if (index < 0 || index >= keyFrameList.size()) return; valuesAreValid = false; pathIsValid = false; currentFrmValid = false; if (interpolationStarted()) stopInterpolation(); KeyFrame kf = keyFrameList.remove(index); for (Agent agent : gScene.inputHandler().agents()) agent.removeGrabber(kf.frm); setInterpolationTime(firstTime()); }
/** * Appends a new keyFrame to the path, with its associated {@code time} (in seconds). * * <p>When {@code setRef} is {@code false} the keyFrame is added by value, meaning that the path * will use the current {@code frame} state. * * <p>When {@code setRef} is {@code true} the keyFrame is given as a reference to a Frame, which * will be connected to the KeyFrameInterpolator: when {@code frame} is modified, the * KeyFrameInterpolator path is updated accordingly. This allows for dynamic paths, where keyFrame * can be edited, even during the interpolation. {@code null} frame references are silently * ignored. The {@link #keyFrameTime(int)} has to be monotonously increasing over keyFrames. */ public void addKeyFrame(GenericFrame frame, float time) { if (frame == null) return; if (keyFrameList.isEmpty()) interpolationTm = time; if ((!keyFrameList.isEmpty()) && (keyFrameList.get(keyFrameList.size() - 1).time() > time)) System.out.println("Error in KeyFrameInterpolator.addKeyFrame: time is not monotone"); else { if (gScene.is3D()) keyFrameList.add(new KeyFrame3D(frame, time)); else keyFrameList.add(new KeyFrame2D(frame, time)); } valuesAreValid = false; pathIsValid = false; currentFrmValid = false; resetInterpolation(); }
protected KeyFrameInterpolator(KeyFrameInterpolator otherKFI) { this.gScene = otherKFI.gScene; this.path = new ArrayList<Frame>(); ListIterator<Frame> frameIt = otherKFI.path.listIterator(); while (frameIt.hasNext()) { this.path.add(frameIt.next().get()); } this.setFrame(otherKFI.frame()); this.period = otherKFI.period; this.interpolationTm = otherKFI.interpolationTm; this.interpolationSpd = otherKFI.interpolationSpd; this.interpolationStrt = otherKFI.interpolationStrt; this.lpInterpolation = otherKFI.lpInterpolation; this.pathIsValid = otherKFI.pathIsValid; this.valuesAreValid = otherKFI.valuesAreValid; this.currentFrmValid = otherKFI.currentFrmValid; this.keyFrameList = new ArrayList<KeyFrame>(); for (KeyFrame element : otherKFI.keyFrameList) { KeyFrame kf = (KeyFrame) element.get(); this.keyFrameList.add(kf); } this.currentFrame0 = keyFrameList.listIterator(otherKFI.currentFrame0.nextIndex()); this.currentFrame1 = keyFrameList.listIterator(otherKFI.currentFrame1.nextIndex()); this.currentFrame2 = keyFrameList.listIterator(otherKFI.currentFrame2.nextIndex()); this.currentFrame3 = keyFrameList.listIterator(otherKFI.currentFrame3.nextIndex()); this.interpolationTimerTask = new TimingTask() { public void execute() { update(); } }; gScene.registerTimingTask(interpolationTimerTask); this.invalidateValues(); }
/** Intenal use. Call {@link #checkValidity()} and if path is not valid recomputes it. */ protected void updatePath() { checkValidity(); if (!pathIsValid) { path.clear(); int nbSteps = 30; if (keyFrameList.isEmpty()) return; if (!valuesAreValid) updateModifiedFrameValues(); if (keyFrameList.get(0) == keyFrameList.get(keyFrameList.size() - 1)) // TODO experimenting really path.add( new Frame( keyFrameList.get(0).position(), keyFrameList.get(0).orientation(), keyFrameList.get(0).magnitude())); else { KeyFrame[] kf = new KeyFrame[4]; kf[0] = keyFrameList.get(0); kf[1] = kf[0]; int index = 1; kf[2] = (index < keyFrameList.size()) ? keyFrameList.get(index) : null; index++; kf[3] = (index < keyFrameList.size()) ? keyFrameList.get(index) : null; while (kf[2] != null) { Vec pdiff = Vec.subtract(kf[2].position(), kf[1].position()); Vec pvec1 = Vec.add(Vec.multiply(pdiff, 3.0f), Vec.multiply(kf[1].tgP(), (-2.0f))); pvec1 = Vec.subtract(pvec1, kf[2].tgP()); Vec pvec2 = Vec.add(Vec.multiply(pdiff, (-2.0f)), kf[1].tgP()); pvec2 = Vec.add(pvec2, kf[2].tgP()); for (int step = 0; step < nbSteps; ++step) { Frame frame = new Frame(); float alpha = step / (float) nbSteps; frame.setPosition( Vec.add( kf[1].position(), Vec.multiply( Vec.add( kf[1].tgP(), Vec.multiply(Vec.add(pvec1, Vec.multiply(pvec2, alpha)), alpha)), alpha))); if (gScene.is3D()) { frame.setOrientation( Quat.squad( (Quat) kf[1].orientation(), ((KeyFrame3D) kf[1]).tgQ(), ((KeyFrame3D) kf[2]).tgQ(), (Quat) kf[2].orientation(), alpha)); } else { // linear interpolation float start = kf[1].orientation().angle(); float stop = kf[2].orientation().angle(); frame.setOrientation(new Rot(start + (stop - start) * alpha)); } frame.setMagnitude(Util.lerp(kf[1].magnitude(), kf[2].magnitude(), alpha)); path.add(frame.get()); } // Shift kf[0] = kf[1]; kf[1] = kf[2]; kf[2] = kf[3]; index++; kf[3] = (index < keyFrameList.size()) ? keyFrameList.get(index) : null; } // Add last KeyFrame path.add(new Frame(kf[1].position(), kf[1].orientation(), kf[1].magnitude())); } pathIsValid = true; } }
/** Internal use. Updates the last frame path was updated. Called by {@link #checkValidity()}. */ protected void checked() { lUpdate = gScene.timingHandler().frameCount(); }