diff --git a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java index e668e2fa7cf..c93f90b2c9f 100644 --- a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java +++ b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java @@ -4102,6 +4102,91 @@ public boolean glassRegion(Object graphics, int x, int y, int width, int height, return glassRegion(graphics, x, y, width, height, radius, cornerRadius, sat, scale, offset, refract, specular); } + /// True when `#colorMatrixRegion` works on this graphics context. Ports override. + /// + /// #### Parameters + /// + /// - `graphics`: the native graphics + /// + /// #### Returns + /// + /// false by default + public boolean isColorMatrixRegionSupported(Object graphics) { + return false; + } + + /// Recolours the painted region through a colour matrix; see + /// `com.codename1.ui.Graphics#colorMatrixRegion`. Ports override; the default does nothing. + /// + /// #### Parameters + /// + /// - `graphics`: the native graphics + /// + /// - `x`: region x + /// + /// - `y`: region y + /// + /// - `width`: region width + /// + /// - `height`: region height + /// + /// - `matrix`: 12-float colour matrix + /// + /// - `mask`: optional coverage mask + /// + /// - `cornerRadius`: corner radius, negative for a capsule + /// + /// - `amount`: strength 0..1 + /// + /// #### Returns + /// + /// false when unsupported + public boolean colorMatrixRegion(Object graphics, int x, int y, int width, int height, float[] matrix, + Image mask, float cornerRadius, float amount) { + return false; + } + + /// True when `glassLensRegion` works on this graphics context. + /// + /// #### Parameters + /// + /// - `graphics`: the graphics context + /// + /// #### Returns + /// + /// false by default + public boolean isGlassLensRegionSupported(Object graphics) { + return false; + } + + /// See Graphics.glassLensRegion; the default does nothing. + /// + /// #### Parameters + /// + /// - `graphics`: the graphics context + /// + /// - `x`: lens left edge (translated) + /// + /// - `y`: lens top edge (translated) + /// + /// - `width`: lens width + /// + /// - `height`: lens height + /// + /// - `cornerRadius`: corner radius, negative for a capsule + /// + /// - `optics`: the GlassLensBlend parameters + /// + /// - `amount`: overall strength 0..1 + /// + /// #### Returns + /// + /// false when unsupported + public boolean glassLensRegion(Object graphics, int x, int y, int width, int height, float cornerRadius, + float[] optics, float amount) { + return false; + } + /// In-place iOS 26 selection-drop LENS (magnify + chromatic aberration + /// dark->accent tint over the painted content). Default unsupported; the iOS /// port overrides it. Returns false so callers can fall back (e.g. to a tint). diff --git a/CodenameOne/src/com/codename1/ui/Button.java b/CodenameOne/src/com/codename1/ui/Button.java index 62db4f62651..13b0e08e91c 100644 --- a/CodenameOne/src/com/codename1/ui/Button.java +++ b/CodenameOne/src/com/codename1/ui/Button.java @@ -92,6 +92,14 @@ public class Button extends Label implements ReleasableComponent, ActionSource 0) { // The glass surface's material INTENT comes from a typed, named @@ -3145,6 +3155,32 @@ void internalPaintImpl(Graphics g, boolean paintIntersects) { } } paintComponentBackground(g); + } + + void internalPaintImpl(Graphics g, boolean paintIntersects) { + g.clipRect(getX(), getY(), getWidth(), getHeight()); + // CSS backdrop-filter:blur() -- the "liquid glass" effect. Blur whatever has + // already been painted behind this component (the clip confines it to our + // bounds) BEFORE our own translucent background and content paint on top. This + // runs regardless of opacity, since a glass surface is by definition + // translucent (opaque would be false and skip paintComponentBackground). The + // port blurs the destination region in place; an unsupported port returns + // false and the component simply paints without the blur. + // COST/CACHING POLICY (per paint path): + // * A glass surface only pays when it repaints; static chrome over static + // content costs nothing between repaints. + // * iOS live screen, selection lens: a pure GPU fragment shader on the + // frame's own command buffer -- no sync, no readback, no cache needed. + // * iOS live screen, glass material: the backdrop readback is required + // (the material is a function of the pixels behind the glass), but the + // composed patch is CACHED per rect+params+backdrop-hash in the port + // (METALView glass patch cache), so a repaint over an unchanged + // backdrop skips the colour transform + blur + optics; scrolling + // content under the glass recomposes that frame from the real bytes. + // * Offscreen mutable images (capture tooling) and the desktop simulator + // blur per paint -- capture renders once, and the simulator is not a + // shipping surface. + paintBackgroundLayer(g); if (isScrollable()) { if (refreshTask != null && !InfiniteProgress.isDefaultMaterialDesignMode() && diff --git a/CodenameOne/src/com/codename1/ui/Container.java b/CodenameOne/src/com/codename1/ui/Container.java index 93c35732150..6ee0d502b69 100644 --- a/CodenameOne/src/com/codename1/ui/Container.java +++ b/CodenameOne/src/com/codename1/ui/Container.java @@ -2534,6 +2534,15 @@ void markComponentsToBePaintedInElevatedPane(boolean shouldPaintInElevatedPane) } } + /// Runs the layout-on-paint step of {@link #paint(Graphics)} for a subclass that + /// paints its children itself (the Tabs bar paints each tab twice for the + /// Liquid Glass selection) and so cannot call super.paint(). + final void layoutForPaint() { + if (allowEnableLayoutOnPaint && enableLayoutOnPaint) { + layoutContainer(); + } + } + /// {@inheritDoc} @Override public void paint(Graphics g) { diff --git a/CodenameOne/src/com/codename1/ui/DampedSpring.java b/CodenameOne/src/com/codename1/ui/DampedSpring.java new file mode 100644 index 00000000000..fd0872d8a05 --- /dev/null +++ b/CodenameOne/src/com/codename1/ui/DampedSpring.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Codename One in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.ui; + +import com.codename1.util.MathUtil; + +/// The free response of a damped harmonic oscillator -- the curve a +/// CASpringAnimation follows -- in closed form. +/// +/// `x'' + 2 * zeta * omega * x' + omega^2 * x = 0`, starting at displacement `x0` +/// with velocity `v0`; the spring rests at 0. `omega` is the natural angular +/// frequency (rad/s, `sqrt(stiffness / mass)`) and `zeta` the damping ratio +/// (`damping / (2 * sqrt(stiffness * mass))`): under 1 it oscillates, 1 is +/// critical, over 1 it creeps back without crossing. +/// +/// A spring animation from `a` to `b` is `b + displacement(omega, zeta, a - b, v0, t)`. +final class DampedSpring { + private DampedSpring() { + } + + /// Displacement from rest at time `t` (seconds). + static double displacement(double omega, double zeta, double x0, double v0, double t) { + if (t <= 0) { + return x0; + } + double decay = MathUtil.exp(-zeta * omega * t); + if (zeta < 1) { + double wd = omega * Math.sqrt(1 - zeta * zeta); + double b = (v0 + zeta * omega * x0) / wd; + return decay * (x0 * Math.cos(wd * t) + b * Math.sin(wd * t)); + } + if (zeta == 1) { + return decay * (x0 + (v0 + omega * x0) * t); + } + double s = omega * Math.sqrt(zeta * zeta - 1); + double r1 = -zeta * omega + s; + double r2 = -zeta * omega - s; + double c2 = (v0 - r1 * x0) / (r2 - r1); + double c1 = x0 - c2; + return c1 * MathUtil.exp(r1 * t) + c2 * MathUtil.exp(r2 * t); + } + + /// Velocity at time `t` (units per second). + static double velocity(double omega, double zeta, double x0, double v0, double t) { + if (t <= 0) { + return v0; + } + double decay = MathUtil.exp(-zeta * omega * t); + if (zeta < 1) { + double wd = omega * Math.sqrt(1 - zeta * zeta); + double b = (v0 + zeta * omega * x0) / wd; + double c = Math.cos(wd * t); + double s = Math.sin(wd * t); + return decay * ((b * wd - zeta * omega * x0) * c - (x0 * wd + zeta * omega * b) * s); + } + if (zeta == 1) { + double k = v0 + omega * x0; + return decay * (k - omega * (x0 + k * t)); + } + double s = omega * Math.sqrt(zeta * zeta - 1); + double r1 = -zeta * omega + s; + double r2 = -zeta * omega - s; + double c2 = (v0 - r1 * x0) / (r2 - r1); + double c1 = x0 - c2; + return c1 * r1 * MathUtil.exp(r1 * t) + c2 * r2 * MathUtil.exp(r2 * t); + } +} diff --git a/CodenameOne/src/com/codename1/ui/Graphics.java b/CodenameOne/src/com/codename1/ui/Graphics.java index 9aa8caa9f56..cd018150a10 100644 --- a/CodenameOne/src/com/codename1/ui/Graphics.java +++ b/CodenameOne/src/com/codename1/ui/Graphics.java @@ -1692,6 +1692,107 @@ public boolean lensRegion(int x, int y, int width, int height, float cornerRadiu return impl.lensRegion(nativeGraphics, x + xTranslate, y + yTranslate, width, height, cornerRadius, magnify, aberration, tintColor, tintStrength); } + /// True when this graphics context can run `#glassLensRegion`. + /// + /// #### Returns + /// + /// true if glassLensRegion is supported on this context + public boolean isGlassLensRegionSupported() { + return impl.isGlassLensRegionSupported(nativeGraphics); + } + + /// Lays the optics of a raised Liquid Glass lens over what is already painted: + /// the rim refracts the surface around it inwards (so the edge of whatever the + /// lens floats over bends into its rim), with slight colour dispersion, a thin + /// outline, a rim highlight, shading along its ends, interior brightening and a + /// soft shadow below. The math and the meaning of each parameter are in + /// `com.codename1.ui.plaf.GlassLensBlend`; the lens may sample up to + /// `optics[GlassLensBlend.MAX_SHIFT]` pixels outside its own rectangle and + /// paints up to `GlassLensBlend.margin(optics)` pixels outside it. + /// + /// #### Parameters + /// + /// - `x`: lens left edge + /// + /// - `y`: lens top edge + /// + /// - `width`: lens width + /// + /// - `height`: lens height + /// + /// - `cornerRadius`: corner radius, negative for a capsule + /// + /// - `optics`: `GlassLensBlend.COUNT` parameters, lengths in pixels + /// + /// - `amount`: overall strength 0..1 (0 leaves the surface untouched) + /// + /// #### Returns + /// + /// false when the port cannot do this (nothing is painted then) + public boolean glassLensRegion(int x, int y, int width, int height, float cornerRadius, float[] optics, + float amount) { + if (width <= 0 || height <= 0 || amount <= 0) { + return true; + } + return impl.glassLensRegion(nativeGraphics, x + xTranslate, y + yTranslate, width, height, cornerRadius, + optics, amount); + } + + /// True when this graphics context can run `#colorMatrixRegion`. + /// + /// #### Returns + /// + /// true if colorMatrixRegion is supported on this context + public boolean isColorMatrixRegionSupported() { + return impl.isColorMatrixRegionSupported(nativeGraphics); + } + + /// Recolours what is ALREADY painted in a region through a colour matrix: every + /// pixel `p` becomes `mix(p, clamp(M * p + offset), k)`. This is how iOS draws + /// vibrant content over glass -- a vibrant glyph's colour is a function of what + /// lies behind it, see com.codename1.ui.plaf.VibrancyMatrix -- and how a glass + /// selection platter derives its material from the glass it sits on. + /// + /// The coverage `k` is `amount`, times the anti-aliased coverage of a rounded + /// rectangle filling the region when `cornerRadius` is not zero (negative makes + /// a capsule), times the alpha of `mask` when one is given. The mask covers the + /// region pixel for pixel, so painting glyphs into a transparent mutable image + /// and passing it here draws them the way a template image is drawn natively. + /// + /// The region is in this context's coordinates; the current transform is not + /// applied. com.codename1.ui.plaf.ColorMatrixBlend is the reference + /// implementation of the per-pixel operation. + /// + /// #### Parameters + /// + /// - `x`: region x + /// + /// - `y`: region y + /// + /// - `width`: region width + /// + /// - `height`: region height + /// + /// - `matrix`: 12 floats: rows r, g, b of `[r, g, b, offset]`, in 0..1 units + /// + /// - `mask`: optional mask the size of the region, or null + /// + /// - `cornerRadius`: 0 for the whole rectangle, a radius, or negative for a capsule + /// + /// - `amount`: overall strength 0..1 + /// + /// #### Returns + /// + /// false when the port cannot do this (nothing is painted then) + public boolean colorMatrixRegion(int x, int y, int width, int height, float[] matrix, Image mask, + float cornerRadius, float amount) { + if (width <= 0 || height <= 0 || amount <= 0) { + return true; + } + return impl.colorMatrixRegion(nativeGraphics, x + xTranslate, y + yTranslate, width, height, matrix, mask, + cornerRadius, amount); + } + /// Fills a rectangle with an optionally translucent fill color /// /// #### Parameters diff --git a/CodenameOne/src/com/codename1/ui/TabGlassGesture.java b/CodenameOne/src/com/codename1/ui/TabGlassGesture.java new file mode 100644 index 00000000000..388e8324ba2 --- /dev/null +++ b/CodenameOne/src/com/codename1/ui/TabGlassGesture.java @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Codename One in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.ui; + +/// The parts of the iOS 27 tab selection that depend on the GESTURE rather than +/// on a tap: holding the press, scrubbing the lens along the bar with the finger, +/// and letting go of a scrub. Measured like TabGlassMotion, with the same probe +/// (scripts/fidelity-app/ios-native-ref/motion-probe), from held presses and drags +/// driven through XCUITest: +/// +/// - While the finger is down the lift holds, and the whole bar grows toward +/// +14.6 pt of width on a spring (omega 17.6 rad/s, zeta 0.59, 20 ms behind the +/// touch); lifting the finger springs it back. A quick tap is the same spring +/// released early. +/// - Dragging along the bar makes the lens FOLLOW the finger on a stiffer spring +/// (omega 32 rad/s, zeta 0.90, 20 ms behind), within the first and last tab. +/// - Releasing a scrub selects the tab nearest the FINGER (not the lens, which +/// can be far behind after a flick; a tie keeps the current tab). The lens +/// travels there on the tap's own spring, starting from the position and +/// velocity the scrub left it with. +/// - While scrubbing, the lens deforms with its own motion: a kernel over its +/// recent speed and acceleration (the FIR_ tables). +/// - Every release ends in a small wobble that starts WOBBLE_START_S after the +/// finger lifts -- whether it was a tap, a hold or a scrub. +/// +/// Pure data and arithmetic, evaluated from the recorded touch events at any +/// time, so a frame is reproducible: TabGlassGestureTest pins it to the capture. +final class TabGlassGesture { + /// Bar press spring (whole-bar width growth while pressed). + static final float BAR_OMEGA = 17.63f; + static final float BAR_ZETA = 0.593f; + static final float BAR_PRESS_PT = 14.58f; + /// Scrub follow spring. + static final float FOLLOW_OMEGA = 32.0f; + static final float FOLLOW_ZETA = 0.903f; + /// Both springs, and the settle, run this far behind the touch. + static final float TOUCH_LAG_S = 0.02f; + /// The release wobble starts this long after touch-up. + static final float WOBBLE_START_S = 0.9f; + /// The tap tables start at the last frame at rest, one frame after the + /// touch; a press is evaluated on them this much later. + static final float TEMPLATE_DELAY_S = 0.018f; + /// After a scrub the follow spring keeps the lens until the release reaches + /// it, TOUCH_LAG_S after touch-up; from there the lens travels to the chosen + /// tab on the tap's own travel spring (TabGlassMotion.POSITION_OMEGA, + /// POSITION_ZETA), started from the position and velocity the scrub left it + /// with. Fitted to the scrub releases alone that spring comes out at + /// 15.5 rad/s and zeta 0.847. + /// + /// Long enough for that spring to come within 0.1 pt of the tab from a + /// release 300 pt away (e^(-zeta omega t) = 1/3000). + static final float SETTLE_DURATION_S = 0.6f; + /// Integration step of the follow spring. + private static final float STEP_S = 1f / 240f; + + // ---- generated tables (tabmotion.py gesture) ---- + /// Release wobble, lens scaleX - 1, from WOBBLE_START_S after touch-up. + static final float[] WOBBLE_X = { + 0.00000f, 0.00000f, 0.00000f, 0.00361f, 0.01156f, 0.01577f, 0.01579f, + 0.01397f, 0.01188f, 0.00980f, 0.00795f, 0.00580f, 0.00341f, 0.00063f, + -0.00225f, -0.00522f, -0.00795f, -0.01039f, -0.01244f, -0.01396f, -0.01503f, + -0.01553f, -0.01552f, -0.01517f, -0.01453f, -0.01358f, -0.01263f, -0.01144f, + -0.01016f, -0.00876f, -0.00743f, -0.00614f, -0.00479f, -0.00367f, -0.00264f, + -0.00183f, -0.00113f, -0.00005f + }; + + /// Release wobble, lens scaleY - 1. + static final float[] WOBBLE_Y = { + 0.00000f, 0.00000f, 0.00000f, -0.00473f, -0.01511f, -0.02060f, -0.02037f, + -0.01772f, -0.01475f, -0.01190f, -0.00938f, -0.00653f, -0.00337f, 0.00030f, + 0.00424f, 0.00825f, 0.01199f, 0.01525f, 0.01793f, 0.02006f, 0.02140f, + 0.02202f, 0.02193f, 0.02140f, 0.02049f, 0.01915f, 0.01775f, 0.01604f, + 0.01418f, 0.01233f, 0.01044f, 0.00863f, 0.00672f, 0.00510f, 0.00375f, + 0.00255f, 0.00164f, 0.00007f + }; + + /// Release wobble, lens centre offset in points (always towards +x). + static final float[] WOBBLE_TX = { + 0.0000f, 0.0000f, 0.0000f, 0.1684f, 0.5498f, 0.7540f, 0.8391f, + 0.8995f, 0.9182f, 0.8944f, 0.8411f, 0.7892f, 0.7481f, 0.7238f, + 0.7200f, 0.7283f, 0.7467f, 0.7583f, 0.7764f, 0.7800f, 0.7717f, + 0.7533f, 0.7252f, 0.6868f, 0.6394f, 0.5891f, 0.5339f, 0.4642f, + 0.3859f, 0.3211f, 0.2607f, 0.2010f, 0.1529f, 0.1125f, 0.0813f, + 0.0608f, 0.0408f, 0.0017f + }; + + // The deformation kernels below are free 60-tap fits. Folded onto velocity + // alone each channel is one smooth kernel, scaleY's that of scaleX inverted and + // about 1.5 times larger. Two damped modes shared by all channels (omega_n 6.0 + // and 15.5 rad/s, zeta 0.38 and 0.42) match their RMS but not their worst frame. + /// Scrub deformation kernels: scaleX - 1 per |v|/1000 pt/s, per frame of delay. + static final float[] FIR_SX_SPEED = { + 0.005386f, 0.012912f, -0.028749f, -0.016629f, -0.033210f, -0.024031f, -0.020600f, + -0.012343f, -0.002749f, 0.004030f, 0.009242f, 0.008953f, 0.012166f, 0.012893f, + 0.012872f, 0.010809f, 0.008817f, 0.007096f, 0.001731f, -0.004369f, -0.012404f, + -0.019141f, -0.025249f, -0.022780f, -0.017689f, -0.005807f, 0.003809f, 0.012035f, + 0.016827f, 0.016782f, 0.031737f, 0.031957f, 0.015308f, 0.007353f, 0.003907f, + 0.015685f, 0.024954f, 0.027492f, 0.020445f, 0.014757f, 0.003888f, 0.002255f, + -0.002126f, -0.000802f, -0.007250f, -0.002595f, -0.007373f, -0.000028f, -0.001768f, + 0.000206f, -0.005118f, -0.013183f, -0.010548f, -0.015463f, -0.008038f, -0.011520f, + -0.009560f, -0.010342f, -0.011173f, -0.008117f + }; + + /// scaleX - 1 per (a * sign(v))/10000 pt/s^2. + static final float[] FIR_SX_ACCEL = { + 0.003827f, -0.006486f, 0.002025f, 0.002359f, 0.010644f, 0.015898f, 0.024219f, + 0.029469f, 0.035889f, 0.038609f, 0.042037f, 0.043221f, 0.044077f, 0.043458f, + 0.041961f, 0.040211f, 0.037419f, 0.035090f, 0.032585f, 0.030200f, 0.029762f, + 0.029196f, 0.031131f, 0.032903f, 0.034329f, 0.033706f, 0.032755f, 0.028075f, + 0.025746f, 0.018850f, 0.017224f, 0.005670f, 0.003474f, -0.000335f, -0.001967f, + -0.001836f, -0.009062f, -0.010145f, -0.020024f, -0.016717f, -0.025467f, -0.018039f, + -0.025212f, -0.017825f, -0.022364f, -0.018854f, -0.019743f, -0.022799f, -0.015406f, + -0.021934f, -0.006911f, -0.022476f, -0.013041f, -0.008297f, -0.012494f, -0.001811f, + -0.013308f, 0.002084f, -0.007376f, 0.005347f + }; + + /// scaleY - 1 per |v|/1000 pt/s. + static final float[] FIR_SY_SPEED = { + 0.093688f, 0.096136f, 0.128985f, 0.107764f, 0.118752f, 0.093075f, 0.079969f, + 0.053671f, 0.030938f, 0.007693f, -0.010222f, -0.022671f, -0.038274f, -0.050198f, + -0.059146f, -0.063568f, -0.063998f, -0.064000f, -0.055460f, -0.044408f, -0.029172f, + -0.016135f, -0.003080f, -0.003318f, -0.008979f, -0.022428f, -0.034123f, -0.042198f, + -0.044854f, -0.039248f, -0.056633f, -0.054080f, -0.027495f, -0.015488f, -0.010086f, + -0.026753f, -0.038404f, -0.040022f, -0.029777f, -0.022289f, -0.017102f, -0.014955f, + -0.000277f, -0.003336f, 0.012116f, 0.003973f, 0.014495f, 0.003322f, 0.007905f, + 0.004756f, 0.013255f, 0.026090f, 0.021316f, 0.029225f, 0.016846f, 0.022248f, + 0.019017f, 0.019924f, 0.022059f, 0.015518f + }; + + /// scaleY - 1 per (a * sign(v))/10000 pt/s^2. + static final float[] FIR_SY_ACCEL = { + -0.005353f, -0.023268f, -0.043439f, -0.063703f, -0.085980f, -0.106162f, -0.127102f, + -0.142632f, -0.157555f, -0.164858f, -0.171349f, -0.171984f, -0.170825f, -0.165800f, + -0.157269f, -0.148163f, -0.135737f, -0.124395f, -0.112132f, -0.100828f, -0.092703f, + -0.084886f, -0.081606f, -0.077825f, -0.074960f, -0.068167f, -0.062789f, -0.050827f, + -0.044336f, -0.030841f, -0.026838f, -0.008056f, -0.004023f, 0.002576f, 0.005864f, + 0.006493f, 0.017673f, 0.019618f, 0.033694f, 0.029886f, 0.041609f, 0.036438f, + 0.044942f, 0.037809f, 0.042021f, 0.039654f, 0.037349f, 0.044313f, 0.030477f, + 0.041752f, 0.014996f, 0.040840f, 0.024000f, 0.015865f, 0.022673f, 0.003961f, + 0.023268f, -0.004889f, 0.012982f, -0.011256f + }; + + /// Centre offset (pt) per v/1000 pt/s. + static final float[] FIR_TX_VEL = { + -0.21568f, -0.69702f, -0.27793f, -0.68219f, -0.39789f, -0.68948f, -0.52792f, + -0.67577f, -0.58201f, -0.59134f, -0.52522f, -0.42958f, -0.37108f, -0.23407f, + -0.17440f, -0.04827f, 0.01979f, 0.09806f, 0.17122f, 0.20404f, 0.26861f, + 0.26498f, 0.31941f, 0.28912f, 0.34208f, 0.29301f, 0.33705f, 0.29164f, + 0.31769f, 0.27556f, 0.28484f, 0.24653f, 0.24153f, 0.20891f, 0.19330f, + 0.16887f, 0.14559f, 0.13959f, 0.10056f, 0.11453f, 0.06234f, 0.09931f, + 0.02636f, 0.08559f, -0.00115f, 0.08183f, -0.00969f, 0.06894f, -0.02416f, + 0.05977f, -0.05126f, 0.05368f, -0.05600f, 0.06126f, -0.01526f, 0.07498f, + -0.05010f, 0.05472f, -0.06587f, 0.16911f + }; + + /// Centre offset (pt) per a/10000 pt/s^2. + static final float[] FIR_TX_ACCEL = { + -0.13426f, 0.18676f, -0.04449f, 0.35988f, 0.02187f, 0.39009f, -0.04113f, + 0.16225f, -0.25327f, -0.17034f, -0.48529f, -0.46244f, -0.58653f, -0.59003f, + -0.55739f, -0.58258f, -0.43902f, -0.45427f, -0.31792f, -0.29220f, -0.18283f, + -0.15240f, -0.07240f, -0.06801f, -0.01168f, 0.01510f, 0.00410f, 0.05807f, + 0.04825f, 0.09856f, 0.08710f, 0.12995f, 0.11285f, 0.14468f, 0.12012f, + 0.14313f, 0.08782f, 0.13510f, 0.07518f, 0.11465f, 0.04567f, 0.10793f, + 0.04117f, 0.08256f, 0.01128f, 0.02560f, 0.03866f, 0.04343f, 0.02751f, + 0.08128f, 0.01827f, 0.01423f, -0.02272f, -0.12222f, -0.04116f, 0.10452f, + 0.06077f, 0.04731f, -0.34318f, 0.32561f + }; + + // ---- end of generated tables ---- + + // Touch events, in seconds after the press. + private final float fromPt; + private final float toPt; + private float upS = -1; + private float scrubStartS = -1; + private float scrubX0; + private float scrubV0; + private float minPt; + private float maxPt; + private float settleToPt; + private float[] fingerS = new float[32]; + private float[] fingerPt = new float[32]; + private int fingerCount; + + /// A press that starts the lens from `fromPt` towards the tab at `toPt` + /// (lens centres in points; equal for a press on the selected tab). + TabGlassGesture(float fromPt, float toPt) { + this.fromPt = fromPt; + this.toPt = toPt; + } + + float getFromPt() { + return fromPt; + } + + float getToPt() { + return toPt; + } + + float getUpS() { + return upS; + } + + boolean isScrubbing() { + return scrubStartS >= 0; + } + + /// Touch-up, `s` seconds after the press. + void up(float s) { + if (upS < 0) { + upS = s; + } + } + + /// Turns the press into a scrub at `s`: the lens leaves its tap motion from + /// where it is now and follows the finger, kept within `[minPt, maxPt]`. + void startScrub(float s, float minPt, float maxPt) { + if (scrubStartS >= 0) { + return; + } + float dt = 1f / TabGlassMotion.SAMPLE_HZ; + scrubX0 = tapCentre(s); + scrubV0 = (tapCentre(s + dt / 2) - tapCentre(s - dt / 2)) / dt; + this.minPt = Math.min(minPt, maxPt); + this.maxPt = Math.max(minPt, maxPt); + scrubStartS = s; + settleToPt = scrubX0; + } + + /// The finger's position in lens-centre points at `s` seconds. + void finger(float s, float pt) { + if (fingerCount == fingerS.length) { + float[] ns = new float[fingerCount * 2]; + float[] np = new float[fingerCount * 2]; + System.arraycopy(fingerS, 0, ns, 0, fingerCount); + System.arraycopy(fingerPt, 0, np, 0, fingerCount); + fingerS = ns; + fingerPt = np; + } + fingerS[fingerCount] = s; + fingerPt[fingerCount] = pt; + fingerCount++; + } + + /// Where a finished scrub goes (the centre of the tab chosen at release). + void settleTo(float pt) { + settleToPt = pt; + } + + /// The finger at `s` (the last sample at or before it), or NaN before any. + float fingerAt(float s) { + float v = Float.NaN; + for (int i = 0; i < fingerCount && fingerS[i] <= s; i++) { + v = fingerPt[i]; + } + return v; + } + + private float clampPt(float v) { + return v < minPt ? minPt : (v > maxPt ? maxPt : v); + } + + /// The lens centre on the tap motion (no scrub). + private float tapCentre(float s) { + float t = s - TEMPLATE_DELAY_S; + return fromPt + (toPt - fromPt) * TabGlassMotion.position(t < 0 ? 0 : t); + } + + /// The scrub lens centre at 60 Hz, from the scrub start to `endS`: the follow + /// spring until the release reaches the lens (TOUCH_LAG_S after the finger + /// lifts), then the settle spring from wherever that left it. + private float[] scrubTrack(float endS) { + int frames = Math.max(1, (int) Math.ceil((endS - scrubStartS) * TabGlassMotion.SAMPLE_HZ) + 2); + float[] out = new float[frames]; + float x = scrubX0; + float v = scrubV0; + float s = scrubStartS; + float handoff = upS < 0 ? Float.MAX_VALUE : upS + TOUCH_LAG_S; + boolean settling = false; + float w = FOLLOW_OMEGA; + float z = FOLLOW_ZETA; + int substeps = Math.round(1f / (TabGlassMotion.SAMPLE_HZ * STEP_S)); + for (int i = 0; i < frames; i++) { + float fs = scrubStartS + i / (float) TabGlassMotion.SAMPLE_HZ; + if (fs >= handoff) { + if (!settling) { + // Finish the follow spring up to the hand-off instant. + while (s < handoff) { + float a = w * w * (followTarget(s) - x) - 2 * z * w * v; + v += a * STEP_S; + x += v * STEP_S; + s += STEP_S; + } + settling = true; + } + out[i] = settleToPt + (float) DampedSpring.displacement(TabGlassMotion.POSITION_OMEGA, + TabGlassMotion.POSITION_ZETA, x - settleToPt, v, fs - handoff); + continue; + } + for (int k = 0; k < substeps && s < fs; k++) { + float a = w * w * (followTarget(s) - x) - 2 * z * w * v; + v += a * STEP_S; + x += v * STEP_S; + s += STEP_S; + } + out[i] = x; + } + return out; + } + + /// What the follow spring pulls towards at `s`: the finger, one touch lag + /// late and kept within the tabs (it stays where the finger lifted). + private float followTarget(float s) { + float f = fingerAt(s - TOUCH_LAG_S); + return f != f ? scrubX0 : clampPt(f); + } + + /// A 60 Hz table that is zero outside itself. + private static float table(float[] a, float t) { + if (t < 0) { + return 0; + } + float fx = t * TabGlassMotion.SAMPLE_HZ; + int i = (int) fx; + if (i >= a.length - 1) { + return i == a.length - 1 ? a[i] * (1 - (fx - i)) : 0; + } + float f = fx - i; + return a[i] * (1 - f) + a[i + 1] * f; + } + + /// The release wobble `sinceUpS` seconds after touch-up. + static float wobble(float[] table, float sinceUpS) { + return table(table, sinceUpS - WOBBLE_START_S); + } + + /// The whole bar's width growth in points `s` seconds after the press, for a + /// touch-up at `upS` (negative while still down). + static float barGrowPt(float s, float upS) { + float g = step(s - TOUCH_LAG_S); + if (upS >= 0) { + g -= step(s - upS - TOUCH_LAG_S); + } + return BAR_PRESS_PT * g; + } + + /// Unit step response of the bar spring. + private static float step(float t) { + if (t <= 0) { + return 0; + } + return (float) (1 + DampedSpring.displacement(BAR_OMEGA, BAR_ZETA, -1, 0, t)); + } + + /// When the lift is released, in seconds after the press, or a large value + /// while the finger is down. + float releaseS(float nowS) { + if (upS < 0) { + return Float.MAX_VALUE; + } + if (!isScrubbing()) { + // The release reaches the lens one touch lag after the finger lifts. + return TabGlassMotion.releaseForPress(toPt - fromPt, upS + TOUCH_LAG_S - TEMPLATE_DELAY_S) + + TEMPLATE_DELAY_S; + } + // A scrub releases once the settling lens is within RELEASE_PT of its tab. + float end = Math.max(nowS, upS) + 1f; + float[] track = scrubTrack(end); + for (int i = 0; i < track.length; i++) { + float fs = scrubStartS + i / (float) TabGlassMotion.SAMPLE_HZ; + if (fs >= upS && Math.abs(settleToPt - track[i]) < TabGlassMotion.RELEASE_PT) { + return Math.max(fs - 1f / TabGlassMotion.SAMPLE_HZ, upS + TOUCH_LAG_S); + } + } + return end; + } + + /// True once everything this gesture started has played out at `s`. + boolean isFinished(float s) { + if (upS < 0) { + return false; + } + float rel = releaseS(s); + float end = TabGlassMotion.heldDurationS(upS, rel) + TEMPLATE_DELAY_S; + if (isScrubbing()) { + end = Math.max(end, upS + TOUCH_LAG_S + SETTLE_DURATION_S + + FIR_SX_SPEED.length / (float) TabGlassMotion.SAMPLE_HZ); + } + return s >= end; + } + + /// The frame `s` seconds after the press. `centreOut[0]` receives the lens + /// centre in points. + TabGlassMotion at(float s, float[] centreOut) { + float rel = releaseS(s); + float d = TEMPLATE_DELAY_S; + // While the finger is down releaseS answers Float.MAX_VALUE; pass it on + // untouched. Test the state, not the float: ParparVM writes the constant as + // a decimal a C float never equals, so an == on it is always false on iOS. + TabGlassMotion m = TabGlassMotion.held(s - d, toPt - fromPt, upS < 0 ? -1 : upS - d, + upS < 0 ? rel : rel - d); + m.barGrowPt = barGrowPt(s, upS); + if (!isScrubbing() || s < scrubStartS) { + centreOut[0] = tapCentre(s); + return m; + } + float[] track = scrubTrack(s + 2f / TabGlassMotion.SAMPLE_HZ); + float fx = (s - scrubStartS) * TabGlassMotion.SAMPLE_HZ; + int n = Math.min((int) fx, track.length - 2); + float f = fx - n; + centreOut[0] = track[n] * (1 - f) + track[n + 1] * f; + // Deformation from the lens's own motion, on top of the press's. + float dx0 = deform(track, n, FIR_SX_SPEED, FIR_SX_ACCEL, true); + float dx1 = deform(track, n + 1, FIR_SX_SPEED, FIR_SX_ACCEL, true); + float dy0 = deform(track, n, FIR_SY_SPEED, FIR_SY_ACCEL, true); + float dy1 = deform(track, n + 1, FIR_SY_SPEED, FIR_SY_ACCEL, true); + float dt0 = deform(track, n, FIR_TX_VEL, FIR_TX_ACCEL, false); + float dt1 = deform(track, n + 1, FIR_TX_VEL, FIR_TX_ACCEL, false); + m.scaleX += dx0 * (1 - f) + dx1 * f; + m.scaleY += dy0 * (1 - f) + dy1 * f; + m.leadPt += dt0 * (1 - f) + dt1 * f; + return m; + } + + /// One output of a deformation kernel at frame `n` of `track`. + private static float deform(float[] track, int n, float[] speedK, float[] accelK, boolean unsigned) { + float sum = 0; + int taps = Math.max(speedK.length, accelK.length); + for (int k = 0; k < taps && n - k >= 0; k++) { + int i = n - k; + float v = velocity(track, i); + float a = (velocity(track, i + 1) - velocity(track, i - 1)) * TabGlassMotion.SAMPLE_HZ / 2f; + if (i == 0) { + a = (velocity(track, 1) - velocity(track, 0)) * TabGlassMotion.SAMPLE_HZ; + } + float sv = v < 0 ? -1f : (v > 0 ? 1f : 0f); + float in1 = unsigned ? Math.abs(v) / 1000f : v / 1000f; + float in2 = unsigned ? a * sv / 10000f : a / 10000f; + if (k < speedK.length) { + sum += speedK[k] * in1; + } + if (k < accelK.length) { + sum += accelK[k] * in2; + } + } + return sum; + } + + /// Central-difference velocity of `track` at frame `i`, in points per second. + private static float velocity(float[] track, int i) { + int last = track.length - 1; + if (last < 1) { + return 0; + } + if (i <= 0) { + return (track[1] - track[0]) * TabGlassMotion.SAMPLE_HZ; + } + if (i >= last) { + return (track[last] - track[last - 1]) * TabGlassMotion.SAMPLE_HZ; + } + return (track[i + 1] - track[i - 1]) * TabGlassMotion.SAMPLE_HZ / 2f; + } +} diff --git a/CodenameOne/src/com/codename1/ui/TabGlassMotion.java b/CodenameOne/src/com/codename1/ui/TabGlassMotion.java new file mode 100644 index 00000000000..09a17f0977d --- /dev/null +++ b/CodenameOne/src/com/codename1/ui/TabGlassMotion.java @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Codename One in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.ui; + +/// The iOS 27 Liquid Glass tab selection motion, as UIKit plays it. +/// +/// Every number in this class was MEASURED, not tuned by eye. An instrumented +/// native app (scripts/fidelity-app/ios-native-ref/motion-probe) hosts a real +/// `UITabBarController` on the iOS 27 simulator, drives genuine taps through +/// XCUITest, and logs the presentation geometry of every layer under the tab bar +/// on every display frame. UIKit attaches no `CAAnimation` for this motion -- it +/// writes the layer values itself each frame -- so the per-frame log IS the curve. +/// +/// Most channels turned out to be springs UIKit evaluates on the display's vsync +/// clock, and are written here in closed form (DampedSpring): the lens travel is a +/// spring of duration 0.4 s and bounce 0.15, the lift and the platter critically +/// damped springs of 0.25 s and 0.4 s, the touch glow critically damped springs of +/// 0.1 s and 0.5 s. They reproduce the capture to its noise, which the averaged +/// tables they replaced did not: every captured frame renders at exactly +/// `start + n / 60` s (to 0.03 ms), so the per-frame log times, which carry the +/// main thread's jitter, are not the times the curves were evaluated at. The +/// deformation and the whole-bar pulse have no closed form that matched the +/// capture as well as a table, and stay 60 Hz tables resampled from the moment the +/// lens starts to move. `scripts/fidelity-app/tools/tab-motion/` regenerates the +/// tables and checks the closed forms against a fresh capture. +/// +/// What UIKit does, channel by channel (lens = the selection capsule): +/// +/// - The lens centre travels on one normalized curve for every jump distance: +/// at the target after ~0.33 s, a 0.6% overshoot at ~0.38 s; UIKit ends it once +/// less than SETTLE_PT remains past the overshoot. +/// - On touch the lens LIFTS: its bounds grow by 16 pt in both directions, the +/// grey platter under it fades out (it becomes clear glass) and the accent copy +/// of the tab content seen through it is magnified by up to 16%. The lift is +/// released once the lens is within 3.5 pt of its target, and falls back on the +/// same spring while the platter springs back from wherever it was. +/// - The lens deforms: a distance-independent stretch pulse (x up to 1.12, y down +/// to 0.84) leading the travel, then a squash on arrival whose depth is linear +/// in the distance travelled (x 0.96 after 64 pt, 0.85 after 258 pt), and a +/// small second wobble about a second in. +/// - The whole bar -- glass, content and lens together -- scales about its centre +/// by a pulse that adds ~8.7 pt to its width at 0.13 s and undershoots slightly. +/// - A white glow brightens under the finger, then, from the touch-up, expands to +/// four times its size across the bar and fades. +/// +/// Deformation is `E(t) + d * S(t)` for travel distance `d`; that fits every +/// captured jump from 64 to 258 pt to within 0.01 of scale. +/// +/// Pure data, no Display or Graphics dependency: `Tabs` resolves the geometry and +/// converts points to pixels, and TabGlassMotionTest pins the model against the +/// native capture. +final class TabGlassMotion { + /// Sample rate of the tables below. + static final int SAMPLE_HZ = 60; + /// Height of the native floating tab bar; points convert to pixels by the + /// ratio of the theme's pill height to this. + static final float BAR_HEIGHT_PT = 62f; + /// How much the lens bounds grow, in both directions, at full lift. + static final float LIFT_PT = 16f; + /// Magnification of the accent content under the lens at full lift. + static final float CONTENT_MAGNIFICATION = 0.16f; + /// Remaining travel below which the lift is released. + static final float RELEASE_PT = 3.5f; + /// UIKit snaps the rising lift to 1 once it passes this value. + static final float LIFT_SNAP = 0.975f; + /// Diameter of the touch glow before it expands. + static final float GLOW_DIAMETER_PT = 93f; + + // ---- closed forms (checked against the capture by tabmotion.py check) ---- + /// The springs start together, this long after the tables' t = 0 (the last + /// frame at rest). + static final float START_S = 0.00226f; + /// Lens travel: duration 0.4 s, bounce 0.15 (omega = 2 pi / duration, + /// zeta = 1 - bounce). + static final double POSITION_OMEGA = 2 * Math.PI / 0.4; + static final double POSITION_ZETA = 0.85; + /// Past its overshoot, UIKit ends the travel once less than this remains + /// (the capture puts it between 0.194 and 0.197 pt). + static final float SETTLE_PT = 0.195f; + /// Lift: critically damped, 0.25 s, both the rise from the press and the fall + /// from full lift at the release. + private static final double LIFT_OMEGA = 2 * Math.PI / 0.25; + /// The lift fall ends (snaps to 0) this long after the release: between the + /// 13th and 14th frame in the capture, so half way. + static final float LIFT_FALL_S = 13.5f / SAMPLE_HZ; + /// Grey platter: 1 - rise until the release, then critically damped, 0.4 s, + /// back to 1 from its opacity and velocity at the release. + private static final double PLATTER_OMEGA = 2 * Math.PI / 0.4; + /// The platter return ends (snaps to 1) this long after the release: between + /// the 28th and 29th frame in the capture. + static final float PLATTER_RETURN_S = 28.5f / SAMPLE_HZ; + /// Touch glow layer: critically damped, 0.1 s, towards GLOW_PEAK from START_S; + /// at GLOW_UP_S (the tap's touch-up reaching the glow) it turns back to 0 on a + /// critically damped 0.5 s spring, from where it was and how fast it moved. + private static final double GLOW_RISE_OMEGA = 2 * Math.PI / 0.1; + private static final double GLOW_FALL_OMEGA = 2 * Math.PI / 0.5; + private static final float GLOW_PEAK = 0.8445f; + static final float GLOW_UP_S = 4f / SAMPLE_HZ; + /// The glow's mask opacity is this fraction of its layer opacity; the glow is + /// drawn with their product. + private static final float GLOW_MASK = 0.33675f; + /// Each glow opacity is hidden (0) once it has fallen under this. + private static final float GLOW_HIDE = 0.005f; + /// From GLOW_UP_S the glow grows by this multiple of GLOW_DIAMETER_PT, on the + /// glow's 0.5 s spring. + private static final float GLOW_GROWTH = 3f; + + /// Lens scaleX - 1, distance-independent part. + private static final float[] STRETCH_X = { + 0.0000f, 0.0000f, 0.0055f, 0.0184f, 0.0384f, 0.0604f, 0.0837f, 0.1028f, 0.1135f, + 0.1140f, 0.1093f, 0.1043f, 0.0986f, 0.0931f, 0.0848f, 0.0761f, 0.0659f, 0.0549f, + 0.0437f, 0.0328f, 0.0227f, 0.0134f, 0.0057f, -0.0007f, -0.0056f, -0.0098f, -0.0113f, + -0.0124f, -0.0126f, -0.0120f, -0.0109f, -0.0094f, -0.0077f, -0.0061f, -0.0045f, + -0.0030f, -0.0010f, 0.0005f, 0.0024f, 0.0035f, 0.0045f, 0.0054f, 0.0057f, 0.0055f, + 0.0054f, 0.0048f, 0.0042f, 0.0035f, 0.0021f, 0.0016f, 0.0020f, 0.0008f, 0.0003f, + -0.0001f, -0.0006f, -0.0009f, -0.0008f, -0.0006f, -0.0004f, -0.0002f, 0.0022f, 0.0067f, + 0.0098f, 0.0093f, 0.0077f, 0.0059f, 0.0044f, 0.0029f, 0.0015f, -0.0001f, -0.0021f, + -0.0039f, -0.0058f, -0.0077f, -0.0093f, -0.0106f, -0.0116f, -0.0122f, -0.0124f, + -0.0123f, -0.0119f, -0.0114f, -0.0107f, -0.0098f, -0.0089f, -0.0080f, -0.0070f, + -0.0060f, -0.0050f, -0.0041f, -0.0032f, -0.0023f, -0.0015f, -0.0011f, -0.0003f, + -0.0002f, 0.0000f + }; + + /// Lens scaleX - 1 per point of travel. + private static final float[] STRETCH_X_PER_PT = { + 0.0000000f, -0.0000001f, -0.0000001f, -0.0000003f, 0.0000000f, 0.0000014f, 0.0000011f, + 0.0000224f, 0.0000376f, 0.0000084f, -0.0000603f, -0.0001281f, -0.0001555f, -0.0001468f, + -0.0001004f, -0.0000470f, 0.0000066f, 0.0000440f, 0.0000571f, 0.0000444f, 0.0000084f, + -0.0000442f, -0.0001096f, -0.0001796f, -0.0002477f, -0.0003013f, -0.0003635f, + -0.0004053f, -0.0004351f, -0.0004533f, -0.0004613f, -0.0004595f, -0.0004497f, + -0.0004325f, -0.0004111f, -0.0003857f, -0.0003623f, -0.0003343f, -0.0003101f, + -0.0002786f, -0.0002474f, -0.0002169f, -0.0001844f, -0.0001508f, -0.0001221f, + -0.0000937f, -0.0000686f, -0.0000474f, -0.0000252f, -0.0000125f, -0.0000118f, + 0.0000034f, 0.0000077f, 0.0000101f, 0.0000124f, 0.0000123f, 0.0000091f, 0.0000053f, + 0.0000012f, -0.0000024f, 0.0000071f, 0.0000215f, 0.0000224f, 0.0000229f, 0.0000204f, + 0.0000195f, 0.0000175f, 0.0000163f, 0.0000130f, 0.0000094f, 0.0000069f, 0.0000024f, + -0.0000028f, -0.0000052f, -0.0000074f, -0.0000103f, -0.0000114f, -0.0000127f, + -0.0000134f, -0.0000135f, -0.0000132f, -0.0000119f, -0.0000107f, -0.0000094f, + -0.0000080f, -0.0000066f, -0.0000049f, -0.0000034f, -0.0000023f, -0.0000010f, + 0.0000001f, -0.0000003f, -0.0000001f, 0.0000015f, 0.0000017f, 0.0000013f, 0.0000000f + }; + + /// Lens scaleY - 1, distance-independent part. + private static final float[] STRETCH_Y = { + 0.0000f, 0.0000f, -0.0074f, -0.0249f, -0.0520f, -0.0817f, -0.1135f, -0.1413f, -0.1574f, + -0.1573f, -0.1489f, -0.1402f, -0.1317f, -0.1238f, -0.1129f, -0.1011f, -0.0868f, + -0.0716f, -0.0563f, -0.0412f, -0.0265f, -0.0124f, 0.0002f, 0.0109f, 0.0192f, 0.0261f, + 0.0281f, 0.0293f, 0.0288f, 0.0269f, 0.0242f, 0.0209f, 0.0174f, 0.0139f, 0.0105f, + 0.0076f, 0.0041f, 0.0012f, -0.0021f, -0.0041f, -0.0059f, -0.0075f, -0.0082f, -0.0081f, + -0.0079f, -0.0071f, -0.0063f, -0.0053f, -0.0034f, -0.0026f, -0.0027f, -0.0013f, + -0.0008f, -0.0002f, 0.0005f, 0.0012f, 0.0011f, 0.0008f, 0.0005f, 0.0002f, -0.0028f, + -0.0089f, -0.0130f, -0.0121f, -0.0098f, -0.0072f, -0.0051f, -0.0032f, -0.0013f, 0.0009f, + 0.0035f, 0.0060f, 0.0085f, 0.0112f, 0.0134f, 0.0151f, 0.0165f, 0.0172f, 0.0176f, + 0.0174f, 0.0168f, 0.0160f, 0.0150f, 0.0139f, 0.0125f, 0.0112f, 0.0098f, 0.0084f, + 0.0070f, 0.0057f, 0.0046f, 0.0033f, 0.0022f, 0.0016f, 0.0005f, 0.0003f, 0.0000f + }; + + /// Lens scaleY - 1 per point of travel. + private static final float[] STRETCH_Y_PER_PT = { + 0.0000000f, 0.0000001f, 0.0000001f, 0.0000005f, 0.0000000f, -0.0000019f, 0.0000010f, + -0.0000202f, -0.0000359f, -0.0000249f, 0.0000211f, 0.0000764f, 0.0000872f, 0.0000533f, + -0.0000259f, -0.0001097f, -0.0001924f, -0.0002409f, -0.0002491f, -0.0002207f, + -0.0001652f, -0.0000940f, -0.0000107f, 0.0000789f, 0.0001687f, 0.0002433f, 0.0003386f, + 0.0004095f, 0.0004668f, 0.0005101f, 0.0005386f, 0.0005534f, 0.0005560f, 0.0005485f, + 0.0005319f, 0.0005068f, 0.0004832f, 0.0004533f, 0.0004256f, 0.0003860f, 0.0003465f, + 0.0003062f, 0.0002638f, 0.0002177f, 0.0001783f, 0.0001372f, 0.0001027f, 0.0000722f, + 0.0000416f, 0.0000219f, 0.0000162f, -0.0000014f, -0.0000074f, -0.0000108f, -0.0000148f, + -0.0000159f, -0.0000122f, -0.0000070f, -0.0000014f, 0.0000035f, -0.0000090f, + -0.0000277f, -0.0000287f, -0.0000281f, -0.0000248f, -0.0000242f, -0.0000215f, + -0.0000191f, -0.0000149f, -0.0000109f, -0.0000070f, -0.0000014f, 0.0000055f, 0.0000079f, + 0.0000118f, 0.0000154f, 0.0000169f, 0.0000191f, 0.0000190f, 0.0000192f, 0.0000182f, + 0.0000169f, 0.0000151f, 0.0000122f, 0.0000120f, 0.0000094f, 0.0000071f, 0.0000044f, + 0.0000030f, 0.0000017f, -0.0000001f, -0.0000006f, -0.0000006f, -0.0000022f, -0.0000024f, + -0.0000018f, 0.0000000f + }; + + /// Lens centre lead in points along the travel direction, distance-independent part. + private static final float[] LEAD_PT = { + 0.0000f, 0.0007f, 0.2938f, 1.0074f, 2.1367f, 3.4290f, 4.8636f, 6.0673f, 6.7727f, + 7.3152f, 7.6047f, 7.3912f, 6.8652f, 6.3555f, 5.4543f, 4.5959f, 3.9845f, 3.3075f, + 2.7106f, 2.2127f, 1.8042f, 1.4686f, 1.2083f, 1.0004f, 0.8345f, 0.7228f, 0.5881f, + 0.4936f, 0.3948f, 0.3138f, 0.2226f, 0.1479f, 0.0715f, 0.0112f, -0.0638f, -0.1546f, + -0.2847f, -0.3548f, -0.4029f, -0.3828f, -0.3557f, -0.3253f, -0.2810f, -0.2326f, + -0.2015f, -0.1652f, -0.1288f, -0.1034f, -0.0651f, -0.0673f, -0.1023f, -0.0594f, + -0.0480f, -0.0359f, -0.0157f, 0.0479f, 0.0616f, 0.0662f, 0.0745f, 0.0675f, 0.2305f, + 0.6303f, 0.8126f, 0.8855f, 0.9489f, 0.9535f, 0.9188f, 0.8523f, 0.8000f, 0.7517f, + 0.7266f, 0.7139f, 0.7224f, 0.7269f, 0.7335f, 0.7476f, 0.7428f, 0.7294f, 0.7084f, + 0.6744f, 0.6362f, 0.5815f, 0.5427f, 0.4898f, 0.4272f, 0.3580f, 0.2872f, 0.2241f, + 0.1722f, 0.1406f, 0.1024f, 0.0708f, 0.0599f, 0.0344f, -0.0121f, -0.0112f, -0.0002f, + 0.0000f + }; + + /// Lens centre lead per point of travel. + private static final float[] LEAD_PER_PT = { + 0.0000000f, -0.0000043f, 0.0000178f, 0.0000811f, 0.0002187f, 0.0004269f, 0.0004713f, + 0.0020937f, 0.0031627f, -0.0016846f, -0.0072994f, -0.0096333f, -0.0114983f, -0.0147134f, + -0.0160013f, -0.0165079f, -0.0188178f, -0.0182175f, -0.0162945f, -0.0132486f, + -0.0093188f, -0.0048343f, -0.0002613f, 0.0041333f, 0.0080513f, 0.0111611f, 0.0141693f, + 0.0162235f, 0.0178172f, 0.0187634f, 0.0193351f, 0.0194228f, 0.0192204f, 0.0186776f, + 0.0180465f, 0.0173706f, 0.0168869f, 0.0157468f, 0.0142691f, 0.0122342f, 0.0102847f, + 0.0084948f, 0.0068241f, 0.0052873f, 0.0041313f, 0.0030503f, 0.0021473f, 0.0014806f, + 0.0007969f, 0.0004478f, 0.0006007f, -0.0000826f, -0.0003399f, -0.0005395f, -0.0007964f, + -0.0012456f, -0.0013857f, -0.0014389f, -0.0014865f, -0.0014572f, -0.0032151f, + -0.0070253f, -0.0086748f, -0.0092016f, -0.0097185f, -0.0096760f, -0.0092408f, + -0.0085522f, -0.0079977f, -0.0075674f, -0.0073058f, -0.0072455f, -0.0073654f, + -0.0074485f, -0.0075856f, -0.0077260f, -0.0077119f, -0.0076050f, -0.0074005f, + -0.0070310f, -0.0066410f, -0.0060947f, -0.0056725f, -0.0050960f, -0.0044412f, + -0.0037326f, -0.0030212f, -0.0023771f, -0.0018567f, -0.0015059f, -0.0011018f, + -0.0007806f, -0.0006532f, -0.0003938f, 0.0000630f, 0.0000641f, 0.0000014f, 0.0000000f + }; + + /// Whole-bar width growth in points; the bar scales uniformly by (w + grow) / w. + private static final float[] BAR_GROW_PT = { + 0.0000f, 0.5275f, 2.0830f, 4.0540f, 6.4835f, 8.1467f, 8.7335f, 8.5351f, 7.7808f, + 6.6930f, 5.4604f, 4.2033f, 3.0110f, 2.0482f, 1.0941f, 0.4194f, -0.0029f, -0.4939f, + -0.7085f, -0.8119f, -0.8255f, -0.7726f, -0.6875f, -0.5753f, -0.4631f, -0.2694f, + -0.0007f, 0.0000f + }; + + // ---- outputs ---- + /// Lens centre travel, 0 at the source and 1 at the target (overshoots slightly). + float position; + /// Lift 0..1: bounds grow by LIFT_PT * lift, content magnifies by + /// CONTENT_MAGNIFICATION * lift. + float lift; + /// Opacity of the grey platter under the lens, 1 at rest. + float platterOpacity; + /// Magnification of the accent content seen through the lens. + float contentScale; + /// Lens deformation about its centre. + float scaleX; + float scaleY; + /// Lens centre offset in points along the travel direction (signed). + float leadPt; + /// Width the whole bar grows by, in points. + float barGrowPt; + /// Touch glow diameter scale and opacity (0 = no glow). + float glowScale; + float glowOpacity; + + private TabGlassMotion() { + } + + /// Duration of the whole motion in milliseconds, the late wobble included. + static int durationMs() { + int n = 0; + // The closed forms are at rest well before the tables end (see + // TabGlassMotionTest.theClosedFormsRestBeforeTheTablesEnd). + float[][] all = {STRETCH_X, STRETCH_X_PER_PT, STRETCH_Y, STRETCH_Y_PER_PT, LEAD_PT, LEAD_PER_PT, + BAR_GROW_PT}; + for (float[] a : all) { + if (a.length > n) { + n = a.length; + } + } + return (n - 1) * 1000 / SAMPLE_HZ; + } + + /// The resting frame: lens on its cell, no deformation, no glow. + static TabGlassMotion rest() { + TabGlassMotion m = new TabGlassMotion(); + m.position = 1f; + m.platterOpacity = 1f; + m.contentScale = 1f; + m.scaleX = 1f; + m.scaleY = 1f; + m.glowScale = 1f; + return m; + } + + /// Linear interpolation into a 60 Hz table; clamps to the first/last sample. + static float sample(float[] table, float seconds) { + float x = seconds * SAMPLE_HZ; + if (x <= 0) { + return table[0]; + } + int i = (int) x; + if (i >= table.length - 1) { + return table[table.length - 1]; + } + float f = x - i; + return table[i] * (1 - f) + table[i + 1] * f; + } + + /// Seconds after the start at which the lift is released for a jump of + /// `travelPt`. The first FALLING frame is the first one whose remaining + /// distance is under RELEASE_PT, but never before the frame after the lift has + /// snapped to full (a short jump waits for the lift); the release instant is + /// the frame before it, because UIKit already shows a falling value there. + static float releaseSeconds(float travelPt) { + float d = Math.abs(travelPt); + int last = 2 * SAMPLE_HZ; + int snap = 0; + while (snap < last && liftRise(snap / (float) SAMPLE_HZ) < LIFT_SNAP) { + snap++; + } + for (int i = 1; i < last; i++) { + if (d * (1f - position(i / (float) SAMPLE_HZ)) < RELEASE_PT) { + return (Math.max(i, snap + 1) - 1) / (float) SAMPLE_HZ; + } + } + return last / (float) SAMPLE_HZ; + } + + /// Computes the frame `elapsedMs` after the selection started, for a jump of + /// `travelPt` points (signed: positive = towards larger x). + static TabGlassMotion at(float elapsedMs, float travelPt) { + if (elapsedMs >= durationMs()) { + return rest(); + } + TabGlassMotion m = new TabGlassMotion(); + float t = elapsedMs < 0 ? 0 : elapsedMs / 1000f; + float d = Math.abs(travelPt); + float sign = travelPt < 0 ? -1f : 1f; + m.position = settledPosition(t, d); + float release = releaseSeconds(travelPt); + if (t < release) { + float u = liftRise(t); + m.lift = u >= LIFT_SNAP ? 1f : u; + m.platterOpacity = 1f - u; + } else { + float s = t - release; + m.lift = liftFall(s); + m.platterOpacity = platterReturn(s, release); + } + m.contentScale = 1f + CONTENT_MAGNIFICATION * m.lift; + m.scaleX = 1f + sample(STRETCH_X, t) + d * sample(STRETCH_X_PER_PT, t); + m.scaleY = 1f + sample(STRETCH_Y, t) + d * sample(STRETCH_Y_PER_PT, t); + m.leadPt = sign * (sample(LEAD_PT, t) + d * sample(LEAD_PER_PT, t)); + m.barGrowPt = sample(BAR_GROW_PT, t); + m.glowScale = glowScale(t); + m.glowOpacity = glowOpacity(t); + return m; + } + + /// Template time of the touch-up the tap tables were captured with; their + /// release wobble sits WOBBLE_START_S after it. + static final float TAP_UP_S = 0.048f; + /// Shortest jump in the tap captures (one tab of a three-tab bar). + static final float SHORTEST_CAPTURED_JUMP_PT = 64f; + + /// The selection frame for a real press that may outlast a tap: `upS` is the + /// touch-up in seconds after the press (negative while the finger is down) and + /// `releaseS` the lift release (see releaseForPress). Everything a press does + /// differently from the captured quick tap: + /// + /// - the lift holds while the finger is down, then falls from `releaseS`; + /// - the whole-bar growth is the press spring (TabGlassGesture.barGrowPt) + /// rather than the tap's recorded pulse; + /// - the release wobble moves from the tap's touch-up to the real one; + /// - the touch glow waits, at its tap-up state, until the finger lifts. + /// + /// For a quick tap this is the captured motion to within the spring fit. + static TabGlassMotion held(float elapsedS, float travelPt, float upS, float releaseS) { + TabGlassMotion m = new TabGlassMotion(); + float t = elapsedS < 0 ? 0 : elapsedS; + float d = Math.abs(travelPt); + float sign = travelPt < 0 ? -1f : 1f; + m.position = settledPosition(t, d); + if (upS < 0 || t < releaseS) { + float u = liftRise(t); + m.lift = u >= LIFT_SNAP ? 1f : u; + m.platterOpacity = 1f - u; + } else { + float s = t - releaseS; + m.lift = liftFall(s); + m.platterOpacity = platterReturn(s, releaseS); + } + m.contentScale = 1f + CONTENT_MAGNIFICATION * m.lift; + float tapWobble = t - TAP_UP_S; + // The stretch pulse leads the TRAVEL: a press that does not move the lens + // (the selected tab) has none, only the release wobble. Captured jumps start + // at one tab (64 pt); shorter ones fade the pulse in. + float lead = Math.min(1f, d / SHORTEST_CAPTURED_JUMP_PT); + m.scaleX = 1f + lead * sample(STRETCH_X, t) + d * sample(STRETCH_X_PER_PT, t) + - lead * TabGlassGesture.wobble(TabGlassGesture.WOBBLE_X, tapWobble); + m.scaleY = 1f + lead * sample(STRETCH_Y, t) + d * sample(STRETCH_Y_PER_PT, t) + - lead * TabGlassGesture.wobble(TabGlassGesture.WOBBLE_Y, tapWobble); + m.leadPt = sign * lead * (sample(LEAD_PT, t) - TabGlassGesture.wobble(TabGlassGesture.WOBBLE_TX, tapWobble)) + + sign * d * sample(LEAD_PER_PT, t); + if (upS >= 0) { + m.scaleX += TabGlassGesture.wobble(TabGlassGesture.WOBBLE_X, t - upS); + m.scaleY += TabGlassGesture.wobble(TabGlassGesture.WOBBLE_Y, t - upS); + m.leadPt += TabGlassGesture.wobble(TabGlassGesture.WOBBLE_TX, t - upS); + } + float glowT; + if (t <= TAP_UP_S) { + glowT = t; + } else if (upS < 0 || t < upS) { + glowT = TAP_UP_S; + } else { + glowT = TAP_UP_S + t - Math.max(upS, TAP_UP_S); + } + m.glowScale = glowScale(glowT); + m.glowOpacity = glowOpacity(glowT); + return m; + } + + /// The normalized lens-centre travel `s` seconds into a selection: the travel + /// spring, without UIKit's settle (see settledPosition). + static float position(float s) { + return (float) (1 + DampedSpring.displacement(POSITION_OMEGA, POSITION_ZETA, -1, 0, s - START_S)); + } + + /// The travel of a jump of `d` points as UIKit shows it: the spring until, past + /// its overshoot peak, less than SETTLE_PT remains, then exactly 1. + static float settledPosition(float s, float d) { + float p = position(s); + double peak = START_S + Math.PI / (POSITION_OMEGA * Math.sqrt(1 - POSITION_ZETA * POSITION_ZETA)); + return s >= peak && d * Math.abs(p - 1f) < SETTLE_PT ? 1f : p; + } + + /// The lift's rise `s` seconds into a selection, before UIKit's LIFT_SNAP. + static float liftRise(float s) { + return (float) (1 + DampedSpring.displacement(LIFT_OMEGA, 1, -1, 0, s - START_S)); + } + + /// The lift `s` seconds after its release, falling from full lift at rest. + static float liftFall(float s) { + return s >= LIFT_FALL_S ? 0f : (float) DampedSpring.displacement(LIFT_OMEGA, 1, 1, 0, s); + } + + /// The grey platter's opacity `s` seconds after a release at `releaseS`: it + /// leaves `1 - liftRise` with that curve's velocity and springs back to 1. + static float platterReturn(float s, float releaseS) { + if (s >= PLATTER_RETURN_S) { + return 1f; + } + double x0 = -liftRise(releaseS); + double v0 = -DampedSpring.velocity(LIFT_OMEGA, 1, -1, 0, releaseS - START_S); + return (float) (1 + DampedSpring.displacement(PLATTER_OMEGA, 1, x0, v0, s)); + } + + /// The touch glow's layer opacity `s` seconds into a tap (see GLOW_PEAK). + static float glowLayerOpacity(float s) { + if (s < GLOW_UP_S) { + return (float) (GLOW_PEAK + DampedSpring.displacement(GLOW_RISE_OMEGA, 1, -GLOW_PEAK, 0, s - START_S)); + } + double up = GLOW_UP_S - START_S; + double x0 = GLOW_PEAK + DampedSpring.displacement(GLOW_RISE_OMEGA, 1, -GLOW_PEAK, 0, up); + double v0 = DampedSpring.velocity(GLOW_RISE_OMEGA, 1, -GLOW_PEAK, 0, up); + float v = (float) DampedSpring.displacement(GLOW_FALL_OMEGA, 1, x0, v0, s - GLOW_UP_S); + return v < GLOW_HIDE ? 0f : v; + } + + /// The touch glow's drawn opacity (mask times layer) `s` seconds into a tap. + static float glowOpacity(float s) { + float layer = glowLayerOpacity(s); + float mask = GLOW_MASK * layer; + if (s >= GLOW_UP_S && mask < GLOW_HIDE) { + return 0f; + } + return mask * layer; + } + + /// The touch glow's diameter, as a multiple of GLOW_DIAMETER_PT, `s` seconds + /// into a tap. + static float glowScale(float s) { + return (float) (1 + GLOW_GROWTH * (1 + DampedSpring.displacement(GLOW_FALL_OMEGA, 1, -1, 0, s - GLOW_UP_S))); + } + + /// When a press on a jump of `travelPt` releases its lift: where the tap would + /// (releaseSeconds), but never while the finger is still down. + static float releaseForPress(float travelPt, float upS) { + return Math.max(releaseSeconds(travelPt), upS); + } + + /// Seconds after the press at which a held press has fully played out. + static float heldDurationS(float upS, float releaseS) { + float tables = durationMs() / 1000f; + float fall = releaseS + Math.max(LIFT_FALL_S, PLATTER_RETURN_S); + float wobble = upS + TabGlassGesture.WOBBLE_START_S + + (TabGlassGesture.WOBBLE_X.length - 1) / (float) SAMPLE_HZ; + return Math.max(tables, Math.max(fall, wobble)); + } +} diff --git a/CodenameOne/src/com/codename1/ui/Tabs.java b/CodenameOne/src/com/codename1/ui/Tabs.java index 48f8edee5ba..4ab1a439156 100644 --- a/CodenameOne/src/com/codename1/ui/Tabs.java +++ b/CodenameOne/src/com/codename1/ui/Tabs.java @@ -35,8 +35,10 @@ import com.codename1.ui.layouts.FlowLayout; import com.codename1.ui.layouts.GridLayout; import com.codename1.ui.layouts.Layout; +import com.codename1.ui.plaf.GlassLensBlend; import com.codename1.ui.plaf.Style; import com.codename1.ui.plaf.UIManager; +import com.codename1.ui.plaf.VibrancyMatrix; import com.codename1.ui.util.EventDispatcher; /// A component that lets the user switch between a group of components by @@ -171,6 +173,37 @@ public class Tabs extends Container { private int indicatorToX; private int indicatorToW; + // ---- iOS 27 Liquid Glass selection motion (tabsMorphPreset "ios27") ---- + // The motion is MEASURED from UIKit, see TabGlassMotion. paintGlassTabs draws + // the whole bar -- glass, grey platter, tab content and lens -- from one + // time-based frame, and reveals an ACCENT copy of the tabs through the lens + // (Button.GLASS_PAINT_SELECTED), so the selection colour travels with the glass + // the way UIKit composites its SelectedContentView under a lens-shaped hole. + private long glassMotionStart = -1; + private int glassTargetIndex = -1; + // Lens centre and width at the start and end of the motion, in the inner-x + // space capsuleCellBounds answers in. Floats, so an interrupted motion restarts + // from exactly where the lens is drawn. + private float glassFromCenter; + private float glassFromW; + private float glassToCenter; + private float glassToW; + // Where the last press landed, tabsContainer-relative (-1 = unknown); the touch + // glow expands from there. + private int glassPressX = -1; + private int glassPressY = -1; + private int glassTouchX = -1; + private int glassTouchY = -1; + // The live press driving the motion (hold, scrub, release), or null for a + // selection made in code, which plays the captured tap motion as is. + private TabGlassGesture glassGesture; + // Where the press driving glassGesture landed, tabsContainer-relative x. + private int glassGesturePressX = -1; + // TEST-ONLY: when >= 0 the glass motion is frozen this many ms after its start. + private int glassTestElapsedMs = -1; + private static final int GLASS_TEST_MS_PER_PERCENT = 8; + private static final long GLASS_MAX_PRESS_MS = 60000; + /// Creates an empty `TabbedPane` with a default /// tab placement of `Component.TOP`. public Tabs() { @@ -196,8 +229,42 @@ public Tabs(int tabP) { // override is a no-op extra call and visually indistinguishable from // a plain Container. tabsContainer = new Container() { + @Override + void paintBackgroundLayer(Graphics g) { + if (isGlassMotion()) { + paintGlassBarBackground(g); + return; + } + super.paintBackgroundLayer(g); + } + + @Override + public Component getComponentAt(int x, int y) { + // The glass tabs' bounds overlap (each is as wide as its resting + // lens), so the tab under a point is the one whose centre is + // nearest, the same rule a scrub's release uses. + if (isGlassMotion() && getComponentCount() > 1 && visibleBoundsContains(x, y)) { + int idx = glassTabNearest(fingerInnerX(x - getAbsoluteX())); + Component c = idx < 0 ? null : getComponentAt(idx); + if (c != null && c.isVisible() && c.visibleBoundsContains(x, y)) { + return c instanceof Container ? ((Container) c).getComponentAt(x, y) : c; + } + } + return super.getComponentAt(x, y); + } + @Override public void paint(Graphics g) { + if (isGlassMotion()) { + // An empty bar (before the first tab, or after the last is + // removed) keeps its glass but has no lens or content to draw. + if (getComponentCount() > 0) { + layoutForPaint(); + paintGlassTabs(g); + } + paintBottomDivider(g); + return; + } super.paint(g); // The iOS 26 selection "drop" is a LENS painted OVER the bar + the // (black) glyphs -- it magnifies, chromatically aberrates and @@ -232,7 +299,7 @@ public void paint(Graphics g) { // Forcing the margin to 0 here defeated the float. The host below is a // transparent spacer that only absorbs the safe-area inset; the pill // itself paints the glass, so the host must not tint behind it. - tabsContainerHost = new Container(new BorderLayout()); + tabsContainerHost = new Container(new GlassHostLayout()); // Dedicated UIID so a theme can tune the host (e.g. a negative bottom // margin to pull the floating pill closer to the home indicator). // Defaults to transparent so only the pill paints the glass. @@ -337,6 +404,15 @@ protected void initLaf(UIManager manager) { setTabPlacement(tabPlace); } } + // A theme switch between the ios26 and ios27 presets flips the glass motion + // without touching the placement; the tab layout has to follow it, or the + // glass renderer runs over the grid (or the regular one over the glass + // layout's overlapping, lens-wide tabs). + if (tabsContainer != null + && (tabsContainer.getLayout() instanceof GlassTabsLayout) != isGlassMotion()) { + setTabsLayout(tabPlacement); + tabsContainer.setShouldCalcPreferredSize(true); + } } /// {@inheritDoc} @@ -382,7 +458,10 @@ protected void deinitialize() { protected void initComponent() { super.initComponent(); TopLevelContainer form = getTopLevelContainer(); - if (form != null && swipeActivated) { + // Registered whether or not swiping is on: the same listeners track the + // glass lens's press, hold and scrub, and skip the swipe themselves when + // swipeActivated is false. + if (form != null) { form.asContainer().addPointerPressedListener(press); form.asContainer().addPointerReleasedListener(release); form.asContainer().addPointerDraggedListener(drag); @@ -393,6 +472,27 @@ protected void initComponent() { @Override public boolean animate() { boolean b = super.animate(); + if (glassMotionStart >= 0) { + // One repaint of the bar per tick while the measured motion runs, then ONE + // more once it is over so the resting frame replaces the last animated one. + // Not `b = true`: that asks the form to repaint the whole Tabs as well, so + // every frame of the motion was painted twice. + tabsContainer.repaint(); + long elapsed = System.currentTimeMillis() - glassMotionStart; + if (glassGesture != null && glassGesture.getUpS() < 0 && elapsed > GLASS_MAX_PRESS_MS) { + // The release never reached us (listeners detached mid-press): + // let the lens settle rather than hold it lifted forever. + glassGesture.up(elapsed / 1000f); + } + boolean over = glassGesture != null ? glassGesture.isFinished(elapsed / 1000f) + : elapsed >= TabGlassMotion.durationMs(); + if (over) { + glassMotionStart = -1; + glassTargetIndex = -1; + glassGesture = null; + deregisterAnimatedInternal(); + } + } // Indicator-animation tick: redraw the tab bar each frame while // the motion is in flight. We let the existing super.animate / // slide motion control deregistration; the indicator motion is @@ -469,7 +569,8 @@ void deregisterAnimatedInternal() { // indicator morph are done. Previously a finished 200ms slide deregistered the // animation while the 550ms morph was still in flight, freezing the drop mid-travel. if ((slideToDestMotion == null || slideToDestMotion.isFinished()) - && (indicatorAnimMotion == null || indicatorAnimMotion.isFinished())) { + && (indicatorAnimMotion == null || indicatorAnimMotion.isFinished()) + && glassMotionStart < 0) { TopLevelContainer f = getTopLevelContainer(); if (f != null) { TopLevelSupport.deregisterAnimatedInternal(f, this); @@ -1456,6 +1557,10 @@ private void startIndicatorAnimation(int fromIndex, int toIndex) { || toIndex < 0 || toIndex >= tabsContainer.getComponentCount()) { return; } + if (isGlassMotion()) { + startGlassMotion(fromIndex, toIndex); + return; + } Component fromTab = tabsContainer.getComponentAt(fromIndex); Component toTab = tabsContainer.getComponentAt(toIndex); // The selection capsule fills the whole CELL (and hugs the pill edge for the @@ -1543,6 +1648,9 @@ void paintBottomDivider(Graphics g) { /// The thin frost rim left around the selection capsule (tabSelInsetMm, default a hair). private int selectionCapsuleInsetPx() { + if (isGlassMotion()) { + return Math.round(glassPx(glassConstantPt("tabsGlassInsetPt", GLASS_INSET_PT))); + } float insetMm = 0.1f; String iv = getUIManager().getThemeConstant("tabSelInsetMm", null); if (iv != null) { @@ -1564,6 +1672,15 @@ private void capsuleCellBounds(int index, int inset, int[] out) { int n = tabsContainer.getComponentCount(); Component t = tabsContainer.getComponentAt(index); int padLeft = tabsContainer.getInnerX() - tabsContainer.getX(); + if (tabsContainer.getLayout() instanceof GlassTabsLayout) { + // UITabBar's resting lens: the tab's own button bounds, centred on the + // tab, not the cell between its neighbours. + float centre = t.getX() + t.getWidth() / 2f; + float half = glassLensWidth / 2f; + out[0] = Math.round(centre - half) - padLeft; + out[1] = Math.round(centre + half) - Math.round(centre - half); + return; + } int padRight = (tabsContainer.getX() + tabsContainer.getWidth()) - (tabsContainer.getInnerX() + tabsContainer.getInnerWidth()); int left; @@ -1573,14 +1690,17 @@ private void capsuleCellBounds(int index, int inset, int[] out) { // padding), so the midpoint branches must subtract padLeft to land in the // same inner-x space as the first/last branches -- otherwise every non-first // tab's capsule drifts right by padLeft (the first tab compensated, hiding it). + // With the glass motion the container's padding also holds the reserved motion + // slack, which lies OUTSIDE the visible pill. + int slack = glassSlackXPx(); if (index <= 0) { - left = -padLeft + inset; // pill outer left edge + left = -padLeft + slack + inset; // pill outer left edge } else { Component p = tabsContainer.getComponentAt(index - 1); left = (p.getX() + p.getWidth() + t.getX()) / 2 - padLeft; // midpoint to previous tab } if (index >= n - 1) { - right = tabsContainer.getInnerWidth() + padRight - inset; // pill outer right edge + right = tabsContainer.getInnerWidth() + padRight - slack - inset; // pill outer right edge } else { Component nx = tabsContainer.getComponentAt(index + 1); right = (t.getX() + t.getWidth() + nx.getX()) / 2 - padLeft; // midpoint to next tab @@ -1598,6 +1718,13 @@ public void setMorphTestState(int fromIndex, int toIndex, int value) { if (tabsContainer == null || tabsContainer.getComponentCount() == 0) { return; } + if (isGlassMotion()) { + // The measured ios27 motion is time based: progress 0..100 maps onto its + // first 800 ms (lift, travel, arrival squash), so a progress-frame + // capture spec keeps working; setGlassMotionTestTime takes exact times. + setGlassMotionTestTime(fromIndex, toIndex, value < 0 ? -1 : value * GLASS_TEST_MS_PER_PERCENT); + return; + } if (value >= 0) { int cInset = selectionCapsuleInsetPx(); int[] from = new int[2]; @@ -1614,6 +1741,1029 @@ public void setMorphTestState(int fromIndex, int toIndex, int value) { tabsContainer.repaint(); } + /// TEST-ONLY hook for the measured iOS 27 motion (`tabsMorphPreset: "ios27"`): + /// freezes the selection change from `fromIndex` to `toIndex` exactly + /// `elapsedMs` milliseconds after it started, so a capture tool can render + /// deterministic frames of the motion (lens, bar scale, platter, accent content + /// and glow). Pass `elapsedMs` < 0 to clear and resume normal behaviour. Has no + /// effect unless the theme selects the ios27 motion. + /// + /// #### Parameters + /// + /// - `fromIndex`: the tab the selection leaves + /// + /// - `toIndex`: the tab the selection moves to + /// + /// - `elapsedMs`: milliseconds since the selection started, or < 0 to clear + public void setGlassMotionTestTime(int fromIndex, int toIndex, int elapsedMs) { + if (tabsContainer == null || tabsContainer.getComponentCount() == 0 || !isGlassMotion()) { + return; + } + if (elapsedMs >= 0) { + int inset = selectionCapsuleInsetPx(); + int[] cb = new int[2]; + capsuleCellBounds(fromIndex, inset, cb); + glassFromCenter = cb[0] + cb[1] / 2f; + glassFromW = cb[1]; + capsuleCellBounds(toIndex, inset, cb); + glassToCenter = cb[0] + cb[1] / 2f; + glassToW = cb[1]; + activeComponent = toIndex; + } + glassTestElapsedMs = elapsedMs; + tabsContainer.repaint(); + } + + /// True when the theme selects the measured iOS 27 Liquid Glass selection motion + /// (`tabsMorphPreset: "ios27"` together with `tabsSelectionCapsuleBool`) and the + /// tabs run horizontally: the floating pill, its lens travel and gestures are + /// horizontal, so LEFT and RIGHT placements keep the regular tab rendering. + boolean isGlassMotion() { + return selectionCapsule && tabsContainer != null + && (tabPlacement == TOP || tabPlacement == BOTTOM) + && "ios27".equals(getUIManager().getThemeConstant("tabsMorphPreset", "ios26")); + } + + /// The motion slack the theme reserved around the pill, in pixels. The bar + /// scales, and the lifted lens overshoots its ends by up to ~11 pt, so the + /// theme widens TabsContainer's padding by this much (and narrows its margin to + /// match) and names the amount here: the pill is the bounds minus the slack, + /// and the whole motion stays inside the component's own repaint region. + private int glassSlackXPx() { + return isGlassMotion() ? mmConstantPx("tabsGlassSlackXMm") : 0; + } + + private int glassSlackYPx() { + return isGlassMotion() ? mmConstantPx("tabsGlassSlackYMm") : 0; + } + + private int mmConstantPx(String name) { + String v = getUIManager().getThemeConstant(name, null); + if (v == null) { + return 0; + } + try { + return Display.getInstance().convertToPixels(Float.parseFloat(v.trim())); + } catch (NumberFormatException ignore) { + return 0; // malformed constant -> no slack + } + } + + /// Remembers where a press landed on the tab bar, so the touch glow of the + /// selection it triggers expands from the finger like the native one. + void recordGlassPress(int x, int y) { + if (tabsContainer != null && tabsContainer.visibleBoundsContains(x, y) && isPressOnTabBar(x, y)) { + glassPressX = x - tabsContainer.getAbsoluteX(); + glassPressY = y - tabsContainer.getAbsoluteY(); + // UIKit moves the lens on touch-DOWN; the selection itself still happens + // on release, and finds this motion already in flight to its target. + if (isGlassMotion()) { + Component target = tabsContainer.getComponentAt(x, y); + int idx = target == null ? -1 : tabsContainer.getComponentIndex(target); + if (idx >= 0) { + // A press on the selected tab lifts and pulses the lens in place. + int pressX = glassPressX; + startGlassMotion(activeComponent, idx, true); + glassGesturePressX = pressX; + } + } + } else { + glassPressX = -1; + glassPressY = -1; + } + } + + /// True when the component a press at (x, y) actually reaches is the tab bar or + /// one of its tabs, not an overlay covering it (a side menu, a sheet in the + /// layered pane): the lens must not lift under something the finger never + /// touched. The coordinates are the listener's, local to the tabs' surface. + private boolean isPressOnTabBar(int x, int y) { + TopLevelContainer top = getTopLevelContainer(); + if (top == null) { + return true; + } + Component target = top.asContainer().getComponentAt(x, y); + return target != null && (tabsContainer.equals(target) || tabsContainer.contains(target)); + } + + /// True while the glass motion is travelling to tab `index` (package-private, + /// for tests). + boolean isGlassMotionRunningTo(int index) { + return glassMotionStart >= 0 && glassTargetIndex == index; + } + + /// Points to pixels for the glass geometry (the pill height over the native + /// bar's 62 pt). + private float glassPtPx() { + int ph = tabsContainer.getHeight() - 2 * glassSlackYPx(); + return ph > 0 ? ph / TabGlassMotion.BAR_HEIGHT_PT : 1f; + } + + /// The finger moved along the bar during a glass press: once it has travelled + /// far enough the lens follows it (a scrub). + void recordGlassDrag(int x) { + TabGlassGesture gg = glassGesture; + if (gg == null || glassMotionStart < 0 || gg.getUpS() >= 0 || glassGesturePressX < 0) { + return; + } + float s = (System.currentTimeMillis() - glassMotionStart) / 1000f; + int rel = x - tabsContainer.getAbsoluteX(); + if (!gg.isScrubbing()) { + if (Math.abs(rel - glassGesturePressX) < Display.getInstance().convertToPixels(1.5f)) { + return; + } + int inset = selectionCapsuleInsetPx(); + int[] cb = new int[2]; + int n = tabsContainer.getComponentCount(); + capsuleCellBounds(0, inset, cb); + float first = cb[0] + cb[1] / 2f; + capsuleCellBounds(n - 1, inset, cb); + float last = cb[0] + cb[1] / 2f; + float ptPx = glassPtPx(); + gg.startScrub(s, first / ptPx, last / ptPx); + } + gg.finger(s, fingerInnerX(rel) / glassPtPx()); + } + + /// A tabsContainer-relative x in the inner-x space of capsuleCellBounds. + private float fingerInnerX(int rel) { + return rel + tabsContainer.getX() - tabsContainer.getInnerX(); + } + + /// Touch-up of a glass press. A scrub selects the tab nearest the finger (a + /// tie keeps the current tab) and the lens settles there. + void recordGlassRelease(int x) { + TabGlassGesture gg = glassGesture; + if (gg == null || glassMotionStart < 0 || gg.getUpS() >= 0) { + return; + } + float s = (System.currentTimeMillis() - glassMotionStart) / 1000f; + if (!gg.isScrubbing()) { + gg.up(s); + return; + } + float ptPx = glassPtPx(); + float fx = fingerInnerX(x - tabsContainer.getAbsoluteX()); + gg.finger(s, fx / ptPx); + int best = glassTabNearest(fx); + int inset = selectionCapsuleInsetPx(); + int[] cb = new int[2]; + capsuleCellBounds(best, inset, cb); + glassToCenter = cb[0] + cb[1] / 2f; + glassToW = cb[1]; + glassTargetIndex = best; + gg.settleTo(glassToCenter / ptPx); + gg.up(s); + if (best != activeComponent && best >= 0) { + setSelectedIndex(best); + } + } + + /// The tab whose centre is nearest `innerX` (relative to the bar's inner x); a + /// finger exactly between two keeps the current tab, as natively. Taps and + /// scrubs both resolve through this: the resting lenses are wider than the + /// pitch between tabs, so the button bounds overlap and cannot decide it. + int glassTabNearest(float innerX) { + int n = tabsContainer.getComponentCount(); + int inset = selectionCapsuleInsetPx(); + int[] cb = new int[2]; + int best = -1; + float bestD = Float.MAX_VALUE; + for (int i = 0; i < n; i++) { + capsuleCellBounds(i, inset, cb); + float d = Math.abs(cb[0] + cb[1] / 2f - innerX); + if (d < bestD || (d == bestD && i == activeComponent)) { + best = i; + bestD = d; + } + } + return best; + } + + /// After a release has been processed: a press that started the lens towards a + /// tab but did not select it (released elsewhere, turned into a swipe) sends the + /// lens back to the selected tab. + void checkGlassPressOutcome() { + if (glassGesture != null && glassGesture.isScrubbing()) { + return; + } + if (glassMotionStart >= 0 && glassTargetIndex >= 0 && glassTargetIndex != activeComponent + && (slideToDestMotion == null || active != glassTargetIndex)) { + startGlassMotion(glassTargetIndex, activeComponent); + } + } + + private void startGlassMotion(int fromIndex, int toIndex) { + startGlassMotion(fromIndex, toIndex, false); + } + + /// `press` starts a live gesture (hold, scrub) instead of the captured tap. + private void startGlassMotion(int fromIndex, int toIndex, boolean press) { + if (!press && glassMotionStart >= 0 && toIndex == glassTargetIndex) { + // Same target already in flight (the content slide re-selecting it); let + // it finish rather than restarting the lift. + return; + } + int inset = selectionCapsuleInsetPx(); + int[] cb = new int[2]; + if (glassMotionStart >= 0) { + // Interrupted: restart from where the lens is drawn right now. + GlassFrame f = glassFrame(); + glassFromCenter = f.centerInner; + glassFromW = f.restWidth; + } else { + capsuleCellBounds(fromIndex, inset, cb); + glassFromCenter = cb[0] + cb[1] / 2f; + glassFromW = cb[1]; + } + capsuleCellBounds(toIndex, inset, cb); + glassToCenter = cb[0] + cb[1] / 2f; + glassToW = cb[1]; + glassTargetIndex = toIndex; + if (press) { + float ptPx = glassPtPx(); + glassGesture = new TabGlassGesture(glassFromCenter / ptPx, glassToCenter / ptPx); + } else { + glassGesture = null; + } + glassTouchX = glassPressX; + glassTouchY = glassPressY; + glassPressX = -1; + glassPressY = -1; + glassMotionStart = System.currentTimeMillis(); + TopLevelContainer f = getTopLevelContainer(); + if (f != null) { + TopLevelSupport.registerAnimatedInternal(f, this); + } + tabsContainer.repaint(); + } + + /// One resolved frame of the glass selection, in tabsContainer PARENT + /// coordinates (the space getX()/getY() answer in), before the bar scale. + static final class GlassFrame { + TabGlassMotion motion; + float ptPx; + int pillX; + int pillY; + int pillW; + int pillH; + float barScale; + float pivotX; + float pivotY; + float centerInner; // lens centre in the capsuleCellBounds inner-x space + float restWidth; // resting lens width at this point of the travel + float lensX; + float lensY; + float lensW; + float lensH; + float glowX; + float glowY; + } + + private GlassFrame glassFrame() { + GlassFrame f = new GlassFrame(); + Container tc = tabsContainer; + int sx = glassSlackXPx(); + int sy = glassSlackYPx(); + f.pillX = tc.getX() + sx; + f.pillY = tc.getY() + sy; + f.pillW = tc.getWidth() - 2 * sx; + f.pillH = tc.getHeight() - 2 * sy; + // Native geometry is expressed in points on a 62 pt tall bar; deriving the + // scale from the pill makes the motion follow the theme's bar height. + f.ptPx = f.pillH > 0 ? f.pillH / TabGlassMotion.BAR_HEIGHT_PT : 1f; + int inset = selectionCapsuleInsetPx(); + float restH = f.pillH - 2 * inset; + boolean live = glassTestElapsedMs >= 0 || glassMotionStart >= 0; + float fromC; + float fromW; + float toC; + float toW; + if (live) { + fromC = glassFromCenter; + fromW = glassFromW; + toC = glassToCenter; + toW = glassToW; + } else if (tc.getComponentCount() == 0) { + // No tabs: a zero-width lens at the centre, so the bar's own glass + // still resolves its geometry. + fromC = tc.getInnerWidth() / 2f; + fromW = 0; + toC = fromC; + toW = 0; + } else { + int[] cb = new int[2]; + int idx = activeComponent < 0 ? 0 : Math.min(activeComponent, tc.getComponentCount() - 1); + capsuleCellBounds(idx, inset, cb); + fromC = cb[0] + cb[1] / 2f; + fromW = cb[1]; + toC = fromC; + toW = fromW; + } + float travelPt = (toC - fromC) / f.ptPx; + TabGlassMotion m; + float gestureCentre = Float.NaN; + if (glassTestElapsedMs >= 0) { + m = TabGlassMotion.at(glassTestElapsedMs, travelPt); + } else if (glassMotionStart >= 0 && glassGesture != null) { + float[] c = new float[1]; + m = glassGesture.at((System.currentTimeMillis() - glassMotionStart) / 1000f, c); + if (glassGesture.isScrubbing()) { + gestureCentre = c[0] * f.ptPx; + } + } else if (glassMotionStart >= 0) { + m = TabGlassMotion.at(System.currentTimeMillis() - glassMotionStart, travelPt); + } else { + m = TabGlassMotion.rest(); + } + f.motion = m; + float p = m.position; + float pw = p < 0 ? 0 : (p > 1 ? 1 : p); + f.centerInner = fromC + (toC - fromC) * p; + f.restWidth = fromW + (toW - fromW) * pw; + if (gestureCentre == gestureCentre) { + // Scrubbing: the lens is wherever the finger has pulled it. + f.centerInner = gestureCentre; + f.restWidth = toW; + } + float lift = TabGlassMotion.LIFT_PT * f.ptPx * m.lift; + f.lensW = (f.restWidth + lift) * m.scaleX; + f.lensH = (restH + lift) * m.scaleY; + float cx = tc.getInnerX() + f.centerInner + m.leadPt * f.ptPx; + float cy = f.pillY + f.pillH / 2f; + f.lensX = cx - f.lensW / 2f; + f.lensY = cy - f.lensH / 2f; + f.barScale = f.pillW > 0 ? 1f + m.barGrowPt * f.ptPx / f.pillW : 1f; + f.pivotX = f.pillX + f.pillW / 2f; + f.pivotY = cy; + f.glowX = glassTouchX >= 0 ? tc.getX() + glassTouchX : tc.getInnerX() + toC; + if (gestureCentre == gestureCentre) { + // A scrub's glow rides the lens, not the spot the finger first touched. + f.glowX = cx; + } + f.glowY = glassTouchY >= 0 ? tc.getY() + glassTouchY : cy; + return f; + } + + /// Scales a coordinate about a pivot (the bar pulse, for the ops that draw + /// straight into the frame and so cannot ride the Graphics transform). + private static float scaleAbout(float v, float pivot, float scale) { + return pivot + (v - pivot) * scale; + } + + /// The bar's own glass and fill, drawn into the (scaled) pill instead of the + /// slack-padded bounds. + void paintGlassBarBackground(Graphics g) { + GlassFrame f = glassFrame(); + float s = f.barScale; + int x = Math.round(scaleAbout(f.pillX, f.pivotX, s)); + int y = Math.round(scaleAbout(f.pillY, f.pivotY, s)); + int r = Math.round(scaleAbout(f.pillX + f.pillW, f.pivotX, s)); + int b = Math.round(scaleAbout(f.pillY + f.pillH, f.pivotY, s)); + tabsContainer.paintBackgroundLayerAt(g, x, y, r - x, b - y); + paintGlassBarRim(g, x, y, r - x, b - y, f.ptPx * f.barScale); + } + + /// Relative strength of each row of the specular rim, from the edge inwards. + private static final float[] GLASS_RIM_FALLOFF = {1f, 0.7f, 0.43f, 0.2f}; + + /// The selection platter's colour matrix, read back from the native bar + /// (the colorMatrix filter UIKit puts on the platter's backdrop layer): a 1.2 + /// gain less a share of the Rec. 709 luminance, `M = 1.2 * I - k * 1 * w^T`, + /// plus an offset -- k 0.33 and offset -0.07 on the dark bar, 0.07 and -0.2 on + /// the light one. Every entry UIKit reports matches within 1.4e-4 (under 0.04 of + /// a colour level), which is as far as UIKit's own rows agree with each other. It + /// turns the dark bar's 134 grey into 98.8 and the light bar's 195 into 169.4, + /// the measured platter. + private static final float[] GLASS_PLATTER_DARK = platterMatrix(0.33f, -0.07f); + private static final float[] GLASS_PLATTER_LIGHT = platterMatrix(0.07f, -0.2f); + + /// Rows r, g, b of [r, g, b, offset] for `1.2 * I - luminanceShare * 1 * w^T + offset`. + static float[] platterMatrix(float luminanceShare, float offset) { + float[] w = {0.2126f, 0.7152f, 0.0722f}; + float[] m = new float[12]; + for (int r = 0; r < 3; r++) { + for (int c = 0; c < 3; c++) { + m[r * 4 + c] = (r == c ? 1.2f : 0f) - luminanceShare * w[c]; + } + m[r * 4 + 3] = offset; + } + return m; + } + + /// The lifted native lens, measured over flat grey and colour backdrops (lossless + /// screenshots of a held press, motion probe): a circular-bevel rim of the height + /// UIKit gives its displacement effect (11.2 pt) that pulls the bar edge in by + /// 3 pt at 8 pt depth; a one-pixel ring at x0.70; a rim highlight of +49 then +19 + /// levels on the top and bottom, +14 at the ends; interior brightening of +4 + /// (light) / +10 (dark) over the pressed bar; a shadow band 4 pt below at -7 + /// levels. Points, converted per frame; see GlassLensBlend for the model. + private static final float GLASS_LENS_BEVEL_PT = 11.2f; + private static final float GLASS_LENS_MAX_SHIFT_PT = 30f; + private static final float GLASS_LENS_DISPERSION = 0.08f; + private static final float GLASS_LENS_OUTLINE = 0.30f; + private static final float GLASS_LENS_OUTLINE_PT = 0.5f; + private static final float GLASS_LENS_RIM = 22 / 255f; + private static final float GLASS_LENS_RIM_VERTICAL = 40 / 255f; + private static final float GLASS_LENS_RIM_PT = 0.6f; + private static final float GLASS_LENS_END_SHADE = 0.05f; + private static final float GLASS_LENS_END_SHADE_PT = 8f; + private static final float GLASS_LENS_BRIGHT_LIGHT = 4 / 255f; + private static final float GLASS_LENS_BRIGHT_DARK = 10 / 255f; + private static final float GLASS_LENS_SHADOW = 0.055f; + private static final float GLASS_LENS_SHADOW_PT = 4f; + + private float[] glassLensOptics(boolean dark) { + float[] o = new float[GlassLensBlend.COUNT]; + o[GlassLensBlend.BEVEL] = glassPx(GLASS_LENS_BEVEL_PT); + o[GlassLensBlend.MAX_SHIFT] = glassPx(GLASS_LENS_MAX_SHIFT_PT); + o[GlassLensBlend.DISPERSION] = GLASS_LENS_DISPERSION; + o[GlassLensBlend.OUTLINE] = GLASS_LENS_OUTLINE; + o[GlassLensBlend.OUTLINE_WIDTH] = glassPx(GLASS_LENS_OUTLINE_PT); + o[GlassLensBlend.RIM_LIGHT] = GLASS_LENS_RIM; + o[GlassLensBlend.RIM_LIGHT_VERTICAL] = GLASS_LENS_RIM_VERTICAL; + o[GlassLensBlend.RIM_WIDTH] = glassPx(GLASS_LENS_RIM_PT); + o[GlassLensBlend.END_SHADE] = GLASS_LENS_END_SHADE; + o[GlassLensBlend.END_SHADE_WIDTH] = glassPx(GLASS_LENS_END_SHADE_PT); + o[GlassLensBlend.BRIGHTNESS] = dark ? GLASS_LENS_BRIGHT_DARK : GLASS_LENS_BRIGHT_LIGHT; + o[GlassLensBlend.SHADOW] = GLASS_LENS_SHADOW; + o[GlassLensBlend.SHADOW_WIDTH] = glassPx(GLASS_LENS_SHADOW_PT); + return o; + } + + /// Coverage masks for the vibrant content, reused between frames. + // Coverage masks of the vibrant tab content, one per distinct tint in each + // paint state (normally one each), reused across frames. + private Image[] glassMasksDefault = new Image[1]; + private Image[] glassMasksSelected = new Image[1]; + + /// The native iOS 27 bar is lit along its top and bottom edges: over flat grey + /// the outermost rows read +42/+29/+18/+7 levels (dark) and +36/+26/+17/+8 + /// (light) above the interior, while its sides carry the dark outline instead + /// (GlassRecipe.getOutline). Drawn as white rows that fade inwards over about + /// 1.3 pt, only along the straight top and bottom runs and the upper and lower + /// quarters of the rounded ends. + private void paintGlassBarRim(Graphics g, int x, int y, int w, int h, float ptPx) { + int fg = tabsContainer.getStyle().getFgColor(); + boolean dark = 0.2126f * ((fg >> 16) & 0xff) + 0.7152f * ((fg >> 8) & 0xff) + 0.0722f * (fg & 0xff) > 128; + int peak = getUIManager().getThemeConstant(dark ? "tabGlassSpecularDarkAlphaInt" : "tabGlassSpecularAlphaInt", 0); + if (peak <= 0 || w <= 0 || h <= 0) { + return; + } + int oldColor = g.getColor(); + int oldAlpha = g.getAlpha(); + boolean aa = g.isAntiAliased(); + g.setAntiAliased(true); + g.setColor(0xffffff); + // Rows are about a third of a point each, like the native @3x pixels. + int step = Math.max(1, Math.round(ptPx / 3f)); + int d = h; + for (int i = 0; i < GLASS_RIM_FALLOFF.length; i++) { + int alpha = Math.round(peak * GLASS_RIM_FALLOFF[i]); + if (alpha <= 0) { + continue; + } + g.setAlpha(alpha); + int inset = i * step; + int ix = x + inset; + int iy = y + inset; + int iw = w - 2 * inset; + int ih = h - 2 * inset; + int dd = d - 2 * inset; + if (iw <= dd || ih <= 0) { + break; + } + for (int t = 0; t < step; t++) { + // straight top and bottom runs + g.drawLine(ix + dd / 2, iy + t, ix + iw - dd / 2, iy + t); + g.drawLine(ix + dd / 2, iy + ih - 1 - t, ix + iw - dd / 2, iy + ih - 1 - t); + // upper and lower quarters of both rounded ends (45 degrees either side + // of vertical, where the edge normal is mostly vertical) + int ax = ix + t; + int ay = iy + t; + int aw = dd - 2 * t; + g.drawArc(ax, ay, aw, aw, 90, 45); + g.drawArc(ax, ay, aw, aw, 225, 45); + g.drawArc(ix + iw - dd + t, ay, aw, aw, 45, 45); + g.drawArc(ix + iw - dd + t, ay, aw, aw, 270, 45); + } + } + g.setAntiAliased(aa); + g.setColor(oldColor); + g.setAlpha(oldAlpha); + } + + /// Paints the tab content and the selection for the ios27 motion. Order, bottom + /// to top, as UIKit composites it: grey platter under the lens, the tabs as + /// UNSELECTED content everywhere outside the lens, the tabs as SELECTED (accent) + /// content inside the lens magnified by the lift, then the lens rim and the + /// touch glow. Everything except the rim and glow rides one transform that + /// scales the whole bar about its centre. + void paintGlassTabs(Graphics g) { + Container tc = tabsContainer; + GlassFrame f = glassFrame(); + TabGlassMotion m = f.motion; + int fg = tc.getStyle().getFgColor(); + boolean dark = 0.2126f * ((fg >> 16) & 0xff) + 0.7152f * ((fg >> 8) & 0xff) + 0.0722f * (fg & 0xff) > 128; + boolean vibrant = g.isColorMatrixRegionSupported(); + if (vibrant && m.platterOpacity > 0) { + // The native platter is the bar's own glass seen through a colour matrix + // (a CABackdropLayer filter), not a translucent fill; it fades out while + // the lens is lifted. + float s = f.barScale; + int px = Math.round(scaleAbout(f.lensX, f.pivotX, s)); + int py = Math.round(scaleAbout(f.lensY, f.pivotY, s)); + int pr = Math.round(scaleAbout(f.lensX + f.lensW, f.pivotX, s)); + int pb = Math.round(scaleAbout(f.lensY + f.lensH, f.pivotY, s)); + g.colorMatrixRegion(px, py, pr - px, pb - py, dark ? GLASS_PLATTER_DARK : GLASS_PLATTER_LIGHT, null, -1, + m.platterOpacity); + } + if (vibrant && m.lift > 0) { + // While pressed the whole native bar brightens by ~11 levels (measured over + // flat grey in both appearances: 196 -> 207 light, 136 -> 147 dark). + float s = f.barScale; + int bx = Math.round(scaleAbout(f.pillX, f.pivotX, s)); + int by = Math.round(scaleAbout(f.pillY, f.pivotY, s)); + int br = Math.round(scaleAbout(f.pillX + f.pillW, f.pivotX, s)); + int bb = Math.round(scaleAbout(f.pillY + f.pillH, f.pivotY, s)); + float lift = getUIManager().getThemeConstant("tabGlassPressLiftInt", 11) / 255f; + float[] brighten = {1, 0, 0, lift, 0, 1, 0, lift, 0, 0, 1, lift}; + g.colorMatrixRegion(bx, by, br - bx, bb - by, brighten, null, -1, m.lift); + } + boolean xf = f.barScale != 1f && g.isTransformSupported(); + Transform saved = null; + if (xf) { + saved = g.getTransform(); + Transform t = Transform.makeTranslation(f.pivotX, f.pivotY); + t.scale(f.barScale, f.barScale); + t.translate(-f.pivotX, -f.pivotY); + g.transform(t); + } + boolean aa = g.isAntiAliased(); + int oldColor = g.getColor(); + int oldAlpha = g.getAlpha(); + g.setAntiAliased(true); + + // Selection platter fallback for ports without colorMatrixRegion; a dark bar + // has its own platter constants, falling back to the light ones. + UIManager uim = getUIManager(); + int pillColor = uim.getThemeConstant("tabSelPillColorInt", 0x767680); + int pillAlpha = uim.getThemeConstant("tabSelPillAlphaInt", 34); + if (dark) { + pillColor = uim.getThemeConstant("tabSelPillColorDarkInt", pillColor); + pillAlpha = uim.getThemeConstant("tabSelPillAlphaDarkInt", pillAlpha); + } + int platterAlpha = Math.round(pillAlpha * m.platterOpacity); + int lx = Math.round(f.lensX); + int ly = Math.round(f.lensY); + int lw = Math.round(f.lensX + f.lensW) - lx; + int lh = Math.round(f.lensY + f.lensH) - ly; + int radius = Math.min(lw, lh); + if (!vibrant && platterAlpha > 0 && lw > 0 && lh > 0) { + g.setColor(pillColor); + g.setAlpha(platterAlpha); + g.fillRoundRect(lx, ly, lw, lh, radius, radius); + } + // The lifted lens is clear glass and reads BRIGHTER than the bar around it + // (+8 levels light, +11 dark over flat grey on the native bar). + int lensLight = Math.round(uim.getThemeConstant("tabGlassLensLightAlphaInt", 28) * m.lift); + if (lensLight > 0 && lw > 0 && lh > 0 && !g.isGlassLensRegionSupported()) { + g.setColor(0xffffff); + g.setAlpha(lensLight); + g.fillRoundRect(lx, ly, lw, lh, radius, radius); + } + g.setColor(oldColor); + g.setAlpha(oldAlpha); + + // Content, in tabsContainer-relative coordinates like Container.paint. + int ox = tc.getX(); + int oy = tc.getY(); + if (!vibrant) { + g.translate(ox, oy); + paintGlassContent(g, g, lx - ox, ly - oy, lw, lh, m.contentScale); + g.translate(-ox, -oy); + } + + g.setAntiAliased(aa); + if (xf) { + g.setTransform(saved); + } + if (vibrant) { + paintGlassContentVibrant(g, f, dark, lx - ox, ly - oy, lw, lh); + } + if (m.lift > 0 && g.isGlassLensRegionSupported()) { + float s = f.barScale; + int gx = Math.round(scaleAbout(f.lensX, f.pivotX, s)); + int gy = Math.round(scaleAbout(f.lensY, f.pivotY, s)); + int gr = Math.round(scaleAbout(f.lensX + f.lensW, f.pivotX, s)); + int gb = Math.round(scaleAbout(f.lensY + f.lensH, f.pivotY, s)); + g.glassLensRegion(gx, gy, gr - gx, gb - gy, -1, glassLensOptics(dark), m.lift); + } + paintGlassRimAndGlow(g, f); + g.setColor(oldColor); + g.setAlpha(oldAlpha); + } + + /// Paints every tab twice through complementary clips: unselected outside the + /// lens capsule, selected (accent, scaled by `contentScale` about each tab's + /// centre) inside it. The capsule's round ends are tiled with thin horizontal + /// bands -- a staircase whose steps are about a point tall -- because the + /// complement of a capsule is not convex and a native shape clip only handles + /// convex polygons; plain rectangles are exact on every port. Bands only cover + /// the rows the tab content occupies, and a tab is only painted into a band it + /// intersects. + private void paintGlassContent(Graphics g, Graphics sel, int lx, int ly, int lw, int lh, float contentScale) { + Container tc = tabsContainer; + int n = tc.getComponentCount(); + int w = tc.getWidth(); + // Rows holding tab content. + int top = Integer.MAX_VALUE; + int bottom = Integer.MIN_VALUE; + for (int i = 0; i < n; i++) { + Component c = tc.getComponentAt(i); + top = Math.min(top, c.getY()); + bottom = Math.max(bottom, c.getY() + c.getHeight()); + } + if (top >= bottom) { + return; + } + int lensTop = Math.max(top, ly); + int lensBottom = Math.min(bottom, ly + lh); + // Above and below the lens: unselected, full width. + paintGlassRegion(g, 0, top, w, lensTop - top, Button.GLASS_PAINT_DEFAULT, 1f); + paintGlassRegion(g, 0, lensBottom, w, bottom - lensBottom, Button.GLASS_PAINT_DEFAULT, 1f); + if (lensBottom > lensTop) { + // Left and right of the lens box: unselected. + paintGlassRegion(g, 0, lensTop, lx, lensBottom - lensTop, Button.GLASS_PAINT_DEFAULT, 1f); + paintGlassRegion(g, lx + lw, lensTop, w - lx - lw, lensBottom - lensTop, Button.GLASS_PAINT_DEFAULT, 1f); + float r = Math.min(lw, lh) / 2f; + float mid = ly + lh / 2f; + float step = Math.max(1f, tc.getHeight() / 62f); + int y = lensTop; + while (y < lensBottom) { + int inset = capsuleInset(y + 0.5f, mid, r, step); + int y2 = y + 1; + while (y2 < lensBottom && capsuleInset(y2 + 0.5f, mid, r, step) == inset) { + y2++; + } + int bh = y2 - y; + if (inset > 0) { + paintGlassRegion(g, lx, y, inset, bh, Button.GLASS_PAINT_DEFAULT, 1f); + paintGlassRegion(g, lx + lw - inset, y, inset, bh, Button.GLASS_PAINT_DEFAULT, 1f); + } + paintGlassRegion(sel, lx + inset, y, lw - 2 * inset, bh, Button.GLASS_PAINT_SELECTED, contentScale); + y = y2; + } + } + } + + /// The native tab content is VIBRANT: each glyph pixel is a colour matrix of + /// whatever lies behind it, weighted by the glyph's coverage (see + /// VibrancyMatrix), so the unselected labels and the accent through the lens + /// pick up the glass under them instead of being flat paint. The content is + /// painted into two coverage masks -- unselected outside the lens, selected + /// (accent, magnified) inside it -- in the bar's scaled frame, and each mask + /// then drives Graphics.colorMatrixRegion with the matrix for its tint. + private void paintGlassContentVibrant(Graphics g, GlassFrame f, boolean dark, int lx, int ly, int lw, int lh) { + Container tc = tabsContainer; + int mw = tc.getWidth(); + int mh = tc.getHeight(); + if (mw <= 0 || mh <= 0) { + return; + } + // The masks are the bar's own size, unscaled, so they are allocated once; + // colorMatrixRegion stretches them over the pulsing bar. + // A mask holds the tabs of one tint (each tab's own colour in that state: + // a custom UIID, a disabled tab), so tabs are grouped by tint -- normally + // one group per state. Each tab is painted once into its mask; then the + // lens capsule is cut out of the unselected masks and everything outside + // it out of the selected ones. One mask is finished before the next is + // touched (see cutGlassCapsule). + float s = f.barScale; + int rx = Math.round(scaleAbout(tc.getX(), f.pivotX, s)); + int ry = Math.round(scaleAbout(tc.getY(), f.pivotY, s)); + int rr = Math.round(scaleAbout(tc.getX() + mw, f.pivotX, s)); + int rb = Math.round(scaleAbout(tc.getY() + mh, f.pivotY, s)); + int n = tc.getComponentCount(); + int[] defaultTints = new int[n]; + int[] selectedTints = new int[n]; + for (int i = 0; i < n; i++) { + defaultTints[i] = glassContentTint(tc.getComponentAt(i), Button.GLASS_PAINT_DEFAULT); + selectedTints[i] = glassContentTint(tc.getComponentAt(i), Button.GLASS_PAINT_SELECTED); + } + int[] groups = distinctTints(defaultTints); + glassMasksDefault = glassMaskSlots(glassMasksDefault, groups.length); + for (int k = 0; k < groups.length; k++) { + glassMasksDefault[k] = clearedGlassMask(glassMasksDefault[k], mw, mh); + Graphics dg = glassMasksDefault[k].getGraphics(); + paintGlassMaskTabs(dg, Button.GLASS_PAINT_DEFAULT, 1f, defaultTints, groups[k]); + cutGlassCapsule(dg, lx, ly, lw, lh, mw, mh, false); + } + int[] selectedGroups = distinctTints(selectedTints); + glassMasksSelected = glassMaskSlots(glassMasksSelected, selectedGroups.length); + for (int k = 0; k < selectedGroups.length; k++) { + glassMasksSelected[k] = clearedGlassMask(glassMasksSelected[k], mw, mh); + Graphics sg = glassMasksSelected[k].getGraphics(); + paintGlassMaskTabs(sg, Button.GLASS_PAINT_SELECTED, f.motion.contentScale, selectedTints, selectedGroups[k]); + cutGlassCapsule(sg, lx, ly, lw, lh, mw, mh, true); + } + for (int k = 0; k < groups.length; k++) { + g.colorMatrixRegion(rx, ry, rr - rx, rb - ry, VibrancyMatrix.forTint(groups[k], dark), glassMasksDefault[k], + 0, 1f); + } + for (int k = 0; k < selectedGroups.length; k++) { + g.colorMatrixRegion(rx, ry, rr - rx, rb - ry, VibrancyMatrix.forTint(selectedGroups[k], dark), + glassMasksSelected[k], 0, 1f); + } + } + + /// The distinct values of `tints`, in first-seen order. + private static int[] distinctTints(int[] tints) { + int[] out = new int[tints.length]; + int count = 0; + for (int tint : tints) { + boolean seen = false; + for (int j = 0; j < count; j++) { + if (out[j] == tint) { + seen = true; + break; + } + } + if (!seen) { + out[count++] = tint; + } + } + int[] trimmed = new int[count]; + System.arraycopy(out, 0, trimmed, 0, count); + return trimmed; + } + + /// `slots` grown to hold at least `count` masks, keeping the ones it has. + private static Image[] glassMaskSlots(Image[] slots, int count) { + if (slots.length >= count) { + return slots; + } + Image[] grown = new Image[count]; + System.arraycopy(slots, 0, grown, 0, slots.length); + return grown; + } + + /// Paints the tabs whose tint in this state is `tint` into a mask, in the + /// given glass paint state, each scaled by `scale` about its own centre (the + /// accent copy's magnification). + private void paintGlassMaskTabs(Graphics mg, int state, float scale, int[] tints, int tint) { + Container tc = tabsContainer; + int n = tc.getComponentCount(); + for (int i = 0; i < n; i++) { + if (tints[i] != tint) { + continue; + } + Component c = tc.getComponentAt(i); + Button b = c instanceof Button ? (Button) c : null; + int oldState = b == null ? 0 : b.glassPaintState; + if (b != null) { + b.glassPaintState = state; + } + boolean xf = scale != 1f && mg.isTransformSupported(); + if (xf) { + float px = c.getX() + c.getWidth() / 2f; + float py = c.getY() + c.getHeight() / 2f; + Transform t = Transform.makeTranslation(px, py); + t.scale(scale, scale); + t.translate(-px, -py); + mg.setTransform(t); + } + try { + mg.setClip(0, 0, tc.getWidth(), tc.getHeight()); + c.paintInternal(mg, false); + } finally { + if (xf) { + mg.setTransform(Transform.makeIdentity()); + } + if (b != null) { + b.glassPaintState = oldState; + } + } + } + } + + /// Clears the lens capsule (lx, ly, lw, lh, tabsContainer coordinates) out of + /// a mask, or with `keepInside` everything but the capsule: whole rectangles + /// around the lens box, then one strip per pixel row across the round ends. + /// Callers finish one mask before touching the other -- on ports that render + /// images on the GPU, every switch between two images' graphics closes one + /// drawing pass and opens another. + private static void cutGlassCapsule(Graphics g, int lx, int ly, int lw, int lh, int mw, int mh, + boolean keepInside) { + g.setClip(0, 0, mw, mh); + if (lw <= 0 || lh <= 0) { + if (keepInside) { + g.clearRect(0, 0, mw, mh); + } + return; + } + if (keepInside) { + g.clearRect(0, 0, mw, Math.max(0, ly)); + g.clearRect(0, ly + lh, mw, Math.max(0, mh - ly - lh)); + g.clearRect(0, ly, Math.max(0, lx), lh); + g.clearRect(lx + lw, ly, Math.max(0, mw - lx - lw), lh); + } + cutCapsuleRows(g, lx, ly, lw, lh, keepInside); + } + + /// Per pixel row of the capsule, clears either its two round-end slivers + /// (`ends`) or its interior. + private static void cutCapsuleRows(Graphics g, int lx, int ly, int lw, int lh, boolean ends) { + float r = Math.min(lw, lh) / 2f; + float mid = ly + lh / 2f; + int y = ly; + while (y < ly + lh) { + int inset = capsuleInset(y + 0.5f, mid, r, 1f); + int y2 = y + 1; + while (y2 < ly + lh && capsuleInset(y2 + 0.5f, mid, r, 1f) == inset) { + y2++; + } + if (ends) { + if (inset > 0) { + g.clearRect(lx, y, inset, y2 - y); + g.clearRect(lx + lw - inset, y, inset, y2 - y); + } + } else if (lw - 2 * inset > 0) { + g.clearRect(lx + inset, y, lw - 2 * inset, y2 - y); + } + y = y2; + } + } + + /// A transparent mask of the given size, reusing the previous frame's when it fits. + private static Image clearedGlassMask(Image mask, int w, int h) { + if (mask == null || mask.getWidth() != w || mask.getHeight() != h) { + return Image.createImage(w, h, 0); + } + Graphics mg = mask.getGraphics(); + if (mg.isTransformSupported()) { + mg.setTransform(Transform.makeIdentity()); + } + mg.setClip(0, 0, w, h); + mg.clearRect(0, 0, w, h); + return mask; + } + + /// The foreground colour tab `c` paints its content with in the given glass + /// paint state (its unselected label colour or its accent -- or, for a + /// disabled tab, its disabled colour, which Button keeps in every state). + private static int glassContentTint(Component c, int state) { + if (!(c instanceof Button)) { + return c.getStyle().getFgColor(); + } + Button b = (Button) c; + int old = b.glassPaintState; + b.glassPaintState = state; + try { + return b.getStyle().getFgColor(); + } finally { + b.glassPaintState = old; + } + } + + /// Horizontal inset of a capsule of radius `r` from its bounding box at row + /// `y`, quantized to `step` pixels so neighbouring rows merge into one band. + private static int capsuleInset(float y, float mid, float r, float step) { + float dy = Math.abs(y - mid); + float inset = dy >= r ? r : r - (float) Math.sqrt(r * r - dy * dy); + return Math.round(Math.round(inset / step) * step); + } + + private void paintGlassRegion(Graphics g, int x, int y, int w, int h, int state, float scale) { + if (w <= 0 || h <= 0) { + return; + } + int clipX = g.getClipX(); + int clipY = g.getClipY(); + int clipW = g.getClipWidth(); + int clipH = g.getClipHeight(); + try { + paintGlassRegionClipped(g, x, y, w, h, state, scale, clipX, clipY, clipW, clipH); + } finally { + g.setClip(clipX, clipY, clipW, clipH); + } + } + + private void paintGlassRegionClipped(Graphics g, int x, int y, int w, int h, int state, float scale, + int clipX, int clipY, int clipW, int clipH) { + Container tc = tabsContainer; + g.clipRect(x, y, w, h); + if (g.getClipWidth() <= 0 || g.getClipHeight() <= 0) { + return; + } + int n = tc.getComponentCount(); + for (int i = 0; i < n; i++) { + Component c = tc.getComponentAt(i); + int grow = scale == 1f ? 0 : Math.round(Math.max(c.getWidth(), c.getHeight()) * (scale - 1f) / 2f); + if (c.getX() - grow >= x + w || c.getX() + c.getWidth() + grow <= x + || c.getY() - grow >= y + h || c.getY() + c.getHeight() + grow <= y) { + continue; + } + Button b = c instanceof Button ? (Button) c : null; + int oldState = b == null ? 0 : b.glassPaintState; + if (b != null) { + b.glassPaintState = state; + } + Transform saved = null; + boolean xf = scale != 1f && g.isTransformSupported(); + if (xf) { + saved = g.getTransform(); + float px = c.getX() + c.getWidth() / 2f; + float py = c.getY() + c.getHeight() / 2f; + Transform t = Transform.makeTranslation(px, py); + t.scale(scale, scale); + t.translate(-px, -py); + g.transform(t); + } + try { + c.paintInternal(Display.impl.getComponentScreenGraphics(tc, g), false); + } finally { + if (xf) { + g.setTransform(saved); + } + if (b != null) { + b.glassPaintState = oldState; + } + } + g.setClip(clipX, clipY, clipW, clipH); + g.clipRect(x, y, w, h); + } + } + + /// The lifted lens's bright rim and the touch glow. Both are drawn in the frame + /// directly (manually scaled by the bar pulse) after the transformed content. + private void paintGlassRimAndGlow(Graphics g, GlassFrame f) { + TabGlassMotion m = f.motion; + float s = f.barScale; + boolean aa = g.isAntiAliased(); + g.setAntiAliased(true); + if (m.lift > 0.01f && !g.isGlassLensRegionSupported()) { + float x = scaleAbout(f.lensX, f.pivotX, s); + float y = scaleAbout(f.lensY, f.pivotY, s); + float w = f.lensW * s; + float h = f.lensH * s; + // Without glassLensRegion: an approximation of the native edge -- a thin + // dark ring on the boundary and a bright rim just inside it. + int rimAlpha = Math.round(getUIManager().getThemeConstant("tabGlassRimAlphaInt", 150) * m.lift); + int edgeAlpha = Math.round(getUIManager().getThemeConstant("tabGlassEdgeAlphaInt", 110) * m.lift); + int ix = Math.round(x); + int iy = Math.round(y); + int iw = Math.round(x + w) - ix; + int ih = Math.round(y + h) - iy; + int rr = Math.min(iw, ih); + if (edgeAlpha > 0) { + g.setColor(0x000000); + g.setAlpha(edgeAlpha); + g.drawRoundRect(ix, iy, iw - 1, ih - 1, rr, rr); + } + if (rimAlpha > 0 && iw > 4 && ih > 4) { + g.setColor(0xffffff); + g.setAlpha(rimAlpha); + g.drawRoundRect(ix + 1, iy + 1, iw - 3, ih - 3, rr - 2, rr - 2); + } + } + if (m.glowOpacity > 0.003f) { + float d = TabGlassMotion.GLOW_DIAMETER_PT * m.glowScale * f.ptPx * s; + int alpha = Math.round(255 * m.glowOpacity * getUIManager().getThemeConstant("tabGlassGlowPct", 40) / 100f); + if (alpha > 0) { + int ocx = g.getClipX(); + int ocy = g.getClipY(); + int ocw = g.getClipWidth(); + int och = g.getClipHeight(); + int px = Math.round(scaleAbout(f.pillX, f.pivotX, s)); + int py = Math.round(scaleAbout(f.pillY, f.pivotY, s)); + g.clipRect(px, py, Math.round(f.pillW * s), Math.round(f.pillH * s)); + float gx = scaleAbout(f.glowX, f.pivotX, s); + float gy = scaleAbout(f.glowY, f.pivotY, s); + // A soft disc: nested translucent discs whose alphas sum to `alpha` at + // the centre and fall off linearly to nothing at the rim. + g.setColor(0xffffff); + int rings = 16; + int painted = 0; + for (int i = rings; i >= 1; i--) { + // cumulative alpha at ring i is alpha * (rings - i + 1) / rings + int target = alpha * (rings - i + 1) / rings; + int a = target - painted; + if (a <= 0) { + continue; + } + painted = target; + g.setAlpha(a); + float rd = d / 2f * i / rings; + int dd = Math.round(rd * 2); + g.fillArc(Math.round(gx - rd), Math.round(gy - rd), dd, dd, 0, 360); + } + g.setClip(ocx, ocy, ocw, och); + } + } + g.setAntiAliased(aa); + } + /// The iOS "selected cell" background: a subtle grey capsule kept at bar height /// (the lens drop, drawn over it, bulges taller). Travels + elongates with the /// drop. systemFill grey so it reads neutral, not blue. Alpha via tabSelPillAlphaInt. @@ -1741,7 +2891,28 @@ void paintSelectionCapsule(Graphics g) { // glyphs carry the accent directly (theme) and the lens keeps only its // magnify/aberration optics. int tint = getUIManager().getThemeConstant("tabSelLensTintColorInt", 0x0a84ff); - g.lensRegion(m.lensX, m.lensY, m.lensW, m.lensH, -1f, m.magnify, m.aberration, tint, dark ? 0f : m.tintStrength); + // In flight the drop is taller than the bar and bulges past its edges, + // as UIKit's does, but this paint is clipped to the bar. Widen the clip + // to the whole Tabs for the drop: while the morph runs, animate() has + // the whole Tabs repainted every frame, so nothing is left behind. At + // rest the drop is inside the bar and the clip is left alone. + int ocx = g.getClipX(); + int ocy = g.getClipY(); + int ocw = g.getClipWidth(); + int och = g.getClipHeight(); + boolean widen = m.flight > 0; + if (widen) { + g.setClip(tabsContainer.getX() + getAbsoluteX() - tabsContainer.getAbsoluteX(), + tabsContainer.getY() + getAbsoluteY() - tabsContainer.getAbsoluteY(), getWidth(), getHeight()); + } + try { + g.lensRegion(m.lensX, m.lensY, m.lensW, m.lensH, -1f, m.magnify, m.aberration, tint, + dark ? 0f : m.tintStrength); + } finally { + if (widen) { + g.setClip(ocx, ocy, ocw, och); + } + } return; } // Non-glass platforms: a translucent rounded capsule. @@ -1885,23 +3056,9 @@ public boolean isSwipeActivated() { /// /// - `swipeActivated` public void setSwipeActivated(boolean swipeActivated) { - if (this.swipeActivated != swipeActivated) { - this.swipeActivated = swipeActivated; - if (isInitialized()) { - TopLevelContainer form = getTopLevelContainer(); - if (form != null) { - if (swipeActivated) { - form.asContainer().addPointerPressedListener(press); - form.asContainer().addPointerReleasedListener(release); - form.asContainer().addPointerDraggedListener(drag); - } else { - form.asContainer().removePointerPressedListener(press); - form.asContainer().removePointerReleasedListener(release); - form.asContainer().removePointerDraggedListener(drag); - } - } - } - } + // The pointer listeners stay registered (they also drive the glass lens); + // SwipeListener consults this flag before swiping. + this.swipeActivated = swipeActivated; } private void initTabsFocus() { @@ -1980,6 +3137,12 @@ public void setTabsContentGap(int tabsGap) { } private void setTabsLayout(int tabPlacement) { + if ((tabPlacement == TOP || tabPlacement == BOTTOM) && isGlassMotion()) { + tabsContainer.setLayout(new GlassTabsLayout()); + tabsContainer.setScrollableX(false); + tabsContainer.setScrollableY(false); + return; + } if (tabPlacement == TOP || tabPlacement == BOTTOM) { // Equal-width cells filling the row (native UITabBar even spacing): a // NON-scrolling GridLayout divides the row width into equal columns, so a @@ -2245,6 +3408,168 @@ public String setPropertyValue(String name, Object value) { return super.setPropertyValue(name, value); } + // ---- iOS 27 floating tab bar geometry (tabsMorphPreset ios27), in points ---- + // Read off a real UITabBarController (the motion probe's layer log): the pill + // is 62 pt tall with its lower edge 21 pt above the screen's, and as wide as its + // tabs need -- 86 pt a tab plus 16 -- but never closer than 21 pt to the sides. + // Each tab's button is 54 pt tall, 4 pt inside the pill, and is where the lens + // rests: 94 pt wide at the natural pitch, never narrower than 84.05 pt, which is + // why the buttons of a crowded bar overlap. A theme may override each number. + static final float GLASS_BAR_PT = 62f; + static final float GLASS_PITCH_PT = 86f; + static final float GLASS_INSET_PT = 4f; + static final float GLASS_MIN_LENS_PT = 84.05f; + static final float GLASS_EDGE_PT = 21f; + // Width of the resting lens, set by GlassTabsLayout. + private float glassLensWidth; + + private float glassConstantPt(String name, float def) { + String v = getUIManager().getThemeConstant(name, null); + if (v == null) { + return def; + } + try { + return Float.parseFloat(v.trim()); + } catch (NumberFormatException ignore) { + return def; + } + } + + /// Points to pixels: the screen's own scale where the port knows it (UIScreen + /// on iOS), else through the physical size of a 1/163 inch point. + static float glassPx(float pt) { + float ratio = Display.getInstance().getDevicePixelRatio(); + if (ratio <= 0) { + ratio = Display.getInstance().convertToPixels(10f) / 10f * 25.4f / 163f; + } + return pt * ratio; + } + + /// The pill width the tabs ask for, in pixels (before the side limit). + private float glassNaturalPillWidth(int n) { + return glassPx(n * glassConstantPt("tabsGlassPitchPt", GLASS_PITCH_PT) + + 4 * glassConstantPt("tabsGlassInsetPt", GLASS_INSET_PT)); + } + + /// Places the tabs inside the iOS 27 pill like UITabBar: equal pitch, each + /// button centred on its tab and as wide as the resting lens. + class GlassTabsLayout extends Layout { + @Override + public void layoutContainer(Container parent) { + int n = parent.getComponentCount(); + if (n == 0) { + return; + } + int sx = glassSlackXPx(); + int sy = glassSlackYPx(); + float pillX = sx; + float pillW = parent.getWidth() - 2 * sx; + float pillH = parent.getHeight() - 2 * sy; + float inset = glassPx(glassConstantPt("tabsGlassInsetPt", GLASS_INSET_PT)); + float span = pillW - 2 * inset; + float lensW; + float pitch; + if (n == 1) { + lensW = span; + pitch = 0; + } else { + pitch = (span - 2 * inset) / n; + lensW = pitch + 2 * inset; + float minLens = glassPx(glassConstantPt("tabsGlassMinLensPt", GLASS_MIN_LENS_PT)); + if (lensW < minLens) { + lensW = Math.min(minLens, span); + pitch = (span - lensW) / (n - 1); + } + } + glassLensWidth = lensW; + int top = Math.round(sy + inset); + int h = Math.max(0, Math.round(pillH - 2 * inset)); + boolean rtl = parent.isRTL(); + for (int i = 0; i < n; i++) { + Component c = parent.getComponentAt(i); + if (c instanceof Label) { + // UITabBar never scrolls a title. A ticker started while the + // tab was briefly narrower (focus before this layout ran) + // would otherwise keep running, and the title would drift + // with the clock on every frame. + Label l = (Label) c; + if (l.isTickerRunning()) { + l.stopTicker(); + } + l.setTickerEnabled(false); + } + // Right to left, the first tab sits at the right end (as GridLayout + // places it for the other presets). + int slot = rtl ? n - 1 - i : i; + float centre = pillX + inset + lensW / 2f + slot * pitch; + int left = Math.round(centre - lensW / 2f); + c.setX(left); + c.setY(top); + c.setWidth(Math.round(centre + lensW / 2f) - left); + c.setHeight(h); + } + } + + @Override + public Dimension getPreferredSize(Container parent) { + return new Dimension(Math.round(glassNaturalPillWidth(parent.getComponentCount())) + 2 * glassSlackXPx(), + Math.round(glassBarHeightPx()) + 2 * glassSlackYPx()); + } + } + + /// The wrapper around the floating tab bar. With the iOS 27 motion it sizes + /// and places the pill the way UITabBar does (centred, as wide as its tabs, + /// its lower edge a fixed distance above the bottom -- inside the home + /// indicator's safe area, as native draws it); otherwise it is a BorderLayout. + /// The floating bar's height: UITabBar's 62 pt, or taller when a tab's content + /// needs more (icons or text larger than the native ones), so the glass never + /// clips a tab. Native-sized tabs keep the native geometry exactly. + private float glassBarHeightPx() { + float bar = glassPx(glassConstantPt("tabsGlassBarPt", GLASS_BAR_PT)); + if (tabsContainer == null) { + return bar; + } + float inset = glassPx(glassConstantPt("tabsGlassInsetPt", GLASS_INSET_PT)); + int tallest = 0; + int n = tabsContainer.getComponentCount(); + for (int i = 0; i < n; i++) { + tallest = Math.max(tallest, tabsContainer.getComponentAt(i).getPreferredH()); + } + return Math.max(bar, tallest + 2 * inset); + } + + class GlassHostLayout extends BorderLayout { + @Override + public void layoutContainer(Container parent) { + if (!isGlassMotion() || tabsContainer == null || tabsContainer.getParent() != parent) { //NOPMD CompareObjectsWithEquals + super.layoutContainer(parent); + return; + } + int sx = glassSlackXPx(); + int sy = glassSlackYPx(); + float edge = glassPx(glassConstantPt("tabsGlassEdgePt", GLASS_EDGE_PT)); + float barH = glassBarHeightPx(); + float pillW = Math.min(glassNaturalPillWidth(tabsContainer.getComponentCount()), + parent.getWidth() - 2 * edge); + int w = Math.round(pillW) + 2 * sx; + int h = Math.round(barH) + 2 * sy; + tabsContainer.setX((parent.getWidth() - w) / 2); + tabsContainer.setY(Math.round(parent.getHeight() - edge - barH) - sy); + tabsContainer.setWidth(w); + tabsContainer.setHeight(h); + } + + @Override + public Dimension getPreferredSize(Container parent) { + if (!isGlassMotion() || tabsContainer == null) { + return super.getPreferredSize(parent); + } + float edge = glassPx(glassConstantPt("tabsGlassEdgePt", GLASS_EDGE_PT)); + float barH = glassBarHeightPx(); + return new Dimension(tabsContainer.getPreferredW(), Math.round(barH + edge) + glassSlackYPx()); + } + } + class TabsLayout extends Layout { @Override @@ -2354,6 +3679,19 @@ public SwipeListener(int type) { @Override public void actionPerformed(ActionEvent evt) { + if (type == PRESS) { + recordGlassPress(evt.getX(), evt.getY()); + } else if (type == DRAG) { + recordGlassDrag(evt.getX()); + } else if (type == RELEASE && glassMotionStart >= 0) { + recordGlassRelease(evt.getX()); + CN.callSerially(new Runnable() { + @Override + public void run() { + checkGlassPressOutcome(); + } + }); + } if (getComponentCount() == 0 || !swipeActivated || slideToDestMotion != null) { return; diff --git a/CodenameOne/src/com/codename1/ui/plaf/ColorMatrixBlend.java b/CodenameOne/src/com/codename1/ui/plaf/ColorMatrixBlend.java new file mode 100644 index 00000000000..048e6295d0a --- /dev/null +++ b/CodenameOne/src/com/codename1/ui/plaf/ColorMatrixBlend.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Codename One in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.ui.plaf; + +/// Reference implementation of Graphics.colorMatrixRegion over an ARGB buffer. +/// +/// Every pixel `p` (channels in 0..1) becomes `p + (clamp(M * p + offset) - p) * k`, +/// where `k = amount * shape * mask`: +/// +/// - `shape` is the anti-aliased coverage of a rounded rectangle filling the +/// region, `clamp(0.5 - sdf, 0, 1)` at the pixel centre, with corner radius +/// `cornerRadius` (a negative radius makes a capsule; zero covers the whole +/// rectangle); +/// - `mask` is the alpha of the optional mask sampled at the same relative +/// position (nearest pixel), which is how vibrant content is drawn: the mask +/// holds the glyphs, and each glyph pixel takes its colour from the matrix +/// applied to what lies behind it. +/// +/// The destination alpha is kept. Ports without a native path (JavaSE, the +/// JavaScript port, iOS off-screen images) call this; the Metal shader +/// cn1_fs_colormatrix mirrors it. +public final class ColorMatrixBlend { + private ColorMatrixBlend() { + } + + /// Applies the blend in place. + /// + /// #### Parameters + /// + /// - `argb`: the destination region, `w * h` pixels, row major + /// + /// - `w`: region width in pixels + /// + /// - `h`: region height in pixels + /// + /// - `matrix`: 12 floats, rows r, g, b of `[r, g, b, offset]` in 0..1 units + /// + /// - `mask`: the mask pixels (ARGB, alpha used), or null for no mask + /// + /// - `maskW`: mask width in pixels + /// + /// - `maskH`: mask height in pixels + /// + /// - `cornerRadius`: corner radius in region pixels, negative for a capsule + /// + /// - `amount`: overall strength 0..1 + public static void apply(int[] argb, int w, int h, float[] matrix, int[] mask, int maskW, int maskH, + float cornerRadius, float amount) { + if (amount <= 0 || w <= 0 || h <= 0) { + return; + } + float hw = w / 2f; + float hh = h / 2f; + float r = 0; + if (cornerRadius != 0) { + r = cornerRadius < 0 ? Math.min(hw, hh) : Math.min(cornerRadius, Math.min(hw, hh)); + } + for (int y = 0; y < h; y++) { + float py = y + 0.5f - hh; + for (int x = 0; x < w; x++) { + float k = amount; + if (r > 0) { + float px = x + 0.5f - hw; + float dx = Math.abs(px) - (hw - r); + float dy = Math.abs(py) - (hh - r); + float ax = dx > 0 ? dx : 0; + float ay = dy > 0 ? dy : 0; + float sdf = (float) Math.sqrt(ax * ax + ay * ay) + Math.min(Math.max(dx, dy), 0) - r; + float cov = 0.5f - sdf; + k *= cov < 0 ? 0 : (cov > 1 ? 1 : cov); + } + if (mask != null) { + int mx = x * maskW / w; + int my = y * maskH / h; + k *= ((mask[my * maskW + mx] >>> 24) & 0xff) / 255f; + } + if (k <= 0) { + continue; + } + int i = y * w + x; + int p = argb[i]; + float pr = ((p >> 16) & 0xff) / 255f; + float pg = ((p >> 8) & 0xff) / 255f; + float pb = (p & 0xff) / 255f; + int out = p & 0xff000000; + for (int row = 0; row < 3; row++) { + float v = matrix[row * 4] * pr + matrix[row * 4 + 1] * pg + matrix[row * 4 + 2] * pb + + matrix[row * 4 + 3]; + v = v < 0 ? 0 : (v > 1 ? 1 : v); + float src = row == 0 ? pr : (row == 1 ? pg : pb); + float o = src + (v - src) * k; + out |= Math.round(o * 255) << (16 - 8 * row); + } + argb[i] = out; + } + } + } +} diff --git a/CodenameOne/src/com/codename1/ui/plaf/GlassLensBlend.java b/CodenameOne/src/com/codename1/ui/plaf/GlassLensBlend.java new file mode 100644 index 00000000000..558f234a002 --- /dev/null +++ b/CodenameOne/src/com/codename1/ui/plaf/GlassLensBlend.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Codename One in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.ui.plaf; + +import com.codename1.util.MathUtil; + +/// Reference implementation of Graphics.glassLensRegion: the optics of a raised +/// Liquid Glass lens (the lifted iOS 27 tab selection) laid over what is already +/// painted. Ports without a native path run this; the Metal shader +/// cn1_fs_glass_lens mirrors it. +/// +/// The lens is a rounded rectangle (a capsule for a negative corner radius) whose +/// rim is a circular bevel of height `bevel`: a pixel `s` inside the rim shows the +/// surface displaced OUTWARDS along the rim's normal by +/// `D(s) = (h - s) / sqrt(1 - ((h - s) / h)^2)` (h = bevel, zero beyond it) -- the +/// refraction of a glass edge, which pulls whatever lies just outside the lens +/// (the edge of the bar it floats over, the backdrop) into its rim. The three +/// channels are displaced by `D * (1 - dispersion)`, `D` and `D * (1 + dispersion)`, +/// which fringes the rim. On top of that the surface: +/// +/// - brightens by `brightness` inside; +/// - darkens by up to `endShade` in a band `endShadeWidth` wide along the rim, +/// weighted by how horizontal the rim's normal is (the rounded ends); +/// - is multiplied by `1 - outline` on a ring `outlineWidth` wide just inside the +/// rim; +/// - gains a highlight of `rimLight + rimLightVertical * |ny|` right inside that +/// ring, decaying as `exp(-d / rimWidth)` with the depth `d` past it; +/// - casts a soft shadow of depth `shadow` around its lower half, a band that +/// peaks `shadowWidth` outside the rim and has the same width. +/// +/// Lengths are in pixels, brightness levels in 0..1 units. Everything is mixed +/// with the untouched surface by `amount`. +public final class GlassLensBlend { + public static final int BEVEL = 0; + public static final int MAX_SHIFT = 1; + public static final int DISPERSION = 2; + public static final int OUTLINE = 3; + public static final int OUTLINE_WIDTH = 4; + public static final int RIM_LIGHT = 5; + public static final int RIM_LIGHT_VERTICAL = 6; + public static final int RIM_WIDTH = 7; + public static final int END_SHADE = 8; + public static final int END_SHADE_WIDTH = 9; + public static final int BRIGHTNESS = 10; + public static final int SHADOW = 11; + public static final int SHADOW_WIDTH = 12; + /// Number of optics parameters. + public static final int COUNT = 13; + + private GlassLensBlend() { + } + + /// How far outside the lens rectangle the effect reaches (the shadow and the + /// outline ring), in pixels. + public static int margin(float[] optics) { + return (int) Math.ceil(optics[SHADOW_WIDTH] * 3) + 1; + } + + /// How far outside the painted area the refraction may sample, in pixels. + public static int reach(float[] optics) { + return (int) Math.ceil(optics[MAX_SHIFT]) + 1; + } + + /// Outward displacement of a point `s` pixels inside a rim of bevel height `h`. + public static float displacement(float s, float h, float max) { + if (s >= h || h <= 0) { + return 0; + } + float t = (h - s) / h; + float d = (h - s) / (float) Math.sqrt(Math.max(1e-6f, 1 - t * t)); + return d > max ? max : d; + } + + /// Applies the lens. + /// + /// #### Parameters + /// + /// - `src`: the surface, `sw * sh` ARGB pixels, read only + /// + /// - `sw`: source width + /// + /// - `sh`: source height + /// + /// - `dst`: receives the result for the output area, `ow * oh` pixels + /// + /// - `ox`: the output area's left edge inside `src` + /// + /// - `oy`: the output area's top edge inside `src` + /// + /// - `ow`: output width + /// + /// - `oh`: output height + /// + /// - `lx`: the lens rectangle's left edge inside `src` (may be fractional) + /// + /// - `ly`: the lens rectangle's top edge inside `src` + /// + /// - `lw`: lens width + /// + /// - `lh`: lens height + /// + /// - `cornerRadius`: corner radius in pixels, negative for a capsule + /// + /// - `optics`: `COUNT` parameters, see the constants + /// + /// - `amount`: overall strength 0..1 + public static void apply(int[] src, int sw, int sh, int[] dst, int ox, int oy, int ow, int oh, + float lx, float ly, float lw, float lh, float cornerRadius, float[] optics, float amount) { + float hw = lw / 2f; + float hh = lh / 2f; + float cx = lx + hw; + float cy = ly + hh; + float r = cornerRadius < 0 ? Math.min(hw, hh) : Math.min(cornerRadius, Math.min(hw, hh)); + float[] rgb = new float[3]; + for (int y = 0; y < oh; y++) { + for (int x = 0; x < ow; x++) { + int sxp = ox + x; + int syp = oy + y; + int base = pixel(src, sw, sh, sxp, syp); + if (amount <= 0) { + dst[y * ow + x] = base; + continue; + } + float px = sxp + 0.5f - cx; + float py = syp + 0.5f - cy; + float dx = Math.abs(px) - (hw - r); + float dy = Math.abs(py) - (hh - r); + float ax = dx > 0 ? dx : 0; + float ay = dy > 0 ? dy : 0; + float outside = (float) Math.sqrt(ax * ax + ay * ay); + float sdf = outside + Math.min(Math.max(dx, dy), 0) - r; + // Outward normal of the rounded rectangle at this point. + float nx; + float ny; + if (dx > 0 && dy > 0 && outside > 0) { + nx = ax / outside; + ny = ay / outside; + } else if (dx > dy) { + nx = 1; + ny = 0; + } else { + nx = 0; + ny = 1; + } + nx = px < 0 ? -nx : nx; + ny = py < 0 ? -ny : ny; + float s = -sdf; + float outR = ((base >> 16) & 0xff) / 255f; + float outG = ((base >> 8) & 0xff) / 255f; + float outB = (base & 0xff) / 255f; + if (s > 0) { + float d = displacement(s, optics[BEVEL], optics[MAX_SHIFT]); + float disp = optics[DISPERSION]; + for (int c = 0; c < 3; c++) { + float k = d * (1 + (c - 1) * disp); + rgb[c] = channel(src, sw, sh, sxp + 0.5f + nx * k, syp + 0.5f + ny * k, c); + } + float lum = optics[BRIGHTNESS]; + float shade = 1; + if (optics[END_SHADE_WIDTH] > 0) { + float w = optics[END_SHADE_WIDTH]; + float band = s < w * 0.7f ? 1 : (s >= w ? 0 : smooth((w - s) / (w * 0.3f))); + shade -= optics[END_SHADE] * Math.abs(nx) * band; + } + float rim = 0; + float past = s - optics[OUTLINE_WIDTH]; + if (optics[RIM_WIDTH] > 0 && past >= 0) { + rim = (optics[RIM_LIGHT] + optics[RIM_LIGHT_VERTICAL] * Math.abs(ny)) + * (float) MathUtil.exp(-past / optics[RIM_WIDTH]); + } + float inR = rgb[0] * shade + lum + rim; + float inG = rgb[1] * shade + lum + rim; + float inB = rgb[2] * shade + lum + rim; + // Anti-aliased rim: blend the lens over the surface by coverage. + float cov = s >= 1 ? 1 : s; + outR = outR + (inR - outR) * cov; + outG = outG + (inG - outG) * cov; + outB = outB + (inB - outB) * cov; + } else if (optics[SHADOW] > 0 && optics[SHADOW_WIDTH] > 0 && ny > 0) { + float t = (sdf - optics[SHADOW_WIDTH]) / optics[SHADOW_WIDTH]; + float f = optics[SHADOW] * ny * (float) MathUtil.exp(-t * t); + outR *= 1 - f; + outG *= 1 - f; + outB *= 1 - f; + } + if (optics[OUTLINE_WIDTH] > 0) { + float half = optics[OUTLINE_WIDTH] / 2f; + float ring = 1 - Math.abs(s - half) / half; + if (ring > 0) { + float f = optics[OUTLINE] * (ring > 1 ? 1 : ring); + outR *= 1 - f; + outG *= 1 - f; + outB *= 1 - f; + } + } + float br = ((base >> 16) & 0xff) / 255f; + float bg = ((base >> 8) & 0xff) / 255f; + float bb = (base & 0xff) / 255f; + outR = br + (clamp(outR) - br) * amount; + outG = bg + (clamp(outG) - bg) * amount; + outB = bb + (clamp(outB) - bb) * amount; + dst[y * ow + x] = (base & 0xff000000) | (Math.round(outR * 255) << 16) + | (Math.round(outG * 255) << 8) | Math.round(outB * 255); + } + } + } + + private static float smooth(float t) { + t = t < 0 ? 0 : (t > 1 ? 1 : t); + return t * t * (3 - 2 * t); + } + + private static float clamp(float v) { + return v < 0 ? 0 : (v > 1 ? 1 : v); + } + + private static int pixel(int[] src, int sw, int sh, int x, int y) { + x = x < 0 ? 0 : (x >= sw ? sw - 1 : x); + y = y < 0 ? 0 : (y >= sh ? sh - 1 : y); + return src[y * sw + x]; + } + + /// Bilinear sample of one channel (0 red, 1 green, 2 blue) at pixel-centre + /// coordinates (x, y), edge clamped. + private static float channel(int[] src, int sw, int sh, float x, float y, int c) { + float fx = x - 0.5f; + float fy = y - 0.5f; + int x0 = (int) Math.floor(fx); + int y0 = (int) Math.floor(fy); + float tx = fx - x0; + float ty = fy - y0; + int shift = 16 - 8 * c; + float c00 = (pixel(src, sw, sh, x0, y0) >> shift) & 0xff; + float c10 = (pixel(src, sw, sh, x0 + 1, y0) >> shift) & 0xff; + float c01 = (pixel(src, sw, sh, x0, y0 + 1) >> shift) & 0xff; + float c11 = (pixel(src, sw, sh, x0 + 1, y0 + 1) >> shift) & 0xff; + float top = c00 + (c10 - c00) * tx; + float bot = c01 + (c11 - c01) * tx; + return (top + (bot - top) * ty) / 255f; + } +} diff --git a/CodenameOne/src/com/codename1/ui/plaf/GlassRecipe.java b/CodenameOne/src/com/codename1/ui/plaf/GlassRecipe.java index 201485b8dea..654344f3e4d 100644 --- a/CodenameOne/src/com/codename1/ui/plaf/GlassRecipe.java +++ b/CodenameOne/src/com/codename1/ui/plaf/GlassRecipe.java @@ -181,12 +181,33 @@ public static GlassRecipe liquidChrome27(boolean dark) { : new GlassRecipe(Kind.LIQUID_CHROME, 1.1f, 0.85f, 20f, 0f, 0f); } - /// The iOS 27 floating-pill material. - /// - /// LIGHT is again measured as unchanged -- the fit returned 1.86 / 0.984 / - /// 111.4 against iOS 26's 1.80 / 1.000 / 108.0 -- so the iOS 26 values are - /// reused as the MATERIAL. DARK carries the luminance curve, which takes the - /// fit from rms 11.4/255 to 4.4/255. + /// The iOS 27 floating-pill material: the tab bar of a real + /// `UITabBarController`. + /// + /// MEASURED DIRECTLY, pixel against backdrop. The motion probe + /// (scripts/fidelity-app/ios-native-ref/motion-probe) puts a controller-managed + /// tab bar over three backdrops whose every pixel is known -- the shared + /// glass-backdrop.png, a field of sharp 23 pt colour bands with 1 pt rulers and + /// a flat 50% grey -- and takes lossless simulator screenshots. The bar interior (clear of the + /// ~10 pt refracting rim, the glyphs and the selection) is then fitted as + /// `clamp((lum + (c - lum) * sat) * scale + offset)` of the Gaussian-blurred + /// backdrop, over all three at once: the bands pin the blur radius down (the + /// photo is too smooth to show it) and the grey pins the absolute level, where + /// the live bar and the committed bare-UITabBar golden agree to the level (195 + /// light, 134 dark). + /// + /// Both fit best with a 24 px blur. LIGHT fits at rms 3.4/255; DARK at rms + /// 7.1/255, a luminance curve does not improve it, and what is left is a faint + /// unblurred copy of the backdrop mixed back in, which no recipe parameter + /// models. The offset is then re-anchored so flat grey lands exactly on the + /// measured level. Either way the material keeps the backdrop's colour at + /// full strength (sat x scale ~1.0) and only compresses its luminance: the bar + /// reads as tinted clear glass, not a frosted or darkened slab. That is the + /// whole recipe -- the theme gives the pill no fill of its own. + /// + /// The earlier recipe here was derived indirectly, by inverting the iOS 26 + /// constants out of a bare `UITabBar` tile, and rendered dark mode as a flat + /// charcoal (luma squeezed into 77..110) and light as milky white. /// /// Both appearances carry the iOS 27 edge outline (see {@link #getOutline()}), /// softer on the pill than on the panel: a 127 backdrop falls to about 85 on @@ -196,8 +217,8 @@ public static GlassRecipe liquidChrome27(boolean dark) { /// @return the iOS 27 floating-pill recipe public static GlassRecipe liquidPill27(boolean dark) { return dark - ? new GlassRecipe(Kind.LIQUID_PILL, 5.9f, 0.129f, 77.1f, 0f, 0.2f, 0.065f, 0.85f, 0.55f) - : new GlassRecipe(Kind.LIQUID_PILL, 1.8f, 1.0f, 108f, 0f, 0.2f, 0f, 0f, 0.55f); + ? new GlassRecipe(Kind.LIQUID_PILL, 1.925f, 0.517f, 67.8f, 0f, 0.2f, 0f, 0f, 0.55f) + : new GlassRecipe(Kind.LIQUID_PILL, 2.101f, 0.422f, 141.0f, 0f, 0.2f, 0f, 0f, 0.55f); } /// The iOS 27 glass-panel material, and the recipe that carries the method diff --git a/CodenameOne/src/com/codename1/ui/plaf/VibrancyMatrix.java b/CodenameOne/src/com/codename1/ui/plaf/VibrancyMatrix.java new file mode 100644 index 00000000000..0064d9c357d --- /dev/null +++ b/CodenameOne/src/com/codename1/ui/plaf/VibrancyMatrix.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Codename One in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.ui.plaf; + +/// The colour matrix iOS uses to draw VIBRANT content over glass -- tab bar +/// icons and labels, among others. +/// +/// A vibrant glyph is not painted in its tint. Each of its pixels is a colour +/// transform of whatever lies BEHIND it: `out = clamp(M * backdrop + offset)`, +/// with the glyph's coverage deciding how much of `out` replaces the backdrop +/// (Graphics.colorMatrixRegion paints exactly that). The tint only chooses the +/// matrix. That is why the selected tab of an iOS tab bar is a different blue +/// over every backdrop, and far more vivid than the plain accent in dark mode. +/// +/// MEASURED, NOT APPROXIMATED BY EYE. The matrices were read from UIKit itself: the +/// motion probe (scripts/fidelity-app/ios-native-ref/motion-probe) sets a tab +/// bar's tint to thousands of colours on the iOS 27 simulator and reads back the +/// `vibrantColorMatrix` filter UIKit installs on the item layers. They follow closed +/// forms exactly, with `t` the tint, `d = 1 - t` and `e = 0.5 * min(t) / max(t)`: +/// +/// - dark: `M = e * I + (0.5 - e) * 1 * t^T / |t|^2`, `offset = t` +/// - light: `M = e * I + 0.3 * (0.5 - e) * (1 + t) * d^T / |d|^2`, +/// `offset = alpha * (1 + t) - 1 + e * t` with +/// `alpha = 1 - e - 0.3 * (0.5 - e) * sum(d) / |d|^2`, which is what makes a +/// white backdrop come out as exactly `t` +/// - grey tints `g` (both appearances): `M = 5/16 * I / q`, +/// `offset = (19/16 * g - 1/4) / q` with `q = 1 + 0.05 * g * (1 - g)` +/// +/// Against every matrix the probe logged (7024 distinct light tints, 1354 dark, and +/// the grey levels of both) the worst entry is off by 5e-6, the precision of the +/// log itself. +/// `tabmotion.py vibrancy` re-checks them against new captures. +public final class VibrancyMatrix { + + private VibrancyMatrix() { + } + + /// The vibrancy matrix for content tinted `rgb`, as 12 floats: three rows + /// (red, green, blue) of `[r, g, b, offset]`, in 0..1 units, the layout + /// Graphics.colorMatrixRegion takes. + /// + /// #### Parameters + /// + /// - `rgb`: the tint, 0xRRGGBB + /// + /// - `dark`: true for the dark appearance + /// + /// #### Returns + /// + /// a new 12-element matrix + public static float[] forTint(int rgb, boolean dark) { + double[] t = {((rgb >> 16) & 0xff) / 255.0, ((rgb >> 8) & 0xff) / 255.0, (rgb & 0xff) / 255.0}; + double mx = Math.max(t[0], Math.max(t[1], t[2])); + double mn = Math.min(t[0], Math.min(t[1], t[2])); + float[] m = new float[12]; + if (mx - mn < 1e-9) { + double q = 1 + 0.05 * mx * (1 - mx); + double gain = 5.0 / 16 / q; + double off = (19.0 / 16 * mx - 0.25) / q; + for (int r = 0; r < 3; r++) { + m[r * 4 + r] = (float) gain; + m[r * 4 + 3] = (float) off; + } + return m; + } + double e = 0.5 * mn / mx; + if (dark) { + double tt = t[0] * t[0] + t[1] * t[1] + t[2] * t[2]; + double a = 0.5 - e; + for (int r = 0; r < 3; r++) { + for (int c = 0; c < 3; c++) { + m[r * 4 + c] = (float) ((r == c ? e : 0) + a * t[c] / tt); + } + m[r * 4 + 3] = (float) t[r]; + } + return m; + } + double sum = 0; + double sq = 0; + for (int i = 0; i < 3; i++) { + double d = 1 - t[i]; + sum += d; + sq += d * d; + } + double c = 0.3 * (0.5 - e) / sq; + double alpha = 1 - e - c * sum; + for (int r = 0; r < 3; r++) { + for (int col = 0; col < 3; col++) { + m[r * 4 + col] = (float) (c * (1 + t[r]) * (1 - t[col]) + (r == col ? e : 0)); + } + m[r * 4 + 3] = (float) (alpha * (1 + t[r]) - 1 + e * t[r]); + } + return m; + } + + /// Applies a 12-float colour matrix to one 0xRRGGBB colour (clamped), the + /// per-pixel operation of Graphics.colorMatrixRegion at full coverage. + /// + /// #### Parameters + /// + /// - `m`: the matrix, as returned by `#forTint(int, boolean)` + /// + /// - `rgb`: the colour, 0xRRGGBB + /// + /// #### Returns + /// + /// the transformed colour, 0xRRGGBB + public static int apply(float[] m, int rgb) { + float r = ((rgb >> 16) & 0xff) / 255f; + float g = ((rgb >> 8) & 0xff) / 255f; + float b = (rgb & 0xff) / 255f; + int out = 0; + for (int row = 0; row < 3; row++) { + float v = m[row * 4] * r + m[row * 4 + 1] * g + m[row * 4 + 2] * b + m[row * 4 + 3]; + int iv = Math.round((v < 0 ? 0 : (v > 1 ? 1 : v)) * 255); + out = (out << 8) | iv; + } + return out; + } +} diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java index 9056edff3c7..c8fc2b88705 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java @@ -21098,7 +21098,204 @@ public boolean lensRegion(Object graphics, int x, int y, int width, int height, int[] src = dest.getRGB(rx, ry, rw, rh, null, 0, rw); int[] out = new int[rw * rh]; applyLensBuffer(src, out, rw, rh, cornerRadius * scale, magnify, aberration, tintColor, tintStrength); - dest.setRGB(rx, ry, rw, rh, out, 0, rw); + writeClipped(graphics, dest, rx, ry, rw, rh, out); + return true; + } catch (Throwable t) { + return false; + } + } + + @Override + public boolean isGlassLensRegionSupported(Object graphics) { + return isColorMatrixRegionSupported(graphics); + } + + /// Graphics.glassLensRegion over the simulator's backing buffer, through the + /// core reference (GlassLensBlend) so the simulator and the devices agree. + @Override + public boolean glassLensRegion(Object graphics, int x, int y, int width, int height, float cornerRadius, + float[] optics, float amount) { + if (width <= 0 || height <= 0 || optics == null || optics.length < com.codename1.ui.plaf.GlassLensBlend.COUNT) { + return true; + } + try { + BufferedImage dest; + double scale; + if (graphics instanceof NativeScreenGraphics) { + NativeScreenGraphics ng = (NativeScreenGraphics) graphics; + if (ng.sourceImage != null) { + dest = ng.sourceImage; + scale = 1.0; + } else if (canvas != null && canvas.edtBuffer != null) { + dest = canvas.edtBuffer; + scale = (double) dest.getWidth() / getDisplayWidthImpl(); + } else { + return false; + } + } else if (mutableImageGraphics.containsKey(graphics)) { + dest = mutableImageGraphics.get(graphics); + scale = 1.0; + } else { + return false; + } + float[] o = new float[optics.length]; + for (int i = 0; i < optics.length; i++) { + o[i] = optics[i]; + } + // Lengths scale with the buffer; levels (brightness, strengths) do not. + int[] lengths = {com.codename1.ui.plaf.GlassLensBlend.BEVEL, com.codename1.ui.plaf.GlassLensBlend.MAX_SHIFT, + com.codename1.ui.plaf.GlassLensBlend.OUTLINE_WIDTH, com.codename1.ui.plaf.GlassLensBlend.RIM_WIDTH, + com.codename1.ui.plaf.GlassLensBlend.END_SHADE_WIDTH, com.codename1.ui.plaf.GlassLensBlend.SHADOW_WIDTH}; + for (int i : lengths) { + o[i] = (float) (optics[i] * scale); + } + float lx = (float) (x * scale); + float ly = (float) (y * scale); + float lw = (float) (width * scale); + float lh = (float) (height * scale); + float cr = cornerRadius < 0 ? cornerRadius : (float) (cornerRadius * scale); + int margin = com.codename1.ui.plaf.GlassLensBlend.margin(o); + int reach = com.codename1.ui.plaf.GlassLensBlend.reach(o) + margin; + int dw = dest.getWidth(); + int dh = dest.getHeight(); + int sx0 = Math.max(0, (int) Math.floor(lx) - reach); + int sy0 = Math.max(0, (int) Math.floor(ly) - reach); + int sx1 = Math.min(dw, (int) Math.ceil(lx + lw) + reach); + int sy1 = Math.min(dh, (int) Math.ceil(ly + lh) + reach); + int ox0 = Math.max(0, (int) Math.floor(lx) - margin); + int oy0 = Math.max(0, (int) Math.floor(ly) - margin); + int ox1 = Math.min(dw, (int) Math.ceil(lx + lw) + margin); + int oy1 = Math.min(dh, (int) Math.ceil(ly + lh) + margin); + if (sx1 <= sx0 || sy1 <= sy0 || ox1 <= ox0 || oy1 <= oy0) { + return true; + } + int sw = sx1 - sx0; + int sh = sy1 - sy0; + int[] src = dest.getRGB(sx0, sy0, sw, sh, null, 0, sw); + int ow = ox1 - ox0; + int oh = oy1 - oy0; + int[] out = new int[ow * oh]; + com.codename1.ui.plaf.GlassLensBlend.apply(src, sw, sh, out, ox0 - sx0, oy0 - sy0, ow, oh, + lx - sx0, ly - sy0, lw, lh, cr, o, amount); + writeClipped(graphics, dest, ox0, oy0, ow, oh, out); + return true; + } catch (Throwable t) { + return false; + } + } + + /// Writes the `w` x `h` pixels of `out` into `dest` at (`dx`, `dy`) (buffer + /// pixels), but only inside `graphics`' clip. The region ops read and write the + /// buffer directly, and a raw setRGB bypasses the Graphics2D clip that keeps a + /// component's painting off its siblings; the Metal and JavaScript paths clip. + private void writeClipped(Object graphics, BufferedImage dest, int dx, int dy, int w, int h, int[] out) { + int x0 = Math.max(0, dx); + int y0 = Math.max(0, dy); + int x1 = Math.min(dest.getWidth(), dx + w); + int y1 = Math.min(dest.getHeight(), dy + h); + Graphics2D g2 = getGraphics(graphics); + java.awt.Shape clip = g2.getClip(); + if (clip != null) { + // The clip is in the graphics' user space; the buffer is in device pixels + // (the simulator zoom and any user transform in between). + java.awt.Rectangle c = g2.getTransform().createTransformedShape(clip).getBounds(); + x0 = Math.max(x0, c.x); + y0 = Math.max(y0, c.y); + x1 = Math.min(x1, c.x + c.width); + y1 = Math.min(y1, c.y + c.height); + } + if (x1 <= x0 || y1 <= y0) { + return; + } + dest.setRGB(x0, y0, x1 - x0, y1 - y0, out, (y0 - dy) * w + (x0 - dx), w); + } + + @Override + public boolean isColorMatrixRegionSupported(Object graphics) { + if (graphics instanceof NativeScreenGraphics) { + NativeScreenGraphics ng = (NativeScreenGraphics) graphics; + return ng.sourceImage != null || (canvas != null && canvas.edtBuffer != null); + } + return mutableImageGraphics.containsKey(graphics); + } + + /// Graphics.colorMatrixRegion over the simulator's backing buffer (or an + /// off-screen mutable image), through the core reference implementation so the + /// simulator and the devices agree. The mask is sampled at the buffer's zoom. + @Override + public boolean colorMatrixRegion(Object graphics, int x, int y, int width, int height, float[] matrix, + com.codename1.ui.Image mask, float cornerRadius, float amount) { + if (width <= 0 || height <= 0) { + return true; + } + try { + BufferedImage dest; + double scale; + if (graphics instanceof NativeScreenGraphics) { + NativeScreenGraphics ng = (NativeScreenGraphics) graphics; + if (ng.sourceImage != null) { + dest = ng.sourceImage; + scale = 1.0; + } else if (canvas != null && canvas.edtBuffer != null) { + dest = canvas.edtBuffer; + scale = (double) dest.getWidth() / getDisplayWidthImpl(); + } else { + return false; + } + } else if (mutableImageGraphics.containsKey(graphics)) { + dest = mutableImageGraphics.get(graphics); + scale = 1.0; + } else { + return false; + } + int rx = (int) Math.round(x * scale); + int ry = (int) Math.round(y * scale); + int rw = (int) Math.round(width * scale); + int rh = (int) Math.round(height * scale); + // Clip to the buffer, remembering where the clipped rect sits inside the + // full region so the shape and the mask stay anchored to the region. + int ox = 0; + int oy = 0; + int fullW = rw; + int fullH = rh; + if (rx < 0) { + ox = -rx; + rw += rx; + rx = 0; + } + if (ry < 0) { + oy = -ry; + rh += ry; + ry = 0; + } + if (rx + rw > dest.getWidth()) { + rw = dest.getWidth() - rx; + } + if (ry + rh > dest.getHeight()) { + rh = dest.getHeight() - ry; + } + if (rw <= 0 || rh <= 0) { + return true; + } + int[] full = new int[fullW * fullH]; + int[] part = dest.getRGB(rx, ry, rw, rh, null, 0, rw); + for (int yy = 0; yy < rh; yy++) { + System.arraycopy(part, yy * rw, full, (yy + oy) * fullW + ox, rw); + } + int[] maskPixels = null; + int mw = 0; + int mh = 0; + if (mask != null) { + maskPixels = mask.getRGB(); + mw = mask.getWidth(); + mh = mask.getHeight(); + } + float cr = cornerRadius < 0 ? cornerRadius : (float) (cornerRadius * scale); + com.codename1.ui.plaf.ColorMatrixBlend.apply(full, fullW, fullH, matrix, maskPixels, mw, mh, cr, amount); + for (int yy = 0; yy < rh; yy++) { + System.arraycopy(full, (yy + oy) * fullW + ox, part, yy * rw, rw); + } + writeClipped(graphics, dest, rx, ry, rw, rh, part); return true; } catch (Throwable t) { return false; diff --git a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/BufferedGraphics.java b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/BufferedGraphics.java index 08a4f28f7c0..3a1317efb0d 100644 --- a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/BufferedGraphics.java +++ b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/BufferedGraphics.java @@ -1021,6 +1021,19 @@ public void lensRegion(int x, int y, int width, int height, float cornerRadius, magnify, aberration, tintColor, tintStrength)); } + @Override + public void colorMatrixRegion(int x, int y, int width, int height, float[] matrix, + HTML5Implementation.NativeImage mask, float cornerRadius, float amount) { + // The matrix recolours what is under it -- vibrant glyphs take their colour from the + // glass behind them. Promoted text is not under it, so it would keep its own colour + // over the effect instead of being recoloured with the rest. Back to the canvas. + noteAlphaIndependentRegion(x, y, width, height, cornerRadius > 0 + ? roundRectCoverTest(x, y, width, height, (int) (cornerRadius * 2), (int) (cornerRadius * 2)) + : null); + addOp(new com.codename1.impl.html5.graphics.ColorMatrixRegion(x, y, width, height, + matrix, mask, cornerRadius, amount)); + } + @Override public void clearRect(int x, int y, int width, int height) { // Erasing the canvas erases nothing in the layer above it, so text promoted out of this diff --git a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Graphics.java b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Graphics.java index 26a75837f3b..8f7cbb57685 100644 --- a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Graphics.java +++ b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Graphics.java @@ -477,6 +477,16 @@ public void lensRegion(int x, int y, int width, int height, float cornerRadius, magnify, aberration, tintColor, tintStrength)); } + /** + * In-place colour matrix (Graphics.colorMatrixRegion); see + * {@link com.codename1.impl.html5.graphics.ColorMatrixRegion}. + */ + public void colorMatrixRegion(int x, int y, int width, int height, float[] matrix, + HTML5Implementation.NativeImage mask, float cornerRadius, float amount) { + dispatchOp(new com.codename1.impl.html5.graphics.ColorMatrixRegion(x, y, width, height, + matrix, mask, cornerRadius, amount)); + } + public void clearRect(int x, int y, int width, int height) { primitiveRenderAdapter.clearRect(x, y, width, height); } diff --git a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java index 937a2b8dd86..37699f9c91d 100644 --- a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java +++ b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java @@ -8844,6 +8844,48 @@ public boolean lensRegion(Object graphics, int x, int y, int width, int height, return true; } + @Override + public boolean isColorMatrixRegionSupported(Object graphics) { + // Supported wherever lensRegion is: both ride the same surface command stream and + // are replayed by the host over the target surface's own pixels. + return graphics instanceof HTML5Graphics; + } + + @Override + public boolean colorMatrixRegion(Object graphics, int x, int y, int width, int height, float[] matrix, + Image mask, float cornerRadius, float amount) { + if (!(graphics instanceof HTML5Graphics)) { + return false; + } + if (width <= 0 || height <= 0 || amount <= 0) { + return true; + } + NativeImage nativeMask = null; + if (mask != null) { + Object peer = mask.getImage(); + if (!(peer instanceof NativeImage)) { + // An image with no native peer of its own (a FontImage, say): rasterize it + // into a mutable image, which does have one, so it can cross as a surface. + int mw = mask.getWidth(); + int mh = mask.getHeight(); + if (mw <= 0 || mh <= 0) { + return true; + } + Image raster = Image.createImage(mw, mh, 0); + raster.getGraphics().drawImage(mask, 0, 0); + peer = raster.getImage(); + if (!(peer instanceof NativeImage)) { + return false; + } + } + nativeMask = (NativeImage) peer; + } + // The host-side canvas bridge runs com.codename1.ui.plaf.ColorMatrixBlend's math, + // float for float, over the surface's own pixels during ordered command replay. + g(graphics).colorMatrixRegion(x, y, width, height, matrix, nativeMask, cornerRadius, amount); + return true; + } + @Override public Image gaussianBlurImage(Image image, float radius) { if (image == null) { @@ -12387,6 +12429,33 @@ public void draw(CanvasRenderingContext2D ctx, int x, int y){ draw(ctx, x, y, getWidth(), getHeight()); } + + /** + * Records a Graphics.colorMatrixRegion that uses this image as its mask. The mask + * crosses the way a drawImage source does: a loaded image as its host-ref, a mutable + * image by surface id after flushing its pending commands, so the host samples + * pixels that are actually there. An image with neither (still loading) records + * nothing: the host would have to skip the op anyway, because applying the matrix + * unmasked would recolour the whole region. + */ + public void recordColorMatrixRegion(SurfaceCommandRecorder recorder, int x, int y, int w, int h, + float[] matrix, float cornerRadius, float amount) { + switch (JavaScriptNativeImageAdapter.resolveSurfaceKind(imageModel)) { + case LOADED_IMAGE: + recorder.colorMatrixSelfRegion(x, y, w, h, matrix, cornerRadius, amount, + SurfaceCommandRecorder.COLOR_MATRIX_MASK_IMAGE, 0, img); + break; + case MUTABLE_SURFACE: + mutableGraphics.flush(); + retainBlitSource(NativeImage.this); + recorder.colorMatrixSelfRegion(x, y, w, h, matrix, cornerRadius, amount, + SurfaceCommandRecorder.COLOR_MATRIX_MASK_SURFACE, + mutableGraphics.getSurfaceId(), null); + break; + default: + break; + } + } } diff --git a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/graphics/ColorMatrixRegion.java b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/graphics/ColorMatrixRegion.java new file mode 100644 index 00000000000..40920f627f3 --- /dev/null +++ b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/graphics/ColorMatrixRegion.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package com.codename1.impl.html5.graphics; + +import com.codename1.html5.js.canvas.CanvasRenderingContext2D; +import com.codename1.impl.html5.HTML5Implementation.NativeImage; + +/** + * In-place Graphics.colorMatrixRegion. Records + * {@link SurfaceCommandRecorder#OP_COLOR_MATRIX_SELF_REGION}: the host reads the + * region back, runs com.codename1.ui.plaf.ColorMatrixBlend's per-pixel math over + * it and draws the result back through the current clip. + * + *

The mask is resolved when the op EXECUTES, not when it is queued, the same + * way {@link DrawImage} resolves its image: a mutable mask is flushed at that + * point so the host holds its pixels before the op that samples them.

+ */ +public class ColorMatrixRegion implements ExecutableOp { + final int x, y, w, h; + final float[] matrix; + final NativeImage mask; + final float cornerRadius, amount; + + public ColorMatrixRegion(int x, int y, int w, int h, float[] matrix, NativeImage mask, + float cornerRadius, float amount) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + // Copied: the op runs after the caller returns and may reuse its array. + float[] m = new float[12]; + if (matrix != null) { + System.arraycopy(matrix, 0, m, 0, Math.min(12, matrix.length)); + } + this.matrix = m; + this.mask = mask; + this.cornerRadius = cornerRadius; + this.amount = amount; + } + + @Override + public void execute(CanvasRenderingContext2D context) { + if (!(context instanceof SurfaceCommandRecorder)) { + return; + } + SurfaceCommandRecorder recorder = (SurfaceCommandRecorder) context; + if (mask == null) { + recorder.colorMatrixSelfRegion(x, y, w, h, matrix, cornerRadius, amount, + SurfaceCommandRecorder.COLOR_MATRIX_MASK_NONE, 0, null); + } else { + mask.recordColorMatrixRegion(recorder, x, y, w, h, matrix, cornerRadius, amount); + } + } + + @Override + public String getDescription() { + return "ColorMatrixRegion"; + } +} diff --git a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/graphics/SurfaceCommandRecorder.java b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/graphics/SurfaceCommandRecorder.java index b39aac9dc46..795ba8358ad 100644 --- a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/graphics/SurfaceCommandRecorder.java +++ b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/graphics/SurfaceCommandRecorder.java @@ -146,6 +146,22 @@ public final class SurfaceCommandRecorder implements CanvasRenderingContext2D { // saturation, scale, offset, refraction, specular. public static final int OP_GLASS_SELF_REGION = 82; + // Graphics.colorMatrixRegion: recolours the surface's own pixels through a + // 3x4 colour matrix, see com.codename1.ui.plaf.ColorMatrixBlend. 20 nums: + // x, y, w, h, cornerRadius, amount, the 12 matrix floats, maskKind (one of + // the COLOR_MATRIX_MASK_* values) and maskSurfaceId; plus ALWAYS 1 obj, + // the mask image's host-ref marker or null. The mask travels the way a + // drawImage source does: a loaded image as its host-ref, a mutable image + // by its surface id (flushed first, like a blit). + public static final int OP_COLOR_MATRIX_SELF_REGION = 83; + + /** No mask: the whole region (times the shape) is recoloured. */ + public static final int COLOR_MATRIX_MASK_NONE = 0; + /** The mask is a loaded image, carried in the op's obj slot. */ + public static final int COLOR_MATRIX_MASK_IMAGE = 1; + /** The mask is another surface, named by the maskSurfaceId num. */ + public static final int COLOR_MATRIX_MASK_SURFACE = 2; + // Text-layer DOM mutations. The layer renders text as real DOM above the canvas, so a frame // is only coherent if its elements and its pixels are applied together. Riding the command // stream is what guarantees that: the host applies these inside the same task that replays @@ -352,6 +368,22 @@ public void lensSelfRegion(double x, double y, double w, double h, double corner num(aberration); num(tintColor); num(tintStrength); } + /** + * Records an in-place colour matrix; see OP_COLOR_MATRIX_SELF_REGION. A + * matrix shorter than 12 floats is padded with zeros so the fixed arity the + * host reads never desyncs the ops behind this one. + */ + public void colorMatrixSelfRegion(double x, double y, double w, double h, float[] matrix, + double cornerRadius, double amount, int maskKind, int maskSurfaceId, Object maskImage) { + op(OP_COLOR_MATRIX_SELF_REGION); + num(x); num(y); num(w); num(h); num(cornerRadius); num(amount); + for (int i = 0; i < 12; i++) { + num(matrix != null && i < matrix.length ? matrix[i] : 0); + } + num(maskKind); num(maskSurfaceId); + obj(maskKind == COLOR_MATRIX_MASK_IMAGE ? maskImage : null); + } + /** Records a complete in-place Liquid Glass material; see OP_GLASS_SELF_REGION. */ public void glassSelfRegion(double x, double y, double w, double h, double blurRadius, double cornerRadius, double saturation, double scale, double offset, diff --git a/Ports/MacPort/nativeSources/METALView.h b/Ports/MacPort/nativeSources/METALView.h index bf30c8a37a7..d4358a69889 100644 --- a/Ports/MacPort/nativeSources/METALView.h +++ b/Ports/MacPort/nativeSources/METALView.h @@ -144,6 +144,10 @@ curve:(float)curve curveMid:(float)curveMid outline:(float)outline; - (void)lensScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h cornerRadius:(float)cornerRadius magnify:(float)magnify aberration:(float)aberration tintColor:(int)tintColor tintStrength:(float)tintStrength; +- (void)colorMatrixScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h matrix:(const float*)matrix + mask:(id)mask cornerRadius:(float)cornerRadius amount:(float)amount; +- (void)glassLensScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h cornerRadius:(float)cornerRadius + optics:(const float*)optics amount:(float)amount; /// Reads the finished frame back as an ARGB raster. Used by the screenshot /// pipeline and by Window.capture(). - (BOOL)readbackInto:(unsigned int *)argb width:(int)w height:(int)h; diff --git a/Ports/MacPort/nativeSources/METALView.m b/Ports/MacPort/nativeSources/METALView.m index bfc26f7bb36..c2f85f0ac7e 100644 --- a/Ports/MacPort/nativeSources/METALView.m +++ b/Ports/MacPort/nativeSources/METALView.m @@ -806,6 +806,8 @@ - (BOOL)cn1DeferFrameAndCommit { } - (BOOL)presentFramebuffer { + // Whatever happens below, this frame is over (see CN1MetalFrameFinished). + CN1MetalFrameFinished(); if (self.renderCommandEncoder == nil) { self.commandBuffer = nil; return NO; @@ -3134,6 +3136,36 @@ - (void)glassScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h radius:(float)radiu int pad = (int)ceilf(rad) * 3 + 1; int bw = fw + 2 * pad, bh = fh + 2 * pad; + // GPU path: the whole material on the frame's own command buffer, no sync and + // no readback (see CN1MetalGlassEncode). The CPU path below is the fallback + // when the pipelines are unavailable, and CN1_GLASS_CPU=1 forces it. + static int cn1GlassForceCpu = -1; + if (cn1GlassForceCpu < 0) { + const char *env = getenv("CN1_GLASS_CPU"); + cn1GlassForceCpu = (env != NULL && env[0] == '1') ? 1 : 0; + } + if (!cn1GlassForceCpu) { + if (self.renderCommandEncoder != nil) { + CN1MetalEndFrame(); + [self.renderCommandEncoder endEncoding]; + self.renderCommandEncoder = nil; + } + if (self.commandBuffer == nil) { + self.commandBuffer = [self.commandQueue commandBuffer]; + } + CN1MetalGlassPatch patch; + if (CN1MetalGlassEncode(self.commandBuffer, self.screenTexture, fx, fy, fw, fh, rad, sat, scale, offset, + curve, curveMid, &patch)) { + [self createRenderPassDescriptor]; + if (self.renderPassDescriptor == nil) { return; } + self.renderCommandEncoder = [self.commandBuffer renderCommandEncoderWithDescriptor:self.renderPassDescriptor]; + [self.renderCommandEncoder setViewport:(MTLViewport){ 0.0, 0.0, (double)framebufferWidth, (double)framebufferHeight, 0.0, 1.0 }]; + CN1MetalBeginFrame(self.renderCommandEncoder, projectionMatrix, framebufferWidth, framebufferHeight); + CN1MetalDrawGlass(&patch, x, y, w, h, fw, fh, cornerRadius, (float)s, refract, specular, outline); + return; + } + } + // 1) End + commit the screen encoder so screenTexture holds the backdrop. if (self.renderCommandEncoder != nil) { CN1MetalEndFrame(); @@ -3376,6 +3408,133 @@ - (void)lensScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h cornerRadius:(float) #endif } +// Graphics.colorMatrixRegion on the live screen. Same GPU route as the lens: end +// the encoder, blit the painted region to a scratch texture on the frame's own +// command buffer, restart the encoder, then draw a quad that reads the scratch +// copy (and the mask) through cn1_fs_colormatrix. No CPU readback. +- (void)colorMatrixScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h matrix:(const float*)matrix + mask:(id)mask cornerRadius:(float)cornerRadius amount:(float)amount { + if (self.screenTexture == nil || w <= 0 || h <= 0 || amount <= 0.0f) { + return; + } + float sv = scaleValue > 0.0f ? scaleValue : 1.0f; + CGFloat s = CN1AppKitBackingScale(self) / sv; + int texW = (int)self.screenTexture.width, texH = (int)self.screenTexture.height; + // The scratch covers the FULL region, with the on-screen part copied to its + // offset inside it, so the quad, the texture coordinates and the mask all stay + // anchored to the region when it runs off a screen edge (the off-screen part + // of the quad falls outside the viewport). + int fullW = (int)(w * s), fullH = (int)(h * s); + int fx = (int)(x * s), fy = (int)(y * s), fw = fullW, fh = fullH, ox = 0, oy = 0; + if (fx < 0) { ox = -fx; fw += fx; fx = 0; } + if (fy < 0) { oy = -fy; fh += fy; fy = 0; } + if (fx + fw > texW) { fw = texW - fx; } + if (fy + fh > texH) { fh = texH - fy; } + if (fw <= 0 || fh <= 0 || fullW <= 0 || fullH <= 0) { return; } + + if (self.renderCommandEncoder != nil) { + CN1MetalEndFrame(); + [self.renderCommandEncoder endEncoding]; + self.renderCommandEncoder = nil; + } + if (self.commandBuffer == nil) { + self.commandBuffer = [self.commandQueue commandBuffer]; + } + id device = CN1MetalDevice(); + MTLTextureDescriptor *desc = [MTLTextureDescriptor + texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:fullW height:fullH mipmapped:NO]; + desc.usage = MTLTextureUsageShaderRead; + desc.storageMode = MTLStorageModePrivate; + id scratch = [device newTextureWithDescriptor:desc]; + if (scratch == nil) { [self setFramebuffer]; CN1_SCRATCH_RELEASE return; } + id blit = [self.commandBuffer blitCommandEncoder]; + [blit copyFromTexture:self.screenTexture sourceSlice:0 sourceLevel:0 + sourceOrigin:MTLOriginMake(fx, fy, 0) sourceSize:MTLSizeMake(fw, fh, 1) + toTexture:scratch destinationSlice:0 destinationLevel:0 + destinationOrigin:MTLOriginMake(ox, oy, 0)]; + [blit endEncoding]; + + [self createRenderPassDescriptor]; + if (self.renderPassDescriptor == nil) { CN1_SCRATCH_RELEASE return; } + self.renderCommandEncoder = [self.commandBuffer renderCommandEncoderWithDescriptor:self.renderPassDescriptor]; + [self.renderCommandEncoder setViewport:(MTLViewport){ 0.0, 0.0, (double)framebufferWidth, (double)framebufferHeight, 0.0, 1.0 }]; + CN1MetalBeginFrame(self.renderCommandEncoder, projectionMatrix, framebufferWidth, framebufferHeight); + + float crPx = cornerRadius < 0.0f ? -1.0f : cornerRadius * (float)s; + CN1MetalDrawColorMatrix(scratch, mask, x, y, w, h, fullW, fullH, matrix, crPx, amount); +#ifndef CN1_USE_ARC + [scratch release]; +#endif +} + +// Graphics.glassLensRegion on the live screen, on the GPU like the colour matrix: +// blit the lens plus the distance its refraction may reach, then draw the lens +// plus its margin (outline, shadow) with cn1_fs_glass_lens sampling the copy. +- (void)glassLensScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h cornerRadius:(float)cornerRadius + optics:(const float*)optics amount:(float)amount { + if (self.screenTexture == nil || w <= 0 || h <= 0 || amount <= 0.0f || optics == NULL) { + return; + } + float sv = scaleValue > 0.0f ? scaleValue : 1.0f; + CGFloat s = CN1AppKitBackingScale(self) / sv; + float o[16]; + for (int i = 0; i < 16; i++) { o[i] = optics[i]; } + // GlassLensBlend lengths: bevel, max shift, outline width, rim width, end-shade + // width, shadow width -- logical units in, physical pixels here. + static const int lengths[6] = { 0, 1, 4, 7, 9, 12 }; + for (int i = 0; i < 6; i++) { o[lengths[i]] *= (float)s; } + int margin = (int)ceilf(o[12] * 3.0f) + 1; + int reach = (int)ceilf(o[1]) + 1 + margin; + float lx = x * (float)s, ly = y * (float)s, lw = w * (float)s, lh = h * (float)s; + int texW = (int)self.screenTexture.width, texH = (int)self.screenTexture.height; + int sx0 = MAX(0, (int)floorf(lx) - reach), sy0 = MAX(0, (int)floorf(ly) - reach); + int sx1 = MIN(texW, (int)ceilf(lx + lw) + reach), sy1 = MIN(texH, (int)ceilf(ly + lh) + reach); + if (sx1 <= sx0 || sy1 <= sy0) { return; } + int sw = sx1 - sx0, sh = sy1 - sy0; + int ml = (int)ceilf(margin / (float)s); + int qx = x - ml, qy = y - ml, qw = w + 2 * ml, qh = h + 2 * ml; + + if (self.renderCommandEncoder != nil) { + CN1MetalEndFrame(); + [self.renderCommandEncoder endEncoding]; + self.renderCommandEncoder = nil; + } + if (self.commandBuffer == nil) { + self.commandBuffer = [self.commandQueue commandBuffer]; + } + id device = CN1MetalDevice(); + MTLTextureDescriptor *desc = [MTLTextureDescriptor + texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:sw height:sh mipmapped:NO]; + desc.usage = MTLTextureUsageShaderRead; + desc.storageMode = MTLStorageModePrivate; + id scratch = [device newTextureWithDescriptor:desc]; + CN1_TEX_NOTE("glassLensScratch", scratch); + if (scratch == nil) { [self setFramebuffer]; return; } + id blit = [self.commandBuffer blitCommandEncoder]; + [blit copyFromTexture:self.screenTexture sourceSlice:0 sourceLevel:0 + sourceOrigin:MTLOriginMake(sx0, sy0, 0) sourceSize:MTLSizeMake(sw, sh, 1) + toTexture:scratch destinationSlice:0 destinationLevel:0 + destinationOrigin:MTLOriginMake(0, 0, 0)]; + [blit endEncoding]; + + [self createRenderPassDescriptor]; + if (self.renderPassDescriptor == nil) { +#ifndef CN1_USE_ARC + [scratch release]; +#endif + return; + } + self.renderCommandEncoder = [self.commandBuffer renderCommandEncoderWithDescriptor:self.renderPassDescriptor]; + [self.renderCommandEncoder setViewport:(MTLViewport){ 0.0, 0.0, (double)framebufferWidth, (double)framebufferHeight, 0.0, 1.0 }]; + CN1MetalBeginFrame(self.renderCommandEncoder, projectionMatrix, framebufferWidth, framebufferHeight); + + float crPx = cornerRadius < 0.0f ? -1.0f : cornerRadius * (float)s; + CN1MetalDrawGlassLens(scratch, sx0, sy0, sw, sh, qx, qy, qw, qh, (float)s, lx, ly, lw, lh, crPx, o, amount); +#ifndef CN1_USE_ARC + [scratch release]; +#endif +} + @end #endif diff --git a/Ports/iOSPort/nativeSources/BlurRegion.h b/Ports/iOSPort/nativeSources/BlurRegion.h index 9693bbdff34..d2460e7e6dc 100644 --- a/Ports/iOSPort/nativeSources/BlurRegion.h +++ b/Ports/iOSPort/nativeSources/BlurRegion.h @@ -23,6 +23,8 @@ #import #import "ExecutableOp.h" +@class GLUIImage; + // Queued op for CSS backdrop-filter:blur on the LIVE screen (real "Liquid // Glass"). Enqueued in paint order; during the drain (after the backdrop ops, // before the glass component's foreground) it asks the METALView to blur the @@ -52,6 +54,18 @@ float aberration; int tintColor; float tintStrength; + // When colorMatrix is YES this op runs Graphics.colorMatrixRegion + // (colorMatrixScreenRegionX): each pixel becomes mix(p, clamp(M.p + off), k) + // with k = amount * shape coverage * mask alpha. matrix is row-major + // [r0 g0 b0 off0 | r1 g1 b1 off1 | r2 g2 b2 off2], maskImage may be nil. + BOOL colorMatrix; + float matrix[12]; + GLUIImage* maskImage; + float amount; + // When glassLens is YES this op runs Graphics.glassLensRegion + // (glassLensScreenRegionX) with the GlassLensBlend parameters in optics. + BOOL glassLens; + float optics[16]; } -(id)initWithArgs:(int)xpos ypos:(int)ypos w:(int)w h:(int)h r:(float)r; @@ -61,5 +75,10 @@ -(id)initWithLensArgs:(int)xpos ypos:(int)ypos w:(int)w h:(int)h cornerRadius:(float)cr magnify:(float)mg aberration:(float)ab tintColor:(int)tc tintStrength:(float)ts; +-(id)initWithGlassLensArgs:(int)xpos ypos:(int)ypos w:(int)w h:(int)h cornerRadius:(float)cr + optics:(const float*)o count:(int)count amount:(float)am; +-(id)initWithColorMatrixArgs:(int)xpos ypos:(int)ypos w:(int)w h:(int)h + matrix:(const float*)m mask:(GLUIImage*)mask + cornerRadius:(float)cr amount:(float)am; @end diff --git a/Ports/iOSPort/nativeSources/BlurRegion.m b/Ports/iOSPort/nativeSources/BlurRegion.m index df18dc4888d..54809bc1c10 100644 --- a/Ports/iOSPort/nativeSources/BlurRegion.m +++ b/Ports/iOSPort/nativeSources/BlurRegion.m @@ -22,6 +22,7 @@ */ #import "BlurRegion.h" #import "CodenameOne_GLViewController.h" +#import "GLUIImage.h" #include "TargetConditionals.h" #ifdef CN1_USE_METAL #import "METALView.h" @@ -77,11 +78,58 @@ -(id)initWithLensArgs:(int)xpos ypos:(int)ypos w:(int)w h:(int)h return self; } +-(id)initWithGlassLensArgs:(int)xpos ypos:(int)ypos w:(int)w h:(int)h cornerRadius:(float)cr + optics:(const float*)o count:(int)count amount:(float)am { + x = xpos; + y = ypos; + width = w; + height = h; + glass = NO; + lens = NO; + colorMatrix = NO; + glassLens = YES; + for (int i = 0; i < 16; i++) { + optics[i] = i < count ? o[i] : 0.0f; + } + cornerRadius = cr; + amount = am; + return self; +} + +-(id)initWithColorMatrixArgs:(int)xpos ypos:(int)ypos w:(int)w h:(int)h + matrix:(const float*)m mask:(GLUIImage*)mask + cornerRadius:(float)cr amount:(float)am { + x = xpos; + y = ypos; + width = w; + height = h; + glass = NO; + lens = NO; + colorMatrix = YES; + for (int i = 0; i < 12; i++) { + matrix[i] = m[i]; + } + maskImage = mask; +#ifndef CN1_USE_ARC + [maskImage retain]; +#endif + cornerRadius = cr; + amount = am; + return self; +} + -(void)execute { #if defined(CN1_USE_METAL) && !TARGET_OS_WATCH id view = [[CodenameOne_GLViewController instance] renderingView]; if ([view isKindOfClass:[METALView class]]) { - if (lens) { + if (glassLens) { + [(METALView*)view glassLensScreenRegionX:x y:y w:width h:height cornerRadius:cornerRadius + optics:optics amount:amount]; + } else if (colorMatrix) { + [(METALView*)view colorMatrixScreenRegionX:x y:y w:width h:height matrix:matrix + mask:(maskImage == nil ? nil : [maskImage getMTLTexture]) + cornerRadius:cornerRadius amount:amount]; + } else if (lens) { [(METALView*)view lensScreenRegionX:x y:y w:width h:height cornerRadius:cornerRadius magnify:magnify aberration:aberration tintColor:tintColor tintStrength:tintStrength]; @@ -105,6 +153,7 @@ -(NSString*)getName { #ifndef CN1_USE_ARC -(void)dealloc { + [maskImage release]; [super dealloc]; } #endif diff --git a/Ports/iOSPort/nativeSources/CN1MetalPipelineCache.m b/Ports/iOSPort/nativeSources/CN1MetalPipelineCache.m index 1fb459a57f9..8ba41398bd0 100644 --- a/Ports/iOSPort/nativeSources/CN1MetalPipelineCache.m +++ b/Ports/iOSPort/nativeSources/CN1MetalPipelineCache.m @@ -147,6 +147,36 @@ static void configureStencilWriteOnly(MTLRenderPipelineColorAttachmentDescriptor desc.fragmentFunction = [library newFunctionWithName:@"cn1_fs_lens"]; configureBlendPremultiplied(desc.colorAttachments[0]); break; + case CN1MetalPipelineGlassMaterial: + desc.vertexFunction = [library newFunctionWithName:@"cn1_vs_fullscreen"]; + desc.fragmentFunction = [library newFunctionWithName:@"cn1_fs_glass_material"]; + configureBlendDisabled(desc.colorAttachments[0]); + break; + case CN1MetalPipelineGlassBoxH: + desc.vertexFunction = [library newFunctionWithName:@"cn1_vs_fullscreen"]; + desc.fragmentFunction = [library newFunctionWithName:@"cn1_fs_glass_box_h"]; + configureBlendDisabled(desc.colorAttachments[0]); + break; + case CN1MetalPipelineGlassBoxV: + desc.vertexFunction = [library newFunctionWithName:@"cn1_vs_fullscreen"]; + desc.fragmentFunction = [library newFunctionWithName:@"cn1_fs_glass_box_v"]; + configureBlendDisabled(desc.colorAttachments[0]); + break; + case CN1MetalPipelineGlassOptics: + desc.vertexFunction = [library newFunctionWithName:@"cn1_vs_textured"]; + desc.fragmentFunction = [library newFunctionWithName:@"cn1_fs_glass_optics"]; + configureBlendPremultiplied(desc.colorAttachments[0]); + break; + case CN1MetalPipelineGlassLens: + desc.vertexFunction = [library newFunctionWithName:@"cn1_vs_textured"]; + desc.fragmentFunction = [library newFunctionWithName:@"cn1_fs_glass_lens"]; + configureBlendDisabled(desc.colorAttachments[0]); + break; + case CN1MetalPipelineColorMatrix: + desc.vertexFunction = [library newFunctionWithName:@"cn1_vs_textured"]; + desc.fragmentFunction = [library newFunctionWithName:@"cn1_fs_colormatrix"]; + configureBlendPremultiplied(desc.colorAttachments[0]); + break; default: return nil; } @@ -156,6 +186,11 @@ static void configureStencilWriteOnly(MTLRenderPipelineColorAttachmentDescriptor // this Metal aborts the draw call with a pixel-format mismatch even // for shaders that never engage the stencil test. desc.stencilAttachmentPixelFormat = MTLPixelFormatStencil8; + if (pipeline == CN1MetalPipelineGlassMaterial || pipeline == CN1MetalPipelineGlassBoxH + || pipeline == CN1MetalPipelineGlassBoxV) { + // Offscreen passes into scratch textures, which carry no stencil. + desc.stencilAttachmentPixelFormat = MTLPixelFormatInvalid; + } if (desc.vertexFunction == nil || desc.fragmentFunction == nil) { NSLog(@"CN1MetalPipelineCache: shader function missing for pipeline %ld", (long)pipeline); return nil; diff --git a/Ports/iOSPort/nativeSources/CN1MetalShaders.metal b/Ports/iOSPort/nativeSources/CN1MetalShaders.metal index 4cdfdb2b82e..6601346ca93 100644 --- a/Ports/iOSPort/nativeSources/CN1MetalShaders.metal +++ b/Ports/iOSPort/nativeSources/CN1MetalShaders.metal @@ -514,6 +514,321 @@ fragment float4 cn1_fs_lens( return float4(fr / 255.0 * alpha, fg / 255.0 * alpha, fb / 255.0 * alpha, alpha); } +// Graphics.colorMatrixRegion. Mirrors com.codename1.ui.plaf.ColorMatrixBlend (the +// reference the JavaSE port runs): v = clamp(M.p + off), k = amount * shape * mask +// alpha, result mix(p, v, k). Drawn premultiplied as (v*k, k) OVER p, which is the +// same mix and leaves the destination alpha alone. +fragment float4 cn1_fs_colormatrix( + VertexOutTextured in [[stage_in]], + constant float4 *rows [[buffer(0)]], // 3 matrix rows (r,g,b,off), then (fw, fh, cornerRadiusPx, amount) + constant float4 &flags [[buffer(1)]], // x: 1 = mask bound + texture2d src [[texture(0)]], + texture2d mask [[texture(1)]]) +{ + constexpr sampler nearestSmp(mag_filter::nearest, min_filter::nearest, address::clamp_to_edge); + // The mask may be stretched over the region (the pulsing tab bar), so it is + // filtered; the source is the region itself, pixel for pixel. + constexpr sampler maskSmp(mag_filter::linear, min_filter::linear, address::clamp_to_edge); + float fw = rows[3].x, fh = rows[3].y, cornerRadius = rows[3].z, amount = rows[3].w; + float hw = fw * 0.5, hh = fh * 0.5; + float k = amount; + if (cornerRadius != 0.0) { + float r = (cornerRadius < 0.0) ? min(hw, hh) : min(cornerRadius, min(hw, hh)); + float px = in.texcoord.x * fw - hw; + float py = in.texcoord.y * fh - hh; + float dxe = abs(px) - (hw - r); + float dye = abs(py) - (hh - r); + float ax = max(dxe, 0.0), ay = max(dye, 0.0); + float sdf = sqrt(ax * ax + ay * ay) + min(max(dxe, dye), 0.0) - r; + k *= clamp(0.5 - sdf, 0.0, 1.0); + } + if (flags.x > 0.5) { + k *= mask.sample(maskSmp, in.texcoord).a; + } + if (k <= 0.0) { return float4(0.0); } + float3 p = src.sample(nearestSmp, in.texcoord).rgb; + float3 v = float3(dot(rows[0].xyz, p) + rows[0].w, + dot(rows[1].xyz, p) + rows[1].w, + dot(rows[2].xyz, p) + rows[2].w); + v = clamp(v, 0.0, 1.0); + k = min(k, 1.0); + return float4(v * k, k); +} + +// ---- Liquid Glass on the GPU (CN1MetalGlassEncode / CN1MetalDrawGlass) ---- +// A port of METALView.m's CPU glass (the material loop, glassGaussianBlur and +// glassApplyOptics) that keeps its integer rounding: every intermediate is an +// 8-bit value, truncated or rounded exactly where the C code does, so the GPU +// patch matches the CPU one. + +struct VertexOutFullscreen { + float4 position [[position]]; +}; + +// One triangle covering the viewport; the fragment works from [[position]]. +vertex VertexOutFullscreen cn1_vs_fullscreen(uint vid [[vertex_id]]) { + float2 p = float2(float((vid << 1) & 2), float(vid & 2)); + VertexOutFullscreen o; + o.position = float4(p.x * 2.0 - 1.0, 1.0 - p.y * 2.0, 0.0, 1.0); + return o; +} + +static inline int cn1_glass_byte(float unit) { + return int(rint(unit * 255.0)); +} + +// params: x,y = the padded buffer's origin inside `src`; z,w = src size. +// material: sat, scale, offset, curve; material2.x = curveMid. +fragment float4 cn1_fs_glass_material( + VertexOutFullscreen in [[stage_in]], + constant int4 ¶ms [[buffer(0)]], + constant float4 &material [[buffer(1)]], + constant float4 &material2 [[buffer(2)]], + texture2d src [[texture(0)]]) +{ + int bx = int(in.position.x); + int by = int(in.position.y); + int ax = clamp(bx + params.x, 0, params.z - 1); + int ay = clamp(by + params.y, 0, params.w - 1); + float4 c = src.read(uint2(ax, ay)); + float rch = float(cn1_glass_byte(c.r)); + float gch = float(cn1_glass_byte(c.g)); + float bch = float(cn1_glass_byte(c.b)); + float sat = material.x, scale = material.y, offset = material.z, curve = material.w; + float lum = 0.2126 * rch + 0.7152 * gch + 0.0722 * bch; + float rr = (lum + (rch - lum) * sat) * scale + offset; + float gg = (lum + (gch - lum) * sat) * scale + offset; + float bb = (lum + (bch - lum) * sat) * scale + offset; + if (curve != 0.0) { + float d = lum / 255.0 - material2.x; + float k = curve * 255.0 * d * d; + rr += k; gg += k; bb += k; + } + // (int) in C truncates toward zero; the clamp comes first. + float ri = rr < 0.0 ? 0.0 : (rr > 255.0 ? 255.0 : trunc(rr)); + float gi = gg < 0.0 ? 0.0 : (gg > 255.0 ? 255.0 : trunc(gg)); + float bi = bb < 0.0 ? 0.0 : (bb > 255.0 ? 255.0 : trunc(bb)); + return float4(ri, gi, bi, 255.0) / 255.0; +} + +// One direction of a box-blur iteration: the mean of 2r+1 edge-clamped +// samples, rounded like glassBoxBlurOnce. params: x = r, y = width, z = height. +static inline float4 cn1_glass_box(VertexOutFullscreen in, int4 params, + texture2d src, bool horizontal) { + int x = int(in.position.x); + int y = int(in.position.y); + int r = params.x; + int sr = 0, sg = 0, sb = 0; + for (int k = -r; k <= r; k++) { + int xx = horizontal ? clamp(x + k, 0, params.y - 1) : x; + int yy = horizontal ? y : clamp(y + k, 0, params.z - 1); + float4 c = src.read(uint2(xx, yy)); + sr += cn1_glass_byte(c.r); + sg += cn1_glass_byte(c.g); + sb += cn1_glass_byte(c.b); + } + float norm = 1.0 / float(2 * r + 1); + return float4(float(int(float(sr) * norm + 0.5)), float(int(float(sg) * norm + 0.5)), + float(int(float(sb) * norm + 0.5)), 255.0) / 255.0; +} + +fragment float4 cn1_fs_glass_box_h(VertexOutFullscreen in [[stage_in]], constant int4 ¶ms [[buffer(0)]], + texture2d src [[texture(0)]]) { + return cn1_glass_box(in, params, src, true); +} + +fragment float4 cn1_fs_glass_box_v(VertexOutFullscreen in [[stage_in]], constant int4 ¶ms [[buffer(0)]], + texture2d src [[texture(0)]]) { + return cn1_glass_box(in, params, src, false); +} + +static inline int cn1_glass_bilerp(int c00, int c10, int c01, int c11, float tx, float ty) { + float top = float(c00) + float(c10 - c00) * tx; + float bot = float(c01) + float(c11 - c01) * tx; + return int(top + (bot - top) * ty + 0.5); +} + +static inline int3 cn1_glass_texel(texture2d t, int x, int y) { + float4 c = t.read(uint2(x, y)); + return int3(cn1_glass_byte(c.r), cn1_glass_byte(c.g), cn1_glass_byte(c.b)); +} + +static inline int cn1_glass_outline_channel(int c, int b, float alpha, float w) { + float v = float(c) * alpha; + float under = float(b) * (1.0 - alpha); + v = v + under; + float dark = 76.0 * w; + float alt = 0.78 * w * float(b); + if (alt < dark) { dark = alt; } + v = v - dark; + return v <= 0.0 ? 0 : (v >= 255.0 ? 255 : int(v + 0.5)); +} + +// The optics of glassApplyOptics, drawn as the patch itself. +// p0: fw, fh, cornerRadius (logical; < 0 = capsule), s +// p1: refract, specular, outline, unused +// p2 (int): pad, bw, bh, unused; p3 (int): rawX, rawY, rawW, rawH +fragment float4 cn1_fs_glass_optics( + VertexOutTextured in [[stage_in]], + constant float4 &p0 [[buffer(0)]], + constant float4 &p1 [[buffer(1)]], + constant int4 &p2 [[buffer(2)]], + constant int4 &p3 [[buffer(3)]], + texture2d blurred [[texture(0)]], + texture2d raw [[texture(1)]]) +{ + float fw = p0.x, fh = p0.y, cornerRadius = p0.z, s = p0.w; + float refract = p1.x, specular = p1.y, outline = p1.z; + int pad = p2.x, bw = p2.y, bh = p2.z; + int x = clamp(int(in.texcoord.x * fw), 0, int(fw) - 1); + int y = clamp(int(in.texcoord.y * fh), 0, int(fh) - 1); + int rw = int(fw), rh = int(fh); + float hw = float(rw) / 2.0, hh = float(rh) / 2.0; + float minhh = hw < hh ? hw : hh; + float r; + if (cornerRadius < 0.0) { r = minhh; } else { r = cornerRadius * s; if (r > minhh) r = minhh; } + if (r < 0.0) r = 0.0; + float band = minhh * 0.6; + float rimW = 3.0 * s; + float px = float(x) + 0.5, py = float(y) + 0.5; + float dx = fabs(px - hw) - (hw - r); + float dy = fabs(py - hh) - (hh - r); + float axx = dx > 0.0 ? dx : 0.0, ayy = dy > 0.0 ? dy : 0.0; + float outside = sqrt(axx * axx + ayy * ayy); + float mxv = dx > dy ? dx : dy; + float inside = mxv < 0.0 ? mxv : 0.0; + float depth = -(outside + inside - r); + if (depth <= 0.0) { return float4(0.0); } + float alpha = depth >= 1.0 ? 1.0 : depth; + if (cornerRadius == 0.0) { + float fb = float(rh) * 0.22; + if (fb > 1.0 && py > float(rh) - fb) { + float fade = (float(rh) - py) / fb; + if (fade < 0.0) fade = 0.0; + alpha *= fade; + } + } + float sx = float(x), sy = float(y); + if (refract > 0.0 && band > 0.0 && depth < band) { + float t = 1.0 - depth / band; + float distortion = 1.0 - sqrt(max(0.0, 1.0 - t * t)); + sx = float(x) - (px - hw) * distortion * refract; + sy = float(y) - (py - hh) * distortion * refract; + } + float fx = sx + float(pad), fy = sy + float(pad); + fx = clamp(fx, 0.0, float(bw - 1)); + fy = clamp(fy, 0.0, float(bh - 1)); + int x0 = int(fx), y0 = int(fy); + int x1 = x0 + 1 < bw ? x0 + 1 : x0, y1 = y0 + 1 < bh ? y0 + 1 : y0; + float tx = fx - float(x0), ty = fy - float(y0); + int3 c00 = cn1_glass_texel(blurred, x0, y0), c10 = cn1_glass_texel(blurred, x1, y0); + int3 c01 = cn1_glass_texel(blurred, x0, y1), c11 = cn1_glass_texel(blurred, x1, y1); + int rr = cn1_glass_bilerp(c00.x, c10.x, c01.x, c11.x, tx, ty); + int gg = cn1_glass_bilerp(c00.y, c10.y, c01.y, c11.y, tx, ty); + int bb = cn1_glass_bilerp(c00.z, c10.z, c01.z, c11.z, tx, ty); + if (specular > 0.0 && depth < rimW) { + float rim = 1.0 - depth / rimW; + float topBias = 0.55 + 0.45 * (1.0 - py / float(rh)); + int add = int(specular * rim * topBias * 70.0); + rr = min(rr + add, 255); + gg = min(gg + add, 255); + bb = min(bb + add, 255); + } + if (outline > 0.0 && depth < 1.0 && p3.z > 0) { + float wx; + if (dx > 0.0 && dy > 0.0) { wx = outside > 0.0 ? axx / outside : 0.0; } + else { wx = dx >= dy ? 1.0 : 0.0; } + float ow = outline * wx; + if (ow > 0.0) { + int3 bk = cn1_glass_texel(raw, clamp(x + p3.x, 0, p3.z - 1), clamp(y + p3.y, 0, p3.w - 1)); + rr = cn1_glass_outline_channel(rr, bk.x, alpha, ow); + gg = cn1_glass_outline_channel(gg, bk.y, alpha, ow); + bb = cn1_glass_outline_channel(bb, bk.z, alpha, ow); + return float4(float(rr), float(gg), float(bb), 255.0) / 255.0; + } + } + int a = int(alpha * 255.0); + return float4(float(rr * a / 255), float(gg * a / 255), float(bb * a / 255), float(a)) / 255.0; +} + +// Graphics.glassLensRegion -- a line-for-line port of +// com.codename1.ui.plaf.GlassLensBlend.apply (the reference the JavaSE port runs). +// geo: the quad in physical pixels; lens: the lens rect (physical); +// src: the blit's physical origin and size; misc.x: corner radius (< 0 capsule), +// misc.y: amount; o: the GlassLensBlend parameters (lengths in physical pixels). +static inline float cn1_glass_lens_disp(float s, float h, float mx) { + if (s >= h || h <= 0.0) { return 0.0; } + float t = (h - s) / h; + float d = (h - s) / sqrt(max(1e-6, 1.0 - t * t)); + return min(d, mx); +} + +fragment float4 cn1_fs_glass_lens( + VertexOutTextured in [[stage_in]], + constant float4 &geo [[buffer(0)]], + constant float4 &lens [[buffer(1)]], + constant float4 &srcr [[buffer(2)]], + constant float4 &misc [[buffer(3)]], + constant float *o [[buffer(4)]], + texture2d src [[texture(0)]]) +{ + constexpr sampler lin(mag_filter::linear, min_filter::linear, address::clamp_to_edge); + float2 P = geo.xy + in.texcoord * geo.zw; // pixel centre, physical + float2 size = float2(src.get_width(), src.get_height()); + float2 tp = P - srcr.xy; // inside the blit + float4 base4 = src.sample(lin, tp / size); + float3 base = base4.rgb; + float amount = misc.y; + float hw = lens.z * 0.5, hh = lens.w * 0.5; + float2 c = lens.xy + float2(hw, hh); + float r = misc.x < 0.0 ? min(hw, hh) : min(misc.x, min(hw, hh)); + float px = P.x - c.x, py = P.y - c.y; + float dx = fabs(px) - (hw - r), dy = fabs(py) - (hh - r); + float ax = max(dx, 0.0), ay = max(dy, 0.0); + float outsideD = sqrt(ax * ax + ay * ay); + float sdf = outsideD + min(max(dx, dy), 0.0) - r; + float nx, ny; + if (dx > 0.0 && dy > 0.0 && outsideD > 0.0) { nx = ax / outsideD; ny = ay / outsideD; } + else if (dx > dy) { nx = 1.0; ny = 0.0; } + else { nx = 0.0; ny = 1.0; } + nx = px < 0.0 ? -nx : nx; + ny = py < 0.0 ? -ny : ny; + float s = -sdf; + float3 outc = base; + if (s > 0.0) { + float d = cn1_glass_lens_disp(s, o[0], o[1]); + float disp = o[2]; + float2 n = float2(nx, ny); + float cr = src.sample(lin, (tp + n * d * (1.0 - disp)) / size).r; + float cg = src.sample(lin, (tp + n * d) / size).g; + float cb = src.sample(lin, (tp + n * d * (1.0 + disp)) / size).b; + float shade = 1.0; + if (o[9] > 0.0) { + float w = o[9]; + float band = s < w * 0.7 ? 1.0 : (s >= w ? 0.0 : smoothstep(0.0, 1.0, (w - s) / (w * 0.3))); + shade -= o[8] * fabs(nx) * band; + } + float rim = 0.0; + float past = s - o[4]; + if (o[7] > 0.0 && past >= 0.0) { + rim = (o[5] + o[6] * fabs(ny)) * exp(-past / o[7]); + } + float3 inside = float3(cr, cg, cb) * shade + o[10] + rim; + float cov = min(s, 1.0); + outc = base + (inside - base) * cov; + } else if (o[11] > 0.0 && o[12] > 0.0 && ny > 0.0) { + float t = (sdf - o[12]) / o[12]; + outc *= 1.0 - o[11] * ny * exp(-t * t); + } + if (o[4] > 0.0) { + float halfW = o[4] * 0.5; + float ring = 1.0 - fabs(s - halfW) / halfW; + if (ring > 0.0) { outc *= 1.0 - o[3] * min(ring, 1.0); } + } + outc = base + (clamp(outc, 0.0, 1.0) - base) * amount; + return float4(outc, base4.a); +} + // Gaussian blur is implemented via MPSImageGaussianBlur on the host side // (CN1Metalcompat.m) rather than a hand-rolled fragment shader. MPS picks // the kernel width automatically from sigma and stays accurate across the diff --git a/Ports/iOSPort/nativeSources/CN1Metalcompat.h b/Ports/iOSPort/nativeSources/CN1Metalcompat.h index e3da5facb87..57ba8db6ff7 100644 --- a/Ports/iOSPort/nativeSources/CN1Metalcompat.h +++ b/Ports/iOSPort/nativeSources/CN1Metalcompat.h @@ -66,6 +66,13 @@ typedef NS_ENUM(NSInteger, CN1MetalPipeline) { CN1MetalPipelineMultiStopGradient, // CSS-style multi-stop gradient (linear / radial / conic). CN1MetalPipelineLens, // iOS-26 selection-drop lens (samples a blitted bar // region on-GPU; warp + tint + light, see cn1_fs_lens). + CN1MetalPipelineColorMatrix, // Graphics.colorMatrixRegion (samples a blitted screen + // region + optional mask; see cn1_fs_colormatrix). + CN1MetalPipelineGlassMaterial, // Liquid Glass on the GPU: colour material (offscreen, + CN1MetalPipelineGlassBoxH, // no stencil), the two passes of one box-blur + CN1MetalPipelineGlassBoxV, // iteration (offscreen), and the optics drawn on + CN1MetalPipelineGlassOptics, // screen. See CN1MetalGlassEncode. + CN1MetalPipelineGlassLens, // Graphics.glassLensRegion (see cn1_fs_glass_lens). CN1MetalPipelineCount }; @@ -112,6 +119,15 @@ void CN1MetalBeginFrame(id encoder, // active encoder reference so subsequent ops no-op until the next frame. void CN1MetalEndFrame(void); +// Marks the end of a frame (METALView.presentFramebuffer). Until then, a +// CN1MetalBeginFrame on a new encoder is a MID-FRAME restart -- the screen +// effects (blur, glass, lens, colour matrix) end the encoder to read the +// frame and open another -- and carries the graphics state the Java side +// already sent (transform, rectangular clip, polygon clip) onto it, since +// the Java side does not re-send state it believes is still in force. After +// this call the next CN1MetalBeginFrame starts a fresh frame. +void CN1MetalFrameFinished(void); + // Returns the active encoder or nil if no frame is in flight. Ops use this // to skip drawing when setFramebuffer couldn't acquire a drawable. id CN1MetalActiveEncoder(void); @@ -207,6 +223,48 @@ void CN1MetalDrawLens(id texture, int x, int y, int w, int h, int fw, int fh, float magnify, float aberration, int tintColor, float tintStrength, float cornerRadiusPx); +// Graphics.colorMatrixRegion: draws a quad at (x,y,w,h) logical coords sampling +// `texture` (a fw x fh px blitted copy of that screen region) and, when non-nil, +// `mask` (stretched over the region, sampled the way CN1MetalDrawImage would +// draw it there). Each pixel becomes mix(p, clamp(M.p + off), k) with +// k = amount * shape coverage * mask alpha; matrix is row-major 3x4. +// cornerRadiusPx < 0 = capsule, 0 = the whole rectangle. +void CN1MetalDrawColorMatrix(id texture, id mask, int x, int y, int w, int h, + int fw, int fh, const float* matrix, float cornerRadiusPx, float amount); + +// Liquid Glass (glassScreenRegion) on the GPU, the same three stages as the CPU +// glassScreenRegionX path -- colour material, a triple box blur approximating a +// Gaussian, then the optics (shape, edge refraction, specular rim, outline) -- with +// the CPU code's integer rounding, so both produce the same patch. Encode runs the +// offscreen passes on `cb` (no render encoder may be open) and hands back the +// blurred material and the raw backdrop; Draw then draws the patch on the open +// screen encoder. Encode returns NO when the pipelines are unavailable. +typedef struct { + id blurred; // bw x bh, the component at (pad, pad) + id raw; // the unmodified backdrop around the component + int pad; + int bw; + int bh; + int rawX; // the component's origin inside `raw` + int rawY; + int rawW; // the part of `raw` holding backdrop pixels + int rawH; +} CN1MetalGlassPatch; +BOOL CN1MetalGlassEncode(id cb, id backdrop, int fx, int fy, int fw, int fh, + float rad, float sat, float scale, float offset, float curve, float curveMid, + CN1MetalGlassPatch *out); +void CN1MetalDrawGlass(const CN1MetalGlassPatch *patch, int x, int y, int w, int h, int fw, int fh, + float cornerRadius, float s, float refract, float specular, float outline); + +// Graphics.glassLensRegion: draws the quad (qx,qy,qw,qh) logical -- the lens plus +// its margin -- sampling `texture`, a blit of the screen whose pixel (0,0) is the +// physical pixel (srcX, srcY). The lens rect (lx,ly,lw,lh) and the optics lengths +// are in physical pixels; s = physical pixels per logical unit. +void CN1MetalDrawGlassLens(id texture, int srcX, int srcY, int srcW, int srcH, + int qx, int qy, int qw, int qh, float s, + float lx, float ly, float lw, float lh, float cornerRadiusPx, + const float* optics, float amount); + // Tile an RGBA image across (x,y,w,h). imageWidth/imageHeight are the // natural size of the source UIImage. Issues one textured quad per // tile (full or clipped at the right/bottom edges); a future batched diff --git a/Ports/iOSPort/nativeSources/CN1Metalcompat.m b/Ports/iOSPort/nativeSources/CN1Metalcompat.m index a4bb9ef9d76..24bc35555ba 100644 --- a/Ports/iOSPort/nativeSources/CN1Metalcompat.m +++ b/Ports/iOSPort/nativeSources/CN1Metalcompat.m @@ -46,6 +46,21 @@ static int currentFramebufferHeight = 0; static CN1MetalPipelineCache *pipelineCache = nil; +// Graphics state a mid-frame encoder restart must carry over (see +// CN1MetalFrameFinished): whether a frame is live, the last scissor set on the +// screen encoder, and the active polygon clip (its points, since a new render +// pass starts with a cleared stencil; 0 = none, -1 = degenerate / clip all). +static BOOL frameLive = NO; +// YES while a mutable image's encoder is swapped in for the screen's (see +// CN1MetalBeginMutableImageDraw); its clips are not the screen's to carry. +static BOOL savedScreenStateValid = NO; +static BOOL carriedScissorValid = NO; +static int carriedScissor[4]; +static float *carriedPolyX = NULL; +static float *carriedPolyY = NULL; +static int carriedPolyCapacity = 0; +static int carriedPolyN = 0; + // --------------- Per-encoder state cache --------------- // // Every drawQuad / drawSolidPrimitive used to call setRenderPipelineState @@ -199,6 +214,11 @@ void CN1MetalBeginFrame(id encoder, simd_float4x4 projection, int framebufferWidth, int framebufferHeight) { + BOOL resume = frameLive && encoder != nil; + simd_float4x4 carriedTransform = currentTransform; + BOOL scissorValid = carriedScissorValid; + int scissor[4] = { carriedScissor[0], carriedScissor[1], carriedScissor[2], carriedScissor[3] }; + int polyN = carriedPolyN; activeEncoder = encoder; invalidateEncoderStateCache(); currentProjection = projection; @@ -207,10 +227,33 @@ void CN1MetalBeginFrame(id encoder, // modelView is always identity for 2D UI rendering. The GL path uses it // only as a y-flip in drawFrame; our ortho projection bakes the flip in. currentModelView = identityMatrix(); - if (modelViewStackTop == 0) { - currentTransform = identityMatrix(); - } ensurePipelineCache(); + if (!resume) { + if (modelViewStackTop == 0) { + currentTransform = identityMatrix(); + } + carriedScissorValid = NO; + carriedPolyN = 0; + frameLive = encoder != nil; + return; + } + // Mid-frame restart: put back what the previous encoder had. The polygon is + // re-stamped first (it opens the scissor), then the scissor narrowed again. + currentTransform = carriedTransform; + if (polyN > 0) { + CN1MetalApplyPolygonStencilClip(carriedPolyX, carriedPolyY, polyN); + } else if (polyN < 0) { + CN1MetalApplyPolygonStencilClip(NULL, NULL, 0); + } + if (scissorValid) { + CN1MetalSetScissor(scissor[0], scissor[1], scissor[2], scissor[3]); + } +} + +void CN1MetalFrameFinished(void) { + frameLive = NO; + carriedScissorValid = NO; + carriedPolyN = 0; } void CN1MetalEndFrame(void) { @@ -339,6 +382,13 @@ void CN1MetalRotate(float angle, float x, float y, float z) { void CN1MetalSetScissor(int x, int y, int width, int height) { if (activeEncoder == nil) return; + if (!savedScreenStateValid) { + carriedScissor[0] = x; + carriedScissor[1] = y; + carriedScissor[2] = width; + carriedScissor[3] = height; + carriedScissorValid = YES; + } if (width <= 0 || height <= 0) { // Empty clip -- this MUST cull everything, it does NOT mean // "disable clipping". A 0/negative-size rect arrives here when the @@ -448,6 +498,36 @@ static void ensureDepthStencilStates(void) { void CN1MetalApplyPolygonStencilClip(const float *xCoords, const float *yCoords, int num) { if (activeEncoder == nil || pipelineCache == nil) return; + if (savedScreenStateValid) { + // Drawing into a mutable image: nothing to carry for the screen. + } else if (num >= 3 && xCoords != NULL && yCoords != NULL) { + // Remember the polygon so a mid-frame restart can re-stamp it (the + // restart itself passes these same buffers back in). + if (xCoords != carriedPolyX) { + if (num > carriedPolyCapacity) { + float *nx = (float *)realloc(carriedPolyX, sizeof(float) * (size_t)num); + float *ny = (float *)realloc(carriedPolyY, sizeof(float) * (size_t)num); + if (nx != NULL) { + carriedPolyX = nx; + } + if (ny != NULL) { + carriedPolyY = ny; + } + if (nx != NULL && ny != NULL) { + carriedPolyCapacity = num; + } + } + if (num <= carriedPolyCapacity) { + memcpy(carriedPolyX, xCoords, sizeof(float) * (size_t)num); + memcpy(carriedPolyY, yCoords, sizeof(float) * (size_t)num); + carriedPolyN = num; + } else { + carriedPolyN = 0; + } + } + } else { + carriedPolyN = -1; + } if (num < 3 || xCoords == NULL || yCoords == NULL) { // Degenerate polygon: nothing inside it can pass -- emulate by // shrinking the scissor to a zero-size rect (matches the @@ -534,6 +614,9 @@ void CN1MetalApplyPolygonStencilClip(const float *xCoords, const float *yCoords, void CN1MetalDisablePolygonStencilClip(void) { if (activeEncoder == nil) return; + if (!savedScreenStateValid) { + carriedPolyN = 0; + } ensureDepthStencilStates(); [activeEncoder setDepthStencilState:depthStencilStateAlwaysPass]; } @@ -863,6 +946,237 @@ void CN1MetalDrawLens(id texture, int x, int y, int w, int h, [activeEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; } +void CN1MetalDrawColorMatrix(id texture, id mask, int x, int y, int w, int h, + int fw, int fh, const float* matrix, float cornerRadiusPx, float amount) { + if (activeEncoder == nil || pipelineCache == nil || texture == nil || matrix == NULL) { + return; + } + id state = [pipelineCache pipelineFor:CN1MetalPipelineColorMatrix]; + if (state == nil) { + return; + } + bindPipelineStateIfChanged(state); + float vertices[8] = { + (float)x, (float)y, + (float)(x+w), (float)y, + (float)x, (float)(y+h), + (float)(x+w), (float)(y+h) + }; + // The source is a direct blit of the screen region (row 0 = region top) and the + // mask is sampled exactly as CN1MetalDrawImage would draw it over the region, so + // both use the V=0-at-top mapping. + static const float texcoords[8] = { 0, 0, 1, 0, 0, 1, 1, 1 }; + [activeEncoder setVertexBytes:vertices length:sizeof(float) * 8 atIndex:0]; + uploadMatricesIfChanged(1); + [activeEncoder setVertexBytes:texcoords length:sizeof(float) * 8 atIndex:2]; + simd_float4 rows[4] = { + (simd_float4){ matrix[0], matrix[1], matrix[2], matrix[3] }, + (simd_float4){ matrix[4], matrix[5], matrix[6], matrix[7] }, + (simd_float4){ matrix[8], matrix[9], matrix[10], matrix[11] }, + (simd_float4){ (float)fw, (float)fh, cornerRadiusPx, amount } + }; + simd_float4 flags = (simd_float4){ mask != nil ? 1.0f : 0.0f, 0.0f, 0.0f, 0.0f }; + [activeEncoder setFragmentBytes:rows length:sizeof(rows) atIndex:0]; + [activeEncoder setFragmentBytes:&flags length:sizeof(flags) atIndex:1]; + [activeEncoder setFragmentTexture:texture atIndex:0]; + // A bound texture is required even when unused; the source doubles as a dummy. + [activeEncoder setFragmentTexture:(mask != nil ? mask : texture) atIndex:1]; + [activeEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; +} + +// Scratch textures of the GPU glass, reused across frames: 0 = the raw backdrop, +// 1 and 2 = the material / blur ping-pong pair. Sizes are rounded up so a glass +// rect that changes every frame (the pulsing tab bar) does not reallocate them. +static id cn1GlassScratch[3]; + +static id cn1GlassTexture(int slot, int w, int h) { + id t = cn1GlassScratch[slot]; + if (t != nil && (int)t.width >= w && (int)t.height >= h) { + return t; + } + id device = CN1MetalDevice(); + if (device == nil) { + return nil; + } + int aw = (w + 63) & ~63; + int ah = (h + 63) & ~63; + if (t != nil) { + aw = MAX(aw, (int)t.width); + ah = MAX(ah, (int)t.height); + } + MTLTextureDescriptor *desc = [MTLTextureDescriptor + texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:aw height:ah mipmapped:NO]; + desc.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead; + desc.storageMode = MTLStorageModePrivate; + id nt = [device newTextureWithDescriptor:desc]; + CN1_TEX_NOTE("glassGpuScratch", nt); + if (nt == nil) { + return nil; + } +#ifndef CN1_USE_ARC + [cn1GlassScratch[slot] release]; +#endif + cn1GlassScratch[slot] = nt; + return nt; +} + +// One offscreen full-viewport pass of a glass pipeline. +static void cn1GlassPass(id cb, id ps, id target, + int w, int h, id src, const void *b0, size_t l0, const void *b1, size_t l1, + const void *b2, size_t l2) { + MTLRenderPassDescriptor *d = [MTLRenderPassDescriptor renderPassDescriptor]; + d.colorAttachments[0].texture = target; + d.colorAttachments[0].loadAction = MTLLoadActionDontCare; + d.colorAttachments[0].storeAction = MTLStoreActionStore; + id enc = [cb renderCommandEncoderWithDescriptor:d]; + if (enc == nil) { + return; + } + [enc setRenderPipelineState:ps]; + [enc setViewport:(MTLViewport){ 0.0, 0.0, (double)w, (double)h, 0.0, 1.0 }]; + if (b0 != NULL) { [enc setFragmentBytes:b0 length:l0 atIndex:0]; } + if (b1 != NULL) { [enc setFragmentBytes:b1 length:l1 atIndex:1]; } + if (b2 != NULL) { [enc setFragmentBytes:b2 length:l2 atIndex:2]; } + [enc setFragmentTexture:src atIndex:0]; + [enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3]; + [enc endEncoding]; +} + +BOOL CN1MetalGlassEncode(id cb, id backdrop, int fx, int fy, int fw, int fh, + float rad, float sat, float scale, float offset, float curve, float curveMid, + CN1MetalGlassPatch *out) { + if (cb == nil || backdrop == nil || pipelineCache == nil || out == NULL || fw <= 0 || fh <= 0) { + return NO; + } + id pm = [pipelineCache pipelineFor:CN1MetalPipelineGlassMaterial]; + id ph = [pipelineCache pipelineFor:CN1MetalPipelineGlassBoxH]; + id pv = [pipelineCache pipelineFor:CN1MetalPipelineGlassBoxV]; + id po = [pipelineCache pipelineFor:CN1MetalPipelineGlassOptics]; + if (pm == nil || ph == nil || pv == nil || po == nil) { + return NO; + } + int texW = (int)backdrop.width, texH = (int)backdrop.height; + int pad = (int)ceilf(rad) * 3 + 1; + int bw = fw + 2 * pad, bh = fh + 2 * pad; + int ax0 = fx - pad; if (ax0 < 0) ax0 = 0; + int ay0 = fy - pad; if (ay0 < 0) ay0 = 0; + int ax1 = fx + fw + pad; if (ax1 > texW) ax1 = texW; + int ay1 = fy + fh + pad; if (ay1 > texH) ay1 = texH; + int aw = ax1 - ax0, ah = ay1 - ay0; + if (aw <= 0 || ah <= 0) { + return NO; + } + id a = cn1GlassTexture(0, aw, ah); + id b = cn1GlassTexture(1, bw, bh); + id t = cn1GlassTexture(2, bw, bh); + if (a == nil || b == nil || t == nil) { + return NO; + } + id blit = [cb blitCommandEncoder]; + [blit copyFromTexture:backdrop sourceSlice:0 sourceLevel:0 + sourceOrigin:MTLOriginMake(ax0, ay0, 0) sourceSize:MTLSizeMake(aw, ah, 1) + toTexture:a destinationSlice:0 destinationLevel:0 + destinationOrigin:MTLOriginMake(0, 0, 0)]; + [blit endEncoding]; + // 1) Colour material into the padded buffer, edges replicated. + simd_int4 mp = (simd_int4){ fx - pad - ax0, fy - pad - ay0, aw, ah }; + simd_float4 m1 = (simd_float4){ sat, scale, offset, curve }; + simd_float4 m2 = (simd_float4){ curveMid, 0, 0, 0 }; + cn1GlassPass(cb, pm, b, bw, bh, a, &mp, sizeof(mp), &m1, sizeof(m1), &m2, sizeof(m2)); + // 2) Three box-blur iterations (glassGaussianBlur), each horizontal then vertical. + if (rad >= 0.75f) { + int r = (int)(rad + 0.5f); + if (r < 1) { r = 1; } + simd_int4 bp = (simd_int4){ r, bw, bh, 0 }; + for (int i = 0; i < 3; i++) { + cn1GlassPass(cb, ph, t, bw, bh, b, &bp, sizeof(bp), NULL, 0, NULL, 0); + cn1GlassPass(cb, pv, b, bw, bh, t, &bp, sizeof(bp), NULL, 0, NULL, 0); + } + } + out->blurred = b; + out->raw = a; + out->pad = pad; + out->bw = bw; + out->bh = bh; + out->rawX = fx - ax0; + out->rawY = fy - ay0; + out->rawW = aw; + out->rawH = ah; + return YES; +} + +void CN1MetalDrawGlass(const CN1MetalGlassPatch *patch, int x, int y, int w, int h, int fw, int fh, + float cornerRadius, float s, float refract, float specular, float outline) { + if (activeEncoder == nil || pipelineCache == nil || patch == NULL || patch->blurred == nil) { + return; + } + id state = [pipelineCache pipelineFor:CN1MetalPipelineGlassOptics]; + if (state == nil) { + return; + } + bindPipelineStateIfChanged(state); + float vertices[8] = { + (float)x, (float)y, + (float)(x+w), (float)y, + (float)x, (float)(y+h), + (float)(x+w), (float)(y+h) + }; + static const float texcoords[8] = { 0, 0, 1, 0, 0, 1, 1, 1 }; + [activeEncoder setVertexBytes:vertices length:sizeof(float) * 8 atIndex:0]; + uploadMatricesIfChanged(1); + [activeEncoder setVertexBytes:texcoords length:sizeof(float) * 8 atIndex:2]; + simd_float4 p0 = (simd_float4){ (float)fw, (float)fh, cornerRadius, s }; + simd_float4 p1 = (simd_float4){ refract, specular, outline, 0 }; + simd_int4 p2 = (simd_int4){ patch->pad, patch->bw, patch->bh, 0 }; + simd_int4 p3 = (simd_int4){ patch->rawX, patch->rawY, patch->rawW, patch->rawH }; + [activeEncoder setFragmentBytes:&p0 length:sizeof(p0) atIndex:0]; + [activeEncoder setFragmentBytes:&p1 length:sizeof(p1) atIndex:1]; + [activeEncoder setFragmentBytes:&p2 length:sizeof(p2) atIndex:2]; + [activeEncoder setFragmentBytes:&p3 length:sizeof(p3) atIndex:3]; + [activeEncoder setFragmentTexture:patch->blurred atIndex:0]; + [activeEncoder setFragmentTexture:patch->raw atIndex:1]; + [activeEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; +} + +void CN1MetalDrawGlassLens(id texture, int srcX, int srcY, int srcW, int srcH, + int qx, int qy, int qw, int qh, float s, + float lx, float ly, float lw, float lh, float cornerRadiusPx, + const float* optics, float amount) { + if (activeEncoder == nil || pipelineCache == nil || texture == nil || optics == NULL) { + return; + } + id state = [pipelineCache pipelineFor:CN1MetalPipelineGlassLens]; + if (state == nil) { + return; + } + bindPipelineStateIfChanged(state); + float vertices[8] = { + (float)qx, (float)qy, + (float)(qx + qw), (float)qy, + (float)qx, (float)(qy + qh), + (float)(qx + qw), (float)(qy + qh) + }; + static const float texcoords[8] = { 0, 0, 1, 0, 0, 1, 1, 1 }; + [activeEncoder setVertexBytes:vertices length:sizeof(float) * 8 atIndex:0]; + uploadMatricesIfChanged(1); + [activeEncoder setVertexBytes:texcoords length:sizeof(float) * 8 atIndex:2]; + // geo: the quad in physical pixels (x, y, w, h); lens: the lens rect; + // src: the blit's origin and size; misc: corner radius, amount. + simd_float4 geo = (simd_float4){ qx * s, qy * s, qw * s, qh * s }; + simd_float4 lens = (simd_float4){ lx, ly, lw, lh }; + simd_float4 src = (simd_float4){ (float)srcX, (float)srcY, (float)srcW, (float)srcH }; + simd_float4 misc = (simd_float4){ cornerRadiusPx, amount, 0, 0 }; + float o[16]; + for (int i = 0; i < 16; i++) { o[i] = optics[i]; } + [activeEncoder setFragmentBytes:&geo length:sizeof(geo) atIndex:0]; + [activeEncoder setFragmentBytes:&lens length:sizeof(lens) atIndex:1]; + [activeEncoder setFragmentBytes:&src length:sizeof(src) atIndex:2]; + [activeEncoder setFragmentBytes:&misc length:sizeof(misc) atIndex:3]; + [activeEncoder setFragmentBytes:o length:sizeof(o) atIndex:4]; + [activeEncoder setFragmentTexture:texture atIndex:0]; + [activeEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; +} + void CN1MetalTileImage(id texture, int alpha, int x, int y, int width, int height, int imageWidth, int imageHeight) { @@ -1571,7 +1885,6 @@ void CN1MetalDrawAlphaMaskRadial(id texture, static int savedScreenFw = 0; static int savedScreenFh = 0; static uint32_t savedScreenStencilReference = 0; -static BOOL savedScreenStateValid = NO; // Build a Y-down ortho projection for an offscreen (w x h) framebuffer. // Mirrors METALView's CN1MetalOrtho -- if that one ever changes, update @@ -1771,13 +2084,32 @@ BOOL CN1MetalBeginMutableImageDraw(GLUIImage *image) { id device = CN1MetalDevice(); id stencilTex = nil; if (device != nil) { - MTLTextureDescriptor *stencilDesc = [MTLTextureDescriptor - texture2DDescriptorWithPixelFormat:MTLPixelFormatStencil8 - width:(NSUInteger)w height:(NSUInteger)h mipmapped:NO]; - stencilDesc.usage = MTLTextureUsageRenderTarget; - stencilDesc.storageMode = MTLStorageModePrivate; - stencilTex = [device newTextureWithDescriptor:stencilDesc]; - CN1_TEX_NOTE("mutableDrawStencil", stencilTex); + // The stencil only lives for one pass (cleared on load, never stored), + // so passes of the same size share one texture: Metal orders passes on + // this queue, and allocating (and zeroing) a fresh image-sized texture + // per pass dominated frames that draw into an image every frame. + static id cachedStencil = nil; + if (cachedStencil != nil && (int)cachedStencil.width == w && (int)cachedStencil.height == h) { + stencilTex = cachedStencil; +#ifndef CN1_USE_ARC + [stencilTex retain]; +#endif + } else { + MTLTextureDescriptor *stencilDesc = [MTLTextureDescriptor + texture2DDescriptorWithPixelFormat:MTLPixelFormatStencil8 + width:(NSUInteger)w height:(NSUInteger)h mipmapped:NO]; + stencilDesc.usage = MTLTextureUsageRenderTarget; + stencilDesc.storageMode = MTLStorageModePrivate; + stencilTex = [device newTextureWithDescriptor:stencilDesc]; + CN1_TEX_NOTE("mutableDrawStencil", stencilTex); + if (stencilTex != nil) { +#ifndef CN1_USE_ARC + [stencilTex retain]; + [cachedStencil release]; +#endif + cachedStencil = stencilTex; + } + } if (stencilTex != nil) { desc.stencilAttachment.texture = stencilTex; desc.stencilAttachment.loadAction = MTLLoadActionClear; diff --git a/Ports/iOSPort/nativeSources/CodenameOne_GLViewController.m b/Ports/iOSPort/nativeSources/CodenameOne_GLViewController.m index 6fa5aaa83fd..4bbd28a9936 100644 --- a/Ports/iOSPort/nativeSources/CodenameOne_GLViewController.m +++ b/Ports/iOSPort/nativeSources/CodenameOne_GLViewController.m @@ -2718,6 +2718,34 @@ void Java_com_codename1_impl_ios_IOSImplementation_setNativeClippingPolygonGloba #endif } +void Java_com_codename1_impl_ios_IOSImplementation_nativeGlassLensScreenRegionImpl +(int x, int y, int width, int height, float cornerRadius, const float* optics, int count, float amount) { + BlurRegion* f = [[BlurRegion alloc] initWithGlassLensArgs:x ypos:y w:width h:height cornerRadius:cornerRadius + optics:optics count:count amount:amount]; + [CodenameOne_GLViewController upcoming:f]; +#ifndef CN1_USE_ARC + [f release]; +#endif +} + +// Defined further down; declared here for the mask hand-off below. +void* Java_com_codename1_impl_ios_IOSImplementation_finishDrawingOnImageImpl(); + +void Java_com_codename1_impl_ios_IOSImplementation_nativeColorMatrixScreenRegionImpl +(int x, int y, int width, int height, const float* matrix, void* maskPeer, float cornerRadius, float amount) { + if (maskPeer != NULL && ((BRIDGE_CAST void*)[CodenameOne_GLViewController instance].currentMutableImage) == maskPeer) { + // The mask is still being drawn into; close it so its texture is complete. + Java_com_codename1_impl_ios_IOSImplementation_finishDrawingOnImageImpl(); + } + BlurRegion* f = [[BlurRegion alloc] initWithColorMatrixArgs:x ypos:y w:width h:height matrix:matrix + mask:(BRIDGE_CAST GLUIImage*)maskPeer + cornerRadius:cornerRadius amount:amount]; + [CodenameOne_GLViewController upcoming:f]; +#ifndef CN1_USE_ARC + [f release]; +#endif +} + void Java_com_codename1_impl_ios_IOSImplementation_nativeRotateGlobalImpl (float angle, int x, int y) { //CN1Log(@"Java_com_codename1_impl_ios_IOSImplementation_nativeDrawLineGlobalImpl started"); diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index d26f8ef658a..478f226296f 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -2393,6 +2393,38 @@ void com_codename1_impl_ios_IOSNative_nativeGlassScreenRegion___int_int_int_int_ POOL_END(); } +extern void Java_com_codename1_impl_ios_IOSImplementation_nativeGlassLensScreenRegionImpl(int x, int y, int width, int height, float cornerRadius, const float* optics, int count, float amount); +void com_codename1_impl_ios_IOSNative_nativeGlassLensScreenRegion___int_int_int_int_float_float_1ARRAY_float(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject, JAVA_INT x, JAVA_INT y, JAVA_INT w, JAVA_INT h, JAVA_FLOAT cornerRadius, JAVA_OBJECT optics, JAVA_FLOAT amount) +{ + POOL_BEGIN(); + if (optics != JAVA_NULL) { + float o[16]; + int n = ((JAVA_ARRAY)optics)->length; + if (n > 16) { n = 16; } + JAVA_ARRAY_FLOAT* data = (JAVA_ARRAY_FLOAT*)((JAVA_ARRAY)optics)->data; + for (int i = 0; i < n; i++) { + o[i] = data[i]; + } + Java_com_codename1_impl_ios_IOSImplementation_nativeGlassLensScreenRegionImpl(x, y, w, h, cornerRadius, o, n, amount); + } + POOL_END(); +} + +extern void Java_com_codename1_impl_ios_IOSImplementation_nativeColorMatrixScreenRegionImpl(int x, int y, int width, int height, const float* matrix, void* maskPeer, float cornerRadius, float amount); +void com_codename1_impl_ios_IOSNative_nativeColorMatrixScreenRegion___int_int_int_int_float_1ARRAY_long_float_float(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject, JAVA_INT x, JAVA_INT y, JAVA_INT w, JAVA_INT h, JAVA_OBJECT matrix, JAVA_LONG maskPeer, JAVA_FLOAT cornerRadius, JAVA_FLOAT amount) +{ + POOL_BEGIN(); + if (matrix != JAVA_NULL && ((JAVA_ARRAY)matrix)->length >= 12) { + float m[12]; + JAVA_ARRAY_FLOAT* data = (JAVA_ARRAY_FLOAT*)((JAVA_ARRAY)matrix)->data; + for (int i = 0; i < 12; i++) { + m[i] = data[i]; + } + Java_com_codename1_impl_ios_IOSImplementation_nativeColorMatrixScreenRegionImpl(x, y, w, h, m, (void*)maskPeer, cornerRadius, amount); + } + POOL_END(); +} + extern void Java_com_codename1_impl_ios_IOSImplementation_nativeLensScreenRegionImpl(int x, int y, int width, int height, float cornerRadius, float magnify, float aberration, int tintColor, float tintStrength); void com_codename1_impl_ios_IOSNative_nativeLensScreenRegion___int_int_int_int_float_float_float_int_float(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject, JAVA_INT n1, JAVA_INT n2, JAVA_INT n3, JAVA_INT n4, JAVA_FLOAT n5, JAVA_FLOAT n6, JAVA_FLOAT n7, JAVA_INT n8, JAVA_FLOAT n9) { diff --git a/Ports/iOSPort/nativeSources/METALView.h b/Ports/iOSPort/nativeSources/METALView.h index 9e156baca64..c1ab10465c3 100644 --- a/Ports/iOSPort/nativeSources/METALView.h +++ b/Ports/iOSPort/nativeSources/METALView.h @@ -100,6 +100,10 @@ curve:(float)curve curveMid:(float)curveMid outline:(float)outline; - (void)lensScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h cornerRadius:(float)cornerRadius magnify:(float)magnify aberration:(float)aberration tintColor:(int)tintColor tintStrength:(float)tintStrength; +- (void)colorMatrixScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h matrix:(const float*)matrix + mask:(id)mask cornerRadius:(float)cornerRadius amount:(float)amount; +- (void)glassLensScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h cornerRadius:(float)cornerRadius + optics:(const float*)optics amount:(float)amount; -(void)presentPreservedFrameIfNeeded; -(void)updateFrameBufferSize:(int)w h:(int)h; -(void)invalidateRetainedFramebuffer; diff --git a/Ports/iOSPort/nativeSources/METALView.m b/Ports/iOSPort/nativeSources/METALView.m index 24ad9d48c5c..fe84325c293 100644 --- a/Ports/iOSPort/nativeSources/METALView.m +++ b/Ports/iOSPort/nativeSources/METALView.m @@ -906,6 +906,8 @@ -(void)updateFrameBufferSize:(int)w h:(int)h { // the old top at the new top (no vertical flip needed). CN1MetalDrawImage(oldScreen, 255, 0, 0, pw, ph); CN1MetalEndFrame(); + // A one-off pass, not the start of a frame to resume. + CN1MetalFrameFinished(); } [clearEnc endEncoding]; [clearCb commit]; @@ -1314,6 +1316,36 @@ - (void)glassScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h radius:(float)radiu int pad = (int)ceilf(rad) * 3 + 1; int bw = fw + 2 * pad, bh = fh + 2 * pad; + // GPU path: the whole material on the frame's own command buffer, no sync and + // no readback (see CN1MetalGlassEncode). The CPU path below is the fallback + // when the pipelines are unavailable, and CN1_GLASS_CPU=1 forces it. + static int cn1GlassForceCpu = -1; + if (cn1GlassForceCpu < 0) { + const char *env = getenv("CN1_GLASS_CPU"); + cn1GlassForceCpu = (env != NULL && env[0] == '1') ? 1 : 0; + } + if (!cn1GlassForceCpu) { + if (self.renderCommandEncoder != nil) { + CN1MetalEndFrame(); + [self.renderCommandEncoder endEncoding]; + self.renderCommandEncoder = nil; + } + if (self.commandBuffer == nil) { + self.commandBuffer = [self.commandQueue commandBuffer]; + } + CN1MetalGlassPatch patch; + if (CN1MetalGlassEncode(self.commandBuffer, [self backdropTexture], fx, fy, fw, fh, rad, sat, scale, offset, + curve, curveMid, &patch)) { + [self createRenderPassDescriptor]; + if (self.renderPassDescriptor == nil) { return; } + self.renderCommandEncoder = [self.commandBuffer renderCommandEncoderWithDescriptor:self.renderPassDescriptor]; + [self.renderCommandEncoder setViewport:(MTLViewport){ 0.0, 0.0, (double)framebufferWidth, (double)framebufferHeight, 0.0, 1.0 }]; + CN1MetalBeginFrame(self.renderCommandEncoder, projectionMatrix, framebufferWidth, framebufferHeight); + CN1MetalDrawGlass(&patch, x, y, w, h, fw, fh, cornerRadius, (float)s, refract, specular, outline); + return; + } + } + // 1) End + commit the screen encoder so screenTexture holds the backdrop. if (self.renderCommandEncoder != nil) { CN1MetalEndFrame(); @@ -1542,7 +1574,12 @@ - (void)lensScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h cornerRadius:(float) // 4) Restart a render encoder on the SAME command buffer (loadAction Load preserves the // bar) and re-publish it to the CN1Metalcompat draw layer. [self createRenderPassDescriptor]; - if (self.renderPassDescriptor == nil) { return; } + if (self.renderPassDescriptor == nil) { +#ifndef CN1_USE_ARC + [scratch release]; +#endif + return; + } self.renderCommandEncoder = [self.commandBuffer renderCommandEncoderWithDescriptor:self.renderPassDescriptor]; [self.renderCommandEncoder setViewport:(MTLViewport){ 0.0, 0.0, (double)framebufferWidth, (double)framebufferHeight, 0.0, 1.0 }]; CN1MetalBeginFrame(self.renderCommandEncoder, projectionMatrix, framebufferWidth, framebufferHeight); @@ -1550,6 +1587,144 @@ - (void)lensScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h cornerRadius:(float) // 5) Draw the lens quad sampling scratch (cornerRadius logical -> physical px; < 0 = capsule). float crPx = cornerRadius < 0.0f ? -1.0f : cornerRadius * (float)s; CN1MetalDrawLens(scratch, x, y, w, h, fw, fh, magnify, aberration, tintColor, tintStrength, crPx); + // The command buffer retains what it encodes; drop the +1 from newTexture. +#ifndef CN1_USE_ARC + [scratch release]; +#endif +} + +// Graphics.colorMatrixRegion on the live screen. Same GPU route as the lens: end +// the encoder, blit the painted region to a scratch texture on the frame's own +// command buffer, restart the encoder, then draw a quad that reads the scratch +// copy (and the mask) through cn1_fs_colormatrix. No CPU readback. +- (void)colorMatrixScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h matrix:(const float*)matrix + mask:(id)mask cornerRadius:(float)cornerRadius amount:(float)amount { + if ([self backdropTexture] == nil || w <= 0 || h <= 0 || amount <= 0.0f) { + return; + } + float sv = scaleValue > 0.0f ? scaleValue : 1.0f; + CGFloat s = self.contentScaleFactor / sv; + int texW = (int)[self backdropTexture].width, texH = (int)[self backdropTexture].height; + // The scratch covers the FULL region, with the on-screen part copied to its + // offset inside it, so the quad, the texture coordinates and the mask all stay + // anchored to the region when it runs off a screen edge (the off-screen part + // of the quad falls outside the viewport). + int fullW = (int)(w * s), fullH = (int)(h * s); + int fx = (int)(x * s), fy = (int)(y * s), fw = fullW, fh = fullH, ox = 0, oy = 0; + if (fx < 0) { ox = -fx; fw += fx; fx = 0; } + if (fy < 0) { oy = -fy; fh += fy; fy = 0; } + if (fx + fw > texW) { fw = texW - fx; } + if (fy + fh > texH) { fh = texH - fy; } + if (fw <= 0 || fh <= 0 || fullW <= 0 || fullH <= 0) { return; } + + if (self.renderCommandEncoder != nil) { + CN1MetalEndFrame(); + [self.renderCommandEncoder endEncoding]; + self.renderCommandEncoder = nil; + } + if (self.commandBuffer == nil) { + self.commandBuffer = [self.commandQueue commandBuffer]; + } + id device = CN1MetalDevice(); + MTLTextureDescriptor *desc = [MTLTextureDescriptor + texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:fullW height:fullH mipmapped:NO]; + desc.usage = MTLTextureUsageShaderRead; + desc.storageMode = MTLStorageModePrivate; + id scratch = [device newTextureWithDescriptor:desc]; + CN1_TEX_NOTE("colorMatrixScratch", scratch); + if (scratch == nil) { [self setFramebuffer]; return; } + id blit = [self.commandBuffer blitCommandEncoder]; + [blit copyFromTexture:[self backdropTexture] sourceSlice:0 sourceLevel:0 + sourceOrigin:MTLOriginMake(fx, fy, 0) sourceSize:MTLSizeMake(fw, fh, 1) + toTexture:scratch destinationSlice:0 destinationLevel:0 + destinationOrigin:MTLOriginMake(ox, oy, 0)]; + [blit endEncoding]; + + [self createRenderPassDescriptor]; + if (self.renderPassDescriptor == nil) { +#ifndef CN1_USE_ARC + [scratch release]; +#endif + return; + } + self.renderCommandEncoder = [self.commandBuffer renderCommandEncoderWithDescriptor:self.renderPassDescriptor]; + [self.renderCommandEncoder setViewport:(MTLViewport){ 0.0, 0.0, (double)framebufferWidth, (double)framebufferHeight, 0.0, 1.0 }]; + CN1MetalBeginFrame(self.renderCommandEncoder, projectionMatrix, framebufferWidth, framebufferHeight); + + float crPx = cornerRadius < 0.0f ? -1.0f : cornerRadius * (float)s; + CN1MetalDrawColorMatrix(scratch, mask, x, y, w, h, fullW, fullH, matrix, crPx, amount); + // The command buffer retains what it encodes; drop the +1 from newTexture. +#ifndef CN1_USE_ARC + [scratch release]; +#endif +} + +// Graphics.glassLensRegion on the live screen, on the GPU like the colour matrix: +// blit the lens plus the distance its refraction may reach, then draw the lens +// plus its margin (outline, shadow) with cn1_fs_glass_lens sampling the copy. +- (void)glassLensScreenRegionX:(int)x y:(int)y w:(int)w h:(int)h cornerRadius:(float)cornerRadius + optics:(const float*)optics amount:(float)amount { + if ([self backdropTexture] == nil || w <= 0 || h <= 0 || amount <= 0.0f || optics == NULL) { + return; + } + float sv = scaleValue > 0.0f ? scaleValue : 1.0f; + CGFloat s = self.contentScaleFactor / sv; + float o[16]; + for (int i = 0; i < 16; i++) { o[i] = optics[i]; } + // GlassLensBlend lengths: bevel, max shift, outline width, rim width, end-shade + // width, shadow width -- logical units in, physical pixels here. + static const int lengths[6] = { 0, 1, 4, 7, 9, 12 }; + for (int i = 0; i < 6; i++) { o[lengths[i]] *= (float)s; } + int margin = (int)ceilf(o[12] * 3.0f) + 1; + int reach = (int)ceilf(o[1]) + 1 + margin; + float lx = x * (float)s, ly = y * (float)s, lw = w * (float)s, lh = h * (float)s; + int texW = (int)[self backdropTexture].width, texH = (int)[self backdropTexture].height; + int sx0 = MAX(0, (int)floorf(lx) - reach), sy0 = MAX(0, (int)floorf(ly) - reach); + int sx1 = MIN(texW, (int)ceilf(lx + lw) + reach), sy1 = MIN(texH, (int)ceilf(ly + lh) + reach); + if (sx1 <= sx0 || sy1 <= sy0) { return; } + int sw = sx1 - sx0, sh = sy1 - sy0; + int ml = (int)ceilf(margin / (float)s); + int qx = x - ml, qy = y - ml, qw = w + 2 * ml, qh = h + 2 * ml; + + if (self.renderCommandEncoder != nil) { + CN1MetalEndFrame(); + [self.renderCommandEncoder endEncoding]; + self.renderCommandEncoder = nil; + } + if (self.commandBuffer == nil) { + self.commandBuffer = [self.commandQueue commandBuffer]; + } + id device = CN1MetalDevice(); + MTLTextureDescriptor *desc = [MTLTextureDescriptor + texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:sw height:sh mipmapped:NO]; + desc.usage = MTLTextureUsageShaderRead; + desc.storageMode = MTLStorageModePrivate; + id scratch = [device newTextureWithDescriptor:desc]; + CN1_TEX_NOTE("glassLensScratch", scratch); + if (scratch == nil) { [self setFramebuffer]; return; } + id blit = [self.commandBuffer blitCommandEncoder]; + [blit copyFromTexture:[self backdropTexture] sourceSlice:0 sourceLevel:0 + sourceOrigin:MTLOriginMake(sx0, sy0, 0) sourceSize:MTLSizeMake(sw, sh, 1) + toTexture:scratch destinationSlice:0 destinationLevel:0 + destinationOrigin:MTLOriginMake(0, 0, 0)]; + [blit endEncoding]; + + [self createRenderPassDescriptor]; + if (self.renderPassDescriptor == nil) { +#ifndef CN1_USE_ARC + [scratch release]; +#endif + return; + } + self.renderCommandEncoder = [self.commandBuffer renderCommandEncoderWithDescriptor:self.renderPassDescriptor]; + [self.renderCommandEncoder setViewport:(MTLViewport){ 0.0, 0.0, (double)framebufferWidth, (double)framebufferHeight, 0.0, 1.0 }]; + CN1MetalBeginFrame(self.renderCommandEncoder, projectionMatrix, framebufferWidth, framebufferHeight); + + float crPx = cornerRadius < 0.0f ? -1.0f : cornerRadius * (float)s; + CN1MetalDrawGlassLens(scratch, sx0, sy0, sw, sh, qx, qy, qw, qh, (float)s, lx, ly, lw, lh, crPx, o, amount); +#ifndef CN1_USE_ARC + [scratch release]; +#endif } // Put the frame held in screenTexture onto \a dst (a drawable's texture), @@ -1573,6 +1748,9 @@ -(void)presentScreenTextureInto:(id)dst commandBuffer:(idaccent tint over the painted content). See lensScreenRegionX. + // Graphics.colorMatrixRegion on the live screen: matrix is 12 floats (3 rows of + // r, g, b, offset), maskPeer an image peer or 0. See colorMatrixScreenRegionX. + // Graphics.glassLensRegion on the live screen: optics are the GlassLensBlend + // parameters. See glassLensScreenRegionX. + native void nativeGlassLensScreenRegion(int x, int y, int width, int height, float cornerRadius, float[] optics, float amount); + native void nativeColorMatrixScreenRegion(int x, int y, int width, int height, float[] matrix, long maskPeer, float cornerRadius, float amount); native void nativeLensScreenRegion(int x, int y, int width, int height, float cornerRadius, float magnify, float aberration, int tintColor, float tintStrength); // Renders an Apple SF Symbol to a GLUIImage peer (iOS 13+). Returns 0 when the // symbol is unavailable; writes the pixel width/height into widthHeight[0]/[1]. diff --git a/Themes/iOSModern27Theme.res b/Themes/iOSModern27Theme.res index e108e1ba187..3b00f7e7cc7 100644 Binary files a/Themes/iOSModern27Theme.res and b/Themes/iOSModern27Theme.res differ diff --git a/docs/developer-guide/Native-Themes.asciidoc b/docs/developer-guide/Native-Themes.asciidoc index c30fb1f17db..1deacb93b41 100644 --- a/docs/developer-guide/Native-Themes.asciidoc +++ b/docs/developer-guide/Native-Themes.asciidoc @@ -700,6 +700,90 @@ doubles the bounce. |The accent the dark glyph is tinted to inside the drop. |=== +The iOS 27 theme uses a third preset, `ios27`, which is time based rather +than a fixed-duration morph. Its curves are measured from a real +`UITabBarController` on the iOS 27 simulator, frame by frame, and played back +by `TabGlassMotion`: + +* on a tap the selection lens *lifts*: it grows, its platter fades so it + becomes clear glass, and the tab content seen through it magnifies; +* it travels on a spring, stretching while it accelerates and squashing on + arrival in proportion to the distance, then settles with a second, smaller + wobble; +* the whole bar -- glass, content and lens -- pulses in scale, and a soft glow + spreads from where the finger touched. + +The motion follows the gesture, not just the tap, the way UIKit's does: + +* holding a press keeps the lens lifted and the bar grown until the finger + lifts; +* pressing the selected tab lifts and pulses the lens in place; +* dragging along the bar pulls the lens with the finger (a *scrub*), and + letting go selects the tab under the finger, with the lens settling there; +* every release ends in a small wobble about a second after the finger lifts. + +The accent color lives in a second copy of the tab content, visible only +through the lens, so the color travels with the glass. Where the port +supports `Graphics.colorMatrixRegion` (iOS, macOS, the JavaScript port and the +simulator), the tab content is *vibrant* like UIKit's: each glyph pixel is a +color matrix of the glass behind it rather than flat paint, so labels and the +accent pick up the colors under the bar. The selection platter is likewise the +bar's own glass seen through a color matrix, not a translucent fill. With this +preset the tabs are painted with their normal style outside the lens and with +their *pressed* style inside it: the theme's `Tab`/`TabIcon` colors are the +unselected ones and `Tab.pressed`/`TabIcon.pressed` the selected ones. Tab +backgrounds should stay transparent. + +The motion reaches past the bar's visible pill, so the bar reserves room for +it inside its own bounds: the theme widens the `TabsContainer` padding by the +slack, narrows its margin by the same amount so the pill stays where it was, +and names the slack in constants. Everything the motion paints then stays +inside the bar's own bounds and repaint region. + +[cols="3,1,4", options="header"] +|=== +|Constant |iOS 27 |Effect + +|`@tabsMorphPreset` +|`ios27` +|Selects the measured time-based motion described above. + +|`@tabsGlassSlackXMm` / `@tabsGlassSlackYMm` +|`2` / `1` +|How much of the `TabsContainer` padding is motion slack rather than pill. + +|`@tabSelInsetMm` +|`0.66` +|Inset of the resting lens inside the pill. + +|`@tabSelPillColorInt` / `@tabSelPillAlphaInt` +|`0` / `35` +|Color and alpha of the resting selection platter on ports without +`colorMatrixRegion`. + +|`@tabSelPillColorDarkInt` / `@tabSelPillAlphaDarkInt` +|`0` / `67` +|The same platter in the dark appearance; defaults to the light values. + +|`@tabGlassLensLightAlphaInt` +|`28` +|White wash of the lifted lens, which reads brighter than the bar. + +|`@tabGlassRimAlphaInt` / `@tabGlassEdgeAlphaInt` +|`150` / `110` +|Alpha of the lifted lens's bright inner rim and dark edge ring, scaled by +the lift. + +|`@tabGlassGlowPct` +|`40` +|Strength of the touch glow; `0` turns it off. + +|`@tabGlassSpecularAlphaInt` / `@tabGlassSpecularDarkAlphaInt` +|`145` / `90` +|Peak alpha of the bright rim along the bar's top and bottom edges; `0` +(the default) draws none. +|=== + Similarly, the *Liquid Glass backdrop materials* themselves are typed, named recipes (`GlassRecipe`): a theme assigns `chrome` (edge-anchored bars), `pill` (the floating tab bar) or `panel` (buttons, dialogs, panels diff --git a/docs/videos/README.md b/docs/videos/README.md new file mode 100644 index 00000000000..89c61cc9679 --- /dev/null +++ b/docs/videos/README.md @@ -0,0 +1,8 @@ +# Videos + +- `tabs-side-by-side-light-bar.mp4`, `tabs-side-by-side-dark-bar.mp4` -- the iOS 27 + tab bar, native `UITabBarController` on top and Codename One below, driven by the + same real touches on the iOS 27 simulator: six taps, a held press, a tap on the + selected tab, a slow drag and a flick. Each gesture has a 4.2 second slot aligned + on its touch, so the two bars can be compared gesture by gesture. Recorded with + `scripts/record-ios-tabs-side-by-side.sh`. diff --git a/docs/videos/tabs-side-by-side-dark-bar.mp4 b/docs/videos/tabs-side-by-side-dark-bar.mp4 new file mode 100644 index 00000000000..7ed811ae943 Binary files /dev/null and b/docs/videos/tabs-side-by-side-dark-bar.mp4 differ diff --git a/docs/videos/tabs-side-by-side-light-bar.mp4 b/docs/videos/tabs-side-by-side-light-bar.mp4 new file mode 100644 index 00000000000..7bf753180ef Binary files /dev/null and b/docs/videos/tabs-side-by-side-light-bar.mp4 differ diff --git a/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java b/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java index 34c24c90f94..cff76a5f0a3 100644 --- a/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java +++ b/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java @@ -1513,6 +1513,9 @@ public void reset() { // teardown, so a test that switches touch off and forgets -- or restores the // wrong value -- cannot decide the command behaviour of the classes after it. touchDevice = true; + colorMatrixRegionSupported = false; + colorMatrixCalls.clear(); + lensCalls.clear(); minimized = false; usesInvokeAndBlockForEditString = false; windowManager = null; @@ -1921,6 +1924,135 @@ public void rotate(Object nativeGraphics, float angle, int pivotX, int pivotY) { public void shear(Object nativeGraphics, float x, float y) { } + private boolean colorMatrixRegionSupported; + private final java.util.List colorMatrixCalls = + java.util.Collections.synchronizedList(new java.util.ArrayList()); + + /// One recorded Graphics.colorMatrixRegion call, with a snapshot of the mask + /// as it was when the call was made (the caller reuses its masks). + public static final class ColorMatrixCall { + public final int x; + public final int y; + public final int width; + public final int height; + public final float[] matrix; + public final int[] mask; + public final int maskWidth; + public final int maskHeight; + public final float cornerRadius; + public final float amount; + + ColorMatrixCall(int x, int y, int width, int height, float[] matrix, int[] mask, int maskWidth, + int maskHeight, float cornerRadius, float amount) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.matrix = matrix; + this.mask = mask; + this.maskWidth = maskWidth; + this.maskHeight = maskHeight; + this.cornerRadius = cornerRadius; + this.amount = amount; + } + } + + /// Makes this implementation claim colorMatrixRegion support and record the calls. + public void setColorMatrixRegionSupported(boolean supported) { + colorMatrixRegionSupported = supported; + } + + public java.util.List getColorMatrixCalls() { + synchronized (colorMatrixCalls) { + return new java.util.ArrayList(colorMatrixCalls); + } + } + + public void clearColorMatrixCalls() { + colorMatrixCalls.clear(); + } + + private final java.util.List lensCalls = + java.util.Collections.synchronizedList(new java.util.ArrayList()); + + /// One recorded Graphics.lensRegion call: the region and the clip in force when + /// it was made, both in the same (translated) coordinates. + public static final class LensCall { + public final int x; + public final int y; + public final int width; + public final int height; + public final int clipX; + public final int clipY; + public final int clipWidth; + public final int clipHeight; + + LensCall(int x, int y, int width, int height, int clipX, int clipY, int clipWidth, int clipHeight) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.clipX = clipX; + this.clipY = clipY; + this.clipWidth = clipWidth; + this.clipHeight = clipHeight; + } + } + + public java.util.List getLensCalls() { + synchronized (lensCalls) { + return new java.util.ArrayList(lensCalls); + } + } + + /// Records the call (painting nothing) and reports it unsupported, like the base. + @Override + public boolean lensRegion(Object graphics, int x, int y, int width, int height, float cornerRadius, + float magnify, float aberration, int tintColor, float tintStrength) { + if (graphics instanceof TestGraphics) { + TestGraphics g = (TestGraphics) graphics; + lensCalls.add(new LensCall(x + g.translateX, y + g.translateY, width, height, g.clipX, g.clipY, + g.clipWidth, g.clipHeight)); + } + return false; + } + + /// Clears to transparent on mutable images, the way the real ports do (the + /// base class only logs), so reused off-screen buffers start empty. + @Override + public void clearRect(Object graphics, int x, int y, int width, int height) { + if (!(graphics instanceof TestGraphics) || ((TestGraphics) graphics).image == null) { + return; + } + TestGraphics g = (TestGraphics) graphics; + int startX = Math.max(Math.max(x + g.translateX, g.clipX), 0); + int startY = Math.max(Math.max(y + g.translateY, g.clipY), 0); + int endX = Math.min(Math.min(x + g.translateX + width, g.clipX + g.clipWidth), g.image.width); + int endY = Math.min(Math.min(y + g.translateY + height, g.clipY + g.clipHeight), g.image.height); + for (int row = startY; row < endY; row++) { + for (int col = startX; col < endX; col++) { + g.image.argb[row * g.image.width + col] = 0; + } + } + } + + @Override + public boolean isColorMatrixRegionSupported(Object graphics) { + return colorMatrixRegionSupported; + } + + @Override + public boolean colorMatrixRegion(Object graphics, int x, int y, int width, int height, float[] matrix, + com.codename1.ui.Image mask, float cornerRadius, float amount) { + if (!colorMatrixRegionSupported) { + return false; + } + int[] maskPixels = mask == null ? null : mask.getRGB(); + colorMatrixCalls.add(new ColorMatrixCall(x, y, width, height, matrix.clone(), maskPixels, + mask == null ? 0 : mask.getWidth(), mask == null ? 0 : mask.getHeight(), cornerRadius, amount)); + return true; + } + @Override public boolean isTransformSupported() { return true; @@ -2991,6 +3123,12 @@ public Object getDefaultFont() { return defaultFont; } + /// A native font with its own metrics, for tests that need two fonts to measure + /// text differently (every other font here is 8 px a character). + public Object createTestFont(int charWidth, int height) { + return new TestFont(charWidth, height); + } + @Override public Object createFont(int face, int style, int size) { return new TestFont(defaultFont.charWidth, defaultFont.height); diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/TabGlassGestureTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/TabGlassGestureTest.java new file mode 100644 index 00000000000..70e91b970b2 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/TabGlassGestureTest.java @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Codename One in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.ui; + +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Holds TabGlassGesture to native iOS 27 captures of held presses and scrubs + * (fixture native-gestures.csv, written by tabmotion.py gesture-fixture from + * the motion probe). Each episode replays its recorded touch events through + * the model and compares every captured frame. + */ +class TabGlassGestureTest { + + private static final class Episode { + String tag; + float fromPt; + float toPt; + float minPt; + float maxPt; + float upS; + float scrubS; + float settlePt; + final List touches = new ArrayList(); + final List frames = new ArrayList(); + } + + private static Map load() throws Exception { + Map eps = new LinkedHashMap(); + InputStream in = TabGlassGestureTest.class.getResourceAsStream("/tab-glass-motion/native-gestures.csv"); + assertNotNull(in, "fixture missing"); + BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8")); + try { + String line; + while ((line = r.readLine()) != null) { + if (line.startsWith("#") || line.trim().length() == 0) { + continue; + } + String[] p = line.split(","); + int id = Integer.parseInt(p[1]); + if ("E".equals(p[0])) { + Episode e = new Episode(); + e.tag = p[2]; + e.fromPt = Float.parseFloat(p[3]); + e.toPt = Float.parseFloat(p[4]); + e.minPt = Float.parseFloat(p[5]); + e.maxPt = Float.parseFloat(p[6]); + e.upS = Float.parseFloat(p[7]); + e.scrubS = Float.parseFloat(p[8]); + e.settlePt = Float.parseFloat(p[9]); + eps.put(id, e); + } else { + float[] v = new float[p.length - 2]; + for (int i = 2; i < p.length; i++) { + v[i - 2] = Float.parseFloat(p[i]); + } + (("T".equals(p[0])) ? eps.get(id).touches : eps.get(id).frames).add(v); + } + } + } finally { + r.close(); + } + return eps; + } + + private static TabGlassGesture replay(Episode e) { + TabGlassGesture g = new TabGlassGesture(e.fromPt, e.toPt); + if (e.scrubS >= 0) { + g.startScrub(e.scrubS, e.minPt, e.maxPt); + for (float[] t : e.touches) { + g.finger(t[0], t[1]); + } + g.settleTo(e.settlePt); + } + g.up(e.upS); + return g; + } + + /// Worst error per channel (x, lift, scaleX, scaleY, lead, bar), then the sum + /// of squared x and lift errors and the frame count. + private static float[] errors(Episode e) { + TabGlassGesture g = replay(e); + float[] worst = new float[9]; + float[] c = new float[1]; + for (float[] f : e.frames) { + float s = f[0]; + if (s < 0) { + continue; + } + TabGlassMotion m = g.at(s, c); + float[] err = {c[0] - f[1], m.lift - f[2], (m.scaleX - 1) - f[3], (m.scaleY - 1) - f[4], + m.leadPt - f[5], m.barGrowPt - f[6]}; + for (int i = 0; i < 6; i++) { + worst[i] = Math.max(worst[i], Math.abs(err[i])); + } + worst[6] += err[0] * err[0]; + worst[7] += err[1] * err[1]; + worst[8]++; + } + return worst; + } + + // Worst error allowed per episode: x (pt), lift, scaleX, scaleY, lead (pt), bar (pt). + private static final float[] HOLD_LIMITS = {1.5f, 0.05f, 0.02f, 0.025f, 1.2f, 1.2f}; + // Scrubs: the worst captured errors plus about 10% -- x 3.44 pt (a flick + // released ~70 pt behind the finger), lift 0.141, scaleX 0.0373, scaleY 0.0415, + // lead 1.77 pt, bar 2.13 pt. The RMS bounds below keep the rest tight. + private static final float[] SCRUB_LIMITS = {3.8f, 0.155f, 0.041f, 0.046f, 1.95f, 2.2f}; + + @Test + void reproducesNativeHoldsAndScrubs() throws Exception { + Map eps = load(); + assertEquals(21, eps.size()); + StringBuilder report = new StringBuilder(); + float sx2 = 0; + float sl2 = 0; + float n = 0; + String[] names = {"x", "lift", "scaleX", "scaleY", "lead", "bar"}; + for (Map.Entry en : eps.entrySet()) { + Episode e = en.getValue(); + if (en.getKey() == 0) { + // The first press of a capture run lags a frame on the device (the + // tap fit leaves those out for the same reason). + continue; + } + float[] w = errors(e); + boolean scrub = e.scrubS >= 0; + float[] lim = scrub ? SCRUB_LIMITS : HOLD_LIMITS; + for (int i = 0; i < 6; i++) { + assertTrue(w[i] <= lim[i], "episode " + en.getKey() + " (" + e.tag + ") " + names[i] + + " off by " + w[i] + ", limit " + lim[i]); + } + if (scrub) { + sx2 += w[6]; + sl2 += w[7]; + n += w[8]; + } + report.append(en.getKey()).append(' ').append(e.tag).append(": x ").append(w[0]) + .append(" lift ").append(w[1]).append('\n'); + } + float rmsX = (float) Math.sqrt(sx2 / n); + float rmsLift = (float) Math.sqrt(sl2 / n); + // Measured 0.346 pt and 0.0166: the settle spring started where the follow + // spring leaves the lens, at the lift release that follows from it. + assertTrue(rmsX <= 0.38f, "scrub x rms " + rmsX + "\n" + report); + assertTrue(rmsLift <= 0.019f, "scrub lift rms " + rmsLift + "\n" + report); + } + + @Test + void scrubSettlesOnTheTapsTravelCurve() { + // The settle is the tap's travel spring restarted from the scrub's state: a + // lens resting under a still finger that is let go towards another tab moves + // exactly like a tap between the two, from the moment the release reaches it. + // Compared on the scrub track's own 60 Hz frames (in between, at() blends + // neighbouring frames); the release is placed so the hand-off is one. They + // agree to float precision (4e-5 pt). + float start = 0.2f; + float up = start + 78 / 60f - TabGlassGesture.TOUCH_LAG_S; + TabGlassGesture g = new TabGlassGesture(51, 51); + g.startScrub(start, 51, 223); + g.finger(start, 51); + g.settleTo(137); + g.up(up); + float[] c = new float[1]; + float worst = 0; + for (int i = 0; i <= 60; i++) { + float t = i / 60f; + g.at(start + (78 + i) / 60f, c); + float tap = 51 + (137 - 51) * TabGlassMotion.position(t + TabGlassMotion.START_S); + worst = Math.max(worst, Math.abs(c[0] - tap)); + } + assertTrue(worst <= 0.001f, "settle vs the tap travel: " + worst + " pt"); + } + + @Test + void scrubDeformationFollowsTheLensMotion() throws Exception { + // While the finger is down nothing but the deformation kernels bend the lens + // (the wobble and the settle come after), so these frames pin them to + // native. Measured over the 17 scrubs: scaleX 0.00535, scaleY 0.00794, lead + // 0.234 pt RMS. + double[] sq = new double[3]; + int n = 0; + float[] c = new float[1]; + for (Map.Entry en : load().entrySet()) { + Episode e = en.getValue(); + if (en.getKey() == 0 || e.scrubS < 0) { + continue; + } + TabGlassGesture g = replay(e); + for (float[] f : e.frames) { + if (f[0] < 0 || f[0] >= e.upS) { + continue; + } + TabGlassMotion m = g.at(f[0], c); + float[] err = {(m.scaleX - 1) - f[3], (m.scaleY - 1) - f[4], m.leadPt - f[5]}; + for (int i = 0; i < 3; i++) { + sq[i] += err[i] * err[i]; + } + n++; + } + } + assertTrue(n > 1000, "scrub frames " + n); + float[] limits = {0.0059f, 0.0088f, 0.26f}; + String[] names = {"scaleX", "scaleY", "lead"}; + for (int i = 0; i < 3; i++) { + float rms = (float) Math.sqrt(sq[i] / n); + assertTrue(rms <= limits[i], names[i] + " rms while scrubbing " + rms + ", limit " + limits[i]); + } + } + + @Test + void mirroredScrubsDeformAlike() { + // Speed drives the scale and signed velocity the centre offset: a scrub to + // the left bends the lens exactly like the same scrub to the right, and + // shifts it the other way. A scrub that stays put gives the part that is not + // the lens's own motion (the wobble always leans towards +x). + TabGlassGesture right = scrub(223); + TabGlassGesture left = scrub(51); + TabGlassGesture still = scrub(137); + float[] c = new float[1]; + float moved = 0; + for (int i = 0; i <= 90; i++) { + float s = 0.2f + i / 60f; + TabGlassMotion r = right.at(s, c); + TabGlassMotion l = left.at(s, c); + TabGlassMotion z = still.at(s, c); + assertEquals(r.scaleX, l.scaleX, 1e-4f, "scaleX at " + s); + assertEquals(r.scaleY, l.scaleY, 1e-4f, "scaleY at " + s); + assertEquals(r.leadPt - z.leadPt, -(l.leadPt - z.leadPt), 1e-3f, "lead at " + s); + moved = Math.max(moved, Math.abs(r.scaleY - z.scaleY)); + } + assertTrue(moved > 0.01f, "the scrub deformed the lens by only " + moved); + } + + /// A scrub from the middle tab (137 pt) to `toPt` over 0.3 s, released at 0.6 s. + private static TabGlassGesture scrub(float toPt) { + TabGlassGesture g = new TabGlassGesture(137, 137); + g.startScrub(0.2f, 51, 223); + for (int i = 0; i <= 12; i++) { + g.finger(0.2f + i * 0.025f, 137 + (toPt - 137) * i / 12f); + } + g.settleTo(toPt); + g.up(0.6f); + return g; + } + + @Test + void scrubReleaseGoesWhereTheFingerIsNotTheLens() { + // A flick: the finger reaches the last tab while the lens is far behind. + TabGlassGesture g = new TabGlassGesture(51, 51); + g.startScrub(0.3f, 51, 223); + g.finger(0.3f, 51); + g.finger(0.35f, 223); + g.settleTo(223); + g.up(0.4f); + float[] c = new float[1]; + g.at(0.4f, c); + assertTrue(c[0] < 200, "lens still behind the finger at release: " + c[0]); + g.at(1.5f, c); + assertEquals(223, c[0], 0.5f); + } + + @Test + void heldPressKeepsTheLiftUntilRelease() { + TabGlassGesture g = new TabGlassGesture(51, 137); + float[] c = new float[1]; + assertEquals(1f, g.at(1.2f, c).lift, 1e-6f, "lift holds while pressed"); + assertFalse(g.isFinished(10f), "never finishes while pressed"); + g.up(1.5f); + assertEquals(1f, g.at(1.5f, c).lift, 1e-6f); + assertTrue(g.at(1.7f, c).lift < 0.9f, "falls after release"); + assertTrue(g.isFinished(4f)); + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/TabGlassMotionTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/TabGlassMotionTest.java new file mode 100644 index 00000000000..83b3a13e1cb --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/TabGlassMotionTest.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Codename One in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.ui; + +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Pins the iOS 27 tab selection motion model against the NATIVE capture it was + * measured from: every row of tab-glass-motion/native-ios27.csv is one display + * frame of a real UITabBarController selection on the iOS 27 simulator, and the + * model must reproduce it frame by frame. The tolerances are in points on the + * native 62 pt bar -- at @3x a point is three device pixels. + * + * Every captured frame is evaluated at its VSYNC time, round(t * 60) / 60: the + * springs are rendered on the display clock (inverting them recovers each frame + * at exactly n / 60 s, to 0.03 ms), while the logged times carry the main + * thread's jitter -- the frame after the touch-up is logged 1.3 ms late in every + * tap. The closed-form channels' tolerances sit just above their measured worst + * error on that clock; the deformation and bar-pulse tables keep theirs. + */ +class TabGlassMotionTest { + + private static final class Frame { + String tap; + float travelPt; + boolean fastLift; + float t; + float position; + float liftPt; + float scaleX; + float scaleY; + float leadPt; + float platterOpacity; + float contentScale; + float barGrowPt; + } + + private static List load() throws IOException { + List out = new ArrayList(); + InputStream in = TabGlassMotionTest.class.getResourceAsStream("/tab-glass-motion/native-ios27.csv"); + assertNotNull(in, "native capture fixture missing"); + BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + try { + String line; + while ((line = r.readLine()) != null) { + if (line.startsWith("#") || line.trim().length() == 0) { + continue; + } + String[] p = line.split(","); + Frame f = new Frame(); + f.tap = p[0] + "#" + p[1]; + f.travelPt = Float.parseFloat(p[2]); + f.fastLift = "fast".equals(p[3]); + f.t = Float.parseFloat(p[4]); + f.position = Float.parseFloat(p[5]); + f.liftPt = Float.parseFloat(p[6]); + f.scaleX = Float.parseFloat(p[7]); + f.scaleY = Float.parseFloat(p[8]); + f.leadPt = Float.parseFloat(p[9]); + f.platterOpacity = Float.parseFloat(p[10]); + f.contentScale = Float.parseFloat(p[11]); + f.barGrowPt = Float.parseFloat(p[12]); + out.add(f); + } + } finally { + r.close(); + } + assertTrue(out.size() > 500, "fixture looks truncated: " + out.size()); + return out; + } + + private static float restWidthPt(Frame f) { + // The native 3-tab bar's lens is 94 pt wide at rest, the 5-tab bar's 84 pt. + return f.tap.startsWith("light-grey-5b") ? 84f : 94f; + } + + @Test + void reproducesTheNativeMotionFrameByFrame() throws IOException { + float worstPos = 0; + float worstW = 0; + float worstH = 0; + float worstLead = 0; + float worstLift = 0; + float worstPlatter = 0; + float worstContent = 0; + float worstBar = 0; + for (Frame f : load()) { + float vsync = Math.round(f.t * TabGlassMotion.SAMPLE_HZ) / (float) TabGlassMotion.SAMPLE_HZ; + TabGlassMotion m = TabGlassMotion.at(vsync * 1000f, f.travelPt); + String where = f.tap + " t=" + f.t; + float d = Math.abs(f.travelPt); + worstPos = Math.max(worstPos, Math.abs(m.position - f.position) * d); + worstLead = Math.max(worstLead, Math.abs(m.leadPt - f.leadPt)); + worstBar = Math.max(worstBar, Math.abs(m.barGrowPt - f.barGrowPt)); + if (f.fastLift) { + float liftPt = TabGlassMotion.LIFT_PT * m.lift; + worstLift = Math.max(worstLift, Math.abs(liftPt - f.liftPt)); + worstW = Math.max(worstW, Math.abs((restWidthPt(f) + liftPt) * m.scaleX + - (restWidthPt(f) + f.liftPt) * f.scaleX)); + worstH = Math.max(worstH, Math.abs((54f + liftPt) * m.scaleY - (54f + f.liftPt) * f.scaleY)); + worstPlatter = Math.max(worstPlatter, Math.abs(m.platterOpacity - f.platterOpacity)); + worstContent = Math.max(worstContent, Math.abs(m.contentScale - f.contentScale)); + } else { + // The slow-lift regime changes the lens SIZE, not its travel or its + // deformation; the scales alone still have to match. + assertEquals(f.scaleX, m.scaleX, 0.02f, "scaleX " + where); + assertEquals(f.scaleY, m.scaleY, 0.03f, "scaleY " + where); + } + } + // Closed forms (measured worst: 0.77 pt, 0.12 pt, 0.007). + assertTrue(worstPos <= 0.9f, "lens centre off by " + worstPos + " pt"); + assertTrue(worstLead <= 1.6f, "lens lead off by " + worstLead + " pt"); + assertTrue(worstBar <= 0.5f, "bar growth off by " + worstBar + " pt"); + assertTrue(worstLift <= 0.15f, "lift off by " + worstLift + " pt"); + assertTrue(worstW <= 2.0f, "lens width off by " + worstW + " pt"); + assertTrue(worstH <= 1.5f, "lens height off by " + worstH + " pt"); + assertTrue(worstPlatter <= 0.01f, "platter opacity off by " + worstPlatter); + assertTrue(worstContent <= 0.004f, "content magnification off by " + worstContent); + } + + @Test + void releaseFollowsTheRemainingDistance() { + // UIKit drops the lift once the lens is within 3.5 pt of its target: a + // longer jump holds the lift longer. + float near = TabGlassMotion.releaseSeconds(86f); + float far = TabGlassMotion.releaseSeconds(172f); + assertTrue(near < far, near + " vs " + far); + assertEquals(0.233f, near, 0.01f); + assertEquals(0.267f, far, 0.01f); + // A short jump still waits for the lift to snap to full before releasing. + assertEquals(near, TabGlassMotion.releaseSeconds(64f), 0.001f); + } + + @Test + void restsAtTheEndAndIsDirectionSymmetric() { + TabGlassMotion done = TabGlassMotion.at(TabGlassMotion.durationMs() + 1, 100f); + assertEquals(1f, done.position, 0f); + assertEquals(1f, done.scaleX, 0f); + assertEquals(1f, done.platterOpacity, 0f); + for (int ms = 0; ms < TabGlassMotion.durationMs(); ms += 17) { + TabGlassMotion r = TabGlassMotion.at(ms, 120f); + TabGlassMotion l = TabGlassMotion.at(ms, -120f); + assertEquals(r.position, l.position, 0f); + assertEquals(r.scaleX, l.scaleX, 0f); + assertEquals(r.leadPt, -l.leadPt, 0f); + assertEquals(r.lift, l.lift, 0f); + } + } + + /// Native touch glow, per-frame means over the eleven clean taps of the capture + /// (the fixture does not carry the glow): frame, diameter scale, drawn opacity. + private static final float[][] NATIVE_GLOW = { + {2, 1.0000f, 0.08153f}, {3, 1.0000f, 0.15444f}, {5, 1.0573f, 0.21906f}, {8, 1.6145f, 0.17483f}, + {12, 2.4973f, 0.07740f}, {20, 3.5425f, 0.00782f}, {30, 3.9165f, 0.00028f}, {34, 3.9592f, 0f}, + {45, 3.9946f, 0f} + }; + + @Test + void theTouchGlowIsTheMeasuredSprings() { + for (float[] g : NATIVE_GLOW) { + TabGlassMotion m = TabGlassMotion.at(g[0] * 1000f / TabGlassMotion.SAMPLE_HZ, 86f); + // The scale has no tap-to-tap spread in the capture; the opacity spreads + // by up to 0.0024 on the rising frames. + assertEquals(g[1], m.glowScale, 0.0005f, "glow scale at frame " + (int) g[0]); + assertEquals(g[2], m.glowOpacity, 0.001f, "glow opacity at frame " + (int) g[0]); + } + } + + @Test + void theGlowMaskIsAFixedFractionOfTheLayerAndHidesAtTheSameLevel() { + // Drawn opacity = mask * layer with mask = 0.33675 * layer, so while both + // are visible it is 0.33675 * layer^2; the mask drops out first. + float s = 10f / TabGlassMotion.SAMPLE_HZ; + float layer = TabGlassMotion.glowLayerOpacity(s); + assertEquals(0.33675f * layer * layer, TabGlassMotion.glowOpacity(s), 1e-6f); + assertEquals(0f, TabGlassMotion.glowOpacity(34f / TabGlassMotion.SAMPLE_HZ), 0f); + assertTrue(TabGlassMotion.glowLayerOpacity(39f / TabGlassMotion.SAMPLE_HZ) > 0f); + assertEquals(0f, TabGlassMotion.glowLayerOpacity(40f / TabGlassMotion.SAMPLE_HZ), 0f); + } + + @Test + void theTravelSettlesLikeUikit() { + // UIKit ends the travel past its overshoot once less than 0.2 pt remains: + // the frame it happened at in the capture, per jump distance. + float[][] snaps = {{64.04f, 29}, {86f, 31}, {128.1f, 33}, {172f, 34}}; + for (float[] sn : snaps) { + float at = sn[1] / TabGlassMotion.SAMPLE_HZ; + float before = (sn[1] - 1) / TabGlassMotion.SAMPLE_HZ; + assertEquals(1f, TabGlassMotion.settledPosition(at, sn[0]), 0f, sn[0] + " pt at frame " + (int) sn[1]); + assertTrue(TabGlassMotion.settledPosition(before, sn[0]) > 1f, sn[0] + " pt before frame " + (int) sn[1]); + } + // The unsettled curve is the spring of duration 0.4 s and bounce 0.15: its + // overshoot peak is 0.6% at ~0.38 s. + float peak = 0; + for (int ms = 300; ms < 500; ms++) { + peak = Math.max(peak, TabGlassMotion.position(ms / 1000f)); + } + assertEquals(1.0063f, peak, 0.0002f); + } + + @Test + void theLiftAndPlatterEndOnTheCapturedFrames() { + // Counted from the release frame, the capture drops the lift to 0 on frame 14 + // (13 in one tap) and lands the platter on 1 on frame 29 (28 in one tap). + float release = TabGlassMotion.releaseSeconds(86f); + float hz = TabGlassMotion.SAMPLE_HZ; + assertTrue(TabGlassMotion.liftFall(13f / hz) > 0f, "lift gone before frame 14"); + assertEquals(0f, TabGlassMotion.liftFall(14f / hz), 0f); + assertTrue(TabGlassMotion.platterReturn(28f / hz, release) < 1f, "platter back before frame 29"); + assertEquals(1f, TabGlassMotion.platterReturn(29f / hz, release), 0f); + } + + @Test + void theClosedFormsRestBeforeTheTablesEnd() { + float end = TabGlassMotion.durationMs() / 1000f; + float release = TabGlassMotion.releaseSeconds(258f); + assertEquals(1f, TabGlassMotion.settledPosition(end, 258f), 0f); + assertEquals(0f, TabGlassMotion.liftFall(end - release), 0f); + assertEquals(1f, TabGlassMotion.platterReturn(end - release, release), 0f); + assertEquals(0f, TabGlassMotion.glowOpacity(end), 0f); + assertEquals(4f, TabGlassMotion.glowScale(end), 0.001f); + } + + @Test + void theBarPulsesAndTheLensLiftsAndSquashes() { + // Coarse shape checks that read as the native description, so a table + // regenerated from a bad capture fails loudly. + float peakBar = 0; + float minBar = 0; + float peakLift = 0; + float minScaleX = 2; + for (int ms = 0; ms < 700; ms += 5) { + TabGlassMotion m = TabGlassMotion.at(ms, 172f); + peakBar = Math.max(peakBar, m.barGrowPt); + minBar = Math.min(minBar, m.barGrowPt); + peakLift = Math.max(peakLift, m.lift); + minScaleX = Math.min(minScaleX, m.scaleX); + } + assertEquals(8.7f, peakBar, 0.3f); + assertTrue(minBar < -0.5f, "bar should undershoot, min " + minBar); + assertEquals(1f, peakLift, 0f); + assertEquals(0.905f, minScaleX, 0.01f); + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/TabsGlassSelectionTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/TabsGlassSelectionTest.java new file mode 100644 index 00000000000..51c1c51cef1 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/TabsGlassSelectionTest.java @@ -0,0 +1,516 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Codename One in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.ui; + +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.ui.layouts.BorderLayout; +import com.codename1.ui.plaf.Style; +import com.codename1.ui.plaf.UIManager; + +import java.util.Hashtable; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration checks of the iOS 27 glass selection (tabsMorphPreset ios27) in + * Tabs: the forced paint states the lens compositing relies on, the lens moving + * on touch-DOWN like UIKit's, and a cancelled press sending it back. + */ +class TabsGlassSelectionTest extends UITestBase { + + private Tabs glassTabs() { + return glassTabs(Component.BOTTOM, new String[]{"One", "Two", "Three"}, false); + } + + private Tabs glassTabs(int placement, String[] names, boolean rtl) { + Hashtable props = new Hashtable(); + props.put("@tabsMorphPreset", "ios27"); + props.put("@tabsSelectionCapsuleBool", "true"); + props.put("@tabsGlassSlackXMm", "2"); + props.put("@tabsGlassSlackYMm", "1"); + UIManager.getInstance().addThemeProps(props); + Form form = Display.getInstance().getCurrent(); + form.removeAll(); + form.setLayout(new BorderLayout()); + Tabs tabs = new Tabs(placement); + for (int i = 0; i < names.length; i++) { + tabs.addTab(names[i], new Label(names[i])); + } + form.add(BorderLayout.CENTER, tabs); + form.revalidate(); + return tabs; + } + + @FormTest + void verticalPlacementsKeepTheRegularTabs() { + // The floating pill and its lens are horizontal; LEFT and RIGHT keep the + // ordinary rendering even under the ios27 preset. + assertFalse(glassTabs(Component.LEFT, new String[]{"One", "Two"}, false).isGlassMotion()); + assertFalse(glassTabs(Component.RIGHT, new String[]{"One", "Two"}, false).isGlassMotion()); + assertTrue(glassTabs(Component.TOP, new String[]{"One", "Two"}, false).isGlassMotion()); + } + + @FormTest + void anEmptyBarPaintsWithoutTabs() { + Tabs tabs = glassTabs(Component.BOTTOM, new String[0], false); + assertTrue(tabs.isGlassMotion()); + Container tc = tabs.getTabsContainer(); + Image img = Image.createImage(Math.max(1, tc.getWidth()), Math.max(1, tc.getHeight()), 0); + Graphics g = img.getGraphics(); + g.translate(-tc.getX(), -tc.getY()); + // Before the first tab, and again after the last one is removed: the bar's + // glass and its paint both resolve the (tab-less) glass geometry. + tabs.paintGlassBarBackground(g); + tc.paintComponent(g, true); + tabs.addTab("One", new Label("1")); + tabs.removeTabAt(0); + tabs.getComponentForm().revalidate(); + tabs.paintGlassBarBackground(g); + tc.paintComponent(g, true); + } + + @FormTest + void rightToLeftPutsTheFirstTabOnTheRight() { + // RTL as an application turns it on: for the whole look and feel. + UIManager.getInstance().getLookAndFeel().setRTL(true); + try { + Tabs tabs = glassTabs(Component.BOTTOM, new String[]{"One", "Two", "Three"}, true); + Container tc = tabs.getTabsContainer(); + assertTrue(tc.isRTL(), "precondition: the bar is right to left"); + assertTrue(tc.getComponentAt(0).getX() > tc.getComponentAt(1).getX() + && tc.getComponentAt(1).getX() > tc.getComponentAt(2).getX(), "tabs must run right to left"); + } finally { + UIManager.getInstance().getLookAndFeel().setRTL(false); + } + } + + @FormTest + void overlappingTabsSplitAtTheMidpointBetweenCentres() { + // Each tab is as wide as its resting lens, wider than the pitch, so + // neighbours overlap. A point just on one side of the midpoint between two + // centres belongs to that side's tab, for taps and scrubs alike. + // Neighbours overlap by twice the lens inset; a larger inset makes that + // many pixels wide at the test display's density. + Hashtable wide = new Hashtable(); + wide.put("@tabsGlassInsetPt", "20"); + UIManager.getInstance().addThemeProps(wide); + Tabs tabs = glassTabs(Component.BOTTOM, new String[]{"A", "B", "C", "D", "E"}, false); + Container tc = tabs.getTabsContainer(); + Component b = tc.getComponentAt(1); + Component c = tc.getComponentAt(2); + assertTrue(b.getX() + b.getWidth() > c.getX(), "precondition: neighbouring tabs overlap"); + float mid = (b.getAbsoluteX() + b.getWidth() / 2f + c.getAbsoluteX() + c.getWidth() / 2f) / 2f; + int y = c.getAbsoluteY() + c.getHeight() / 2; + // Both probe points lie inside the overlap, one on each side of the midpoint. + int leftOfMid = c.getAbsoluteX(); + int rightOfMid = b.getAbsoluteX() + b.getWidth() - 1; + assertTrue(leftOfMid < mid - 1 && rightOfMid > mid + 1, + "precondition: the overlap straddles the midpoint (" + leftOfMid + ".." + rightOfMid + " around " + mid + ")"); + assertSame(b, tc.getComponentAt(leftOfMid, y), "left of the midpoint is the left tab"); + assertSame(c, tc.getComponentAt(rightOfMid, y), "right of the midpoint is the right tab"); + } + + @FormTest + void theLensTracksPressesWithSwipingOff() { + Tabs tabs = glassTabs(); + tabs.setSwipeActivated(false); + Component target = tabs.getTabsContainer().getComponentAt(2); + implementation.dispatchPointerPress(target.getAbsoluteX() + target.getWidth() / 2, + target.getAbsoluteY() + target.getHeight() / 2); + flushSerialCalls(); + assertTrue(tabs.isGlassMotionRunningTo(2), "touch-down must lift the lens with swiping disabled"); + } + + @FormTest + void forcedGlassStatesOverrideSelectionAndFocus() { + Tabs tabs = glassTabs(); + Button selected = (Button) tabs.getTabsContainer().getComponentAt(0); + assertTrue(selected.isSelected()); + // The case that broke on device: a focused selected tab under rendered + // selection answers its SELECTED style, which kept the accent colour on the + // label after the lens had left it. + implementation.setTouchDevice(false); + selected.requestFocus(); + assertTrue(selected.hasFocus()); + assertNotSame(selected.getUnselectedStyle(), selected.getStyle(), "precondition: focus changes the style"); + selected.glassPaintState = Button.GLASS_PAINT_DEFAULT; + try { + assertSame(selected.getUnselectedStyle(), selected.getStyle(), + "outside the lens the selected tab must paint unselected"); + assertFalse(selected.isPressedStyle()); + } finally { + selected.glassPaintState = Button.GLASS_PAINT_NONE; + } + Button other = (Button) tabs.getTabsContainer().getComponentAt(2); + other.glassPaintState = Button.GLASS_PAINT_SELECTED; + try { + assertSame(other.getPressedStyle(), other.getStyle(), + "inside the lens every tab paints as selected"); + } finally { + other.glassPaintState = Button.GLASS_PAINT_NONE; + } + } + + @FormTest + void lensStartsMovingOnPressAndSelectionFollowsOnRelease() { + Tabs tabs = glassTabs(); + assertTrue(tabs.isGlassMotion()); + Component target = tabs.getTabsContainer().getComponentAt(2); + int x = target.getAbsoluteX() + target.getWidth() / 2; + int y = target.getAbsoluteY() + target.getHeight() / 2; + implementation.dispatchPointerPress(x, y); + flushSerialCalls(); + assertEquals(0, tabs.getSelectedIndex(), "selection still changes on release"); + tabs.recordGlassPress(x, y); + assertTrue(tabs.isGlassMotionRunningTo(2), "the lens must already travel on touch-down"); + implementation.dispatchPointerRelease(x, y); + flushSerialCalls(); + assertTrue(tabs.isGlassMotionRunningTo(2), "the release must not restart or reverse the motion"); + } + + @FormTest + void cancelledPressSendsTheLensBack() { + Tabs tabs = glassTabs(); + Component target = tabs.getTabsContainer().getComponentAt(2); + tabs.recordGlassPress(target.getAbsoluteX() + target.getWidth() / 2, + target.getAbsoluteY() + target.getHeight() / 2); + assertTrue(tabs.isGlassMotionRunningTo(2)); + // The press never became a selection (released elsewhere). + tabs.checkGlassPressOutcome(); + assertTrue(tabs.isGlassMotionRunningTo(0), "the lens must return to the selected tab"); + } + + @FormTest + void pressOnTheSelectedTabLiftsTheLensInPlace() { + Tabs tabs = glassTabs(); + Component selected = tabs.getTabsContainer().getComponentAt(0); + tabs.recordGlassPress(selected.getAbsoluteX() + selected.getWidth() / 2, + selected.getAbsoluteY() + selected.getHeight() / 2); + assertTrue(tabs.isGlassMotionRunningTo(0), "UIKit lifts and pulses the lens on the selected tab too"); + } + + @FormTest + void scrubbingSelectsTheTabUnderTheFinger() { + Tabs tabs = glassTabs(); + Container tc = tabs.getTabsContainer(); + Component first = tc.getComponentAt(0); + Component last = tc.getComponentAt(2); + int y = first.getAbsoluteY() + first.getHeight() / 2; + int x0 = first.getAbsoluteX() + first.getWidth() / 2; + int x1 = last.getAbsoluteX() + last.getWidth() / 2; + tabs.recordGlassPress(x0, y); + for (int k = 1; k <= 8; k++) { + tabs.recordGlassDrag(x0 + (x1 - x0) * k / 8); + } + tabs.recordGlassRelease(x1); + flushSerialCalls(); + assertEquals(2, tabs.getSelectedIndex(), "a scrub selects where the finger let go"); + assertTrue(tabs.isGlassMotionRunningTo(2), "and the lens settles there"); + tabs.checkGlassPressOutcome(); + assertTrue(tabs.isGlassMotionRunningTo(2), "the press outcome check must not send a scrub back"); + } + + @FormTest + void aShortWiggleIsATapNotAScrub() { + Tabs tabs = glassTabs(); + Component target = tabs.getTabsContainer().getComponentAt(2); + int x = target.getAbsoluteX() + target.getWidth() / 2; + int y = target.getAbsoluteY() + target.getHeight() / 2; + tabs.recordGlassPress(x, y); + tabs.recordGlassDrag(x + 1); + tabs.recordGlassRelease(x + 1); + flushSerialCalls(); + assertTrue(tabs.isGlassMotionRunningTo(2)); + assertEquals(0, tabs.getSelectedIndex(), "selection is left to the button's own release"); + } + + @FormTest + void vibrantContentUsesColorMatrixRegionWithLensSplitMasks() { + Tabs tabs = glassTabs(); + Container tc = tabs.getTabsContainer(); + for (int i = 0; i < tc.getComponentCount(); i++) { + Button b = (Button) tc.getComponentAt(i); + Style[] styles = {b.getUnselectedStyle(), b.getSelectedStyle(), b.getPressedStyle()}; + for (Style st : styles) { + st.setBgTransparency(255); + st.setBgColor(0xff0000); + } + } + implementation.setColorMatrixRegionSupported(true); + try { + Form form = Display.getInstance().getCurrent(); + Image img = Image.createImage(form.getWidth(), form.getHeight(), 0xffffffff); + form.paintComponent(img.getGraphics()); + java.util.List calls = + implementation.getColorMatrixCalls(); + assertEquals(3, calls.size(), "platter, unselected content, selected content"); + + int fg = tc.getStyle().getFgColor(); + boolean dark = 0.2126f * ((fg >> 16) & 0xff) + 0.7152f * ((fg >> 8) & 0xff) + 0.0722f * (fg & 0xff) > 128; + com.codename1.testing.TestCodenameOneImplementation.ColorMatrixCall platter = calls.get(0); + assertNull(platter.mask); + assertTrue(platter.cornerRadius < 0, "the platter is a capsule"); + assertEquals(dark ? -0.07f : -0.2f, platter.matrix[3], 1e-6f); + + com.codename1.testing.TestCodenameOneImplementation.ColorMatrixCall unselected = calls.get(1); + com.codename1.testing.TestCodenameOneImplementation.ColorMatrixCall selected = calls.get(2); + Button first = (Button) tc.getComponentAt(0); + Button last = (Button) tc.getComponentAt(2); + assertArrayEquals(com.codename1.ui.plaf.VibrancyMatrix.forTint( + first.getUnselectedStyle().getFgColor(), dark), unselected.matrix, 1e-6f); + assertEquals(unselected.width, unselected.maskWidth); + assertEquals(unselected.height, unselected.maskHeight); + + // The lens rests on the selected first tab: its centre is in the selected + // mask only, the last tab's centre in the unselected mask only. + int fx = first.getAbsoluteX() + first.getWidth() / 2 - selected.x; + int fy = first.getAbsoluteY() + first.getHeight() / 2 - selected.y; + int lx = last.getAbsoluteX() + last.getWidth() / 2 - unselected.x; + int ly = last.getAbsoluteY() + last.getHeight() / 2 - unselected.y; + assertTrue(alpha(selected, fx, fy) > 0, "selected tab inside the lens"); + assertEquals(0, alpha(unselected, fx, fy)); + assertTrue(alpha(unselected, lx, ly) > 0, "other tab outside the lens"); + assertEquals(0, alpha(selected, lx, ly)); + } finally { + implementation.setColorMatrixRegionSupported(false); + implementation.clearColorMatrixCalls(); + } + } + + @FormTest + void theIos26DropMayBulgePastTheBarInFlight() { + // The iOS 26 selection drop is taller than the bar mid-flight, as UIKit's + // is. Its lens must be drawn with a clip that lets it past the bar's edges; + // on Metal the bulge used to show only because an encoder restart dropped + // the clip, and it vanished once the clip was honoured. + Hashtable props = new Hashtable(); + props.put("@tabsMorphPreset", "ios26"); + props.put("@tabsSelectionCapsuleBool", "true"); + props.put("@glassMaterialBool", "true"); + UIManager.getInstance().addThemeProps(props); + Form form = Display.getInstance().getCurrent(); + form.removeAll(); + form.setLayout(new BorderLayout()); + Tabs tabs = new Tabs(Component.BOTTOM); + tabs.addTab("One", new Label("1")); + tabs.addTab("Two", new Label("2")); + tabs.addTab("Three", new Label("3")); + form.add(BorderLayout.CENTER, tabs); + form.revalidate(); + assertFalse(tabs.isGlassMotion(), "precondition: the iOS 26 morph, not the iOS 27 glass"); + Container tc = tabs.getTabsContainer(); + tabs.setMorphTestState(0, 2, 50); + Image img = Image.createImage(form.getWidth(), form.getHeight(), 0xffffffff); + form.paintComponent(img.getGraphics()); + java.util.List calls = + implementation.getLensCalls(); + assertFalse(calls.isEmpty(), "precondition: the drop is drawn through lensRegion"); + com.codename1.testing.TestCodenameOneImplementation.LensCall drop = calls.get(calls.size() - 1); + int barTop = tc.getAbsoluteY(); + int barBottom = barTop + tc.getHeight(); + assertTrue(drop.y < barTop || drop.y + drop.height > barBottom, + "precondition: mid-flight the drop is taller than the bar (" + drop.y + ".." + (drop.y + drop.height) + + " vs " + barTop + ".." + barBottom + ")"); + // The clip reaches past the bar wherever the drop bulges, as far as the Tabs + // itself (the area its morph repaints every frame) -- never beyond it. + int tabsTop = tabs.getAbsoluteY(); + int tabsBottom = tabsTop + tabs.getHeight(); + int clipTop = drop.clipY; + int clipBottom = drop.clipY + drop.clipHeight; + String where = " (clip " + clipTop + ".." + clipBottom + ", drop " + drop.y + ".." + (drop.y + drop.height) + + ", bar " + barTop + ".." + barBottom + ", tabs " + tabsTop + ".." + tabsBottom + ")"; + if (drop.y + drop.height > barBottom) { + assertTrue(clipBottom >= Math.min(drop.y + drop.height, tabsBottom), "the bulge below the bar is clipped" + where); + } + if (drop.y < barTop) { + assertTrue(clipTop <= Math.max(drop.y, tabsTop), "the bulge above the bar is clipped" + where); + } + assertTrue(clipTop >= tabsTop && clipBottom <= tabsBottom, "the clip stays inside the Tabs" + where); + } + + @FormTest + void forcedGlassStatesMeasureTheirOwnFont() { + // Label caches its string width for the unselected style only; the forced + // selected state must not be measured with the unselected font's width. + Button b = new Button("Two"); + // Distinct classes: every test-port font reports the same face, size and + // style, so plain Fonts compare equal and Style.setFont would skip them. + Font small = new Font(implementation.createTestFont(6, 12)) { + }; + Font large = new Font(implementation.createTestFont(11, 20)) { + }; + b.getUnselectedStyle().setFont(small); + b.getPressedStyle().setFont(large); + assertNotEquals(small.stringWidth(b.getText()), large.stringWidth(b.getText()), + "precondition: the two fonts measure the title differently"); + // A normal unselected paint primes Label's unselected width cache. + assertSame(b.getUnselectedStyle(), b.getStyle()); + assertEquals(small.stringWidth(b.getText()), b.getStringWidth(b.getStyle().getFont())); + b.glassPaintState = Button.GLASS_PAINT_SELECTED; + try { + Style selected = b.getStyle(); + assertSame(large, selected.getFont(), "precondition: the selected pass paints the pressed style"); + assertEquals(large.stringWidth(b.getText()), b.getStringWidth(selected.getFont()), + "the selected pass measures with the selected font"); + } finally { + b.glassPaintState = Button.GLASS_PAINT_NONE; + } + } + + @FormTest + void aPresetSwitchReinstallsTheTabLayout() { + // Switching between the ios26 and ios27 presets flips the glass motion with + // the placement unchanged; the tab layout must follow. + Tabs tabs = glassTabs(); + Container tc = tabs.getTabsContainer(); + assertTrue(tabs.isGlassMotion()); + assertTrue(tc.getLayout() instanceof Tabs.GlassTabsLayout, "precondition: the glass layout"); + Hashtable ios26 = new Hashtable(); + ios26.put("@tabsMorphPreset", "ios26"); + UIManager.getInstance().addThemeProps(ios26); + tabs.refreshTheme(false); + assertFalse(tabs.isGlassMotion()); + assertFalse(tc.getLayout() instanceof Tabs.GlassTabsLayout, "back to the regular layout"); + Hashtable ios27 = new Hashtable(); + ios27.put("@tabsMorphPreset", "ios27"); + UIManager.getInstance().addThemeProps(ios27); + tabs.refreshTheme(false); + assertTrue(tc.getLayout() instanceof Tabs.GlassTabsLayout, "and the glass layout again"); + } + + @FormTest + void aPressUnderAnOverlayDoesNotLiftTheLens() { + // A layered-pane overlay (a side menu, a sheet) covering the bar receives + // the press; the lens under it must not lift or travel. + Tabs tabs = glassTabs(); + Component target = tabs.getTabsContainer().getComponentAt(2); + int x = target.getAbsoluteX() + target.getWidth() / 2; + int y = target.getAbsoluteY() + target.getHeight() / 2; + Form form = Display.getInstance().getCurrent(); + Container layer = form.getLayeredPane(); + layer.setLayout(new BorderLayout()); + layer.add(BorderLayout.CENTER, new Button("overlay")); + form.revalidate(); + assertFalse(tabs.getTabsContainer().equals(form.getComponentAt(x, y)) + || tabs.getTabsContainer().contains(form.getComponentAt(x, y)), "precondition: the overlay is on top"); + tabs.recordGlassPress(x, y); + assertFalse(tabs.isGlassMotionRunningTo(2), "the lens must stay under the overlay"); + } + + @FormTest + void tallTabContentGrowsTheBarInsteadOfBeingClipped() { + // The bar is UITabBar's 62 pt, but a tab whose content is taller than a + // native one (a large icon) must fit: the bar grows rather than clip it. + Tabs tabs = glassTabs(); + Container tc = tabs.getTabsContainer(); + int before = tc.getHeight(); + tabs.addTab("Tall", Image.createImage(24, before * 2, 0xff0000ff), new Label("tall")); + tabs.getComponentForm().revalidate(); + assertTrue(tc.getHeight() > before, "the bar grows for the taller tab (" + before + " -> " + tc.getHeight() + ")"); + for (int i = 0; i < tc.getComponentCount(); i++) { + Component c = tc.getComponentAt(i); + assertTrue(c.getHeight() >= c.getPreferredH(), + "tab " + i + " is clipped: " + c.getHeight() + " < " + c.getPreferredH()); + } + } + + @FormTest + void glassTabTitlesNeverScroll() { + // A ticker started on a tab (focus arriving while the tab was narrower) + // would drift the title with the clock; UITabBar never scrolls one. + Tabs tabs = glassTabs(); + Button first = (Button) tabs.getTabsContainer().getComponentAt(0); + first.setTickerEnabled(true); + first.startTicker(50, true); + assertTrue(first.isTickerRunning(), "precondition: the ticker runs"); + tabs.getTabsContainer().setShouldCalcPreferredSize(true); + tabs.getComponentForm().revalidate(); + assertFalse(first.isTickerRunning(), "the glass layout stops it"); + assertFalse(first.isTickerEnabled(), "and it cannot start again"); + } + + @FormTest + void eachTabIsVibrantInItsOwnTint() { + // A tab with its own colour (a custom UIID, a disabled tab) keeps it: the + // unselected content splits into one mask per tint, each with its matrix. + Tabs tabs = glassTabs(); + Container tc = tabs.getTabsContainer(); + for (int i = 0; i < tc.getComponentCount(); i++) { + Button b = (Button) tc.getComponentAt(i); + Style[] styles = {b.getUnselectedStyle(), b.getSelectedStyle(), b.getPressedStyle()}; + for (Style st : styles) { + st.setBgTransparency(255); + st.setBgColor(0xff0000); + } + } + Button middle = (Button) tc.getComponentAt(1); + Button last = (Button) tc.getComponentAt(2); + int own = 0x00aa00; + last.getUnselectedStyle().setFgColor(own); + assertNotEquals(own, middle.getUnselectedStyle().getFgColor(), "precondition: the tints differ"); + implementation.setColorMatrixRegionSupported(true); + try { + Form form = Display.getInstance().getCurrent(); + Image img = Image.createImage(form.getWidth(), form.getHeight(), 0xffffffff); + form.paintComponent(img.getGraphics()); + java.util.List calls = + implementation.getColorMatrixCalls(); + assertEquals(4, calls.size(), "platter, two unselected tints, selected content"); + int fg = tc.getStyle().getFgColor(); + boolean dark = 0.2126f * ((fg >> 16) & 0xff) + 0.7152f * ((fg >> 8) & 0xff) + 0.0722f * (fg & 0xff) > 128; + com.codename1.testing.TestCodenameOneImplementation.ColorMatrixCall shared = calls.get(1); + com.codename1.testing.TestCodenameOneImplementation.ColorMatrixCall ownTint = calls.get(2); + assertArrayEquals(com.codename1.ui.plaf.VibrancyMatrix.forTint( + middle.getUnselectedStyle().getFgColor(), dark), shared.matrix, 1e-6f); + assertArrayEquals(com.codename1.ui.plaf.VibrancyMatrix.forTint(own, dark), ownTint.matrix, 1e-6f); + int mx = middle.getAbsoluteX() + middle.getWidth() / 2 - shared.x; + int my = middle.getAbsoluteY() + middle.getHeight() / 2 - shared.y; + int lx = last.getAbsoluteX() + last.getWidth() / 2 - ownTint.x; + int ly = last.getAbsoluteY() + last.getHeight() / 2 - ownTint.y; + assertTrue(alpha(shared, mx, my) > 0, "the middle tab is in the shared tint's mask"); + assertEquals(0, alpha(shared, lx, ly), "the last tab is not in the shared tint's mask"); + assertTrue(alpha(ownTint, lx, ly) > 0, "the last tab is in its own tint's mask"); + assertEquals(0, alpha(ownTint, mx, my), "the middle tab is not in the last tab's mask"); + } finally { + implementation.setColorMatrixRegionSupported(false); + implementation.clearColorMatrixCalls(); + } + } + + @org.junit.jupiter.api.Test + void platterMatrixReproducesTheNativeReadback() { + // What UIKit reports for the platter's colorMatrix filter (motion probe KVC + // readback, four decimals), dark then light. + float[] dark = {1.1298f, -0.2361f, -0.0237f, -0.07f, -0.0701f, 0.964f, -0.0238f, -0.07f, + -0.0702f, -0.236f, 1.1762f, -0.07f}; + float[] light = {1.1851f, -0.0502f, -0.005f, -0.2f, -0.0149f, 1.1499f, -0.0051f, -0.2f, + -0.0149f, -0.05f, 1.1949f, -0.2f}; + assertArrayEquals(dark, Tabs.platterMatrix(0.33f, -0.07f), 1.5e-4f); + assertArrayEquals(light, Tabs.platterMatrix(0.07f, -0.2f), 1.5e-4f); + } + + private static int alpha(com.codename1.testing.TestCodenameOneImplementation.ColorMatrixCall c, int x, int y) { + return (c.mask[y * c.maskWidth + x] >>> 24) & 0xff; + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/plaf/ColorMatrixBlendTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/plaf/ColorMatrixBlendTest.java new file mode 100644 index 00000000000..632d46034a9 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/plaf/ColorMatrixBlendTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Codename One in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.ui.plaf; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * The reference math of Graphics.colorMatrixRegion, which the JavaSE port runs and + * the Metal shader cn1_fs_colormatrix mirrors. + */ +class ColorMatrixBlendTest { + + private static final float[] IDENTITY = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0}; + + private static int[] fill(int w, int h, int argb) { + int[] p = new int[w * h]; + java.util.Arrays.fill(p, argb); + return p; + } + + @Test + void identityMatrixLeavesPixelsAlone() { + int[] p = fill(8, 4, 0xff336699); + ColorMatrixBlend.apply(p, 8, 4, IDENTITY, null, 0, 0, 0, 1f); + for (int v : p) { + assertEquals(0xff336699, v); + } + } + + @Test + void platterMatrixReproducesTheMeasuredGrey() { + // The native dark platter turns the bar's 134 grey into 98.8. + float[] dark = {1.1298f, -0.2361f, -0.0237f, -0.07f, -0.0701f, 0.964f, -0.0238f, -0.07f, + -0.0702f, -0.236f, 1.1762f, -0.07f}; + int[] p = fill(4, 4, 0xff868686); + ColorMatrixBlend.apply(p, 4, 4, dark, null, 0, 0, 0, 1f); + assertEquals(99, (p[0] >> 16) & 0xff, 1); + assertEquals(99, (p[0] >> 8) & 0xff, 1); + assertEquals(99, p[0] & 0xff, 1); + } + + @Test + void amountMixesAndDestinationAlphaIsKept() { + float[] black = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + int[] p = fill(2, 2, 0x80c8c8c8); + ColorMatrixBlend.apply(p, 2, 2, black, null, 0, 0, 0, 0.5f); + assertEquals(0x80, (p[0] >>> 24) & 0xff); + assertEquals(100, (p[0] >> 16) & 0xff); + } + + @Test + void capsuleLeavesTheCornersAndMaskZeroLeavesPixels() { + float[] white = {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1}; + int w = 40; + int h = 10; + int[] p = fill(w, h, 0xff000000); + ColorMatrixBlend.apply(p, w, h, white, null, 0, 0, -1, 1f); + assertEquals(0xff000000, p[0], "outside the capsule's round end"); + assertEquals(0xffffffff, p[5 * w + 20], "inside the capsule"); + + int[] q = fill(w, h, 0xff000000); + int[] mask = new int[4]; + mask[1] = 0xff000000; // only the top right quarter of a 2x2 mask + ColorMatrixBlend.apply(q, w, h, white, mask, 2, 2, 0, 1f); + assertEquals(0xff000000, q[0], "mask alpha 0 leaves the pixel"); + assertEquals(0xffffffff, q[w - 1], "mask alpha 255 applies fully"); + assertEquals(0xff000000, q[(h - 1) * w + w - 1]); + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/plaf/GlassRecipeTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/plaf/GlassRecipeTest.java index c9923fddc51..c5c4b7e5977 100644 --- a/maven/core-unittests/src/test/java/com/codename1/ui/plaf/GlassRecipeTest.java +++ b/maven/core-unittests/src/test/java/com/codename1/ui/plaf/GlassRecipeTest.java @@ -155,7 +155,10 @@ public void lightChromeAndPillAreMeasuredUnchangedInIOS27() { // rather than a drive-by. assertSameMaterial(GlassRecipe.liquidChrome(false), GlassRecipe.liquidChrome27(false), "light chrome"); - assertSameMaterial(GlassRecipe.liquidPill(false), GlassRecipe.liquidPill27(false), + // The light PILL is no longer shared: measured directly against a known + // backdrop on a controller-managed tab bar it is a far lower-contrast + // material than the iOS 26 constants (see liquidPill27). + assertDifferentMaterial(GlassRecipe.liquidPill(false), GlassRecipe.liquidPill27(false), "light pill"); } @@ -172,9 +175,9 @@ public void darkMaterialsActuallyMovedInIOS27() { } @Test - public void onlyIOS27DarkChromeAndPillAreCurved() { - // The curve term is what distinguishes iOS 27's dark chrome and pill, - // whose measured residual against the best affine fit is a parabola in + public void onlyIOS27DarkChromeIsCurved() { + // The curve term is what distinguishes iOS 27's dark chrome, whose + // measured residual against the best affine fit is a parabola in // backdrop luma. Everywhere else the material is affine and the recipe must // say so exactly -- including the dark panel, where the curve was measured // on device and made every affected tile worse (see liquidPanel27). @@ -189,7 +192,9 @@ public void onlyIOS27DarkChromeAndPillAreCurved() { assertEquals(0f, GlassRecipe.liquidPill27(false).getCurve(), 0f, "light pill27 curve"); assertEquals(0f, GlassRecipe.liquidPanel27(false).getCurve(), 0f, "light panel27 curve"); assertTrue(GlassRecipe.liquidChrome27(true).getCurve() != 0f, "dark chrome27 lost its curve"); - assertTrue(GlassRecipe.liquidPill27(true).getCurve() != 0f, "dark pill27 lost its curve"); + // Measured directly against a known backdrop the dark pill is affine: a + // curve term did not reduce the residual (see liquidPill27). + assertEquals(0f, GlassRecipe.liquidPill27(true).getCurve(), 0f, "dark pill27 curve"); assertEquals(0f, GlassRecipe.liquidPanel27(true).getCurve(), 0f, "dark panel27 curve"); } diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/plaf/VibrancyMatrixTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/plaf/VibrancyMatrixTest.java new file mode 100644 index 00000000000..6eba6de99ff --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/plaf/VibrancyMatrixTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Codename One in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.ui.plaf; + +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Holds VibrancyMatrix to the matrices UIKit itself installs for a tab bar tint, + * read from the iOS 27 simulator (tab-glass-motion/native-vibrancy.csv). The + * closed forms must reproduce every native matrix entry to the precision the + * fixture stores, and the OUTPUT colours -- what a user sees -- over a spread of + * backdrops to within rounding. + */ +class VibrancyMatrixTest { + + private static final int[] BACKDROPS = {0x000000, 0xffffff, 0x808080, 0xc8c8c8, 0x3a3a3c, 0xff3b30, 0x34c759, + 0x007aff, 0xffcc00, 0xaf52de, 0x5ac8fa, 0xa9a9a9, 0x626262}; + + @Test + void matchesUikitForEveryMeasuredTint() throws IOException { + InputStream in = VibrancyMatrixTest.class.getResourceAsStream("/tab-glass-motion/native-vibrancy.csv"); + assertNotNull(in); + BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + int n = 0; + int grey = 0; + int worstDark = 0; + int worstLight = 0; + double worstEntry = 0; + try { + String line; + while ((line = r.readLine()) != null) { + if (line.startsWith("#") || line.trim().length() == 0) { + continue; + } + String[] p = line.split(","); + boolean dark = "dark".equals(p[0]); + int tint = Integer.parseInt(p[1], 16); + float[] nat = new float[12]; + for (int i = 0; i < 12; i++) { + nat[i] = Float.parseFloat(p[2 + i]); + } + float[] m = VibrancyMatrix.forTint(tint, dark); + for (int i = 0; i < 12; i++) { + worstEntry = Math.max(worstEntry, Math.abs(m[i] - nat[i])); + } + if (((tint >> 16) & 0xff) == (tint & 0xff) && ((tint >> 8) & 0xff) == (tint & 0xff)) { + grey++; + } + for (int b : BACKDROPS) { + int d = maxChannelDelta(VibrancyMatrix.apply(nat, b), VibrancyMatrix.apply(m, b)); + if (dark) { + worstDark = Math.max(worstDark, d); + } else { + worstLight = Math.max(worstLight, d); + } + } + n++; + } + } finally { + r.close(); + } + assertTrue(n > 600, "fixture truncated: " + n); + assertTrue(grey >= 30, "fixture lost its grey ramp: " + grey); + // The fixture stores five decimals (half a unit is 5e-6); the capture itself + // is float precision. Anything beyond that is a different formula. + assertTrue(worstEntry <= 2e-5, "matrix entry off by " + worstEntry); + assertTrue(worstDark <= 1, "dark off by " + worstDark + " levels"); + assertTrue(worstLight <= 1, "light off by " + worstLight + " levels"); + } + + @Test + void lightPaintsTheTintItselfOverWhite() { + // The light alpha is exactly the value that maps a white backdrop to the tint; + // sweep a coarse RGB grid (grey tints follow their own ramp and are skipped). + int worst = 0; + for (int r = 0; r < 256; r += 17) { + for (int g = 0; g < 256; g += 17) { + for (int b = 0; b < 256; b += 17) { + if (r == g && g == b) { + continue; + } + int tint = (r << 16) | (g << 8) | b; + worst = Math.max(worst, maxChannelDelta(tint, VibrancyMatrix.apply(VibrancyMatrix.forTint(tint, false), 0xffffff))); + } + } + } + assertTrue(worst <= 1, "white backdrop off the tint by " + worst + " levels"); + } + + @Test + void theSystemBlueSelectionIsVividInDark() { + // Dark system blue over the dark glass's grey: the native bar draws a bright + // cyan-blue, far from the plain #0A84FF a flat tint would paint. + int out = VibrancyMatrix.apply(VibrancyMatrix.forTint(0x0a84ff, true), 0x626262); + assertTrue(((out >> 8) & 0xff) > 180, "green channel " + ((out >> 8) & 0xff)); + } + + private static int maxChannelDelta(int a, int b) { + int d = 0; + for (int s = 0; s <= 16; s += 8) { + d = Math.max(d, Math.abs(((a >> s) & 0xff) - ((b >> s) & 0xff))); + } + return d; + } +} diff --git a/maven/core-unittests/src/test/resources/tab-glass-motion/native-gestures.csv b/maven/core-unittests/src/test/resources/tab-glass-motion/native-gestures.csv new file mode 100644 index 00000000000..e52be98323b --- /dev/null +++ b/maven/core-unittests/src/test/resources/tab-glass-motion/native-gestures.csv @@ -0,0 +1,3708 @@ +# kind,episode,... see TabGlassGestureTest +E,0,hold,51.000,137.000,51.000,223.000,1.4821,-1.0000,137.000 +F,0,0.0333,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,0.0667,59.830,0.2062,0.01950,-0.02640,1.0900,2.356 +F,0,0.0832,68.080,0.3594,0.03870,-0.05240,2.2100,4.521 +F,0,0.0998,77.150,0.5006,0.06080,-0.08220,3.5400,6.823 +F,0,0.1164,86.260,0.6200,0.08390,-0.11360,5.0000,8.987 +F,0,0.1332,94.890,0.7162,0.10540,-0.14380,6.4100,10.878 +F,0,0.1498,102.730,0.7912,0.11830,-0.16270,7.2800,12.412 +F,0,0.1665,109.640,0.8481,0.11700,-0.16280,7.1900,13.590 +F,0,0.1832,115.580,0.8906,0.10500,-0.14890,6.7200,14.412 +F,0,0.1998,120.560,0.9219,0.09070,-0.13090,6.2500,14.933 +F,0,0.2165,124.650,0.9444,0.07920,-0.11650,5.6100,15.207 +F,0,0.2332,127.960,0.9606,0.07150,-0.10740,4.8000,15.289 +F,0,0.2499,130.570,0.9725,0.06650,-0.10210,3.9000,15.234 +F,0,0.2664,132.600,1.0000,0.06280,-0.09840,3.0200,15.097 +F,0,0.2831,134.140,1.0000,0.05890,-0.09380,2.2500,14.933 +F,0,0.2998,135.290,1.0000,0.05390,-0.08690,1.6600,14.741 +F,0,0.3165,136.120,1.0000,0.04720,-0.07680,1.2700,14.577 +F,0,0.3331,136.690,1.0000,0.03870,-0.06350,1.0700,14.412 +F,0,0.3498,137.080,1.0000,0.02890,-0.04770,1.0400,14.275 +F,0,0.3665,137.330,1.0000,0.01820,-0.03030,1.1500,14.193 +F,0,0.3831,137.470,1.0000,0.00720,-0.01230,1.3400,14.138 +F,0,0.3997,137.530,1.0000,-0.00340,0.00520,1.5700,14.138 +F,0,0.4164,137.540,1.0000,-0.01320,0.02140,1.8000,14.138 +F,0,0.4331,137.520,1.0000,-0.02180,0.03570,2.0200,14.138 +F,0,0.4498,137.470,1.0000,-0.02900,0.04760,2.2000,14.138 +F,0,0.4664,137.420,1.0000,-0.03470,0.05710,2.3400,14.138 +F,0,0.4831,137.360,1.0000,-0.03880,0.06410,2.4300,14.138 +F,0,0.4996,137.310,1.0000,-0.04160,0.06880,2.4700,14.138 +F,0,0.5164,137.250,1.0000,-0.04320,0.07140,2.4700,14.138 +F,0,0.5330,137.210,1.0000,-0.04370,0.07230,2.4400,14.138 +F,0,0.5497,137.000,1.0000,-0.04330,0.07180,2.3700,14.138 +F,0,0.5681,137.000,1.0000,-0.04230,0.07010,2.2900,14.138 +F,0,0.5852,137.000,1.0000,-0.04070,0.06750,2.1800,14.138 +F,0,0.6021,137.000,1.0000,-0.03880,0.06440,2.0700,14.138 +F,0,0.6181,137.000,1.0000,-0.03660,0.06080,1.9400,14.138 +F,0,0.6331,137.000,1.0000,-0.03420,0.05680,1.7900,14.138 +F,0,0.6513,137.000,1.0000,-0.03150,0.05240,1.6100,14.138 +F,0,0.6684,137.000,1.0000,-0.02870,0.04760,1.4200,14.138 +F,0,0.6846,137.000,1.0000,-0.02570,0.04270,1.2400,14.138 +F,0,0.7026,137.000,1.0000,-0.02270,0.03770,1.0800,14.138 +F,0,0.7187,137.000,1.0000,-0.01970,0.03280,0.9300,14.138 +F,0,0.7346,137.000,1.0000,-0.01690,0.02810,0.7900,14.138 +F,0,0.7506,137.000,1.0000,-0.01420,0.02370,0.6800,14.138 +F,0,0.7679,137.000,1.0000,-0.01190,0.01970,0.5800,14.138 +F,0,0.7841,137.000,1.0000,-0.00970,0.01620,0.4900,14.138 +F,0,0.8011,137.000,1.0000,-0.00790,0.01320,0.4100,14.138 +F,0,0.8184,137.000,1.0000,-0.00640,0.01060,0.3500,14.138 +F,0,0.8347,137.000,1.0000,-0.00510,0.00840,0.2900,14.138 +F,0,0.8497,137.000,1.0000,-0.00400,0.00670,0.2300,14.138 +F,0,0.8664,137.000,1.0000,-0.00320,0.00540,0.1900,14.138 +F,0,0.8850,137.000,1.0000,-0.00260,0.00440,0.1400,14.138 +F,0,0.9016,137.000,1.0000,-0.00220,0.00360,0.1100,14.138 +F,0,0.9190,137.000,1.0000,-0.00180,0.00310,0.0700,14.138 +F,0,0.9353,137.000,1.0000,-0.00160,0.00270,0.0400,14.138 +F,0,0.9516,137.000,1.0000,-0.00150,0.00240,0.0200,14.138 +F,0,0.9683,137.000,1.0000,-0.00170,0.00230,-0.0000,14.138 +F,0,0.9846,137.000,1.0000,-0.00160,0.00260,-0.0200,14.138 +F,0,0.9997,137.000,1.0000,-0.00150,0.00250,-0.0400,14.138 +F,0,1.0180,137.000,1.0000,-0.00140,0.00230,-0.0500,14.138 +F,0,1.0336,137.000,1.0000,-0.00130,0.00220,-0.0600,14.138 +F,0,1.0511,137.000,1.0000,-0.00120,0.00200,-0.0600,14.138 +F,0,1.0680,137.000,1.0000,-0.00110,0.00190,-0.0700,14.138 +F,0,1.0844,137.000,1.0000,-0.00110,0.00180,-0.0700,14.138 +F,0,1.1004,137.000,1.0000,-0.00100,0.00160,-0.0700,14.138 +F,0,1.1181,137.000,1.0000,-0.00090,0.00150,-0.0700,14.138 +F,0,1.1346,137.000,1.0000,-0.00080,0.00140,-0.0700,14.138 +F,0,1.1513,137.000,1.0000,-0.00080,0.00130,-0.0700,14.138 +F,0,1.1664,137.000,1.0000,-0.00070,0.00110,-0.0700,14.138 +F,0,1.1848,137.000,1.0000,-0.00060,0.00100,-0.0600,14.138 +F,0,1.2013,137.000,1.0000,0.00000,0.00000,0.0000,14.138 +F,0,1.5332,137.000,0.7950,0.00000,0.00000,0.0000,12.275 +F,0,1.5498,137.000,0.6425,0.00000,0.00000,0.0000,10.467 +F,0,1.5664,137.000,0.5006,0.00000,0.00000,0.0000,8.439 +F,0,1.5853,137.000,0.3813,0.00000,0.00000,0.0000,6.412 +F,0,1.6019,137.000,0.2844,0.00000,0.00000,0.0000,4.521 +F,0,1.6164,137.000,0.2094,0.00000,0.00000,0.0000,2.877 +F,0,1.6352,137.000,0.1525,0.00000,0.00000,0.0000,1.507 +F,0,1.6498,137.000,0.1100,0.00000,0.00000,0.0000,0.438 +F,0,1.6665,137.000,0.0787,0.00000,0.00000,0.0000,-0.356 +F,0,1.6846,137.000,0.0556,0.00000,0.00000,0.0000,-0.877 +F,0,1.6998,137.000,0.0394,0.00000,0.00000,0.0000,-1.178 +F,0,1.7164,137.000,0.0281,0.00000,0.00000,0.0000,-1.315 +F,0,1.7331,137.000,0.0000,0.00000,0.00000,0.0000,-1.315 +F,0,1.7497,137.000,0.0000,0.00000,0.00000,0.0000,-1.233 +F,0,1.7664,137.000,0.0000,0.00000,0.00000,0.0000,-1.069 +F,0,1.7831,137.000,0.0000,0.00000,0.00000,0.0000,-0.904 +F,0,1.8010,137.000,0.0000,0.00000,0.00000,0.0000,-0.712 +F,0,1.8185,137.000,0.0000,0.00000,0.00000,0.0000,-0.521 +F,0,1.8351,137.000,0.0000,0.00000,0.00000,0.0000,-0.356 +F,0,1.8517,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,1.8684,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,1.8849,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,1.9016,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,1.9184,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,1.9352,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,1.9519,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,1.9689,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,1.9850,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,1.9998,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.0182,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.0350,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.0515,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.0678,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.0848,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.1012,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.1184,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.1346,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.1515,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.1681,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.1853,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.2013,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.2185,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.2349,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.2514,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.2680,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.2847,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.3016,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.3182,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.3330,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.3496,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.3663,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.3830,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.3995,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.4163,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,0,2.4330,137.000,0.0000,0.00450,-0.00590,0.2100,0.000 +F,0,2.4496,137.000,0.0000,0.01240,-0.01620,0.5900,0.000 +F,0,2.4662,137.000,0.0000,0.01630,-0.02130,0.7800,0.000 +F,0,2.4829,137.000,0.0000,0.01570,-0.02020,0.8500,0.000 +F,0,2.4995,137.000,0.0000,0.01360,-0.01720,0.9100,0.000 +F,0,2.5162,137.000,0.0000,0.01150,-0.01420,0.9200,0.000 +F,0,2.5329,137.000,0.0000,0.00950,-0.01150,0.8900,0.000 +F,0,2.5495,137.000,0.0000,0.00760,-0.00890,0.8300,0.000 +F,0,2.5663,137.000,0.0000,0.00540,-0.00600,0.7800,0.000 +F,0,2.5829,137.000,0.0000,0.00290,-0.00270,0.7400,0.000 +F,0,2.5995,137.000,0.0000,0.00010,0.00100,0.7200,0.000 +F,0,2.6162,137.000,0.0000,-0.00280,0.00500,0.7200,0.000 +F,0,2.6329,137.000,0.0000,-0.00570,0.00890,0.7300,0.000 +F,0,2.6496,137.000,0.0000,-0.00840,0.01260,0.7500,0.000 +F,0,2.6663,137.000,0.0000,-0.01080,0.01580,0.7600,0.000 +F,0,2.6829,137.000,0.0000,-0.01280,0.01840,0.7800,0.000 +F,0,2.6996,137.000,0.0000,-0.01420,0.02040,0.7800,0.000 +F,0,2.7163,137.000,0.0000,-0.01520,0.02160,0.7700,0.000 +F,0,2.7329,137.000,0.0000,-0.01560,0.02210,0.7500,0.000 +F,0,2.7497,137.000,0.0000,-0.01550,0.02190,0.7200,0.000 +F,0,2.7662,137.000,0.0000,-0.01510,0.02130,0.6800,0.000 +F,0,2.7826,137.000,0.0000,-0.01440,0.02030,0.6300,0.000 +F,0,2.7992,137.000,0.0000,-0.01340,0.01890,0.5800,0.000 +F,0,2.8159,137.000,0.0000,-0.01230,0.01730,0.5200,0.000 +F,0,2.8326,137.000,0.0000,-0.01100,0.01540,0.4600,0.000 +F,0,2.8502,137.000,0.0000,-0.00960,0.01350,0.4000,0.000 +F,0,2.8671,137.000,0.0000,-0.00820,0.01150,0.3400,0.000 +F,0,2.8842,137.000,0.0000,-0.00690,0.00970,0.2800,0.000 +F,0,2.9017,137.000,0.0000,-0.00570,0.00800,0.2300,0.000 +F,0,2.9184,137.000,0.0000,-0.00460,0.00650,0.1800,0.000 +F,0,2.9360,137.000,0.0000,-0.00370,0.00510,0.1400,0.000 +F,0,2.9525,137.000,0.0000,-0.00280,0.00400,0.1100,0.000 +F,0,2.9679,137.000,0.0000,-0.00210,0.00300,0.0800,0.000 +F,0,2.9847,137.000,0.0000,-0.00150,0.00220,0.0600,0.000 +F,0,3.0016,137.000,0.0000,-0.00110,0.00150,0.0400,0.000 +F,0,3.0184,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,1,hold,137.000,137.000,51.000,223.000,1.4999,-1.0000,137.000 +F,1,0.0058,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,0.0177,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,0.0344,137.000,0.0544,0.00000,0.00000,0.0000,0.548 +F,1,0.0510,137.000,0.1881,0.00000,0.00000,0.0000,2.137 +F,1,0.0677,137.000,0.3413,0.00000,0.00000,0.0000,4.247 +F,1,0.0844,137.000,0.4844,0.00000,0.00000,0.0000,6.549 +F,1,0.1011,137.000,0.6069,0.00000,0.00000,0.0000,8.741 +F,1,0.1177,137.000,0.7056,0.00000,0.00000,0.0000,10.659 +F,1,0.1344,137.000,0.7831,0.00000,0.00000,0.0000,12.248 +F,1,0.1511,137.000,0.8419,0.00000,0.00000,0.0000,13.481 +F,1,0.1677,137.000,0.8856,0.00000,0.00000,0.0000,14.330 +F,1,0.1843,137.000,0.9181,0.00000,0.00000,0.0000,14.878 +F,1,0.2011,137.000,0.9419,0.00000,0.00000,0.0000,15.180 +F,1,0.2177,137.000,0.9588,0.00000,0.00000,0.0000,15.289 +F,1,0.2344,137.000,0.9713,0.00000,0.00000,0.0000,15.234 +F,1,0.2510,137.000,1.0000,0.00000,0.00000,0.0000,15.125 +F,1,0.2677,137.000,1.0000,0.00000,0.00000,0.0000,14.960 +F,1,0.2843,137.000,1.0000,0.00000,0.00000,0.0000,14.769 +F,1,0.3011,137.000,1.0000,0.00000,0.00000,0.0000,14.577 +F,1,0.3177,137.000,1.0000,0.00000,0.00000,0.0000,14.440 +F,1,0.3343,137.000,1.0000,0.00000,0.00000,0.0000,14.303 +F,1,0.3510,137.000,1.0000,0.00000,0.00000,0.0000,14.193 +F,1,0.3677,137.000,1.0000,0.00000,0.00000,0.0000,14.138 +F,1,1.5343,137.000,0.9488,0.00000,0.00000,0.0000,13.727 +F,1,1.5509,137.000,0.8163,0.00000,0.00000,0.0000,12.494 +F,1,1.5676,137.000,0.6631,0.00000,0.00000,0.0000,10.713 +F,1,1.5861,137.000,0.5194,0.00000,0.00000,0.0000,8.713 +F,1,1.6028,137.000,0.3963,0.00000,0.00000,0.0000,6.686 +F,1,1.6192,137.000,0.2969,0.00000,0.00000,0.0000,4.768 +F,1,1.6365,137.000,0.2188,0.00000,0.00000,0.0000,3.096 +F,1,1.6510,137.000,0.1594,0.00000,0.00000,0.0000,1.671 +F,1,1.6700,137.000,0.1150,0.00000,0.00000,0.0000,0.575 +F,1,1.6843,137.000,0.0825,0.00000,0.00000,0.0000,0.000 +F,1,1.7009,137.000,0.0587,0.00000,0.00000,0.0000,-0.822 +F,1,1.7194,137.000,0.0412,0.00000,0.00000,0.0000,-1.151 +F,1,1.7362,137.000,0.0294,0.00000,0.00000,0.0000,-1.315 +F,1,1.7509,137.000,0.0000,0.00000,0.00000,0.0000,-1.343 +F,1,1.7691,137.000,0.0000,0.00000,0.00000,0.0000,-1.260 +F,1,1.7863,137.000,0.0000,0.00000,0.00000,0.0000,-1.096 +F,1,1.8024,137.000,0.0000,0.00000,0.00000,0.0000,-0.932 +F,1,1.8194,137.000,0.0000,0.00000,0.00000,0.0000,-0.740 +F,1,1.8361,137.000,0.0000,0.00000,0.00000,0.0000,-0.548 +F,1,1.8525,137.000,0.0000,0.00000,0.00000,0.0000,-0.356 +F,1,1.8698,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,1.8842,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,1.9028,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,1.9196,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,1.9362,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,1.9529,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,1.9676,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,1.9870,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.0023,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.0175,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.0363,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.0527,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.0685,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.0857,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.1029,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.1191,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.1362,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.1523,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.1695,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.1857,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.2029,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.2198,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.2360,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.2527,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.2695,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.2862,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.3028,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.3197,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.3363,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.3531,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.3698,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.3864,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.4034,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.4201,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.4356,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,1,2.4529,137.000,0.0000,0.00450,-0.00590,0.2100,0.000 +F,1,2.4690,137.000,0.0000,0.01240,-0.01620,0.5900,0.000 +F,1,2.4861,137.000,0.0000,0.01630,-0.02130,0.7800,0.000 +F,1,2.5030,137.000,0.0000,0.01570,-0.02020,0.8500,0.000 +F,1,2.5192,137.000,0.0000,0.01360,-0.01720,0.9100,0.000 +F,1,2.5360,137.000,0.0000,0.01150,-0.01420,0.9200,0.000 +F,1,2.5524,137.000,0.0000,0.00950,-0.01150,0.8900,0.000 +F,1,2.5691,137.000,0.0000,0.00760,-0.00890,0.8300,0.000 +F,1,2.5857,137.000,0.0000,0.00540,-0.00600,0.7800,0.000 +F,1,2.6022,137.000,0.0000,0.00290,-0.00270,0.7400,0.000 +F,1,2.6175,137.000,0.0000,0.00010,0.00100,0.7200,0.000 +F,1,2.6341,137.000,0.0000,-0.00280,0.00500,0.7200,0.000 +F,1,2.6507,137.000,0.0000,-0.00570,0.00890,0.7300,0.000 +F,1,2.6674,137.000,0.0000,-0.00840,0.01260,0.7500,0.000 +F,1,2.6841,137.000,0.0000,-0.01080,0.01580,0.7600,0.000 +F,1,2.7026,137.000,0.0000,-0.01280,0.01840,0.7800,0.000 +F,1,2.7196,137.000,0.0000,-0.01420,0.02040,0.7800,0.000 +F,1,2.7363,137.000,0.0000,-0.01520,0.02160,0.7700,0.000 +F,1,2.7528,137.000,0.0000,-0.01560,0.02210,0.7500,0.000 +F,1,2.7695,137.000,0.0000,-0.01550,0.02190,0.7200,0.000 +F,1,2.7861,137.000,0.0000,-0.01510,0.02130,0.6800,0.000 +F,1,2.8016,137.000,0.0000,-0.01440,0.02030,0.6300,0.000 +F,1,2.8182,137.000,0.0000,-0.01340,0.01890,0.5800,0.000 +F,1,2.8350,137.000,0.0000,-0.01230,0.01730,0.5200,0.000 +F,1,2.8521,137.000,0.0000,-0.01100,0.01540,0.4600,0.000 +F,1,2.8694,137.000,0.0000,-0.00960,0.01350,0.4000,0.000 +F,1,2.8856,137.000,0.0000,-0.00820,0.01150,0.3400,0.000 +F,1,2.9018,137.000,0.0000,-0.00690,0.00970,0.2800,0.000 +F,1,2.9190,137.000,0.0000,-0.00570,0.00800,0.2300,0.000 +F,1,2.9361,137.000,0.0000,-0.00460,0.00650,0.1800,0.000 +F,1,2.9525,137.000,0.0000,-0.00370,0.00510,0.1400,0.000 +F,1,2.9693,137.000,0.0000,-0.00280,0.00400,0.1100,0.000 +F,1,2.9855,137.000,0.0000,-0.00210,0.00300,0.0800,0.000 +F,1,3.0029,137.000,0.0000,-0.00150,0.00220,0.0600,0.000 +F,1,3.0187,137.000,0.0000,-0.00110,0.00150,0.0400,0.000 +F,1,3.0356,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,2,hold,137.000,137.000,51.000,223.000,0.0664,-1.0000,137.000 +F,2,0.0057,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.0177,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.0344,137.000,0.0531,0.00000,0.00000,0.0000,0.548 +F,2,0.0510,137.000,0.1862,0.00000,0.00000,0.0000,2.110 +F,2,0.0685,137.000,0.3394,0.00000,0.00000,0.0000,4.220 +F,2,0.0843,137.000,0.4831,0.00000,0.00000,0.0000,6.521 +F,2,0.1009,137.000,0.6056,0.00000,0.00000,0.0000,8.165 +F,2,0.1176,137.000,0.7050,0.00000,0.00000,0.0000,8.768 +F,2,0.1343,137.000,0.7825,0.00000,0.00000,0.0000,8.549 +F,2,0.1509,137.000,0.8412,0.00000,0.00000,0.0000,7.782 +F,2,0.1676,137.000,0.8856,0.00000,0.00000,0.0000,6.713 +F,2,0.1843,137.000,0.9181,0.00000,0.00000,0.0000,5.480 +F,2,0.2009,137.000,0.9419,0.00000,0.00000,0.0000,4.220 +F,2,0.2175,137.000,0.9588,0.00000,0.00000,0.0000,3.014 +F,2,0.2342,137.000,0.9713,0.00000,0.00000,0.0000,1.973 +F,2,0.2509,137.000,1.0000,0.00000,0.00000,0.0000,1.096 +F,2,0.2675,137.000,0.9331,0.00000,0.00000,0.0000,0.384 +F,2,0.2843,137.000,0.7950,0.00000,0.00000,0.0000,0.000 +F,2,0.3009,137.000,0.6425,0.00000,0.00000,0.0000,-0.493 +F,2,0.3176,137.000,0.5006,0.00000,0.00000,0.0000,-0.712 +F,2,0.3343,137.000,0.3813,0.00000,0.00000,0.0000,-0.822 +F,2,0.3509,137.000,0.2844,0.00000,0.00000,0.0000,-0.822 +F,2,0.3676,137.000,0.2094,0.00000,0.00000,0.0000,-0.767 +F,2,0.3843,137.000,0.1525,0.00000,0.00000,0.0000,-0.685 +F,2,0.4009,137.000,0.1100,0.00000,0.00000,0.0000,-0.575 +F,2,0.4176,137.000,0.0787,0.00000,0.00000,0.0000,-0.466 +F,2,0.4368,137.000,0.0556,0.00000,0.00000,0.0000,-0.329 +F,2,0.4531,137.000,0.0394,0.00000,0.00000,0.0000,0.000 +F,2,0.4696,137.000,0.0281,0.00000,0.00000,0.0000,0.000 +F,2,0.4842,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.5009,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.5192,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.5343,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.5530,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.5695,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.5861,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.6025,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.6175,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.6358,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.6531,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.6693,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.6866,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.7009,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.7198,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.7368,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.7509,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.7696,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.7862,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.8026,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.8190,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.8361,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.8528,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.8696,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.8859,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.9027,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.9176,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.9342,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.9526,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.9694,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,0.9860,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,1.0024,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,2,1.0198,137.000,0.0000,0.00450,-0.00590,0.2100,0.000 +F,2,1.0346,137.000,0.0000,0.01240,-0.01620,0.5900,0.000 +F,2,1.0521,137.000,0.0000,0.01630,-0.02130,0.7800,0.000 +F,2,1.0690,137.000,0.0000,0.01570,-0.02020,0.8500,0.000 +F,2,1.0860,137.000,0.0000,0.01360,-0.01720,0.9100,0.000 +F,2,1.1027,137.000,0.0000,0.01150,-0.01420,0.9200,0.000 +F,2,1.1187,137.000,0.0000,0.00950,-0.01150,0.8900,0.000 +F,2,1.1363,137.000,0.0000,0.00760,-0.00890,0.8300,0.000 +F,2,1.1527,137.000,0.0000,0.00540,-0.00600,0.7800,0.000 +F,2,1.1698,137.000,0.0000,0.00290,-0.00270,0.7400,0.000 +F,2,1.1861,137.000,0.0000,0.00010,0.00100,0.7200,0.000 +F,2,1.2029,137.000,0.0000,-0.00280,0.00500,0.7200,0.000 +F,2,1.2190,137.000,0.0000,-0.00570,0.00890,0.7300,0.000 +F,2,1.2358,137.000,0.0000,-0.00840,0.01260,0.7500,0.000 +F,2,1.2526,137.000,0.0000,-0.01080,0.01580,0.7600,0.000 +F,2,1.2694,137.000,0.0000,-0.01280,0.01840,0.7800,0.000 +F,2,1.2859,137.000,0.0000,-0.01420,0.02040,0.7800,0.000 +F,2,1.3025,137.000,0.0000,-0.01520,0.02160,0.7700,0.000 +F,2,1.3190,137.000,0.0000,-0.01560,0.02210,0.7500,0.000 +F,2,1.3359,137.000,0.0000,-0.01550,0.02190,0.7200,0.000 +F,2,1.3525,137.000,0.0000,-0.01510,0.02130,0.6800,0.000 +F,2,1.3696,137.000,0.0000,-0.01440,0.02030,0.6300,0.000 +F,2,1.3860,137.000,0.0000,-0.01340,0.01890,0.5800,0.000 +F,2,1.4038,137.000,0.0000,-0.01240,0.01740,0.5200,0.000 +F,2,1.4195,137.000,0.0000,-0.01120,0.01570,0.4500,0.000 +F,2,1.4364,137.000,0.0000,-0.00990,0.01380,0.3700,0.000 +F,2,1.4527,137.000,0.0000,-0.00850,0.01200,0.3100,0.000 +F,2,1.4693,137.000,0.0000,-0.00720,0.01010,0.2500,0.000 +F,2,1.4861,137.000,0.0000,-0.00590,0.00830,0.1900,0.000 +F,2,1.5008,137.000,0.0000,-0.00470,0.00660,0.1500,0.000 +F,2,1.5174,137.000,0.0000,-0.00360,0.00500,0.1100,0.000 +F,2,1.5337,137.000,0.0000,-0.00260,0.00370,0.0800,0.000 +F,2,1.5504,137.000,0.0000,-0.00180,0.00250,0.0600,0.000 +F,2,1.5670,137.000,0.0000,-0.00110,0.00160,0.0400,0.000 +F,2,1.5837,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,3,hold,137.000,223.000,51.000,223.000,3.0000,-1.0000,223.000 +F,3,0.0055,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,0.0178,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,0.0345,139.100,0.0556,0.00000,0.00000,0.0000,0.575 +F,3,0.0512,145.040,0.1900,0.00550,-0.00740,0.3000,2.165 +F,3,0.0679,153.140,0.3431,0.01940,-0.02620,1.0900,4.274 +F,3,0.0845,162.170,0.4863,0.03850,-0.05210,2.2000,6.576 +F,3,0.1012,171.300,0.6081,0.06050,-0.08190,3.5300,8.768 +F,3,0.1178,180.000,0.7069,0.08370,-0.11330,4.9900,10.686 +F,3,0.1345,187.940,0.7838,0.10470,-0.14290,6.3600,12.275 +F,3,0.1512,194.950,0.8425,0.11650,-0.16010,7.1600,13.481 +F,3,0.1678,200.990,0.8863,0.11430,-0.15860,7.5600,14.330 +F,3,0.1845,206.070,0.9188,0.10420,-0.14680,7.7700,14.878 +F,3,0.2012,210.250,0.9419,0.09460,-0.13530,7.5400,15.180 +F,3,0.2178,213.640,0.9594,0.08820,-0.12840,6.9000,15.289 +F,3,0.2345,216.320,0.9713,0.08450,-0.12570,5.9800,15.234 +F,3,0.2512,218.410,1.0000,0.08220,-0.12460,4.9500,15.125 +F,3,0.2678,220.000,1.0000,0.07960,-0.12260,3.9400,14.960 +F,3,0.2845,221.180,1.0000,0.07520,-0.11740,3.0800,14.769 +F,3,0.3011,222.040,1.0000,0.06860,-0.10790,2.4300,14.577 +F,3,0.3176,222.640,1.0000,0.05950,-0.09410,2.0100,14.440 +F,3,0.3343,223.050,1.0000,0.04830,-0.07660,1.8100,14.303 +F,3,0.3511,223.310,1.0000,0.03560,-0.05640,1.7800,14.193 +F,3,0.3678,223.450,1.0000,0.02230,-0.03510,1.8700,14.138 +F,3,0.3844,223.520,1.0000,0.00920,-0.01380,2.0400,14.138 +F,3,0.4010,223.540,1.0000,-0.00310,0.00620,2.2300,14.138 +F,3,0.4177,223.520,1.0000,-0.01400,0.02400,2.4200,14.138 +F,3,0.4344,223.480,1.0000,-0.02320,0.03910,2.5700,14.138 +F,3,0.4511,223.430,1.0000,-0.03060,0.05130,2.6800,14.138 +F,3,0.4676,223.370,1.0000,-0.03620,0.06040,2.7400,14.138 +F,3,0.4843,223.310,1.0000,-0.04010,0.06670,2.7500,14.138 +F,3,0.5009,223.260,1.0000,-0.04240,0.07060,2.7200,14.138 +F,3,0.5177,223.210,1.0000,-0.04350,0.07230,2.6600,14.138 +F,3,0.5359,223.000,1.0000,-0.04340,0.07220,2.5600,14.138 +F,3,0.5531,223.000,1.0000,-0.04260,0.07080,2.4400,14.138 +F,3,0.5696,223.000,1.0000,-0.04110,0.06830,2.3100,14.138 +F,3,0.5865,223.000,1.0000,-0.03920,0.06520,2.1700,14.138 +F,3,0.6030,223.000,1.0000,-0.03690,0.06140,2.0000,14.138 +F,3,0.6191,223.000,1.0000,-0.03430,0.05690,1.8000,14.138 +F,3,0.6362,223.000,1.0000,-0.03130,0.05200,1.5900,14.138 +F,3,0.6525,223.000,1.0000,-0.02810,0.04670,1.3900,14.138 +F,3,0.6698,223.000,1.0000,-0.02490,0.04140,1.2100,14.138 +F,3,0.6861,223.000,1.0000,-0.02170,0.03600,1.0400,14.138 +F,3,0.7030,223.000,1.0000,-0.01860,0.03090,0.8900,14.138 +F,3,0.7197,223.000,1.0000,-0.01570,0.02620,0.7600,14.138 +F,3,0.7358,223.000,1.0000,-0.01310,0.02180,0.6500,14.138 +F,3,0.7529,223.000,1.0000,-0.01080,0.01800,0.5500,14.138 +F,3,0.7691,223.000,1.0000,-0.00880,0.01460,0.4700,14.138 +F,3,0.7859,223.000,1.0000,-0.00710,0.01180,0.3900,14.138 +F,3,0.8027,223.000,1.0000,-0.00570,0.00950,0.3200,14.138 +F,3,0.8178,223.000,1.0000,-0.00460,0.00760,0.2600,14.138 +F,3,0.8360,223.000,1.0000,-0.00370,0.00610,0.2100,14.138 +F,3,0.8511,223.000,1.0000,-0.00300,0.00500,0.1600,14.138 +F,3,0.8699,223.000,1.0000,-0.00250,0.00410,0.1200,14.138 +F,3,0.8860,223.000,1.0000,-0.00210,0.00350,0.0800,14.138 +F,3,0.9026,223.000,1.0000,-0.00190,0.00310,0.0500,14.138 +F,3,0.9195,223.000,1.0000,-0.00170,0.00280,0.0200,14.138 +F,3,0.9347,223.000,1.0000,-0.00190,0.00260,-0.0000,14.138 +F,3,0.9528,223.000,1.0000,-0.00180,0.00300,-0.0200,14.138 +F,3,0.9677,223.000,1.0000,-0.00170,0.00280,-0.0400,14.138 +F,3,0.9860,223.000,1.0000,-0.00160,0.00260,-0.0500,14.138 +F,3,1.0010,223.000,1.0000,-0.00150,0.00250,-0.0600,14.138 +F,3,1.0197,223.000,1.0000,-0.00140,0.00230,-0.0700,14.138 +F,3,1.0360,223.000,1.0000,-0.00130,0.00210,-0.0800,14.138 +F,3,1.0527,223.000,1.0000,-0.00120,0.00200,-0.0800,14.138 +F,3,1.0695,223.000,1.0000,-0.00110,0.00180,-0.0800,14.138 +F,3,1.0860,223.000,1.0000,-0.00100,0.00170,-0.0800,14.138 +F,3,1.1027,223.000,1.0000,-0.00090,0.00150,-0.0800,14.138 +F,3,1.1176,223.000,1.0000,-0.00080,0.00140,-0.0800,14.138 +F,3,1.1343,223.000,1.0000,-0.00080,0.00130,-0.0800,14.138 +F,3,1.1509,223.000,1.0000,-0.00070,0.00110,-0.0700,14.138 +F,3,1.1676,223.000,1.0000,-0.00060,0.00100,-0.0700,14.138 +F,3,1.1843,223.000,1.0000,0.00000,0.00000,0.0000,14.138 +F,3,3.0510,223.000,0.8406,0.00000,0.00000,0.0000,12.741 +F,3,3.0678,223.000,0.6887,0.00000,0.00000,0.0000,11.042 +F,3,3.0844,223.000,0.5419,0.00000,0.00000,0.0000,9.069 +F,3,3.1011,223.000,0.4150,0.00000,0.00000,0.0000,7.014 +F,3,3.1192,223.000,0.3119,0.00110,-0.00150,0.0300,5.069 +F,3,3.1367,223.000,0.2306,0.00170,-0.00240,0.0700,3.343 +F,3,3.1511,223.000,0.1681,0.00250,-0.00360,0.1100,1.891 +F,3,3.1699,223.000,0.1219,0.00350,-0.00500,0.1600,0.740 +F,3,3.1864,223.000,0.0875,0.00450,-0.00630,0.2000,0.000 +F,3,3.2011,223.000,0.0619,0.00540,-0.00760,0.2500,-0.740 +F,3,3.2177,223.000,0.0438,0.00600,-0.00840,0.2800,-1.123 +F,3,3.2344,223.000,0.0312,0.00630,-0.00880,0.2900,-1.288 +F,3,3.2510,223.000,0.0219,0.00620,-0.00870,0.2900,-1.343 +F,3,3.2677,223.000,0.0000,0.00580,-0.00810,0.2700,-1.260 +F,3,3.2844,223.000,0.0000,0.00500,-0.00710,0.2400,-1.123 +F,3,3.3024,223.000,0.0000,0.00410,-0.00570,0.1900,-0.959 +F,3,3.3183,223.000,0.0000,0.00290,-0.00410,0.1400,-0.767 +F,3,3.3358,223.000,0.0000,0.00160,-0.00230,0.0800,-0.575 +F,3,3.3511,223.000,0.0000,0.00000,0.00000,0.0000,-0.384 +F,3,3.3692,223.000,0.0000,-0.00080,0.00110,-0.0400,0.000 +F,3,3.3843,223.000,0.0000,-0.00180,0.00260,-0.0800,0.000 +F,3,3.4027,223.000,0.0000,-0.00270,0.00380,-0.1300,0.000 +F,3,3.4195,223.000,0.0000,-0.00340,0.00480,-0.1600,0.000 +F,3,3.4362,223.000,0.0000,-0.00390,0.00550,-0.1800,0.000 +F,3,3.4530,223.000,0.0000,-0.00420,0.00590,-0.2000,0.000 +F,3,3.4698,223.000,0.0000,-0.00430,0.00610,-0.2000,0.000 +F,3,3.4866,223.000,0.0000,-0.00430,0.00600,-0.2000,0.000 +F,3,3.5034,223.000,0.0000,-0.00410,0.00580,-0.1900,0.000 +F,3,3.5189,223.000,0.0000,-0.00380,0.00540,-0.1800,0.000 +F,3,3.5344,223.000,0.0000,-0.00350,0.00490,-0.1600,0.000 +F,3,3.5526,223.000,0.0000,-0.00320,0.00450,-0.1500,0.000 +F,3,3.5695,223.000,0.0000,-0.00280,0.00400,-0.1300,0.000 +F,3,3.5861,223.000,0.0000,-0.00250,0.00350,-0.1200,0.000 +F,3,3.6010,223.000,0.0000,-0.00220,0.00300,-0.1000,0.000 +F,3,3.6197,223.000,0.0000,-0.00180,0.00260,-0.0800,0.000 +F,3,3.6366,223.000,0.0000,-0.00150,0.00220,-0.0700,0.000 +F,3,3.6530,223.000,0.0000,-0.00130,0.00180,-0.0500,0.000 +F,3,3.6690,223.000,0.0000,-0.00100,0.00140,-0.0400,0.000 +F,3,3.6863,223.000,0.0000,-0.00080,0.00110,-0.0300,0.000 +F,3,3.7025,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.7198,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.7362,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.7529,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.7698,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.7843,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.8028,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.8189,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.8365,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.8531,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.8695,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.8862,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.9026,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.9200,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.9362,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.9526,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,3,3.9693,223.000,0.0000,0.00440,-0.00580,0.2200,0.000 +F,3,3.9859,223.000,0.0000,0.01230,-0.01610,0.6000,0.000 +F,3,4.0027,223.000,0.0000,0.01620,-0.02110,0.7900,0.000 +F,3,4.0194,223.000,0.0000,0.01560,-0.02010,0.8500,0.000 +F,3,4.0360,223.000,0.0000,0.01360,-0.01710,0.9200,0.000 +F,3,4.0526,223.000,0.0000,0.01140,-0.01410,0.9200,0.000 +F,3,4.0696,223.000,0.0000,0.00950,-0.01140,0.8900,0.000 +F,3,4.0867,223.000,0.0000,0.00750,-0.00880,0.8300,0.000 +F,3,4.1027,223.000,0.0000,0.00530,-0.00590,0.7800,0.000 +F,3,4.1197,223.000,0.0000,0.00290,-0.00260,0.7400,0.000 +F,3,4.1359,223.000,0.0000,0.00010,0.00110,0.7200,0.000 +F,3,4.1528,223.000,0.0000,-0.00280,0.00500,0.7200,0.000 +F,3,4.1692,223.000,0.0000,-0.00570,0.00890,0.7300,0.000 +F,3,4.1862,223.000,0.0000,-0.00850,0.01260,0.7500,0.000 +F,3,4.2030,223.000,0.0000,-0.01090,0.01590,0.7700,0.000 +F,3,4.2196,223.000,0.0000,-0.01280,0.01850,0.7800,0.000 +F,3,4.2362,223.000,0.0000,-0.01430,0.02040,0.7800,0.000 +F,3,4.2530,223.000,0.0000,-0.01520,0.02160,0.7700,0.000 +F,3,4.2694,223.000,0.0000,-0.01560,0.02210,0.7500,0.000 +F,3,4.2861,223.000,0.0000,-0.01550,0.02200,0.7200,0.000 +F,3,4.3019,223.000,0.0000,-0.01510,0.02130,0.6800,0.000 +F,3,4.3184,223.000,0.0000,-0.01440,0.02030,0.6300,0.000 +F,3,4.3349,223.000,0.0000,-0.01340,0.01880,0.5700,0.000 +F,3,4.3518,223.000,0.0000,-0.01210,0.01700,0.5100,0.000 +F,3,4.3686,223.000,0.0000,-0.01070,0.01500,0.4500,0.000 +F,3,4.3852,223.000,0.0000,-0.00930,0.01300,0.3800,0.000 +F,3,4.4017,223.000,0.0000,-0.00790,0.01100,0.3200,0.000 +F,3,4.4184,223.000,0.0000,-0.00660,0.00920,0.2600,0.000 +F,3,4.4353,223.000,0.0000,-0.00540,0.00750,0.2100,0.000 +F,3,4.4516,223.000,0.0000,-0.00430,0.00600,0.1700,0.000 +F,3,4.4685,223.000,0.0000,-0.00340,0.00470,0.1300,0.000 +F,3,4.4853,223.000,0.0000,-0.00260,0.00360,0.1000,0.000 +F,3,4.5016,223.000,0.0000,-0.00190,0.00270,0.0700,0.000 +F,3,4.5194,223.000,0.0000,-0.00140,0.00190,0.0500,0.000 +F,3,4.5351,223.000,0.0000,-0.00090,0.00130,0.0300,0.000 +F,3,4.5515,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,4,dragA,51.000,51.000,51.000,223.000,2.0987,0.3153,223.000 +T,4,0.0000,51.020 +T,4,0.3153,52.350 +T,4,0.3319,53.680 +T,4,0.3486,55.350 +T,4,0.3653,56.680 +T,4,0.3846,58.350 +T,4,0.3986,60.020 +T,4,0.4153,61.680 +T,4,0.4319,63.350 +T,4,0.4486,65.020 +T,4,0.4653,67.020 +T,4,0.4819,68.680 +T,4,0.4986,70.680 +T,4,0.5153,72.350 +T,4,0.5319,74.350 +T,4,0.5486,76.350 +T,4,0.5653,78.350 +T,4,0.5819,80.350 +T,4,0.5987,82.350 +T,4,0.6153,84.350 +T,4,0.6319,86.350 +T,4,0.6487,88.350 +T,4,0.6653,90.350 +T,4,0.6819,92.350 +T,4,0.6986,94.680 +T,4,0.7153,96.680 +T,4,0.7320,98.680 +T,4,0.7486,101.020 +T,4,0.7653,103.020 +T,4,0.7819,105.020 +T,4,0.7986,107.350 +T,4,0.8153,109.350 +T,4,0.8320,111.680 +T,4,0.8487,113.680 +T,4,0.8653,116.020 +T,4,0.8819,118.020 +T,4,0.8986,120.350 +T,4,0.9153,122.350 +T,4,0.9320,124.680 +T,4,0.9486,127.020 +T,4,0.9653,129.020 +T,4,0.9820,131.350 +T,4,0.9986,133.350 +T,4,1.0153,135.680 +T,4,1.0319,137.680 +T,4,1.0486,140.020 +T,4,1.0653,142.350 +T,4,1.0819,144.350 +T,4,1.0986,146.680 +T,4,1.1153,148.680 +T,4,1.1319,151.020 +T,4,1.1486,153.020 +T,4,1.1654,155.350 +T,4,1.1819,157.350 +T,4,1.1987,159.680 +T,4,1.2153,161.680 +T,4,1.2319,163.680 +T,4,1.2486,166.020 +T,4,1.2653,168.020 +T,4,1.2821,170.020 +T,4,1.2986,172.350 +T,4,1.3154,174.350 +T,4,1.3320,176.350 +T,4,1.3486,178.350 +T,4,1.3654,180.350 +T,4,1.3819,182.680 +T,4,1.3987,184.680 +T,4,1.4153,186.680 +T,4,1.4319,188.350 +T,4,1.4486,190.350 +T,4,1.4653,192.350 +T,4,1.4819,194.350 +T,4,1.4986,196.020 +T,4,1.5153,198.020 +T,4,1.5320,199.680 +T,4,1.5486,201.680 +T,4,1.5653,203.350 +T,4,1.5820,205.020 +T,4,1.5986,206.680 +T,4,1.6154,208.350 +T,4,1.6319,210.020 +T,4,1.6486,211.680 +T,4,1.6654,213.350 +T,4,1.6819,214.680 +T,4,1.6986,216.020 +T,4,1.7154,217.350 +T,4,1.7319,218.680 +T,4,1.7486,220.020 +T,4,1.7653,221.020 +T,4,1.7819,222.020 +T,4,1.7987,223.020 +T,4,2.0987,223.020 +F,4,0.0335,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,0.0666,51.000,0.2156,0.00000,0.00000,0.0000,2.493 +F,4,0.0834,51.000,0.3687,0.00000,0.00000,0.0000,4.658 +F,4,0.0999,51.000,0.5088,0.00000,0.00000,0.0000,6.960 +F,4,0.1167,51.000,0.6269,0.00000,0.00000,0.0000,9.097 +F,4,0.1333,51.000,0.7213,0.00110,-0.00180,0.0600,10.987 +F,4,0.1499,51.000,0.7950,0.00190,-0.00320,0.1100,12.494 +F,4,0.1666,51.000,0.8513,0.00290,-0.00480,0.1600,13.645 +F,4,0.1832,51.000,0.8925,0.00390,-0.00650,0.2200,14.467 +F,4,0.1999,51.000,0.9231,0.00480,-0.00810,0.2700,14.960 +F,4,0.2166,51.000,0.9456,0.00550,-0.00920,0.3100,15.207 +F,4,0.2332,51.000,0.9612,0.00590,-0.00980,0.3200,15.289 +F,4,0.2499,51.000,0.9731,0.00590,-0.00980,0.3200,15.234 +F,4,0.2665,51.000,1.0000,0.00550,-0.00920,0.3000,15.097 +F,4,0.2832,51.000,1.0000,0.00480,-0.00810,0.2700,14.906 +F,4,0.2998,51.000,1.0000,0.00390,-0.00650,0.2200,14.741 +F,4,0.3169,51.000,1.0000,0.00290,-0.00480,0.1600,14.549 +F,4,0.3336,51.000,1.0000,0.00170,-0.00290,0.1000,14.412 +F,4,0.3502,51.100,1.0000,0.00060,-0.00110,0.0400,14.275 +F,4,0.3669,51.400,1.0000,0.00000,0.00000,0.0000,14.193 +F,4,0.3862,52.020,1.0000,-0.00130,0.00210,-0.0700,14.193 +F,4,0.4002,52.900,1.0000,-0.00200,0.00340,-0.1000,14.193 +F,4,0.4169,54.010,1.0000,-0.00260,0.00430,-0.1100,14.221 +F,4,0.4335,55.380,1.0000,-0.00270,0.00460,-0.1200,14.221 +F,4,0.4502,56.870,1.0000,-0.00230,0.00390,-0.1500,14.248 +F,4,0.4669,58.410,1.0000,-0.00130,0.00220,-0.2000,14.248 +F,4,0.4835,60.070,1.0000,0.00030,-0.00050,-0.2700,14.275 +F,4,0.5002,61.820,1.0000,0.00240,-0.00400,-0.3700,14.275 +F,4,0.5168,63.570,1.0000,0.00480,-0.00810,-0.4800,14.303 +F,4,0.5335,65.270,1.0000,0.00750,-0.01250,-0.6100,14.303 +F,4,0.5502,67.020,1.0000,0.01010,-0.01680,-0.7300,14.330 +F,4,0.5668,68.820,1.0000,0.01260,-0.02090,-0.8500,14.330 +F,4,0.5835,70.680,1.0000,0.01470,-0.02450,-0.9500,14.358 +F,4,0.6020,72.600,1.0000,0.01650,-0.02750,-1.0300,14.385 +F,4,0.6168,74.540,1.0000,0.01780,-0.02970,-1.0900,14.385 +F,4,0.6335,76.520,1.0000,0.01880,-0.03130,-1.1300,14.412 +F,4,0.6520,78.500,1.0000,0.01940,-0.03230,-1.1500,14.412 +F,4,0.6669,80.500,1.0000,0.01960,-0.03270,-1.1500,14.440 +F,4,0.6835,82.500,1.0000,0.01970,-0.03280,-1.1500,14.440 +F,4,0.7002,84.500,1.0000,0.01950,-0.03240,-1.1300,14.467 +F,4,0.7168,86.500,1.0000,0.01910,-0.03190,-1.1000,14.467 +F,4,0.7360,88.500,1.0000,0.01860,-0.03100,-1.0700,14.495 +F,4,0.7502,90.500,1.0000,0.01800,-0.03000,-1.0300,14.522 +F,4,0.7669,92.500,1.0000,0.01730,-0.02880,-0.9800,14.522 +F,4,0.7835,94.610,1.0000,0.01650,-0.02750,-0.9300,14.549 +F,4,0.8001,96.810,1.0000,0.01560,-0.02600,-0.8800,14.577 +F,4,0.8168,99.020,1.0000,0.01470,-0.02450,-0.8300,14.577 +F,4,0.8335,101.190,1.0000,0.01380,-0.02300,-0.7800,14.604 +F,4,0.8520,103.310,1.0000,0.01300,-0.02160,-0.7300,14.604 +F,4,0.8669,105.400,1.0000,0.01220,-0.02030,-0.6800,14.632 +F,4,0.8853,107.450,1.0000,0.01150,-0.01910,-0.6400,14.632 +F,4,0.9002,109.590,1.0000,0.01080,-0.01800,-0.6000,14.659 +F,4,0.9168,111.810,1.0000,0.01010,-0.01690,-0.5600,14.686 +F,4,0.9354,114.020,1.0000,0.00940,-0.01570,-0.5200,14.686 +F,4,0.9502,116.190,1.0000,0.00870,-0.01450,-0.4800,14.714 +F,4,0.9670,118.320,1.0000,0.00800,-0.01330,-0.4400,14.741 +F,4,0.9853,120.510,1.0000,0.00730,-0.01220,-0.4100,14.741 +F,4,1.0004,122.760,1.0000,0.00670,-0.01120,-0.3700,14.769 +F,4,1.0169,125.000,1.0000,0.00610,-0.01020,-0.3400,14.769 +F,4,1.0347,127.180,1.0000,0.00560,-0.00930,-0.3100,14.796 +F,4,1.0502,129.320,1.0000,0.00510,-0.00850,-0.2800,14.823 +F,4,1.0684,131.410,1.0000,0.00460,-0.00770,-0.2600,14.823 +F,4,1.0835,133.560,1.0000,0.00420,-0.00710,-0.2300,14.851 +F,4,1.1003,135.790,1.0000,0.00380,-0.00640,-0.2100,14.878 +F,4,1.1168,138.020,1.0000,0.00340,-0.00570,-0.1900,14.878 +F,4,1.1336,140.190,1.0000,0.00300,-0.00500,-0.1600,14.906 +F,4,1.1502,142.320,1.0000,0.00260,-0.00430,-0.1400,14.906 +F,4,1.1687,144.510,1.0000,0.00220,-0.00370,-0.1200,14.933 +F,4,1.1835,146.760,1.0000,0.00190,-0.00320,-0.1000,14.960 +F,4,1.2020,149.000,1.0000,0.00160,-0.00270,-0.0900,14.960 +F,4,1.2168,151.190,1.0000,0.00140,-0.00230,-0.0700,14.988 +F,4,1.2335,153.320,1.0000,0.00110,-0.00190,-0.0600,14.988 +F,4,1.2502,155.410,1.0000,0.00100,-0.00160,-0.0500,15.015 +F,4,1.2669,157.460,1.0000,0.00090,-0.00140,-0.0500,15.043 +F,4,1.2853,159.590,1.0000,0.00070,-0.00120,-0.0400,15.043 +F,4,1.3001,161.810,1.0000,0.00000,0.00000,0.0000,15.070 +F,4,1.3186,164.020,1.0000,0.00000,0.00000,0.0000,15.097 +F,4,1.3335,166.190,1.0000,0.00000,0.00000,0.0000,15.097 +F,4,1.3502,168.320,1.0000,0.00000,0.00000,0.0000,15.125 +F,4,1.3686,170.410,1.0000,0.00000,0.00000,0.0000,15.125 +F,4,1.3836,172.460,1.0000,0.00000,0.00000,0.0000,15.152 +F,4,1.4024,174.490,1.0000,0.00000,0.00000,0.0000,15.152 +F,4,1.4168,176.500,1.0000,-0.00160,0.00080,0.0300,15.180 +F,4,1.4334,178.510,1.0000,-0.00230,0.00100,0.0300,15.207 +F,4,1.4502,180.510,1.0000,-0.00280,0.00120,0.0400,15.207 +F,4,1.4668,182.510,1.0000,-0.00310,0.00150,0.0500,15.234 +F,4,1.4852,184.510,1.0000,-0.00330,0.00190,0.0600,15.234 +F,4,1.5002,186.510,1.0000,-0.00330,0.00230,0.0700,15.262 +F,4,1.5168,188.510,1.0000,-0.00310,0.00260,0.0900,15.262 +F,4,1.5353,190.510,1.0000,-0.00280,0.00290,0.1000,15.289 +F,4,1.5502,192.400,1.0000,-0.00250,0.00320,0.1100,15.289 +F,4,1.5668,194.200,1.0000,-0.00220,0.00350,0.1100,15.317 +F,4,1.5853,196.000,1.0000,-0.00250,0.00360,0.1200,15.317 +F,4,1.6002,197.820,1.0000,-0.00250,0.00380,0.1200,15.344 +F,4,1.6186,199.600,1.0000,-0.00260,0.00400,0.1300,15.344 +F,4,1.6335,201.310,1.0000,-0.00270,0.00430,0.1400,15.371 +F,4,1.6502,203.050,1.0000,-0.00290,0.00460,0.1500,15.371 +F,4,1.6687,204.740,1.0000,-0.00310,0.00510,0.1700,15.399 +F,4,1.6835,206.400,1.0000,-0.00330,0.00550,0.1800,15.399 +F,4,1.7002,208.100,1.0000,-0.00360,0.00600,0.2000,15.426 +F,4,1.7187,209.770,1.0000,-0.00390,0.00640,0.2100,15.426 +F,4,1.7335,211.310,1.0000,-0.00410,0.00680,0.2200,15.426 +F,4,1.7517,212.800,1.0000,-0.00430,0.00720,0.2400,15.454 +F,4,1.7701,214.260,1.0000,-0.00450,0.00750,0.2500,15.454 +F,4,1.7835,215.620,1.0000,-0.00470,0.00780,0.2600,15.454 +F,4,1.8020,216.880,1.0000,-0.00490,0.00810,0.2700,15.481 +F,4,1.8165,218.060,1.0000,-0.00510,0.00850,0.2800,15.481 +F,4,1.8331,219.160,1.0000,-0.00540,0.00900,0.2900,15.481 +F,4,1.8498,220.120,1.0000,-0.00570,0.00960,0.3100,15.481 +F,4,1.8665,220.850,1.0000,-0.00620,0.01030,0.3400,15.481 +F,4,1.8831,221.350,1.0000,-0.00670,0.01120,0.3700,15.481 +F,4,1.8998,221.670,1.0000,-0.00740,0.01230,0.4000,15.481 +F,4,1.9165,221.860,1.0000,-0.00820,0.01360,0.4500,15.481 +F,4,1.9331,222.000,1.0000,-0.00920,0.01530,0.5000,15.481 +F,4,1.9498,222.000,1.0000,-0.01040,0.01730,0.5600,15.481 +F,4,1.9665,222.000,1.0000,-0.01170,0.01960,0.6400,15.481 +F,4,1.9832,222.000,1.0000,-0.01320,0.02210,0.7200,15.481 +F,4,1.9998,222.000,1.0000,-0.01470,0.02460,0.8000,15.481 +F,4,2.0165,222.000,1.0000,-0.01620,0.02700,0.8800,15.481 +F,4,2.0332,222.000,1.0000,-0.01750,0.02920,0.9500,15.481 +F,4,2.0498,222.000,1.0000,-0.01860,0.03090,1.0000,15.481 +F,4,2.0665,222.000,1.0000,-0.01930,0.03220,1.0400,15.481 +F,4,2.0831,222.000,1.0000,-0.01970,0.03290,1.0600,15.481 +F,4,2.1050,222.000,1.0000,-0.01980,0.03300,1.0700,15.481 +F,4,2.1165,222.000,1.0000,-0.01960,0.03270,1.0600,15.481 +F,4,2.1332,222.030,0.9331,-0.01910,0.03180,1.0300,14.906 +F,4,2.1499,222.100,0.7950,-0.01830,0.03030,0.9800,13.453 +F,4,2.1665,222.200,0.6425,-0.01720,0.02840,0.9200,11.453 +F,4,2.1831,222.300,0.5006,-0.01610,0.02630,0.8400,9.234 +F,4,2.1999,222.410,0.3813,-0.01470,0.02380,0.7400,7.014 +F,4,2.2166,222.510,0.2844,-0.01280,0.02070,0.6500,4.959 +F,4,2.2332,222.600,0.2094,-0.01050,0.01700,0.5800,3.151 +F,4,2.2499,222.680,0.1525,-0.00780,0.01280,0.5300,1.644 +F,4,2.2665,222.750,0.1100,-0.00500,0.00840,0.5000,0.493 +F,4,2.2832,222.810,0.0787,-0.00210,0.00400,0.4900,-0.384 +F,4,2.2998,223.000,0.0556,0.00050,0.00010,0.4800,-0.959 +F,4,2.3165,223.000,0.0394,0.00260,-0.00320,0.4600,-1.315 +F,4,2.3332,223.000,0.0281,0.00410,-0.00550,0.4400,-1.452 +F,4,2.3498,223.000,0.0000,0.00500,-0.00690,0.4000,-1.452 +F,4,2.3666,223.000,0.0000,0.00520,-0.00730,0.3400,-1.343 +F,4,2.3849,223.000,0.0000,0.00490,-0.00690,0.2800,-1.178 +F,4,2.4015,223.000,0.0000,0.00400,-0.00580,0.2000,-0.986 +F,4,2.4165,223.000,0.0000,0.00290,-0.00420,0.1200,-0.767 +F,4,2.4344,223.000,0.0000,0.00160,-0.00240,0.0300,-0.575 +F,4,2.4514,223.000,0.0000,0.00000,0.00000,0.0000,-0.384 +F,4,2.4665,223.000,0.0000,-0.00120,0.00150,-0.1200,0.000 +F,4,2.4848,223.000,0.0000,-0.00250,0.00330,-0.1800,0.000 +F,4,2.5013,223.000,0.0000,-0.00350,0.00480,-0.2300,0.000 +F,4,2.5180,223.000,0.0000,-0.00440,0.00610,-0.2600,0.000 +F,4,2.5332,223.000,0.0000,-0.00500,0.00700,-0.2900,0.000 +F,4,2.5515,223.000,0.0000,-0.00540,0.00750,-0.3000,0.000 +F,4,2.5665,223.000,0.0000,-0.00560,0.00780,-0.3000,0.000 +F,4,2.5847,223.000,0.0000,-0.00550,0.00770,-0.2900,0.000 +F,4,2.6014,223.000,0.0000,-0.00530,0.00740,-0.2800,0.000 +F,4,2.6180,223.000,0.0000,-0.00500,0.00700,-0.2600,0.000 +F,4,2.6331,223.000,0.0000,-0.00460,0.00650,-0.2300,0.000 +F,4,2.6514,223.000,0.0000,-0.00420,0.00590,-0.2100,0.000 +F,4,2.6664,223.000,0.0000,-0.00380,0.00530,-0.1900,0.000 +F,4,2.6847,223.000,0.0000,-0.00330,0.00470,-0.1600,0.000 +F,4,2.7014,223.000,0.0000,-0.00290,0.00410,-0.1400,0.000 +F,4,2.7180,223.000,0.0000,-0.00250,0.00350,-0.1100,0.000 +F,4,2.7347,223.000,0.0000,-0.00210,0.00290,-0.0900,0.000 +F,4,2.7513,223.000,0.0000,-0.00170,0.00240,-0.0700,0.000 +F,4,2.7678,223.000,0.0000,-0.00130,0.00190,-0.0500,0.000 +F,4,2.7847,223.000,0.0000,-0.00100,0.00140,-0.0400,0.000 +F,4,2.8012,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,2.8178,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,2.8346,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,2.8513,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,2.8664,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,2.8845,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,2.9011,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,2.9179,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,2.9345,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,2.9512,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,2.9664,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,2.9830,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,2.9997,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,4,3.0163,223.000,0.0000,0.00430,-0.00550,0.2300,0.000 +F,4,3.0330,223.000,0.0000,0.01210,-0.01590,0.6000,0.000 +F,4,3.0496,223.000,0.0000,0.01600,-0.02090,0.7900,0.000 +F,4,3.0662,223.000,0.0000,0.01550,-0.01990,0.8600,0.000 +F,4,3.0829,223.000,0.0000,0.01340,-0.01690,0.9200,0.000 +F,4,3.0996,223.000,0.0000,0.01130,-0.01390,0.9300,0.000 +F,4,3.1163,223.000,0.0000,0.00940,-0.01130,0.8900,0.000 +F,4,3.1329,223.000,0.0000,0.00740,-0.00870,0.8300,0.000 +F,4,3.1495,223.000,0.0000,0.00530,-0.00580,0.7800,0.000 +F,4,3.1663,223.000,0.0000,0.00280,-0.00250,0.7400,0.000 +F,4,3.1830,223.000,0.0000,0.00000,0.00120,0.7200,0.000 +F,4,3.1996,223.000,0.0000,-0.00290,0.00510,0.7200,0.000 +F,4,3.2163,223.000,0.0000,-0.00580,0.00900,0.7300,0.000 +F,4,3.2329,223.000,0.0000,-0.00850,0.01270,0.7500,0.000 +F,4,3.2496,223.000,0.0000,-0.01090,0.01590,0.7700,0.000 +F,4,3.2663,223.000,0.0000,-0.01280,0.01850,0.7800,0.000 +F,4,3.2830,223.000,0.0000,-0.01430,0.02040,0.7800,0.000 +F,4,3.2996,223.000,0.0000,-0.01520,0.02160,0.7700,0.000 +F,4,3.3163,223.000,0.0000,-0.01560,0.02210,0.7500,0.000 +F,4,3.3329,223.000,0.0000,-0.01550,0.02200,0.7200,0.000 +F,4,3.3496,223.000,0.0000,-0.01510,0.02130,0.6800,0.000 +F,4,3.3663,223.000,0.0000,-0.01440,0.02030,0.6300,0.000 +F,4,3.3830,223.000,0.0000,-0.01340,0.01890,0.5800,0.000 +F,4,3.3992,223.000,0.0000,-0.01240,0.01740,0.5200,0.000 +F,4,3.4159,223.000,0.0000,-0.01120,0.01570,0.4500,0.000 +F,4,3.4326,223.000,0.0000,-0.00990,0.01380,0.3800,0.000 +F,4,3.4492,223.000,0.0000,-0.00850,0.01200,0.3100,0.000 +F,4,3.4659,223.000,0.0000,-0.00730,0.01020,0.2500,0.000 +F,4,3.4826,223.000,0.0000,-0.00610,0.00850,0.2000,0.000 +F,4,3.4992,223.000,0.0000,-0.00500,0.00690,0.1600,0.000 +F,4,3.5159,223.000,0.0000,-0.00400,0.00560,0.1200,0.000 +F,4,3.5326,223.000,0.0000,-0.00310,0.00440,0.0900,0.000 +F,4,3.5492,223.000,0.0000,-0.00240,0.00330,0.0600,0.000 +F,4,3.5659,223.000,0.0000,-0.00180,0.00250,0.0400,0.000 +F,4,3.5825,223.000,0.0000,-0.00130,0.00180,0.0200,0.000 +F,4,3.5993,223.000,0.0000,-0.00090,0.00120,0.0100,0.000 +F,4,3.6159,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,5,dragA,223.000,223.000,51.000,223.000,1.5999,0.3164,51.000 +T,5,0.0000,223.020 +T,5,0.3164,221.020 +T,5,0.3331,218.680 +T,5,0.3497,216.350 +T,5,0.3664,214.020 +T,5,0.3831,211.350 +T,5,0.3997,209.020 +T,5,0.4164,206.020 +T,5,0.4331,203.350 +T,5,0.4497,200.680 +T,5,0.4664,197.680 +T,5,0.4831,194.680 +T,5,0.4997,191.680 +T,5,0.5164,188.680 +T,5,0.5331,185.680 +T,5,0.5498,182.680 +T,5,0.5664,179.350 +T,5,0.5831,176.350 +T,5,0.5997,173.020 +T,5,0.6164,170.020 +T,5,0.6332,166.680 +T,5,0.6497,163.350 +T,5,0.6664,160.350 +T,5,0.6832,157.020 +T,5,0.6997,153.680 +T,5,0.7164,150.350 +T,5,0.7331,147.020 +T,5,0.7497,144.020 +T,5,0.7666,140.680 +T,5,0.7831,137.350 +T,5,0.7997,134.020 +T,5,0.8165,130.680 +T,5,0.8331,127.350 +T,5,0.8497,124.350 +T,5,0.8665,121.020 +T,5,0.8831,117.680 +T,5,0.8999,114.350 +T,5,0.9164,111.350 +T,5,0.9331,108.020 +T,5,0.9498,105.020 +T,5,0.9664,101.680 +T,5,0.9832,98.680 +T,5,0.9997,95.680 +T,5,1.0164,92.680 +T,5,1.0332,89.350 +T,5,1.0497,86.680 +T,5,1.0666,83.680 +T,5,1.0831,80.680 +T,5,1.0997,78.020 +T,5,1.1165,75.020 +T,5,1.1331,72.350 +T,5,1.1497,69.680 +T,5,1.1665,67.350 +T,5,1.1831,64.680 +T,5,1.1997,62.350 +T,5,1.2164,60.020 +T,5,1.2331,58.020 +T,5,1.2498,56.020 +T,5,1.2664,54.020 +T,5,1.2832,52.350 +T,5,1.2998,51.020 +T,5,1.5999,51.020 +F,5,0.0058,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,0.0177,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,0.0344,223.000,0.0537,0.00000,0.00000,0.0000,0.548 +F,5,0.0511,223.000,0.1875,0.00000,0.00000,0.0000,2.110 +F,5,0.0677,223.000,0.3400,0.00000,0.00000,0.0000,4.247 +F,5,0.0845,223.000,0.4838,0.00100,-0.00020,-0.0100,6.521 +F,5,0.1011,223.000,0.6063,0.00130,-0.00070,-0.0200,8.713 +F,5,0.1177,223.000,0.7056,0.00170,-0.00160,-0.0500,10.659 +F,5,0.1344,223.000,0.7825,0.00240,-0.00290,-0.1000,12.248 +F,5,0.1511,223.000,0.8419,0.00330,-0.00450,-0.1500,13.453 +F,5,0.1678,223.000,0.8856,0.00430,-0.00620,-0.2100,14.330 +F,5,0.1844,223.000,0.9181,0.00510,-0.00780,-0.2600,14.878 +F,5,0.2011,223.000,0.9419,0.00580,-0.00900,-0.3000,15.180 +F,5,0.2177,223.000,0.9588,0.00620,-0.00970,-0.3200,15.289 +F,5,0.2343,223.000,0.9713,0.00620,-0.00980,-0.3300,15.234 +F,5,0.2511,223.000,1.0000,0.00580,-0.00930,-0.3100,15.125 +F,5,0.2676,223.000,1.0000,0.00520,-0.00830,-0.2800,14.960 +F,5,0.2843,223.000,1.0000,0.00430,-0.00690,-0.2300,14.769 +F,5,0.3011,223.000,1.0000,0.00320,-0.00510,-0.1700,14.577 +F,5,0.3181,223.000,1.0000,0.00210,-0.00330,-0.1100,14.440 +F,5,0.3347,223.000,1.0000,0.00090,-0.00140,-0.0500,14.303 +F,5,0.3514,222.800,1.0000,0.00000,0.00000,0.0000,14.193 +F,5,0.3680,222.190,1.0000,-0.00100,0.00180,0.0600,14.166 +F,5,0.3847,221.170,1.0000,-0.00180,0.00310,0.1000,14.193 +F,5,0.4014,219.710,1.0000,-0.00240,0.00410,0.1300,14.221 +F,5,0.4180,217.890,1.0000,-0.00270,0.00450,0.1500,14.248 +F,5,0.4346,215.790,1.0000,-0.00230,0.00390,0.1800,14.248 +F,5,0.4513,213.420,1.0000,-0.00120,0.00210,0.2400,14.275 +F,5,0.4680,210.930,1.0000,0.00070,-0.00110,0.3300,14.303 +F,5,0.4846,208.370,1.0000,0.00340,-0.00570,0.4600,14.330 +F,5,0.5013,205.700,1.0000,0.00670,-0.01120,0.6200,14.358 +F,5,0.5180,202.940,1.0000,0.01050,-0.01750,0.8100,14.385 +F,5,0.5361,200.090,1.0000,0.01440,-0.02400,1.0100,14.385 +F,5,0.5514,197.180,1.0000,0.01830,-0.03040,1.2000,14.412 +F,5,0.5679,194.220,1.0000,0.02180,-0.03640,1.3800,14.440 +F,5,0.5847,191.250,1.0000,0.02490,-0.04150,1.5400,14.467 +F,5,0.6015,188.260,1.0000,0.02750,-0.04580,1.6700,14.495 +F,5,0.6180,185.260,1.0000,0.02950,-0.04920,1.7700,14.522 +F,5,0.6365,182.250,1.0000,0.03090,-0.05150,1.8300,14.522 +F,5,0.6513,179.250,1.0000,0.03170,-0.05290,1.8700,14.549 +F,5,0.6680,176.150,1.0000,0.03200,-0.05340,1.8700,14.577 +F,5,0.6864,172.940,1.0000,0.03190,-0.05310,1.8500,14.604 +F,5,0.7014,169.730,1.0000,0.03130,-0.05220,1.8100,14.632 +F,5,0.7194,166.560,1.0000,0.03040,-0.05070,1.7500,14.659 +F,5,0.7346,163.330,1.0000,0.02940,-0.04900,1.6900,14.686 +F,5,0.7512,160.040,1.0000,0.02820,-0.04710,1.6200,14.714 +F,5,0.7699,156.780,1.0000,0.02700,-0.04500,1.5400,14.741 +F,5,0.7847,153.580,1.0000,0.02570,-0.04290,1.4600,14.769 +F,5,0.8013,150.330,1.0000,0.02440,-0.04070,1.3900,14.796 +F,5,0.8200,147.040,1.0000,0.02310,-0.03860,1.3100,14.823 +F,5,0.8347,143.780,1.0000,0.02180,-0.03640,1.2300,14.823 +F,5,0.8514,140.470,1.0000,0.02050,-0.03410,1.1500,14.878 +F,5,0.8695,137.130,1.0000,0.01910,-0.03190,1.0700,14.878 +F,5,0.8847,133.830,1.0000,0.01780,-0.02960,1.0000,14.906 +F,5,0.9033,130.600,1.0000,0.01650,-0.02750,0.9200,14.933 +F,5,0.9181,127.340,1.0000,0.01520,-0.02530,0.8500,14.960 +F,5,0.9348,124.040,1.0000,0.01400,-0.02330,0.7800,14.988 +F,5,0.9532,120.780,1.0000,0.01270,-0.02120,0.7100,15.015 +F,5,0.9680,117.570,1.0000,0.01160,-0.01930,0.6400,15.043 +F,5,0.9860,114.430,1.0000,0.01040,-0.01730,0.5800,15.070 +F,5,1.0015,111.240,1.0000,0.00930,-0.01550,0.5200,15.097 +F,5,1.0179,107.980,1.0000,0.00820,-0.01370,0.4500,15.125 +F,5,1.0366,104.740,1.0000,0.00710,-0.01190,0.4000,15.125 +F,5,1.0514,101.560,1.0000,0.00610,-0.01020,0.3400,15.152 +F,5,1.0698,98.420,1.0000,0.00520,-0.00860,0.2800,15.180 +F,5,1.0847,95.340,1.0000,0.00430,-0.00710,0.2400,15.207 +F,5,1.1013,92.290,1.0000,0.00340,-0.00570,0.1900,15.234 +F,5,1.1199,89.260,1.0000,0.00270,-0.00450,0.1500,15.262 +F,5,1.1346,86.340,1.0000,0.00200,-0.00330,0.1100,15.262 +F,5,1.1526,83.540,1.0000,0.00130,-0.00210,0.0700,15.289 +F,5,1.1699,80.740,1.0000,0.00000,0.00000,0.0000,15.317 +F,5,1.1846,77.920,1.0000,0.00000,0.00000,0.0000,15.344 +F,5,1.2014,75.150,1.0000,-0.00090,0.00160,-0.0500,15.344 +F,5,1.2180,72.440,1.0000,-0.00170,0.00290,-0.1000,15.371 +F,5,1.2346,69.800,1.0000,-0.00260,0.00430,-0.1400,15.399 +F,5,1.2533,67.310,1.0000,-0.00330,0.00560,-0.1800,15.399 +F,5,1.2681,64.960,1.0000,-0.00410,0.00680,-0.2300,15.426 +F,5,1.2857,62.730,1.0000,-0.00480,0.00810,-0.2700,15.454 +F,5,1.3013,60.600,1.0000,-0.00560,0.00930,-0.3100,15.454 +F,5,1.3176,58.530,1.0000,-0.00640,0.01060,-0.3500,15.481 +F,5,1.3358,56.590,1.0000,-0.00720,0.01200,-0.3900,15.481 +F,5,1.3510,54.980,1.0000,-0.00810,0.01350,-0.4400,15.481 +F,5,1.3693,53.790,1.0000,-0.00910,0.01510,-0.5000,15.481 +F,5,1.3859,52.990,1.0000,-0.01010,0.01690,-0.5500,15.481 +F,5,1.4024,52.480,1.0000,-0.01130,0.01890,-0.6200,15.481 +F,5,1.4192,52.190,1.0000,-0.01280,0.02130,-0.6900,15.481 +F,5,1.4359,52.030,1.0000,-0.01450,0.02420,-0.7900,15.481 +F,5,1.4525,52.000,1.0000,-0.01660,0.02770,-0.9000,15.481 +F,5,1.4691,52.000,1.0000,-0.01910,0.03180,-1.0300,15.481 +F,5,1.4859,52.000,1.0000,-0.02170,0.03620,-1.1700,15.481 +F,5,1.5025,52.000,1.0000,-0.02440,0.04070,-1.3100,15.481 +F,5,1.5191,52.000,1.0000,-0.02700,0.04500,-1.4400,15.481 +F,5,1.5358,52.000,1.0000,-0.02930,0.04880,-1.5600,15.481 +F,5,1.5510,52.000,1.0000,-0.03110,0.05180,-1.6600,15.481 +F,5,1.5689,52.000,1.0000,-0.03240,0.05400,-1.7300,15.481 +F,5,1.5843,52.000,1.0000,-0.03320,0.05530,-1.7600,15.481 +F,5,1.6085,52.000,1.0000,-0.03300,0.05500,-1.7500,15.481 +F,5,1.6511,51.900,0.7950,-0.03070,0.05090,-1.6300,13.426 +F,5,1.6677,51.800,0.6425,-0.02900,0.04770,-1.5300,11.453 +F,5,1.6844,51.700,0.5006,-0.02710,0.04420,-1.4200,9.234 +F,5,1.7011,51.590,0.3813,-0.02490,0.04040,-1.2900,7.014 +F,5,1.7195,51.490,0.2844,-0.02230,0.03580,-1.1500,4.959 +F,5,1.7344,51.400,0.2094,-0.01910,0.03050,-1.0200,3.151 +F,5,1.7527,51.320,0.1525,-0.01540,0.02460,-0.9100,1.644 +F,5,1.7695,51.250,0.1100,-0.01150,0.01850,-0.8200,0.493 +F,5,1.7844,51.190,0.0787,-0.00760,0.01250,-0.7400,-0.384 +F,5,1.8010,51.000,0.0556,-0.00410,0.00700,-0.6800,-0.959 +F,5,1.8175,51.000,0.0394,-0.00100,0.00220,-0.6300,-1.315 +F,5,1.8343,51.000,0.0281,0.00150,-0.00150,-0.5700,-1.452 +F,5,1.8509,51.000,0.0000,0.00310,-0.00410,-0.5000,-1.452 +F,5,1.8676,51.000,0.0000,0.00400,-0.00550,-0.4200,-1.343 +F,5,1.8860,51.000,0.0000,0.00420,-0.00590,-0.3300,-1.178 +F,5,1.9028,51.000,0.0000,0.00380,-0.00550,-0.2300,-0.986 +F,5,1.9192,51.000,0.0000,0.00300,-0.00440,-0.1400,-0.767 +F,5,1.9359,51.000,0.0000,0.00180,-0.00280,-0.0400,-0.575 +F,5,1.9510,51.000,0.0000,0.00060,-0.00110,0.0400,-0.384 +F,5,1.9693,51.000,0.0000,-0.00080,0.00080,0.1200,0.000 +F,5,1.9859,51.000,0.0000,-0.00200,0.00260,0.1900,0.000 +F,5,2.0025,51.000,0.0000,-0.00310,0.00420,0.2400,0.000 +F,5,2.0193,51.000,0.0000,-0.00410,0.00550,0.2900,0.000 +F,5,2.0359,51.000,0.0000,-0.00480,0.00660,0.3100,0.000 +F,5,2.0510,51.000,0.0000,-0.00530,0.00730,0.3300,0.000 +F,5,2.0693,51.000,0.0000,-0.00550,0.00770,0.3300,0.000 +F,5,2.0860,51.000,0.0000,-0.00560,0.00770,0.3200,0.000 +F,5,2.1010,51.000,0.0000,-0.00550,0.00760,0.3100,0.000 +F,5,2.1191,51.000,0.0000,-0.00520,0.00730,0.2900,0.000 +F,5,2.1358,51.000,0.0000,-0.00490,0.00680,0.2700,0.000 +F,5,2.1509,51.000,0.0000,-0.00450,0.00630,0.2400,0.000 +F,5,2.1692,51.000,0.0000,-0.00410,0.00580,0.2100,0.000 +F,5,2.1860,51.000,0.0000,-0.00370,0.00520,0.1900,0.000 +F,5,2.2025,51.000,0.0000,-0.00330,0.00470,0.1700,0.000 +F,5,2.2192,51.000,0.0000,-0.00290,0.00410,0.1400,0.000 +F,5,2.2358,51.000,0.0000,-0.00250,0.00350,0.1200,0.000 +F,5,2.2524,51.000,0.0000,-0.00210,0.00290,0.0900,0.000 +F,5,2.2691,51.000,0.0000,-0.00170,0.00240,0.0700,0.000 +F,5,2.2857,51.000,0.0000,-0.00130,0.00190,0.0600,0.000 +F,5,2.3025,51.000,0.0000,-0.00100,0.00140,0.0400,0.000 +F,5,2.3190,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,2.3357,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,2.3524,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,2.3690,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,2.3857,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,2.4023,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,2.4190,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,2.4356,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,2.4524,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,2.4690,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,2.4855,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,2.5021,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,5,2.5188,51.000,0.0000,0.00420,-0.00550,0.2000,0.000 +F,5,2.5355,51.000,0.0000,0.01210,-0.01580,0.5700,0.000 +F,5,2.5521,51.000,0.0000,0.01600,-0.02090,0.7600,0.000 +F,5,2.5688,51.000,0.0000,0.01550,-0.01990,0.8300,0.000 +F,5,2.5855,51.000,0.0000,0.01340,-0.01680,0.9000,0.000 +F,5,2.6022,51.000,0.0000,0.01120,-0.01380,0.9100,0.000 +F,5,2.6188,51.000,0.0000,0.00930,-0.01110,0.8800,0.000 +F,5,2.6356,51.000,0.0000,0.00730,-0.00850,0.8300,0.000 +F,5,2.6521,51.000,0.0000,0.00520,-0.00560,0.7700,0.000 +F,5,2.6689,51.000,0.0000,0.00270,-0.00240,0.7400,0.000 +F,5,2.6856,51.000,0.0000,0.00000,0.00130,0.7200,0.000 +F,5,2.7022,51.000,0.0000,-0.00290,0.00520,0.7200,0.000 +F,5,2.7189,51.000,0.0000,-0.00580,0.00910,0.7300,0.000 +F,5,2.7357,51.000,0.0000,-0.00850,0.01270,0.7500,0.000 +F,5,2.7522,51.000,0.0000,-0.01090,0.01590,0.7600,0.000 +F,5,2.7688,51.000,0.0000,-0.01280,0.01850,0.7800,0.000 +F,5,2.7855,51.000,0.0000,-0.01430,0.02040,0.7800,0.000 +F,5,2.8021,51.000,0.0000,-0.01520,0.02160,0.7700,0.000 +F,5,2.8187,51.000,0.0000,-0.01560,0.02210,0.7500,0.000 +F,5,2.8354,51.000,0.0000,-0.01550,0.02190,0.7200,0.000 +F,5,2.8521,51.000,0.0000,-0.01510,0.02130,0.6800,0.000 +F,5,2.8688,51.000,0.0000,-0.01440,0.02020,0.6300,0.000 +F,5,2.8854,51.000,0.0000,-0.01340,0.01890,0.5800,0.000 +F,5,2.9004,51.000,0.0000,-0.01230,0.01730,0.5200,0.000 +F,5,2.9179,51.000,0.0000,-0.01110,0.01560,0.4500,0.000 +F,5,2.9355,51.000,0.0000,-0.00990,0.01380,0.3800,0.000 +F,5,2.9523,51.000,0.0000,-0.00860,0.01200,0.3100,0.000 +F,5,2.9692,51.000,0.0000,-0.00730,0.01020,0.2500,0.000 +F,5,2.9849,51.000,0.0000,-0.00610,0.00850,0.2000,0.000 +F,5,3.0013,51.000,0.0000,-0.00500,0.00700,0.1600,0.000 +F,5,3.0180,51.000,0.0000,-0.00400,0.00560,0.1200,0.000 +F,5,3.0347,51.000,0.0000,-0.00310,0.00440,0.0900,0.000 +F,5,3.0514,51.000,0.0000,-0.00240,0.00330,0.0600,0.000 +F,5,3.0681,51.000,0.0000,-0.00180,0.00250,0.0400,0.000 +F,5,3.0837,51.000,0.0000,-0.00130,0.00180,0.0200,0.000 +F,5,3.1004,51.000,0.0000,-0.00090,0.00120,0.0100,0.000 +F,5,3.1171,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,6,dragA,51.000,51.000,51.000,223.000,1.0002,0.3165,223.000 +T,6,0.0000,51.020 +T,6,0.3165,54.020 +T,6,0.3332,57.350 +T,6,0.3498,60.680 +T,6,0.3665,64.350 +T,6,0.3832,68.350 +T,6,0.3998,72.350 +T,6,0.4165,76.350 +T,6,0.4331,80.680 +T,6,0.4498,85.020 +T,6,0.4665,89.350 +T,6,0.4831,93.680 +T,6,0.4998,98.020 +T,6,0.5164,102.680 +T,6,0.5331,107.350 +T,6,0.5498,112.020 +T,6,0.5665,116.680 +T,6,0.5832,121.350 +T,6,0.5999,126.020 +T,6,0.6165,130.680 +T,6,0.6499,140.020 +T,6,0.6665,144.680 +T,6,0.6832,149.350 +T,6,0.7000,154.020 +T,6,0.7165,158.680 +T,6,0.7332,163.350 +T,6,0.7499,167.680 +T,6,0.7666,172.350 +T,6,0.7831,176.680 +T,6,0.7998,181.020 +T,6,0.8166,185.350 +T,6,0.8332,189.680 +T,6,0.8498,193.680 +T,6,0.8665,197.680 +T,6,0.8831,201.680 +T,6,0.8999,205.350 +T,6,0.9165,209.020 +T,6,0.9331,212.350 +T,6,0.9498,215.350 +T,6,0.9665,218.350 +T,6,0.9831,221.020 +T,6,0.9998,223.020 +T,6,1.0002,223.020 +F,6,0.0055,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,6,0.0178,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,6,0.0346,51.000,0.0575,0.00000,0.00000,0.0000,0.575 +F,6,0.0512,51.000,0.1925,0.00000,0.00000,0.0000,2.192 +F,6,0.0678,51.000,0.3456,0.00000,0.00000,0.0000,4.329 +F,6,0.0845,51.000,0.4881,0.00000,0.00000,0.0000,6.603 +F,6,0.1012,51.000,0.6100,0.00000,0.00000,0.0000,8.795 +F,6,0.1177,51.000,0.7081,0.00100,-0.00160,0.0500,10.713 +F,6,0.1345,51.000,0.7850,0.00180,-0.00300,0.1000,12.303 +F,6,0.1512,51.000,0.8438,0.00270,-0.00460,0.1500,13.508 +F,6,0.1678,51.000,0.8869,0.00380,-0.00630,0.2100,14.358 +F,6,0.1844,51.000,0.9194,0.00470,-0.00780,0.2600,14.906 +F,6,0.2011,51.000,0.9425,0.00540,-0.00900,0.3000,15.180 +F,6,0.2177,51.000,0.9594,0.00580,-0.00970,0.3200,15.289 +F,6,0.2345,51.000,0.9713,0.00590,-0.00980,0.3300,15.234 +F,6,0.2512,51.000,1.0000,0.00560,-0.00930,0.3100,15.125 +F,6,0.2678,51.000,1.0000,0.00500,-0.00830,0.2700,14.933 +F,6,0.2845,51.000,1.0000,0.00410,-0.00680,0.2300,14.769 +F,6,0.3011,51.000,1.0000,0.00300,-0.00510,0.1700,14.577 +F,6,0.3182,51.000,1.0000,0.00190,-0.00320,0.1100,14.412 +F,6,0.3348,51.000,1.0000,0.00080,-0.00130,0.0400,14.303 +F,6,0.3514,51.310,1.0000,0.00000,0.00000,0.0000,14.193 +F,6,0.3680,52.210,1.0000,-0.00110,0.00190,-0.0600,14.193 +F,6,0.3848,53.740,1.0000,-0.00190,0.00320,-0.1100,14.221 +F,6,0.4014,55.890,1.0000,-0.00250,0.00410,-0.1400,14.248 +F,6,0.4181,58.620,1.0000,-0.00250,0.00420,-0.1700,14.303 +F,6,0.4347,61.810,1.0000,-0.00170,0.00290,-0.2200,14.330 +F,6,0.4515,65.340,1.0000,0.00010,-0.00010,-0.3200,14.358 +F,6,0.4680,69.080,1.0000,0.00300,-0.00500,-0.4700,14.385 +F,6,0.4848,73.070,1.0000,0.00710,-0.01180,-0.6700,14.440 +F,6,0.5016,77.230,1.0000,0.01200,-0.02000,-0.9300,14.467 +F,6,0.5181,81.440,1.0000,0.01750,-0.02920,-1.2100,14.495 +F,6,0.5346,85.720,1.0000,0.02330,-0.03890,-1.5200,14.522 +F,6,0.5514,90.070,1.0000,0.02900,-0.04840,-1.8300,14.577 +F,6,0.5681,94.480,1.0000,0.03430,-0.05720,-2.1100,14.604 +F,6,0.5849,99.020,1.0000,0.03890,-0.06490,-2.3600,14.632 +F,6,0.6032,103.600,1.0000,0.04270,-0.07120,-2.5700,14.686 +F,6,0.6181,108.190,1.0000,0.04560,-0.07600,-2.7200,14.714 +F,6,0.6344,112.860,1.0000,0.04760,-0.07930,-2.8200,14.741 +F,6,0.6533,117.510,1.0000,0.04870,-0.08120,-2.8800,14.769 +F,6,0.6680,121.640,1.0000,0.04910,-0.08180,-2.8900,14.769 +F,6,0.6848,125.840,1.0000,0.04880,-0.08130,-2.8600,14.851 +F,6,0.7033,130.480,1.0000,0.04790,-0.07980,-2.8000,14.878 +F,6,0.7181,135.300,1.0000,0.04640,-0.07730,-2.7000,14.933 +F,6,0.7348,140.220,1.0000,0.04440,-0.07400,-2.5700,14.960 +F,6,0.7532,145.090,1.0000,0.04210,-0.07010,-2.4300,14.988 +F,6,0.7702,149.890,1.0000,0.03970,-0.06610,-2.2800,15.043 +F,6,0.7848,154.600,1.0000,0.03740,-0.06230,-2.1400,15.070 +F,6,0.8014,159.240,1.0000,0.03510,-0.05860,-2.0100,15.097 +F,6,0.8204,163.820,1.0000,0.03310,-0.05510,-1.8800,15.125 +F,6,0.8348,168.370,1.0000,0.03100,-0.05170,-1.7600,15.180 +F,6,0.8515,172.890,1.0000,0.02900,-0.04840,-1.6500,15.207 +F,6,0.8681,177.300,1.0000,0.02690,-0.04490,-1.5200,15.234 +F,6,0.8847,181.590,1.0000,0.02480,-0.04130,-1.4000,15.262 +F,6,0.9032,185.790,1.0000,0.02250,-0.03750,-1.2700,15.289 +F,6,0.9182,189.910,1.0000,0.02010,-0.03350,-1.1300,15.317 +F,6,0.9348,193.980,1.0000,0.01770,-0.02940,-0.9900,15.371 +F,6,0.9521,197.910,1.0000,0.01510,-0.02520,-0.8400,15.371 +F,6,0.9698,201.720,1.0000,0.01250,-0.02090,-0.7000,15.399 +F,6,0.9847,205.420,1.0000,0.00990,-0.01660,-0.5500,15.426 +F,6,1.0025,208.940,1.0000,0.00740,-0.01230,-0.4100,15.454 +F,6,1.0177,212.200,1.0000,0.00480,-0.00800,-0.2600,15.481 +F,6,1.0368,214.950,1.0000,0.00230,-0.00380,-0.1300,14.906 +F,6,1.0511,217.150,1.0000,0.00000,0.00000,0.0000,13.426 +F,6,1.0677,218.890,0.9331,-0.00280,0.00460,0.1500,11.453 +F,6,1.0844,220.230,0.7950,-0.00590,0.00920,0.3000,9.234 +F,6,1.1011,221.240,0.6425,-0.00950,0.01460,0.4800,7.014 +F,6,1.1177,221.980,0.5006,-0.01400,0.02110,0.6900,4.959 +F,6,1.1345,222.510,0.3813,-0.01940,0.02870,0.9300,3.151 +F,6,1.1511,222.880,0.2844,-0.02550,0.03720,1.2000,1.644 +F,6,1.1694,223.120,0.2094,-0.03190,0.04620,1.4900,0.493 +F,6,1.1861,223.270,0.1525,-0.03840,0.05520,1.7700,-0.384 +F,6,1.2028,223.350,0.1100,-0.04450,0.06370,2.0300,-0.959 +F,6,1.2178,223.380,0.0787,-0.04980,0.07100,2.2500,-1.315 +F,6,1.2345,223.380,0.0556,-0.05400,0.07680,2.4200,-1.452 +F,6,1.2511,223.360,0.0394,-0.05690,0.08080,2.5400,-1.452 +F,6,1.2695,223.330,0.0281,-0.05840,0.08280,2.6000,-1.343 +F,6,1.2844,223.290,0.0000,-0.05850,0.08280,2.6000,-1.178 +F,6,1.3011,223.250,0.0000,-0.05740,0.08120,2.5500,-0.986 +F,6,1.3178,223.000,0.0000,-0.05520,0.07800,2.4600,-0.767 +F,6,1.3361,223.000,0.0000,-0.05210,0.07360,2.3300,-0.575 +F,6,1.3527,223.000,0.0000,-0.04850,0.06840,2.1700,-0.384 +F,6,1.3695,223.000,0.0000,-0.04450,0.06270,2.0000,0.000 +F,6,1.3860,223.000,0.0000,-0.04020,0.05680,1.8200,0.000 +F,6,1.4029,223.000,0.0000,-0.03600,0.05070,1.6300,0.000 +F,6,1.4178,223.000,0.0000,-0.03170,0.04470,1.4300,0.000 +F,6,1.4344,223.000,0.0000,-0.02750,0.03880,1.2200,0.000 +F,6,1.4511,223.000,0.0000,-0.02340,0.03300,1.0100,0.000 +F,6,1.4677,223.000,0.0000,-0.01950,0.02740,0.8200,0.000 +F,6,1.4845,223.000,0.0000,-0.01570,0.02210,0.6500,0.000 +F,6,1.5010,223.000,0.0000,-0.01220,0.01720,0.5100,0.000 +F,6,1.5178,223.000,0.0000,-0.00910,0.01280,0.3800,0.000 +F,6,1.5344,223.000,0.0000,-0.00640,0.00890,0.2900,0.000 +F,6,1.5510,223.000,0.0000,-0.00410,0.00570,0.2000,0.000 +F,6,1.5676,223.000,0.0000,-0.00220,0.00300,0.1400,0.000 +F,6,1.5844,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,6,1.6010,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,6,1.6176,223.000,0.0000,0.00110,-0.00150,0.0100,0.000 +F,6,1.6342,223.000,0.0000,0.00150,-0.00210,-0.0200,0.000 +F,6,1.6510,223.000,0.0000,0.00170,-0.00240,-0.0400,0.000 +F,6,1.6676,223.000,0.0000,0.00170,-0.00230,-0.0600,0.000 +F,6,1.6843,223.000,0.0000,0.00150,-0.00210,-0.0700,0.000 +F,6,1.7010,223.000,0.0000,0.00120,-0.00170,-0.0900,0.000 +F,6,1.7177,223.000,0.0000,0.00080,-0.00120,-0.1000,0.000 +F,6,1.7342,223.000,0.0000,0.00040,-0.00060,-0.1000,0.000 +F,6,1.7509,223.000,0.0000,0.00010,-0.00010,-0.1100,0.000 +F,6,1.7676,223.000,0.0000,0.00430,-0.00560,0.1100,0.000 +F,6,1.7843,223.000,0.0000,0.01190,-0.01560,0.4800,0.000 +F,6,1.8009,223.000,0.0000,0.01560,-0.02030,0.6700,0.000 +F,6,1.8175,223.000,0.0000,0.01480,-0.01900,0.7500,0.000 +F,6,1.8342,223.000,0.0000,0.01250,-0.01560,0.8300,0.000 +F,6,1.8509,223.000,0.0000,0.01020,-0.01240,0.8600,0.000 +F,6,1.8676,223.000,0.0000,0.00820,-0.00960,0.8400,0.000 +F,6,1.8842,223.000,0.0000,0.00620,-0.00690,0.8000,0.000 +F,6,1.9009,223.000,0.0000,0.00400,-0.00400,0.7600,0.000 +F,6,1.9176,223.000,0.0000,0.00160,-0.00090,0.7300,0.000 +F,6,1.9341,223.000,0.0000,-0.00100,0.00270,0.7100,0.000 +F,6,1.9509,223.000,0.0000,-0.00380,0.00640,0.7200,0.000 +F,6,1.9675,223.000,0.0000,-0.00650,0.01010,0.7300,0.000 +F,6,1.9842,223.000,0.0000,-0.00910,0.01350,0.7500,0.000 +F,6,2.0008,223.000,0.0000,-0.01140,0.01660,0.7700,0.000 +F,6,2.0176,223.000,0.0000,-0.01320,0.01900,0.7800,0.000 +F,6,2.0342,223.000,0.0000,-0.01450,0.02080,0.7800,0.000 +F,6,2.0509,223.000,0.0000,-0.01540,0.02190,0.7700,0.000 +F,6,2.0676,223.000,0.0000,-0.01570,0.02230,0.7500,0.000 +F,6,2.0842,223.000,0.0000,-0.01560,0.02210,0.7200,0.000 +F,6,2.1009,223.000,0.0000,-0.01520,0.02140,0.6800,0.000 +F,6,2.1176,223.000,0.0000,-0.01440,0.02030,0.6400,0.000 +F,6,2.1342,223.000,0.0000,-0.01350,0.01900,0.5800,0.000 +F,6,2.1508,223.000,0.0000,-0.01240,0.01740,0.5200,0.000 +F,6,2.1676,223.000,0.0000,-0.01120,0.01570,0.4500,0.000 +F,6,2.1842,223.000,0.0000,-0.00990,0.01390,0.3800,0.000 +F,6,2.2009,223.000,0.0000,-0.00860,0.01200,0.3100,0.000 +F,6,2.2175,223.000,0.0000,-0.00730,0.01020,0.2500,0.000 +F,6,2.2342,223.000,0.0000,-0.00600,0.00840,0.1900,0.000 +F,6,2.2509,223.000,0.0000,-0.00480,0.00670,0.1500,0.000 +F,6,2.2676,223.000,0.0000,-0.00370,0.00510,0.1100,0.000 +F,6,2.2842,223.000,0.0000,-0.00270,0.00370,0.0800,0.000 +F,6,2.3009,223.000,0.0000,-0.00180,0.00260,0.0600,0.000 +F,6,2.3189,223.000,0.0000,-0.00110,0.00160,0.0400,0.000 +F,6,2.3348,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,7,dragA,223.000,223.000,51.000,223.000,0.7666,0.3165,51.000 +T,7,0.0000,223.020 +T,7,0.3165,218.350 +T,7,0.3331,213.020 +T,7,0.3498,207.020 +T,7,0.3665,201.020 +T,7,0.3831,194.350 +T,7,0.3998,187.680 +T,7,0.4165,180.680 +T,7,0.4331,173.680 +T,7,0.4498,166.680 +T,7,0.4665,159.350 +T,7,0.4831,152.350 +T,7,0.4998,145.020 +T,7,0.5165,137.680 +T,7,0.5331,130.350 +T,7,0.5498,123.020 +T,7,0.5665,116.020 +T,7,0.5831,108.680 +T,7,0.5999,101.680 +T,7,0.6165,95.020 +T,7,0.6332,88.020 +T,7,0.6498,81.680 +T,7,0.6665,75.350 +T,7,0.6833,69.350 +T,7,0.6998,64.020 +T,7,0.7165,59.020 +T,7,0.7331,54.350 +T,7,0.7498,51.020 +T,7,0.7666,51.020 +F,7,0.0056,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,7,0.0177,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,7,0.0345,223.000,0.0562,0.00000,0.00000,0.0000,0.575 +F,7,0.0512,223.000,0.1913,0.00000,0.00000,0.0000,2.165 +F,7,0.0678,223.000,0.3438,0.00000,0.00000,0.0000,4.302 +F,7,0.0845,223.000,0.4869,0.00000,0.00000,0.0000,6.576 +F,7,0.1012,223.000,0.6088,0.00000,0.00000,0.0000,8.768 +F,7,0.1178,223.000,0.7075,0.00100,-0.00160,-0.0500,10.713 +F,7,0.1345,223.000,0.7844,0.00180,-0.00290,-0.1000,12.275 +F,7,0.1511,223.000,0.8431,0.00270,-0.00460,-0.1500,13.481 +F,7,0.1678,223.000,0.8869,0.00380,-0.00630,-0.2100,14.358 +F,7,0.1844,223.000,0.9188,0.00470,-0.00780,-0.2600,14.878 +F,7,0.2011,223.000,0.9425,0.00540,-0.00900,-0.3000,15.180 +F,7,0.2178,223.000,0.9594,0.00580,-0.00970,-0.3200,15.289 +F,7,0.2345,223.000,0.9713,0.00590,-0.00980,-0.3300,15.234 +F,7,0.2511,223.000,1.0000,0.00560,-0.00930,-0.3100,15.125 +F,7,0.2678,223.000,1.0000,0.00500,-0.00830,-0.2700,14.933 +F,7,0.2845,223.000,1.0000,0.00410,-0.00680,-0.2300,14.769 +F,7,0.3011,223.000,1.0000,0.00300,-0.00510,-0.1700,14.577 +F,7,0.3181,223.000,1.0000,0.00190,-0.00320,-0.1100,14.412 +F,7,0.3348,223.000,1.0000,0.00080,-0.00140,-0.0400,14.303 +F,7,0.3515,222.590,1.0000,0.00000,0.00000,0.0000,14.193 +F,7,0.3681,221.180,1.0000,-0.00110,0.00190,0.0600,14.221 +F,7,0.3848,218.530,1.0000,-0.00190,0.00320,0.1100,14.275 +F,7,0.4014,214.790,1.0000,-0.00240,0.00400,0.1400,14.330 +F,7,0.4181,210.190,1.0000,-0.00210,0.00360,0.1900,14.358 +F,7,0.4350,204.900,1.0000,-0.00060,0.00090,0.2900,14.412 +F,7,0.4515,199.010,1.0000,0.00270,-0.00440,0.4700,14.495 +F,7,0.4681,192.680,1.0000,0.00770,-0.01290,0.7400,14.549 +F,7,0.4848,186.060,1.0000,0.01450,-0.02420,1.1000,14.604 +F,7,0.5014,179.240,1.0000,0.02270,-0.03780,1.5400,14.659 +F,7,0.5182,172.310,1.0000,0.03180,-0.05310,2.0500,14.714 +F,7,0.5347,165.330,1.0000,0.04130,-0.06890,2.5800,14.769 +F,7,0.5514,158.220,1.0000,0.05060,-0.08430,3.1100,14.823 +F,7,0.5681,150.990,1.0000,0.05900,-0.09840,3.6100,14.878 +F,7,0.5848,143.770,1.0000,0.06630,-0.11050,4.0300,14.933 +F,7,0.6032,136.580,1.0000,0.07210,-0.12020,4.3800,14.988 +F,7,0.6183,129.340,1.0000,0.07640,-0.12740,4.6300,15.043 +F,7,0.6366,122.040,1.0000,0.07910,-0.13190,4.7800,15.097 +F,7,0.6514,114.880,1.0000,0.08040,-0.13390,4.8500,15.152 +F,7,0.6682,107.870,1.0000,0.08030,-0.13380,4.8300,15.207 +F,7,0.6866,100.930,1.0000,0.07900,-0.13170,4.7400,15.262 +F,7,0.7014,94.110,1.0000,0.07690,-0.12810,4.5900,15.289 +F,7,0.7181,87.490,1.0000,0.07390,-0.12320,4.4000,15.344 +F,7,0.7348,81.150,1.0000,0.07020,-0.11710,4.1600,15.371 +F,7,0.7514,75.180,1.0000,0.06610,-0.11010,3.8900,15.426 +F,7,0.7716,69.520,1.0000,0.06140,-0.10230,3.6000,15.454 +F,7,0.7843,64.310,1.0000,0.05630,-0.09390,3.2800,15.481 +F,7,0.8034,60.050,1.0000,0.05080,-0.08470,2.9400,14.906 +F,7,0.8178,56.820,1.0000,0.04490,-0.07480,2.5800,13.426 +F,7,0.8344,54.430,0.9331,0.03810,-0.06370,2.1900,11.453 +F,7,0.8510,52.710,0.7950,0.03020,-0.05090,1.7300,9.234 +F,7,0.8677,51.510,0.6425,0.02090,-0.03630,1.2200,7.014 +F,7,0.8844,50.720,0.5006,0.01000,-0.01970,0.6500,4.959 +F,7,0.9011,50.230,0.3813,-0.00210,-0.00150,0.0300,3.151 +F,7,0.9193,49.950,0.2844,-0.01530,0.01750,-0.6100,1.644 +F,7,0.9360,49.830,0.2094,-0.02900,0.03660,-1.2500,0.493 +F,7,0.9527,49.820,0.1525,-0.04240,0.05480,-1.8600,-0.384 +F,7,0.9677,49.880,0.1100,-0.05490,0.07170,-2.4100,-0.959 +F,7,0.9844,49.980,0.0787,-0.06590,0.08650,-2.8800,-1.315 +F,7,1.0010,50.100,0.0556,-0.07500,0.09890,-3.2600,-1.452 +F,7,1.0176,50.220,0.0394,-0.08180,0.10850,-3.5300,-1.452 +F,7,1.0360,50.350,0.0281,-0.08620,0.11510,-3.7100,-1.343 +F,7,1.0511,50.460,0.0000,-0.08830,0.11850,-3.8000,-1.178 +F,7,1.0677,50.570,0.0000,-0.08830,0.11910,-3.8000,-0.986 +F,7,1.0844,50.660,0.0000,-0.08630,0.11700,-3.7200,-0.767 +F,7,1.1010,50.730,0.0000,-0.08280,0.11280,-3.5900,-0.575 +F,7,1.1177,50.800,0.0000,-0.07810,0.10680,-3.4000,-0.384 +F,7,1.1360,50.850,0.0000,-0.07260,0.09960,-3.1800,0.000 +F,7,1.1525,51.000,0.0000,-0.06650,0.09160,-2.9300,0.000 +F,7,1.1676,51.000,0.0000,-0.06020,0.08310,-2.6700,0.000 +F,7,1.1860,51.000,0.0000,-0.05380,0.07460,-2.3700,0.000 +F,7,1.2010,51.000,0.0000,-0.04750,0.06600,-2.0400,0.000 +F,7,1.2193,51.000,0.0000,-0.04110,0.05730,-1.7000,0.000 +F,7,1.2344,51.000,0.0000,-0.03480,0.04870,-1.3800,0.000 +F,7,1.2526,51.000,0.0000,-0.02880,0.04040,-1.1000,0.000 +F,7,1.2677,51.000,0.0000,-0.02300,0.03240,-0.8600,0.000 +F,7,1.2860,51.000,0.0000,-0.01780,0.02520,-0.6500,0.000 +F,7,1.3026,51.000,0.0000,-0.01310,0.01860,-0.4800,0.000 +F,7,1.3193,51.000,0.0000,-0.00910,0.01300,-0.3400,0.000 +F,7,1.3343,51.000,0.0000,-0.00570,0.00830,-0.2300,0.000 +F,7,1.3526,51.000,0.0000,-0.00300,0.00450,-0.1400,0.000 +F,7,1.3692,51.000,0.0000,-0.00100,0.00160,-0.0700,0.000 +F,7,1.3859,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,7,1.4010,51.000,0.0000,0.00140,-0.00180,0.0400,0.000 +F,7,1.4192,51.000,0.0000,0.00190,-0.00250,0.0700,0.000 +F,7,1.4359,51.000,0.0000,0.00210,-0.00280,0.1100,0.000 +F,7,1.4525,51.000,0.0000,0.00200,-0.00260,0.1300,0.000 +F,7,1.4677,51.000,0.0000,0.00160,-0.00220,0.1500,0.000 +F,7,1.4859,51.000,0.0000,0.00120,-0.00160,0.1600,0.000 +F,7,1.5025,51.000,0.0000,0.00070,-0.00090,0.1700,0.000 +F,7,1.5190,51.000,0.0000,0.00020,-0.00020,0.1800,0.000 +F,7,1.5343,51.000,0.0000,-0.00030,0.00050,0.1800,0.000 +F,7,1.5522,51.000,0.0000,0.00380,-0.00490,0.3900,0.000 +F,7,1.5689,51.000,0.0000,0.01120,-0.01470,0.7500,0.000 +F,7,1.5855,51.000,0.0000,0.01470,-0.01920,0.9100,0.000 +F,7,1.6023,51.000,0.0000,0.01390,-0.01780,0.9500,0.000 +F,7,1.6189,51.000,0.0000,0.01170,-0.01460,0.9900,0.000 +F,7,1.6356,51.000,0.0000,0.00960,-0.01160,0.9800,0.000 +F,7,1.6523,51.000,0.0000,0.00770,-0.00900,0.9300,0.000 +F,7,1.6690,51.000,0.0000,0.00580,-0.00650,0.8600,0.000 +F,7,1.6855,51.000,0.0000,0.00370,-0.00370,0.8000,0.000 +F,7,1.7022,51.000,0.0000,0.00140,-0.00060,0.7500,0.000 +F,7,1.7190,51.000,0.0000,-0.00130,0.00300,0.7300,0.000 +F,7,1.7356,51.000,0.0000,-0.00410,0.00670,0.7300,0.000 +F,7,1.7523,51.000,0.0000,-0.00680,0.01050,0.7400,0.000 +F,7,1.7688,51.000,0.0000,-0.00940,0.01390,0.7500,0.000 +F,7,1.7855,51.000,0.0000,-0.01170,0.01700,0.7700,0.000 +F,7,1.8022,51.000,0.0000,-0.01350,0.01940,0.7800,0.000 +F,7,1.8190,51.000,0.0000,-0.01480,0.02110,0.7800,0.000 +F,7,1.8356,51.000,0.0000,-0.01560,0.02210,0.7700,0.000 +F,7,1.8523,51.000,0.0000,-0.01590,0.02250,0.7500,0.000 +F,7,1.8689,51.000,0.0000,-0.01570,0.02220,0.7200,0.000 +F,7,1.8855,51.000,0.0000,-0.01520,0.02150,0.6700,0.000 +F,7,1.9023,51.000,0.0000,-0.01440,0.02030,0.6300,0.000 +F,7,1.9189,51.000,0.0000,-0.01340,0.01890,0.5700,0.000 +F,7,1.9356,51.000,0.0000,-0.01230,0.01720,0.5100,0.000 +F,7,1.9523,51.000,0.0000,-0.01100,0.01550,0.4400,0.000 +F,7,1.9689,51.000,0.0000,-0.00970,0.01360,0.3700,0.000 +F,7,1.9856,51.000,0.0000,-0.00840,0.01170,0.3000,0.000 +F,7,2.0022,51.000,0.0000,-0.00700,0.00980,0.2400,0.000 +F,7,2.0189,51.000,0.0000,-0.00570,0.00800,0.1900,0.000 +F,7,2.0355,51.000,0.0000,-0.00450,0.00630,0.1500,0.000 +F,7,2.0521,51.000,0.0000,-0.00340,0.00480,0.1100,0.000 +F,7,2.0689,51.000,0.0000,-0.00250,0.00340,0.0800,0.000 +F,7,2.0857,51.000,0.0000,-0.00170,0.00230,0.0600,0.000 +F,7,2.1014,51.000,0.0000,-0.00100,0.00140,0.0400,0.000 +F,7,2.1181,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,8,dragA,51.000,51.000,51.000,223.000,0.6006,0.3165,223.000 +T,8,0.0000,51.020 +T,8,0.3165,58.350 +T,8,0.3331,67.020 +T,8,0.3498,76.350 +T,8,0.3831,96.680 +T,8,0.4165,118.020 +T,8,0.4331,129.020 +T,8,0.4498,140.020 +T,8,0.4665,151.020 +T,8,0.4831,161.680 +T,8,0.4998,172.350 +T,8,0.5165,182.680 +T,8,0.5331,192.350 +T,8,0.5498,201.680 +T,8,0.5665,210.020 +T,8,0.5831,217.350 +T,8,0.5999,223.020 +T,8,0.6006,223.020 +F,8,0.0054,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,8,0.0178,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,8,0.0344,51.000,0.0575,0.00000,0.00000,0.0000,0.575 +F,8,0.0511,51.000,0.1925,0.00000,0.00000,0.0000,2.192 +F,8,0.0678,51.000,0.3456,0.00000,0.00000,0.0000,4.329 +F,8,0.0845,51.000,0.4888,0.00000,0.00000,0.0000,6.603 +F,8,0.1011,51.000,0.6100,0.00000,0.00000,0.0000,8.795 +F,8,0.1178,51.000,0.7088,0.00100,-0.00160,0.0500,10.713 +F,8,0.1344,51.000,0.7850,0.00180,-0.00300,0.1000,12.303 +F,8,0.1512,51.000,0.8438,0.00270,-0.00460,0.1500,13.508 +F,8,0.1678,51.000,0.8869,0.00380,-0.00630,0.2100,14.358 +F,8,0.1844,51.000,0.9194,0.00470,-0.00780,0.2600,14.906 +F,8,0.2011,51.000,0.9425,0.00540,-0.00900,0.3000,15.180 +F,8,0.2179,51.000,0.9594,0.00580,-0.00970,0.3200,15.289 +F,8,0.2344,51.000,0.9713,0.00590,-0.00980,0.3300,15.234 +F,8,0.2511,51.000,1.0000,0.00560,-0.00930,0.3100,15.125 +F,8,0.2677,51.000,1.0000,0.00500,-0.00830,0.2700,14.933 +F,8,0.2845,51.000,1.0000,0.00410,-0.00680,0.2200,14.769 +F,8,0.3012,51.000,1.0000,0.00300,-0.00510,0.1700,14.577 +F,8,0.3181,51.000,1.0000,0.00190,-0.00320,0.1100,14.412 +F,8,0.3347,51.000,1.0000,0.00080,-0.00130,0.0400,14.303 +F,8,0.3514,51.710,1.0000,0.00000,0.00000,0.0000,14.193 +F,8,0.3676,54.040,1.0000,-0.00110,0.00190,-0.0600,14.275 +F,8,0.3847,58.210,1.0000,-0.00190,0.00320,-0.0900,14.358 +F,8,0.4010,63.080,1.0000,-0.00220,0.00370,-0.1200,14.358 +F,8,0.4183,69.410,1.0000,-0.00130,0.00220,-0.1900,14.522 +F,8,0.4348,76.730,1.0000,0.00140,-0.00230,-0.3400,14.522 +F,8,0.4515,85.390,1.0000,0.00620,-0.01040,-0.6100,14.686 +F,8,0.4680,95.860,1.0000,0.01350,-0.02250,-1.0000,14.769 +F,8,0.4846,107.160,1.0000,0.02300,-0.03830,-1.5300,14.878 +F,8,0.5013,118.750,1.0000,0.03440,-0.05730,-2.1700,14.960 +F,8,0.5180,130.250,1.0000,0.04710,-0.07850,-2.9100,15.043 +F,8,0.5349,141.550,1.0000,0.06050,-0.10070,-3.7000,15.125 +F,8,0.5514,152.610,1.0000,0.07390,-0.12310,-4.5200,15.207 +F,8,0.5681,163.390,1.0000,0.08670,-0.14430,-5.3200,15.289 +F,8,0.5848,173.810,1.0000,0.09830,-0.16360,-6.0500,15.344 +F,8,0.6052,183.730,1.0000,0.10810,-0.17980,-6.6800,15.426 +F,8,0.6177,193.100,1.0000,0.11540,-0.19210,-7.1600,15.481 +F,8,0.6344,201.120,1.0000,0.11990,-0.19960,-7.4500,14.906 +F,8,0.6510,207.460,1.0000,0.12130,-0.20190,-7.5400,13.426 +F,8,0.6702,212.380,1.0000,0.11940,-0.19880,-7.4000,11.453 +F,8,0.6845,216.110,1.0000,0.11430,-0.19020,-7.0400,9.234 +F,8,0.7011,218.880,0.9331,0.10510,-0.17520,-6.4300,7.014 +F,8,0.7178,220.890,0.7950,0.09160,-0.15360,-5.5600,4.959 +F,8,0.7345,222.290,0.6425,0.07420,-0.12630,-4.4900,3.151 +F,8,0.7511,223.220,0.5006,0.05370,-0.09530,-3.2800,1.644 +F,8,0.7678,223.810,0.3813,0.03100,-0.06230,-2.0100,0.493 +F,8,0.7862,224.140,0.2844,0.00760,-0.02900,-0.7700,-0.384 +F,8,0.8017,224.290,0.2094,-0.01560,0.00300,0.4000,-0.959 +F,8,0.8178,224.320,0.1525,-0.03740,0.03280,1.4500,-1.315 +F,8,0.8344,224.260,0.1100,-0.05690,0.05930,2.3400,-1.452 +F,8,0.8511,224.160,0.0787,-0.07350,0.08220,3.0700,-1.452 +F,8,0.8678,224.020,0.0556,-0.08680,0.10100,3.6400,-1.343 +F,8,0.8844,223.880,0.0394,-0.09680,0.11580,4.0600,-1.178 +F,8,0.9027,223.750,0.0281,-0.10350,0.12670,4.3400,-0.986 +F,8,0.9177,223.620,0.0000,-0.10710,0.13360,4.4900,-0.767 +F,8,0.9344,223.500,0.0000,-0.10810,0.13700,4.5400,-0.575 +F,8,0.9511,223.400,0.0000,-0.10670,0.13720,4.5000,-0.384 +F,8,0.9694,223.310,0.0000,-0.10350,0.13470,4.3800,0.000 +F,8,0.9844,223.240,0.0000,-0.09880,0.13000,4.2100,0.000 +F,8,1.0026,223.180,0.0000,-0.09300,0.12370,3.9900,0.000 +F,8,1.0193,223.000,0.0000,-0.08650,0.11610,3.7400,0.000 +F,8,1.0361,223.000,0.0000,-0.07950,0.10770,3.4700,0.000 +F,8,1.0527,223.000,0.0000,-0.07240,0.09890,3.1800,0.000 +F,8,1.0695,223.000,0.0000,-0.06540,0.09000,2.8500,0.000 +F,8,1.0860,223.000,0.0000,-0.05840,0.08090,2.4600,0.000 +F,8,1.1027,223.000,0.0000,-0.05130,0.07150,2.0500,0.000 +F,8,1.1193,223.000,0.0000,-0.04410,0.06200,1.6700,0.000 +F,8,1.1360,223.000,0.0000,-0.03710,0.05240,1.3200,0.000 +F,8,1.1528,223.000,0.0000,-0.03040,0.04320,1.0200,0.000 +F,8,1.1678,223.000,0.0000,-0.02410,0.03450,0.7600,0.000 +F,8,1.1860,223.000,0.0000,-0.01840,0.02660,0.5500,0.000 +F,8,1.2011,223.000,0.0000,-0.01330,0.01950,0.3800,0.000 +F,8,1.2193,223.000,0.0000,-0.00910,0.01350,0.2500,0.000 +F,8,1.2344,223.000,0.0000,-0.00560,0.00860,0.1400,0.000 +F,8,1.2527,223.000,0.0000,-0.00290,0.00470,0.0500,0.000 +F,8,1.2694,223.000,0.0000,-0.00080,0.00180,-0.0100,0.000 +F,8,1.2860,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,8,1.3026,223.000,0.0000,0.00140,-0.00150,-0.1100,0.000 +F,8,1.3193,223.000,0.0000,0.00170,-0.00210,-0.1400,0.000 +F,8,1.3343,223.000,0.0000,0.00180,-0.00220,-0.1700,0.000 +F,8,1.3525,223.000,0.0000,0.00150,-0.00190,-0.1900,0.000 +F,8,1.3690,223.000,0.0000,0.00110,-0.00140,-0.2100,0.000 +F,8,1.3842,223.000,0.0000,0.00520,-0.00680,0.0000,0.000 +F,8,1.4010,223.000,0.0000,0.01260,-0.01650,0.3700,0.000 +F,8,1.4190,223.000,0.0000,0.01610,-0.02100,0.5600,0.000 +F,8,1.4357,223.000,0.0000,0.01500,-0.01920,0.6500,0.000 +F,8,1.4524,223.000,0.0000,0.01220,-0.01520,0.7500,0.000 +F,8,1.4690,223.000,0.0000,0.00950,-0.01130,0.8000,0.000 +F,8,1.4856,223.000,0.0000,0.00710,-0.00800,0.8000,0.000 +F,8,1.5024,223.000,0.0000,0.00480,-0.00500,0.7800,0.000 +F,8,1.5190,223.000,0.0000,0.00260,-0.00200,0.7500,0.000 +F,8,1.5356,223.000,0.0000,0.00010,0.00120,0.7300,0.000 +F,8,1.5523,223.000,0.0000,-0.00240,0.00460,0.7200,0.000 +F,8,1.5690,223.000,0.0000,-0.00510,0.00810,0.7300,0.000 +F,8,1.5856,223.000,0.0000,-0.00770,0.01160,0.7500,0.000 +F,8,1.6023,223.000,0.0000,-0.01010,0.01490,0.7700,0.000 +F,8,1.6190,223.000,0.0000,-0.01220,0.01760,0.7800,0.000 +F,8,1.6356,223.000,0.0000,-0.01380,0.01990,0.7900,0.000 +F,8,1.6523,223.000,0.0000,-0.01500,0.02140,0.7900,0.000 +F,8,1.6689,223.000,0.0000,-0.01570,0.02240,0.7800,0.000 +F,8,1.6855,223.000,0.0000,-0.01600,0.02260,0.7600,0.000 +F,8,1.7022,223.000,0.0000,-0.01580,0.02240,0.7300,0.000 +F,8,1.7189,223.000,0.0000,-0.01530,0.02160,0.6900,0.000 +F,8,1.7355,223.000,0.0000,-0.01450,0.02050,0.6400,0.000 +F,8,1.7521,223.000,0.0000,-0.01360,0.01910,0.5900,0.000 +F,8,1.7688,223.000,0.0000,-0.01250,0.01750,0.5300,0.000 +F,8,1.7855,223.000,0.0000,-0.01130,0.01580,0.4600,0.000 +F,8,1.8023,223.000,0.0000,-0.01000,0.01400,0.3800,0.000 +F,8,1.8190,223.000,0.0000,-0.00860,0.01210,0.3100,0.000 +F,8,1.8357,223.000,0.0000,-0.00730,0.01020,0.2500,0.000 +F,8,1.8523,223.000,0.0000,-0.00600,0.00840,0.2000,0.000 +F,8,1.8689,223.000,0.0000,-0.00480,0.00670,0.1500,0.000 +F,8,1.8856,223.000,0.0000,-0.00370,0.00510,0.1100,0.000 +F,8,1.9024,223.000,0.0000,-0.00270,0.00380,0.0800,0.000 +F,8,1.9200,223.000,0.0000,-0.00190,0.00260,0.0600,0.000 +F,8,1.9359,223.000,0.0000,-0.00120,0.00160,0.0400,0.000 +F,8,1.9528,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,8,1.9683,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,9,dragA,223.000,223.000,51.000,223.000,0.5003,0.3165,51.000 +T,9,0.0000,223.020 +T,9,0.3165,211.350 +T,9,0.3332,197.680 +T,9,0.3498,182.680 +T,9,0.3665,166.680 +T,9,0.3832,150.350 +T,9,0.3998,134.020 +T,9,0.4165,117.680 +T,9,0.4332,101.680 +T,9,0.4498,86.680 +T,9,0.4665,72.350 +T,9,0.4832,60.020 +T,9,0.4998,51.020 +T,9,0.5003,51.020 +F,9,0.0052,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,9,0.0178,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,9,0.0344,223.000,0.0600,0.00000,0.00000,0.0000,0.603 +F,9,0.0511,223.000,0.1956,0.00000,0.00000,0.0000,2.219 +F,9,0.0678,223.000,0.3487,0.00000,0.00000,0.0000,4.357 +F,9,0.0845,223.000,0.4912,0.00000,0.00000,0.0000,6.658 +F,9,0.1012,223.000,0.6125,0.00000,0.00000,0.0000,8.850 +F,9,0.1178,223.000,0.7100,0.00100,-0.00160,-0.0500,10.741 +F,9,0.1345,223.000,0.7862,0.00180,-0.00300,-0.1000,12.330 +F,9,0.1513,223.000,0.8444,0.00280,-0.00460,-0.1500,13.508 +F,9,0.1679,223.000,0.8875,0.00380,-0.00630,-0.2100,14.358 +F,9,0.1845,223.000,0.9194,0.00470,-0.00790,-0.2600,14.906 +F,9,0.2012,223.000,0.9431,0.00540,-0.00910,-0.3000,15.180 +F,9,0.2178,223.000,0.9594,0.00580,-0.00970,-0.3200,15.289 +F,9,0.2345,223.000,0.9719,0.00590,-0.00980,-0.3300,15.234 +F,9,0.2512,223.000,1.0000,0.00560,-0.00930,-0.3100,15.125 +F,9,0.2678,223.000,1.0000,0.00490,-0.00820,-0.2700,14.933 +F,9,0.2846,223.000,1.0000,0.00410,-0.00680,-0.2200,14.769 +F,9,0.3011,223.000,1.0000,0.00300,-0.00500,-0.1700,14.577 +F,9,0.3182,223.000,1.0000,0.00190,-0.00320,-0.1000,14.412 +F,9,0.3348,223.000,1.0000,0.00080,-0.00130,-0.0400,14.303 +F,9,0.3514,221.880,1.0000,0.00000,0.00000,0.0000,14.193 +F,9,0.3685,218.240,1.0000,-0.00110,0.00190,0.0600,14.330 +F,9,0.3848,211.630,1.0000,-0.00190,0.00310,0.1000,14.467 +F,9,0.4014,202.200,1.0000,-0.00190,0.00310,0.1400,14.577 +F,9,0.4181,190.450,1.0000,-0.00010,0.00020,0.2600,14.714 +F,9,0.4348,177.000,1.0000,0.00460,-0.00760,0.5200,14.851 +F,9,0.4516,162.290,1.0000,0.01290,-0.02150,0.9800,14.988 +F,9,0.4681,146.760,1.0000,0.02470,-0.04110,1.6500,15.125 +F,9,0.4848,130.910,1.0000,0.03930,-0.06510,2.4800,15.234 +F,9,0.5025,115.180,1.0000,0.05570,-0.09170,3.4600,15.344 +F,9,0.5177,100.020,1.0000,0.07310,-0.11950,4.5100,15.454 +F,9,0.5343,86.990,1.0000,0.09030,-0.14690,5.5900,14.878 +F,9,0.5510,76.670,1.0000,0.10650,-0.17270,6.6400,13.399 +F,9,0.5676,68.640,1.0000,0.12090,-0.19580,7.5900,11.426 +F,9,0.5867,62.530,1.0000,0.13220,-0.21440,8.3500,9.206 +F,9,0.6011,57.970,1.0000,0.13900,-0.22550,8.8100,7.014 +F,9,0.6177,54.670,0.9331,0.13910,-0.22600,8.8000,4.932 +F,9,0.6344,52.360,0.7950,0.13140,-0.21410,8.2700,3.151 +F,9,0.6511,50.800,0.6425,0.11600,-0.19110,7.2500,1.644 +F,9,0.6678,49.810,0.5006,0.09400,-0.15950,5.8600,0.493 +F,9,0.6844,49.240,0.3813,0.06710,-0.12250,4.2400,-0.384 +F,9,0.7011,48.970,0.2844,0.03740,-0.08290,2.5500,-0.959 +F,9,0.7178,48.910,0.2094,0.00700,-0.04310,0.9200,-1.288 +F,9,0.7344,48.990,0.1525,-0.02230,-0.00500,-0.5600,-1.452 +F,9,0.7511,49.150,0.1100,-0.04910,0.03000,-1.8300,-1.452 +F,9,0.7677,49.350,0.0787,-0.07220,0.06070,-2.8700,-1.343 +F,9,0.7844,49.580,0.0556,-0.09110,0.08680,-3.6900,-1.178 +F,9,0.8012,49.800,0.0394,-0.10550,0.10800,-4.3000,-0.986 +F,9,0.8177,50.000,0.0281,-0.11570,0.12430,-4.7200,-0.767 +F,9,0.8344,50.190,0.0000,-0.12190,0.13600,-4.9800,-0.575 +F,9,0.8511,50.360,0.0000,-0.12460,0.14340,-5.1000,-0.384 +F,9,0.8678,50.500,0.0000,-0.12420,0.14690,-5.1100,0.000 +F,9,0.8844,50.610,0.0000,-0.12150,0.14710,-5.0300,0.000 +F,9,0.9011,50.710,0.0000,-0.11680,0.14440,-4.8700,0.000 +F,9,0.9177,50.790,0.0000,-0.11070,0.13950,-4.6600,0.000 +F,9,0.9345,50.850,0.0000,-0.10350,0.13280,-4.4000,0.000 +F,9,0.9511,51.000,0.0000,-0.09580,0.12480,-4.1100,0.000 +F,9,0.9677,51.000,0.0000,-0.08780,0.11610,-3.8000,0.000 +F,9,0.9843,51.000,0.0000,-0.07970,0.10680,-3.4800,0.000 +F,9,1.0010,51.000,0.0000,-0.07160,0.09720,-3.1200,0.000 +F,9,1.0177,51.000,0.0000,-0.06340,0.08710,-2.7000,0.000 +F,9,1.0343,51.000,0.0000,-0.05510,0.07660,-2.2700,0.000 +F,9,1.0511,51.000,0.0000,-0.04690,0.06590,-1.8600,0.000 +F,9,1.0677,51.000,0.0000,-0.03900,0.05540,-1.4900,0.000 +F,9,1.0843,51.000,0.0000,-0.03150,0.04530,-1.1600,0.000 +F,9,1.1010,51.000,0.0000,-0.02460,0.03590,-0.8800,0.000 +F,9,1.1177,51.000,0.0000,-0.01850,0.02730,-0.6500,0.000 +F,9,1.1358,51.000,0.0000,-0.01310,0.01990,-0.4600,0.000 +F,9,1.1525,51.000,0.0000,-0.00860,0.01350,-0.3000,0.000 +F,9,1.1677,51.000,0.0000,-0.00500,0.00840,-0.1800,0.000 +F,9,1.1860,51.000,0.0000,-0.00220,0.00430,-0.0800,0.000 +F,9,1.2010,51.000,0.0000,-0.00020,0.00130,-0.0000,0.000 +F,9,1.2191,51.000,0.0000,0.00110,-0.00070,0.0600,0.000 +F,9,1.2359,51.000,0.0000,0.00190,-0.00190,0.1100,0.000 +F,9,1.2523,51.000,0.0000,0.00220,-0.00250,0.1500,0.000 +F,9,1.2676,51.000,0.0000,0.00220,-0.00250,0.1800,0.000 +F,9,1.2856,51.000,0.0000,0.00630,-0.00810,0.4100,0.000 +F,9,1.3024,51.000,0.0000,0.01350,-0.01770,0.7700,0.000 +F,9,1.3190,51.000,0.0000,0.01640,-0.02150,0.9200,0.000 +F,9,1.3357,51.000,0.0000,0.01510,-0.01960,0.9400,0.000 +F,9,1.3523,51.000,0.0000,0.01260,-0.01610,0.9600,0.000 +F,9,1.3690,51.000,0.0000,0.01030,-0.01290,0.9300,0.000 +F,9,1.3856,51.000,0.0000,0.00820,-0.01010,0.8700,0.000 +F,9,1.4023,51.000,0.0000,0.00620,-0.00740,0.8000,0.000 +F,9,1.4190,51.000,0.0000,0.00390,-0.00440,0.7400,0.000 +F,9,1.4356,51.000,0.0000,0.00140,-0.00100,0.7000,0.000 +F,9,1.4523,51.000,0.0000,-0.00140,0.00280,0.6800,0.000 +F,9,1.4690,51.000,0.0000,-0.00430,0.00670,0.6900,0.000 +F,9,1.4856,51.000,0.0000,-0.00720,0.01060,0.7100,0.000 +F,9,1.5023,51.000,0.0000,-0.00980,0.01430,0.7300,0.000 +F,9,1.5190,51.000,0.0000,-0.01210,0.01740,0.7600,0.000 +F,9,1.5355,51.000,0.0000,-0.01400,0.01990,0.7700,0.000 +F,9,1.5523,51.000,0.0000,-0.01520,0.02160,0.7800,0.000 +F,9,1.5689,51.000,0.0000,-0.01600,0.02260,0.7700,0.000 +F,9,1.5857,51.000,0.0000,-0.01630,0.02290,0.7500,0.000 +F,9,1.6024,51.000,0.0000,-0.01610,0.02260,0.7200,0.000 +F,9,1.6190,51.000,0.0000,-0.01550,0.02180,0.6800,0.000 +F,9,1.6355,51.000,0.0000,-0.01470,0.02060,0.6300,0.000 +F,9,1.6523,51.000,0.0000,-0.01360,0.01910,0.5800,0.000 +F,9,1.6689,51.000,0.0000,-0.01240,0.01750,0.5200,0.000 +F,9,1.6856,51.000,0.0000,-0.01120,0.01570,0.4400,0.000 +F,9,1.7023,51.000,0.0000,-0.00990,0.01380,0.3700,0.000 +F,9,1.7189,51.000,0.0000,-0.00850,0.01190,0.3000,0.000 +F,9,1.7357,51.000,0.0000,-0.00710,0.01000,0.2400,0.000 +F,9,1.7523,51.000,0.0000,-0.00580,0.00820,0.1900,0.000 +F,9,1.7690,51.000,0.0000,-0.00460,0.00650,0.1500,0.000 +F,9,1.7856,51.000,0.0000,-0.00350,0.00490,0.1100,0.000 +F,9,1.8023,51.000,0.0000,-0.00250,0.00360,0.0800,0.000 +F,9,1.8190,51.000,0.0000,-0.00170,0.00240,0.0600,0.000 +F,9,1.8357,51.000,0.0000,-0.00100,0.00150,0.0400,0.000 +F,9,1.8521,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,9,1.8855,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,10,dragA,51.000,51.000,51.000,223.000,0.4502,0.3165,223.000 +T,10,0.0000,51.020 +T,10,0.3165,67.020 +T,10,0.3331,86.350 +T,10,0.3498,107.350 +T,10,0.3832,129.020 +T,10,0.3999,151.020 +T,10,0.4165,172.350 +T,10,0.4332,210.020 +T,10,0.4498,223.020 +T,10,0.4502,223.020 +F,10,0.0054,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,10,0.0179,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,10,0.0345,51.000,0.0575,0.00000,0.00000,0.0000,0.575 +F,10,0.0511,51.000,0.1925,0.00000,0.00000,0.0000,2.192 +F,10,0.0678,51.000,0.3456,0.00000,0.00000,0.0000,4.302 +F,10,0.0846,51.000,0.4881,0.00000,0.00000,0.0000,6.603 +F,10,0.1012,51.000,0.6100,0.00000,0.00000,0.0000,8.795 +F,10,0.1179,51.000,0.7081,0.00100,-0.00160,0.0500,10.713 +F,10,0.1345,51.000,0.7850,0.00180,-0.00300,0.1000,12.303 +F,10,0.1511,51.000,0.8431,0.00270,-0.00460,0.1500,13.508 +F,10,0.1677,51.000,0.8869,0.00380,-0.00630,0.2100,14.358 +F,10,0.1845,51.000,0.9194,0.00470,-0.00780,0.2600,14.906 +F,10,0.2012,51.000,0.9425,0.00540,-0.00900,0.3000,15.180 +F,10,0.2179,51.000,0.9594,0.00580,-0.00970,0.3200,15.289 +F,10,0.2345,51.000,0.9713,0.00590,-0.00980,0.3300,15.234 +F,10,0.2511,51.000,1.0000,0.00560,-0.00930,0.3100,15.125 +F,10,0.2678,51.000,1.0000,0.00500,-0.00830,0.2700,14.933 +F,10,0.2844,51.000,1.0000,0.00410,-0.00680,0.2300,14.769 +F,10,0.3011,51.000,1.0000,0.00300,-0.00510,0.1700,14.577 +F,10,0.3181,51.000,1.0000,0.00190,-0.00320,0.1100,14.412 +F,10,0.3347,51.000,1.0000,0.00080,-0.00130,0.0400,14.303 +F,10,0.3518,52.630,1.0000,0.00000,0.00000,0.0000,14.193 +F,10,0.3677,57.780,1.0000,-0.00110,0.00190,-0.0600,14.166 +F,10,0.3849,67.050,1.0000,-0.00180,0.00300,-0.1000,14.166 +F,10,0.4015,77.940,1.0000,-0.00150,0.00240,-0.1600,14.193 +F,10,0.4182,89.810,1.0000,0.00130,-0.00220,-0.3300,14.275 +F,10,0.4351,103.880,1.0000,0.00780,-0.01300,-0.7000,14.385 +F,10,0.4525,120.160,1.0000,0.01810,-0.03010,-1.2800,14.495 +F,10,0.4677,139.810,1.0000,0.03160,-0.05240,-2.0400,14.659 +F,10,0.4844,159.050,1.0000,0.04730,-0.07810,-2.9600,14.248 +F,10,0.5010,174.890,1.0000,0.06470,-0.10580,-4.0100,12.933 +F,10,0.5177,187.690,1.0000,0.08280,-0.13430,-5.1300,11.097 +F,10,0.5343,197.830,1.0000,0.10070,-0.16210,-6.2800,9.015 +F,10,0.5510,205.710,1.0000,0.11730,-0.18780,-7.3700,6.905 +F,10,0.5701,211.720,1.0000,0.13090,-0.20950,-8.2800,4.932 +F,10,0.5845,216.180,1.0000,0.13970,-0.22360,-8.8700,3.206 +F,10,0.6011,219.420,0.9331,0.14120,-0.22630,-8.9700,1.726 +F,10,0.6179,221.690,0.7950,0.13420,-0.21560,-8.4800,0.575 +F,10,0.6344,223.220,0.6425,0.11870,-0.19290,-7.4500,-0.274 +F,10,0.6511,224.190,0.5006,0.09610,-0.16130,-6.0200,-0.849 +F,10,0.6678,224.740,0.3813,0.06840,-0.12400,-4.3500,-1.206 +F,10,0.6844,225.000,0.2844,0.03800,-0.08400,-2.6200,-1.370 +F,10,0.7010,225.060,0.2094,0.00690,-0.04380,-0.9500,-1.370 +F,10,0.7178,224.980,0.1525,-0.02290,-0.00530,0.5600,-1.288 +F,10,0.7361,224.820,0.1100,-0.05000,0.02990,1.8500,-1.151 +F,10,0.7529,224.620,0.0787,-0.07330,0.06080,2.9000,-0.959 +F,10,0.7694,224.400,0.0556,-0.09230,0.08700,3.7200,-0.767 +F,10,0.7863,224.180,0.0394,-0.10680,0.10830,4.3300,-0.548 +F,10,0.8011,223.980,0.0281,-0.11690,0.12470,4.7500,-0.384 +F,10,0.8194,223.800,0.0000,-0.12310,0.13650,5.0100,0.000 +F,10,0.8344,223.630,0.0000,-0.12580,0.14410,5.1400,0.000 +F,10,0.8527,223.490,0.0000,-0.12560,0.14780,5.1500,0.000 +F,10,0.8693,223.380,0.0000,-0.12290,0.14820,5.0800,0.000 +F,10,0.8860,223.280,0.0000,-0.11830,0.14590,4.9200,0.000 +F,10,0.9027,223.210,0.0000,-0.11240,0.14130,4.7200,0.000 +F,10,0.9179,223.150,0.0000,-0.10540,0.13500,4.4700,0.000 +F,10,0.9359,223.000,0.0000,-0.09780,0.12730,4.1800,0.000 +F,10,0.9527,223.000,0.0000,-0.08990,0.11880,3.8800,0.000 +F,10,0.9696,223.000,0.0000,-0.08180,0.10970,3.5700,0.000 +F,10,0.9861,223.000,0.0000,-0.07370,0.10010,3.2000,0.000 +F,10,1.0021,223.000,0.0000,-0.06540,0.08990,2.7800,0.000 +F,10,1.0193,223.000,0.0000,-0.05700,0.07930,2.3400,0.000 +F,10,1.0344,223.000,0.0000,-0.04870,0.06840,1.9200,0.000 +F,10,1.0528,223.000,0.0000,-0.04060,0.05770,1.5400,0.000 +F,10,1.0678,223.000,0.0000,-0.03290,0.04730,1.2100,0.000 +F,10,1.0861,223.000,0.0000,-0.02580,0.03760,0.9200,0.000 +F,10,1.1011,223.000,0.0000,-0.01950,0.02880,0.6800,0.000 +F,10,1.1193,223.000,0.0000,-0.01390,0.02100,0.4800,0.000 +F,10,1.1360,223.000,0.0000,-0.00930,0.01450,0.3200,0.000 +F,10,1.1525,223.000,0.0000,-0.00550,0.00910,0.1900,0.000 +F,10,1.1693,223.000,0.0000,-0.00260,0.00480,0.0900,0.000 +F,10,1.1862,223.000,0.0000,-0.00040,0.00170,0.0100,0.000 +F,10,1.2010,223.000,0.0000,0.00100,-0.00050,-0.0600,0.000 +F,10,1.2191,223.000,0.0000,0.00190,-0.00180,-0.1100,0.000 +F,10,1.2357,223.000,0.0000,0.00680,-0.00850,0.0700,0.000 +F,10,1.2524,223.000,0.0000,0.01480,-0.01910,0.4100,0.000 +F,10,1.2690,223.000,0.0000,0.01840,-0.02400,0.5800,0.000 +F,10,1.2857,223.000,0.0000,0.01720,-0.02200,0.6600,0.000 +F,10,1.3024,223.000,0.0000,0.01380,-0.01730,0.7600,0.000 +F,10,1.3190,223.000,0.0000,0.01040,-0.01260,0.8300,0.000 +F,10,1.3357,223.000,0.0000,0.00740,-0.00840,0.8500,0.000 +F,10,1.3524,223.000,0.0000,0.00460,-0.00470,0.8400,0.000 +F,10,1.3689,223.000,0.0000,0.00190,-0.00110,0.8200,0.000 +F,10,1.3856,223.000,0.0000,-0.00080,0.00250,0.8000,0.000 +F,10,1.4023,223.000,0.0000,-0.00350,0.00600,0.7900,0.000 +F,10,1.4189,223.000,0.0000,-0.00610,0.00960,0.7900,0.000 +F,10,1.4356,223.000,0.0000,-0.00870,0.01300,0.8000,0.000 +F,10,1.4523,223.000,0.0000,-0.01100,0.01610,0.8100,0.000 +F,10,1.4689,223.000,0.0000,-0.01300,0.01870,0.8200,0.000 +F,10,1.4857,223.000,0.0000,-0.01450,0.02070,0.8200,0.000 +F,10,1.5022,223.000,0.0000,-0.01550,0.02210,0.8100,0.000 +F,10,1.5189,223.000,0.0000,-0.01610,0.02290,0.8000,0.000 +F,10,1.5356,223.000,0.0000,-0.01630,0.02300,0.7700,0.000 +F,10,1.5523,223.000,0.0000,-0.01610,0.02270,0.7300,0.000 +F,10,1.5690,223.000,0.0000,-0.01550,0.02180,0.6900,0.000 +F,10,1.5857,223.000,0.0000,-0.01460,0.02060,0.6400,0.000 +F,10,1.6024,223.000,0.0000,-0.01360,0.01910,0.5900,0.000 +F,10,1.6190,223.000,0.0000,-0.01250,0.01750,0.5300,0.000 +F,10,1.6357,223.000,0.0000,-0.01120,0.01570,0.4500,0.000 +F,10,1.6523,223.000,0.0000,-0.00990,0.01380,0.3800,0.000 +F,10,1.6689,223.000,0.0000,-0.00850,0.01190,0.3100,0.000 +F,10,1.6857,223.000,0.0000,-0.00710,0.01000,0.2500,0.000 +F,10,1.7023,223.000,0.0000,-0.00580,0.00820,0.2000,0.000 +F,10,1.7191,223.000,0.0000,-0.00460,0.00650,0.1500,0.000 +F,10,1.7356,223.000,0.0000,-0.00350,0.00490,0.1100,0.000 +F,10,1.7525,223.000,0.0000,-0.00250,0.00350,0.0800,0.000 +F,10,1.7689,223.000,0.0000,-0.00170,0.00240,0.0600,0.000 +F,10,1.7856,223.000,0.0000,-0.00100,0.00140,0.0400,0.000 +F,10,1.8030,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,10,1.8681,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,11,dragA,223.000,223.000,51.000,223.000,0.4335,0.3165,51.000 +T,11,0.0000,223.020 +T,11,0.3165,202.350 +T,11,0.3331,177.350 +T,11,0.3498,150.350 +T,11,0.3664,123.020 +T,11,0.3831,96.680 +T,11,0.3998,72.350 +T,11,0.4165,53.680 +T,11,0.4331,51.020 +T,11,0.4335,51.020 +F,11,0.0056,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,11,0.0178,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,11,0.0345,223.000,0.0562,0.00000,0.00000,0.0000,0.575 +F,11,0.0511,223.000,0.1906,0.00000,0.00000,0.0000,2.165 +F,11,0.0678,223.000,0.3438,0.00000,0.00000,0.0000,4.302 +F,11,0.0845,223.000,0.4869,0.00000,0.00000,0.0000,6.576 +F,11,0.1011,223.000,0.6088,0.00000,0.00000,0.0000,8.768 +F,11,0.1178,223.000,0.7075,0.00100,-0.00160,-0.0500,10.713 +F,11,0.1345,223.000,0.7844,0.00180,-0.00290,-0.1000,12.275 +F,11,0.1512,223.000,0.8431,0.00270,-0.00460,-0.1500,13.481 +F,11,0.1678,223.000,0.8869,0.00380,-0.00630,-0.2100,14.358 +F,11,0.1844,223.000,0.9188,0.00470,-0.00780,-0.2600,14.878 +F,11,0.2011,223.000,0.9425,0.00540,-0.00900,-0.3000,15.180 +F,11,0.2177,223.000,0.9594,0.00580,-0.00970,-0.3200,15.289 +F,11,0.2344,223.000,0.9713,0.00590,-0.00980,-0.3300,15.234 +F,11,0.2511,223.000,1.0000,0.00560,-0.00930,-0.3100,15.125 +F,11,0.2678,223.000,1.0000,0.00500,-0.00830,-0.2700,14.933 +F,11,0.2844,223.000,1.0000,0.00410,-0.00680,-0.2300,14.769 +F,11,0.3011,223.000,1.0000,0.00300,-0.00510,-0.1700,14.577 +F,11,0.3181,223.000,1.0000,0.00190,-0.00320,-0.1100,14.412 +F,11,0.3347,223.000,1.0000,0.00080,-0.00140,-0.0500,14.303 +F,11,0.3517,220.960,1.0000,0.00000,0.00000,0.0000,14.193 +F,11,0.3680,214.390,1.0000,-0.00110,0.00190,0.0600,14.166 +F,11,0.3850,202.490,1.0000,-0.00170,0.00290,0.1000,14.166 +F,11,0.4013,185.710,1.0000,-0.00110,0.00180,0.1800,14.248 +F,11,0.4180,165.120,1.0000,0.00260,-0.00430,0.4000,14.358 +F,11,0.4358,142.130,1.0000,0.01080,-0.01790,0.8700,14.522 +F,11,0.4510,118.490,1.0000,0.02370,-0.03880,1.6000,14.686 +F,11,0.4677,98.100,1.0000,0.04050,-0.06520,2.5700,14.303 +F,11,0.4843,82.440,1.0000,0.06000,-0.09460,3.7400,13.015 +F,11,0.5010,70.680,1.0000,0.08080,-0.12520,5.0300,11.179 +F,11,0.5200,62.060,1.0000,0.10150,-0.15530,6.3500,9.097 +F,11,0.5344,55.930,1.0000,0.12030,-0.18330,7.6000,6.987 +F,11,0.5511,51.730,0.9331,0.13540,-0.20580,8.5900,4.987 +F,11,0.5678,49.000,0.7950,0.14360,-0.21750,9.1000,3.233 +F,11,0.5844,47.360,0.6425,0.14190,-0.21470,8.9400,1.781 +F,11,0.6011,46.510,0.5006,0.12950,-0.19750,8.0900,0.603 +F,11,0.6178,46.210,0.3813,0.10760,-0.16890,6.6800,-0.247 +F,11,0.6344,46.280,0.2844,0.07890,-0.13260,4.9200,-0.849 +F,11,0.6510,46.590,0.2094,0.04610,-0.09230,3.0400,-1.206 +F,11,0.6678,47.030,0.1525,0.01180,-0.05070,1.1900,-1.370 +F,11,0.6862,47.530,0.1100,-0.02170,-0.01010,-0.5000,-1.397 +F,11,0.7011,48.040,0.0787,-0.05280,0.02770,-1.9600,-1.315 +F,11,0.7195,48.530,0.0556,-0.08010,0.06140,-3.1700,-1.151 +F,11,0.7361,48.980,0.0394,-0.10260,0.09010,-4.1100,-0.959 +F,11,0.7511,49.380,0.0281,-0.11990,0.11360,-4.8100,-0.767 +F,11,0.7678,49.720,0.0000,-0.13200,0.13170,-5.2800,-0.575 +F,11,0.7844,50.020,0.0000,-0.13910,0.14460,-5.5700,-0.384 +F,11,0.8010,50.260,0.0000,-0.14190,0.15280,-5.6900,0.000 +F,11,0.8176,50.450,0.0000,-0.14100,0.15660,-5.6800,0.000 +F,11,0.8344,50.610,0.0000,-0.13720,0.15660,-5.5700,0.000 +F,11,0.8511,50.730,0.0000,-0.13100,0.15330,-5.3700,0.000 +F,11,0.8677,50.820,0.0000,-0.12320,0.14750,-5.1100,0.000 +F,11,0.8844,50.890,0.0000,-0.11430,0.13980,-4.7900,0.000 +F,11,0.9010,51.000,0.0000,-0.10480,0.13080,-4.4500,0.000 +F,11,0.9177,51.000,0.0000,-0.09510,0.12100,-4.0800,0.000 +F,11,0.9344,51.000,0.0000,-0.08520,0.11050,-3.6600,0.000 +F,11,0.9511,51.000,0.0000,-0.07530,0.09930,-3.1700,0.000 +F,11,0.9677,51.000,0.0000,-0.06530,0.08750,-2.6700,0.000 +F,11,0.9844,51.000,0.0000,-0.05540,0.07550,-2.1900,0.000 +F,11,1.0010,51.000,0.0000,-0.04590,0.06350,-1.7600,0.000 +F,11,1.0177,51.000,0.0000,-0.03700,0.05200,-1.3800,0.000 +F,11,1.0344,51.000,0.0000,-0.02870,0.04120,-1.0500,0.000 +F,11,1.0510,51.000,0.0000,-0.02140,0.03140,-0.7800,0.000 +F,11,1.0676,51.000,0.0000,-0.01510,0.02290,-0.5500,0.000 +F,11,1.0843,51.000,0.0000,-0.00980,0.01560,-0.3700,0.000 +F,11,1.1010,51.000,0.0000,-0.00550,0.00960,-0.2200,0.000 +F,11,1.1176,51.000,0.0000,-0.00220,0.00490,-0.1000,0.000 +F,11,1.1344,51.000,0.0000,0.00020,0.00150,-0.0100,0.000 +F,11,1.1510,51.000,0.0000,0.00170,-0.00090,0.0600,0.000 +F,11,1.1677,51.000,0.0000,0.00260,-0.00230,0.1200,0.000 +F,11,1.1843,51.000,0.0000,0.00290,-0.00290,0.1700,0.000 +F,11,1.2009,51.000,0.0000,0.00280,-0.00300,0.2100,0.000 +F,11,1.2176,51.000,0.0000,0.00690,-0.00850,0.4400,0.000 +F,11,1.2355,51.000,0.0000,0.01390,-0.01800,0.8000,0.000 +F,11,1.2522,51.000,0.0000,0.01660,-0.02160,0.9400,0.000 +F,11,1.2689,51.000,0.0000,0.01520,-0.01960,0.9600,0.000 +F,11,1.2856,51.000,0.0000,0.01260,-0.01600,0.9700,0.000 +F,11,1.3023,51.000,0.0000,0.01020,-0.01280,0.9300,0.000 +F,11,1.3190,51.000,0.0000,0.00810,-0.01000,0.8700,0.000 +F,11,1.3356,51.000,0.0000,0.00600,-0.00720,0.8000,0.000 +F,11,1.3522,51.000,0.0000,0.00370,-0.00420,0.7300,0.000 +F,11,1.3688,51.000,0.0000,0.00120,-0.00080,0.6900,0.000 +F,11,1.3856,51.000,0.0000,-0.00160,0.00300,0.6800,0.000 +F,11,1.4023,51.000,0.0000,-0.00450,0.00700,0.6800,0.000 +F,11,1.4189,51.000,0.0000,-0.00740,0.01090,0.7000,0.000 +F,11,1.4356,51.000,0.0000,-0.01010,0.01450,0.7300,0.000 +F,11,1.4523,51.000,0.0000,-0.01240,0.01770,0.7500,0.000 +F,11,1.4689,51.000,0.0000,-0.01420,0.02010,0.7700,0.000 +F,11,1.4856,51.000,0.0000,-0.01540,0.02180,0.7800,0.000 +F,11,1.5021,51.000,0.0000,-0.01620,0.02280,0.7700,0.000 +F,11,1.5189,51.000,0.0000,-0.01640,0.02310,0.7500,0.000 +F,11,1.5355,51.000,0.0000,-0.01620,0.02280,0.7200,0.000 +F,11,1.5521,51.000,0.0000,-0.01560,0.02190,0.6800,0.000 +F,11,1.5688,51.000,0.0000,-0.01470,0.02070,0.6300,0.000 +F,11,1.5855,51.000,0.0000,-0.01360,0.01920,0.5800,0.000 +F,11,1.6023,51.000,0.0000,-0.01250,0.01750,0.5200,0.000 +F,11,1.6188,51.000,0.0000,-0.01120,0.01570,0.4600,0.000 +F,11,1.6356,51.000,0.0000,-0.00990,0.01380,0.4000,0.000 +F,11,1.6521,51.000,0.0000,-0.00850,0.01190,0.3300,0.000 +F,11,1.6689,51.000,0.0000,-0.00720,0.01000,0.2700,0.000 +F,11,1.6856,51.000,0.0000,-0.00590,0.00820,0.2200,0.000 +F,11,1.7022,51.000,0.0000,-0.00470,0.00650,0.1700,0.000 +F,11,1.7188,51.000,0.0000,-0.00360,0.00500,0.1300,0.000 +F,11,1.7355,51.000,0.0000,-0.00260,0.00360,0.1000,0.000 +F,11,1.7535,51.000,0.0000,-0.00180,0.00250,0.0700,0.000 +F,11,1.7695,51.000,0.0000,-0.00110,0.00150,0.0500,0.000 +F,11,1.7860,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,11,1.8182,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,12,dragC,47.410,47.410,47.410,304.980,2.0986,0.2985,304.980 +T,12,0.0000,47.210 +T,12,0.2985,49.210 +T,12,0.3318,51.550 +T,12,0.3485,55.880 +T,12,0.3819,58.210 +T,12,0.3985,60.880 +T,12,0.4152,63.210 +T,12,0.4318,65.880 +T,12,0.4485,68.550 +T,12,0.4652,71.210 +T,12,0.4818,76.550 +T,12,0.4985,79.550 +T,12,0.5318,85.210 +T,12,0.5652,88.210 +T,12,0.5818,91.210 +T,12,0.5985,94.210 +T,12,0.6152,97.210 +T,12,0.6318,103.210 +T,12,0.6652,106.210 +T,12,0.6819,109.550 +T,12,0.6985,115.550 +T,12,0.7318,121.880 +T,12,0.7485,125.210 +T,12,0.7651,128.550 +T,12,0.7818,131.550 +T,12,0.7985,134.880 +T,12,0.8151,138.210 +T,12,0.8318,141.210 +T,12,0.8652,144.550 +T,12,0.8819,147.880 +T,12,0.8985,154.210 +T,12,0.9319,157.550 +T,12,0.9485,160.880 +T,12,0.9652,164.210 +T,12,0.9819,167.550 +T,12,0.9985,173.880 +T,12,1.0319,177.210 +T,12,1.0485,180.550 +T,12,1.0652,183.880 +T,12,1.0818,187.210 +T,12,1.0985,193.550 +T,12,1.1152,196.880 +T,12,1.1485,200.210 +T,12,1.1652,206.550 +T,12,1.1985,209.880 +T,12,1.2152,212.880 +T,12,1.2319,216.210 +T,12,1.2485,219.550 +T,12,1.2652,222.550 +T,12,1.2819,228.880 +T,12,1.2985,231.880 +T,12,1.3319,235.210 +T,12,1.3486,238.210 +T,12,1.3652,241.210 +T,12,1.3819,244.210 +T,12,1.3985,250.210 +T,12,1.4319,253.210 +T,12,1.4485,256.210 +T,12,1.4652,258.880 +T,12,1.4819,261.880 +T,12,1.4985,264.550 +T,12,1.5152,267.550 +T,12,1.5319,270.210 +T,12,1.5485,272.880 +T,12,1.5652,275.550 +T,12,1.5819,278.210 +T,12,1.5985,280.550 +T,12,1.6152,283.210 +T,12,1.6319,285.550 +T,12,1.6485,290.210 +T,12,1.6652,292.550 +T,12,1.6818,294.550 +T,12,1.7152,298.550 +T,12,1.7485,301.880 +T,12,1.7819,303.550 +T,12,1.7986,304.880 +T,12,2.0986,304.880 +F,12,0.0160,47.410,0.0000,0.00000,0.00000,0.0000,0.000 +F,12,0.0334,47.410,0.0000,0.00000,0.00000,0.0000,0.000 +F,12,0.0671,47.410,0.1925,0.00000,0.00000,0.0000,2.176 +F,12,0.0835,47.410,0.3456,0.00000,0.00000,0.0000,4.317 +F,12,0.1001,47.410,0.4881,0.00000,0.00000,0.0000,6.599 +F,12,0.1169,47.410,0.6100,0.00000,0.00000,0.0000,8.810 +F,12,0.1336,47.410,0.7081,0.00110,-0.00190,0.0600,10.705 +F,12,0.1502,47.410,0.7850,0.00210,-0.00340,0.1100,12.285 +F,12,0.1669,47.410,0.8431,0.00320,-0.00530,0.1600,13.513 +F,12,0.1835,47.410,0.8869,0.00440,-0.00730,0.2300,14.356 +F,12,0.2002,47.410,0.9194,0.00550,-0.00910,0.2800,14.882 +F,12,0.2168,47.410,0.9425,0.00630,-0.01050,0.3300,15.198 +F,12,0.2335,47.410,0.9594,0.00680,-0.01130,0.3500,15.269 +F,12,0.2501,47.410,0.9713,0.00690,-0.01150,0.3600,15.233 +F,12,0.2668,47.410,1.0000,0.00650,-0.01090,0.3400,15.128 +F,12,0.2835,47.410,1.0000,0.00580,-0.00970,0.3000,14.953 +F,12,0.3005,47.410,1.0000,0.00480,-0.00800,0.2500,14.742 +F,12,0.3168,47.410,1.0000,0.00360,-0.00590,0.1800,14.567 +F,12,0.3339,47.610,1.0000,0.00230,-0.00380,0.1200,14.426 +F,12,0.3505,48.010,1.0000,0.00100,-0.00160,0.0500,14.286 +F,12,0.3667,48.630,1.0000,0.00000,0.00000,0.0000,14.180 +F,12,0.3839,49.880,1.0000,-0.00140,0.00240,-0.0600,14.215 +F,12,0.4005,51.560,1.0000,-0.00230,0.00390,-0.0700,14.215 +F,12,0.4172,53.310,1.0000,-0.00270,0.00450,-0.0900,14.251 +F,12,0.4338,55.240,1.0000,-0.00230,0.00390,-0.1300,14.286 +F,12,0.4505,57.330,1.0000,-0.00110,0.00180,-0.2000,14.286 +F,12,0.4673,59.570,1.0000,0.00090,-0.00160,-0.2900,14.321 +F,12,0.4839,61.910,1.0000,0.00360,-0.00610,-0.4200,14.356 +F,12,0.5006,64.330,1.0000,0.00680,-0.01130,-0.5700,14.391 +F,12,0.5167,67.090,1.0000,0.01020,-0.01700,-0.7200,14.426 +F,12,0.5339,70.180,1.0000,0.01360,-0.02270,-0.8800,14.461 +F,12,0.5501,73.090,1.0000,0.01690,-0.02820,-1.0400,14.461 +F,12,0.5671,76.010,1.0000,0.02000,-0.03330,-1.1800,14.531 +F,12,0.5852,78.880,1.0000,0.02280,-0.03800,-1.3200,14.531 +F,12,0.6006,81.520,1.0000,0.02530,-0.04220,-1.4300,14.567 +F,12,0.6172,84.130,1.0000,0.02740,-0.04560,-1.5300,14.567 +F,12,0.6339,86.800,1.0000,0.02890,-0.04810,-1.6000,14.602 +F,12,0.6502,89.550,1.0000,0.02980,-0.04960,-1.6300,14.637 +F,12,0.6673,92.680,1.0000,0.03000,-0.05000,-1.6300,14.707 +F,12,0.6840,95.870,1.0000,0.02970,-0.04950,-1.6000,14.707 +F,12,0.7007,98.820,1.0000,0.02890,-0.04820,-1.5500,14.742 +F,12,0.7168,101.680,1.0000,0.02790,-0.04650,-1.4800,14.742 +F,12,0.7338,104.830,1.0000,0.02680,-0.04460,-1.4200,14.812 +F,12,0.7506,108.000,1.0000,0.02560,-0.04260,-1.3500,14.812 +F,12,0.7672,111.330,1.0000,0.02440,-0.04060,-1.2800,14.882 +F,12,0.7839,114.960,1.0000,0.02320,-0.03860,-1.2100,14.917 +F,12,0.8005,118.610,1.0000,0.02200,-0.03670,-1.1500,14.953 +F,12,0.8172,122.150,1.0000,0.02100,-0.03500,-1.0900,14.988 +F,12,0.8339,125.650,1.0000,0.02020,-0.03370,-1.0400,15.023 +F,12,0.8501,129.130,1.0000,0.01960,-0.03270,-1.0100,15.058 +F,12,0.8673,132.500,1.0000,0.01920,-0.03190,-0.9800,15.093 +F,12,0.8839,135.470,1.0000,0.01880,-0.03130,-0.9600,15.093 +F,12,0.9005,138.040,1.0000,0.01830,-0.03050,-0.9400,15.093 +F,12,0.9167,140.630,1.0000,0.01770,-0.02940,-0.9000,15.163 +F,12,0.9344,143.690,1.0000,0.01670,-0.02780,-0.8500,15.198 +F,12,0.9506,146.840,1.0000,0.01520,-0.02540,-0.7700,15.198 +F,12,0.9673,149.780,1.0000,0.01330,-0.02220,-0.6800,15.233 +F,12,0.9841,152.750,1.0000,0.01130,-0.01880,-0.5700,15.269 +F,12,1.0005,155.810,1.0000,0.00910,-0.01520,-0.4600,15.304 +F,12,1.0168,158.890,1.0000,0.00710,-0.01190,-0.3600,15.339 +F,12,1.0341,162.380,1.0000,0.00530,-0.00890,-0.2700,15.409 +F,12,1.0508,165.940,1.0000,0.00380,-0.00630,-0.1900,15.409 +F,12,1.0673,169.210,1.0000,0.00260,-0.00430,-0.1300,15.444 +F,12,1.0838,172.310,1.0000,0.00180,-0.00300,-0.0900,15.479 +F,12,1.1007,175.430,1.0000,0.00140,-0.00240,-0.0700,15.514 +F,12,1.1173,178.600,1.0000,0.00140,-0.00230,-0.0700,15.549 +F,12,1.1335,182.080,1.0000,0.00140,-0.00240,-0.0700,15.584 +F,12,1.1506,185.920,1.0000,0.00170,-0.00250,-0.0800,15.619 +F,12,1.1673,189.530,1.0000,0.00220,-0.00270,-0.0800,15.619 +F,12,1.1835,192.720,1.0000,0.00230,-0.00290,-0.0900,15.655 +F,12,1.2008,196.000,1.0000,0.00250,-0.00340,-0.1000,15.725 +F,12,1.2175,199.190,1.0000,0.00280,-0.00400,-0.1200,15.725 +F,12,1.2341,202.190,1.0000,0.00310,-0.00450,-0.1400,15.760 +F,12,1.2512,205.180,1.0000,0.00320,-0.00490,-0.1500,15.795 +F,12,1.2675,208.170,1.0000,0.00320,-0.00490,-0.1500,15.830 +F,12,1.2841,211.170,1.0000,0.00290,-0.00460,-0.1400,15.865 +F,12,1.3008,214.160,1.0000,0.00240,-0.00380,-0.1100,15.900 +F,12,1.3170,217.560,1.0000,0.00170,-0.00260,-0.0800,15.971 +F,12,1.3344,221.370,1.0000,0.00090,-0.00130,-0.0400,15.971 +F,12,1.3509,224.890,1.0000,0.00000,0.00000,0.0000,15.971 +F,12,1.3676,227.970,1.0000,-0.00050,0.00100,0.0300,16.006 +F,12,1.3843,230.860,1.0000,-0.00080,0.00150,0.0400,16.041 +F,12,1.4010,233.690,1.0000,-0.00080,0.00140,0.0400,16.076 +F,12,1.4172,236.520,1.0000,-0.00060,0.00110,0.0300,16.111 +F,12,1.4343,239.690,1.0000,0.00000,0.00000,0.0000,16.181 +F,12,1.4510,242.900,1.0000,0.00000,0.00000,0.0000,16.146 +F,12,1.4675,245.850,1.0000,-0.00080,0.00140,0.0400,16.181 +F,12,1.4843,248.700,1.0000,-0.00110,0.00190,0.0600,16.216 +F,12,1.5010,251.550,1.0000,-0.00150,0.00250,0.0700,16.251 +F,12,1.5177,254.410,1.0000,-0.00180,0.00310,0.0900,16.286 +F,12,1.5345,257.210,1.0000,-0.00220,0.00370,0.1100,16.286 +F,12,1.5510,259.940,1.0000,-0.00260,0.00430,0.1300,16.321 +F,12,1.5677,262.690,1.0000,-0.00300,0.00500,0.1500,16.357 +F,12,1.5844,265.490,1.0000,-0.00340,0.00570,0.1700,16.392 +F,12,1.6010,268.250,1.0000,-0.00380,0.00630,0.1900,16.427 +F,12,1.6179,270.950,1.0000,-0.00420,0.00700,0.2100,16.427 +F,12,1.6343,273.590,1.0000,-0.00450,0.00760,0.2300,16.462 +F,12,1.6509,276.180,1.0000,-0.00480,0.00800,0.2400,16.497 +F,12,1.6677,278.730,1.0000,-0.00510,0.00840,0.2500,16.497 +F,12,1.6843,281.470,1.0000,-0.00530,0.00880,0.2600,16.567 +F,12,1.7007,284.380,1.0000,-0.00540,0.00910,0.2700,16.567 +F,12,1.7178,287.200,1.0000,-0.00560,0.00930,0.2800,16.602 +F,12,1.7340,289.640,1.0000,-0.00570,0.00940,0.2800,16.602 +F,12,1.7512,291.890,1.0000,-0.00560,0.00930,0.2800,16.637 +F,12,1.7674,293.970,1.0000,-0.00540,0.00900,0.2700,16.637 +F,12,1.7846,296.010,1.0000,-0.00530,0.00880,0.2600,16.673 +F,12,1.8012,297.990,1.0000,-0.00520,0.00870,0.2600,16.673 +F,12,1.8174,299.680,1.0000,-0.00540,0.00900,0.2700,16.708 +F,12,1.8341,301.240,1.0000,-0.00590,0.00980,0.2900,16.708 +F,12,1.8506,302.620,1.0000,-0.00660,0.01090,0.3300,16.708 +F,12,1.8674,303.670,1.0000,-0.00750,0.01250,0.3700,16.708 +F,12,1.8839,304.410,1.0000,-0.00870,0.01450,0.4300,16.708 +F,12,1.9007,304.890,1.0000,-0.01010,0.01680,0.5000,16.708 +F,12,1.9174,305.180,1.0000,-0.01170,0.01960,0.5800,16.708 +F,12,1.9340,305.330,1.0000,-0.01360,0.02270,0.6700,16.708 +F,12,1.9508,305.390,1.0000,-0.01570,0.02620,0.7700,16.708 +F,12,1.9674,305.390,1.0000,-0.01800,0.03010,0.8900,16.708 +F,12,1.9841,305.390,1.0000,-0.02040,0.03400,1.0000,16.708 +F,12,2.0007,305.390,1.0000,-0.02280,0.03800,1.1100,16.708 +F,12,2.0175,305.390,1.0000,-0.02500,0.04170,1.2200,16.708 +F,12,2.0342,305.390,1.0000,-0.02690,0.04490,1.3100,16.708 +F,12,2.0507,305.390,1.0000,-0.02850,0.04740,1.3800,16.708 +F,12,2.0674,305.390,1.0000,-0.02950,0.04920,1.4300,16.708 +F,12,2.0841,305.390,1.0000,-0.03010,0.05020,1.4600,16.708 +F,12,2.1074,305.390,1.0000,-0.02980,0.04970,1.4500,16.708 +F,12,2.1509,305.350,0.7950,-0.02770,0.04590,1.3400,14.496 +F,12,2.1675,305.310,0.6425,-0.02610,0.04300,1.2600,12.355 +F,12,2.1843,305.270,0.5006,-0.02430,0.03970,1.1500,9.968 +F,12,2.2010,305.220,0.3813,-0.02210,0.03580,1.0200,7.582 +F,12,2.2175,304.980,0.2844,-0.01910,0.03090,0.9000,5.335 +F,12,2.2350,304.980,0.2094,-0.01540,0.02500,0.8100,3.405 +F,12,2.2509,304.980,0.1525,-0.01120,0.01830,0.7500,1.790 +F,12,2.2677,304.980,0.1100,-0.00650,0.01120,0.7200,0.527 +F,12,2.2841,304.980,0.0787,-0.00200,0.00430,0.7100,-0.421 +F,12,2.3008,304.980,0.0556,0.00210,-0.00190,0.7100,-1.053 +F,12,2.3175,304.980,0.0394,0.00550,-0.00700,0.6900,-1.404 +F,12,2.3342,304.980,0.0281,0.00790,-0.01060,0.6600,-1.579 +F,12,2.3508,304.980,0.0000,0.00920,-0.01270,0.6100,-1.579 +F,12,2.3675,304.980,0.0000,0.00940,-0.01320,0.5300,-1.474 +F,12,2.3841,304.980,0.0000,0.00870,-0.01240,0.4300,-1.264 +F,12,2.4008,304.980,0.0000,0.00730,-0.01040,0.3200,-1.053 +F,12,2.4175,304.980,0.0000,0.00530,-0.00770,0.1900,-0.842 +F,12,2.4341,304.980,0.0000,0.00300,-0.00440,0.0700,-0.597 +F,12,2.4507,304.980,0.0000,0.00050,-0.00100,-0.0500,-0.421 +F,12,2.4674,304.980,0.0000,-0.00180,0.00230,-0.1600,0.000 +F,12,2.4840,304.980,0.0000,-0.00400,0.00540,-0.2500,0.000 +F,12,2.5008,304.980,0.0000,-0.00580,0.00800,-0.3300,0.000 +F,12,2.5174,304.980,0.0000,-0.00730,0.01010,-0.3800,0.000 +F,12,2.5341,304.980,0.0000,-0.00840,0.01170,-0.4200,0.000 +F,12,2.5508,304.980,0.0000,-0.00910,0.01270,-0.4400,0.000 +F,12,2.5674,304.980,0.0000,-0.00940,0.01310,-0.4500,0.000 +F,12,2.5841,304.980,0.0000,-0.00930,0.01310,-0.4400,0.000 +F,12,2.6006,304.980,0.0000,-0.00900,0.01260,-0.4200,0.000 +F,12,2.6174,304.980,0.0000,-0.00850,0.01200,-0.3900,0.000 +F,12,2.6341,304.980,0.0000,-0.00790,0.01110,-0.3500,0.000 +F,12,2.6507,304.980,0.0000,-0.00720,0.01010,-0.3200,0.000 +F,12,2.6674,304.980,0.0000,-0.00650,0.00910,-0.2900,0.000 +F,12,2.6840,304.980,0.0000,-0.00580,0.00810,-0.2500,0.000 +F,12,2.7007,304.980,0.0000,-0.00510,0.00710,-0.2100,0.000 +F,12,2.7173,304.980,0.0000,-0.00440,0.00610,-0.1700,0.000 +F,12,2.7340,304.980,0.0000,-0.00370,0.00520,-0.1400,0.000 +F,12,2.7505,304.980,0.0000,-0.00300,0.00430,-0.1100,0.000 +F,12,2.7671,304.980,0.0000,-0.00240,0.00340,-0.0800,0.000 +F,12,2.7838,304.980,0.0000,-0.00180,0.00260,-0.0600,0.000 +F,12,2.8004,304.980,0.0000,-0.00130,0.00190,-0.0400,0.000 +F,12,2.8171,304.980,0.0000,-0.00090,0.00130,-0.0300,0.000 +F,12,2.8338,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,12,2.8504,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,12,2.8671,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,12,2.8837,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,12,2.9004,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,12,2.9170,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,12,2.9337,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,12,2.9504,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,12,2.9671,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,12,2.9836,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,12,3.0003,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,12,3.0168,304.980,0.0000,0.00210,-0.00290,0.1200,0.000 +F,12,3.0335,304.980,0.0000,0.00630,-0.00880,0.3000,0.000 +F,12,3.0501,304.980,0.0000,0.00860,-0.01210,0.4000,0.000 +F,12,3.0668,304.980,0.0000,0.00890,-0.01260,0.4400,0.000 +F,12,3.0851,304.980,0.0000,0.00850,-0.01200,0.4600,0.000 +F,12,3.1018,304.980,0.0000,0.00780,-0.01100,0.4600,0.000 +F,12,3.1184,304.980,0.0000,0.00700,-0.00990,0.4400,0.000 +F,12,3.1352,304.980,0.0000,0.00620,-0.00870,0.4100,0.000 +F,12,3.1502,304.980,0.0000,0.00510,-0.00720,0.3700,0.000 +F,12,3.1685,304.980,0.0000,0.00390,-0.00550,0.3400,0.000 +F,12,3.1835,304.980,0.0000,0.00250,-0.00350,0.3200,0.000 +F,12,3.2002,304.980,0.0000,0.00110,-0.00150,0.3000,0.000 +F,12,3.2168,304.980,0.0000,-0.00030,0.00050,0.3000,0.000 +F,12,3.2351,304.980,0.0000,-0.00170,0.00240,0.2900,0.000 +F,12,3.2501,304.980,0.0000,-0.00290,0.00410,0.2900,0.000 +F,12,3.2668,304.980,0.0000,-0.00390,0.00550,0.2800,0.000 +F,12,3.2835,304.980,0.0000,-0.00470,0.00660,0.2800,0.000 +F,12,3.3015,304.980,0.0000,-0.00520,0.00730,0.2700,0.000 +F,12,3.3168,304.980,0.0000,-0.00550,0.00780,0.2600,0.000 +F,12,3.3353,304.980,0.0000,-0.00560,0.00790,0.2400,0.000 +F,12,3.3520,304.980,0.0000,-0.00560,0.00790,0.2300,0.000 +F,12,3.3686,304.980,0.0000,-0.00540,0.00760,0.2100,0.000 +F,12,3.3852,304.980,0.0000,-0.00510,0.00710,0.1800,0.000 +F,12,3.3997,304.980,0.0000,-0.00460,0.00650,0.1500,0.000 +F,12,3.4178,304.980,0.0000,-0.00420,0.00590,0.1300,0.000 +F,12,3.4344,304.980,0.0000,-0.00370,0.00510,0.1000,0.000 +F,12,3.4511,304.980,0.0000,-0.00310,0.00440,0.0800,0.000 +F,12,3.4678,304.980,0.0000,-0.00260,0.00370,0.0600,0.000 +F,12,3.4845,304.980,0.0000,-0.00220,0.00310,0.0500,0.000 +F,12,3.5011,304.980,0.0000,-0.00180,0.00250,0.0300,0.000 +F,12,3.5180,304.980,0.0000,-0.00140,0.00200,0.0200,0.000 +F,12,3.5343,304.980,0.0000,-0.00110,0.00160,0.0100,0.000 +F,12,3.5510,304.980,0.0000,-0.00080,0.00120,0.0100,0.000 +F,12,3.5676,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +E,13,dragC,304.980,304.980,47.410,304.980,0.9001,0.3163,47.410 +T,13,0.0000,304.880 +T,13,0.3163,299.550 +T,13,0.3330,293.880 +T,13,0.3497,287.550 +T,13,0.3663,280.880 +T,13,0.3830,274.210 +T,13,0.3997,266.880 +T,13,0.4163,259.550 +T,13,0.4330,251.880 +T,13,0.4496,244.210 +T,13,0.4663,236.550 +T,13,0.4830,228.550 +T,13,0.4997,220.550 +T,13,0.5163,212.550 +T,13,0.5330,204.210 +T,13,0.5497,196.210 +T,13,0.5663,187.880 +T,13,0.5830,179.880 +T,13,0.5996,171.550 +T,13,0.6163,163.550 +T,13,0.6330,155.210 +T,13,0.6497,147.210 +T,13,0.6663,139.210 +T,13,0.6830,131.210 +T,13,0.6997,123.210 +T,13,0.7163,115.550 +T,13,0.7330,107.880 +T,13,0.7496,100.550 +T,13,0.7663,93.210 +T,13,0.7830,86.210 +T,13,0.7997,79.210 +T,13,0.8163,72.880 +T,13,0.8330,66.550 +T,13,0.8496,60.880 +T,13,0.8663,55.550 +T,13,0.8830,50.880 +T,13,0.8997,47.210 +T,13,0.9001,47.210 +F,13,0.0188,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,13,0.0353,304.980,0.0500,0.00000,0.00000,0.0000,0.491 +F,13,0.0520,304.980,0.1812,0.00000,0.00000,0.0000,2.036 +F,13,0.0684,304.980,0.3344,0.00000,0.00000,0.0000,4.142 +F,13,0.0851,304.980,0.4787,0.00000,0.00000,0.0000,6.423 +F,13,0.1017,304.980,0.6019,0.00000,0.00000,0.0000,8.635 +F,13,0.1185,304.980,0.7019,0.00110,-0.00180,-0.0500,10.600 +F,13,0.1351,304.980,0.7800,0.00200,-0.00330,-0.1000,12.180 +F,13,0.1517,304.980,0.8400,0.00310,-0.00520,-0.1600,13.408 +F,13,0.1684,304.980,0.8844,0.00430,-0.00720,-0.2200,14.286 +F,13,0.1851,304.980,0.9169,0.00550,-0.00910,-0.2700,14.847 +F,13,0.2018,304.980,0.9413,0.00630,-0.01060,-0.3200,15.163 +F,13,0.2184,304.980,0.9581,0.00690,-0.01140,-0.3500,15.269 +F,13,0.2351,304.980,0.9706,0.00700,-0.01160,-0.3500,15.233 +F,13,0.2517,304.980,1.0000,0.00660,-0.01110,-0.3300,15.128 +F,13,0.2684,304.980,1.0000,0.00590,-0.00990,-0.3000,14.953 +F,13,0.2850,304.980,1.0000,0.00490,-0.00820,-0.2500,14.777 +F,13,0.3017,304.980,1.0000,0.00370,-0.00620,-0.1900,14.602 +F,13,0.3189,304.980,1.0000,0.00240,-0.00400,-0.1200,14.426 +F,13,0.3356,304.980,1.0000,0.00110,-0.00180,-0.0500,14.286 +F,13,0.3522,304.370,1.0000,0.00000,0.00000,0.0000,14.180 +F,13,0.3688,302.650,1.0000,-0.00130,0.00210,0.0600,14.251 +F,13,0.3854,299.690,1.0000,-0.00220,0.00370,0.1000,14.321 +F,13,0.4021,295.610,1.0000,-0.00280,0.00460,0.1200,14.391 +F,13,0.4190,290.620,1.0000,-0.00240,0.00400,0.1600,14.461 +F,13,0.4354,284.910,1.0000,-0.00060,0.00100,0.2600,14.531 +F,13,0.4520,278.570,1.0000,0.00300,-0.00500,0.4300,14.602 +F,13,0.4688,271.770,1.0000,0.00860,-0.01430,0.7000,14.672 +F,13,0.4855,264.650,1.0000,0.01590,-0.02660,1.0600,14.777 +F,13,0.5021,257.250,1.0000,0.02470,-0.04120,1.5000,14.847 +F,13,0.5189,249.620,1.0000,0.03450,-0.05750,1.9900,14.917 +F,13,0.5355,241.830,1.0000,0.04460,-0.07430,2.5100,15.023 +F,13,0.5520,233.940,1.0000,0.05440,-0.09070,3.0300,15.093 +F,13,0.5688,225.980,1.0000,0.06350,-0.10580,3.5200,15.163 +F,13,0.5855,218.000,1.0000,0.07130,-0.11890,3.9500,15.233 +F,13,0.6020,210.000,1.0000,0.07780,-0.12970,4.3000,15.339 +F,13,0.6186,201.990,1.0000,0.08260,-0.13770,4.5600,15.409 +F,13,0.6355,193.880,1.0000,0.08590,-0.14310,4.7400,15.479 +F,13,0.6527,185.670,1.0000,0.08750,-0.14580,4.8200,15.584 +F,13,0.6688,177.450,1.0000,0.08770,-0.14620,4.8200,15.655 +F,13,0.6854,169.280,1.0000,0.08670,-0.14450,4.7500,15.725 +F,13,0.7021,161.150,1.0000,0.08470,-0.14110,4.6300,15.795 +F,13,0.7187,153.060,1.0000,0.08190,-0.13650,4.4600,15.865 +F,13,0.7352,145.010,1.0000,0.07850,-0.13080,4.2600,15.971 +F,13,0.7519,136.970,1.0000,0.07470,-0.12450,4.0300,16.041 +F,13,0.7686,129.060,1.0000,0.07050,-0.11760,3.7900,16.111 +F,13,0.7864,121.250,1.0000,0.06610,-0.11020,3.5400,16.181 +F,13,0.8020,113.560,1.0000,0.06160,-0.10260,3.2800,16.251 +F,13,0.8186,106.030,1.0000,0.05690,-0.09480,3.0100,16.321 +F,13,0.8353,98.670,1.0000,0.05210,-0.08690,2.7500,16.392 +F,13,0.8519,91.540,1.0000,0.04720,-0.07870,2.4800,16.462 +F,13,0.8686,84.610,1.0000,0.04230,-0.07050,2.2100,16.532 +F,13,0.8853,77.940,1.0000,0.03730,-0.06220,1.9400,16.567 +F,13,0.9032,71.580,1.0000,0.03220,-0.05370,1.6700,16.637 +F,13,0.9182,65.620,1.0000,0.02710,-0.04510,1.3900,16.673 +F,13,0.9347,60.590,1.0000,0.02190,-0.03640,1.1200,16.041 +F,13,0.9543,56.650,1.0000,0.01660,-0.02760,0.8400,14.461 +F,13,0.9682,53.610,1.0000,0.01100,-0.01840,0.5500,12.320 +F,13,0.9849,51.330,0.9331,0.00480,-0.00820,0.2400,9.933 +F,13,1.0016,49.650,0.7950,-0.00280,0.00340,-0.1200,7.547 +F,13,1.0182,48.450,0.6425,-0.01190,0.01690,-0.5300,5.335 +F,13,1.0349,47.620,0.5006,-0.02260,0.03180,-0.9900,3.405 +F,13,1.0516,47.080,0.3813,-0.03450,0.04750,-1.4800,1.790 +F,13,1.0682,46.750,0.2844,-0.04710,0.06340,-1.9900,0.527 +F,13,1.0849,46.570,0.2094,-0.05970,0.07890,-2.4800,-0.421 +F,13,1.1015,46.510,0.1525,-0.07160,0.09310,-2.9300,-1.018 +F,13,1.1182,46.520,0.1100,-0.08210,0.10570,-3.3100,-1.404 +F,13,1.1348,46.570,0.0787,-0.09070,0.11610,-3.6200,-1.579 +F,13,1.1515,46.650,0.0556,-0.09700,0.12420,-3.8300,-1.579 +F,13,1.1682,46.740,0.0394,-0.10080,0.12940,-3.9600,-1.439 +F,13,1.1849,46.840,0.0281,-0.10210,0.13180,-4.0000,-1.264 +F,13,1.2015,46.930,0.0000,-0.10120,0.13140,-3.9700,-1.053 +F,13,1.2182,47.020,0.0000,-0.09830,0.12830,-3.8600,-0.842 +F,13,1.2349,47.090,0.0000,-0.09370,0.12300,-3.7000,-0.597 +F,13,1.2515,47.160,0.0000,-0.08780,0.11600,-3.4900,-0.421 +F,13,1.2682,47.220,0.0000,-0.08110,0.10770,-3.2400,0.000 +F,13,1.2849,47.410,0.0000,-0.07380,0.09860,-2.9700,0.000 +F,13,1.3015,47.410,0.0000,-0.06630,0.08910,-2.6900,0.000 +F,13,1.3181,47.410,0.0000,-0.05880,0.07960,-2.3800,0.000 +F,13,1.3349,47.410,0.0000,-0.05150,0.07000,-2.0300,0.000 +F,13,1.3515,47.410,0.0000,-0.04420,0.06040,-1.6900,0.000 +F,13,1.3683,47.410,0.0000,-0.03710,0.05100,-1.3700,0.000 +F,13,1.3847,47.410,0.0000,-0.03030,0.04190,-1.0900,0.000 +F,13,1.4014,47.410,0.0000,-0.02400,0.03350,-0.8400,0.000 +F,13,1.4181,47.410,0.0000,-0.01830,0.02570,-0.6300,0.000 +F,13,1.4348,47.410,0.0000,-0.01330,0.01880,-0.4600,0.000 +F,13,1.4514,47.410,0.0000,-0.00900,0.01290,-0.3200,0.000 +F,13,1.4680,47.410,0.0000,-0.00540,0.00800,-0.2100,0.000 +F,13,1.4847,47.410,0.0000,-0.00260,0.00410,-0.1200,0.000 +F,13,1.5013,47.410,0.0000,-0.00050,0.00120,-0.0500,0.000 +F,13,1.5180,47.410,0.0000,0.00000,0.00000,0.0000,0.000 +F,13,1.5347,47.410,0.0000,0.00190,-0.00220,0.0500,0.000 +F,13,1.5513,47.410,0.0000,0.00230,-0.00290,0.0900,0.000 +F,13,1.5679,47.410,0.0000,0.00240,-0.00310,0.1200,0.000 +F,13,1.5847,47.410,0.0000,0.00220,-0.00290,0.1400,0.000 +F,13,1.6013,47.410,0.0000,0.00190,-0.00240,0.1500,0.000 +F,13,1.6179,47.410,0.0000,0.00140,-0.00170,0.1700,0.000 +F,13,1.6346,47.410,0.0000,0.00080,-0.00100,0.1700,0.000 +F,13,1.6512,47.410,0.0000,0.00030,-0.00030,0.1800,0.000 +F,13,1.6678,47.410,0.0000,0.00210,-0.00290,0.2800,0.000 +F,13,1.6845,47.410,0.0000,0.00560,-0.00780,0.4300,0.000 +F,13,1.7013,47.410,0.0000,0.00720,-0.01010,0.5000,0.000 +F,13,1.7179,47.410,0.0000,0.00710,-0.01000,0.5200,0.000 +F,13,1.7346,47.410,0.0000,0.00640,-0.00900,0.5200,0.000 +F,13,1.7512,47.410,0.0000,0.00560,-0.00790,0.5000,0.000 +F,13,1.7680,47.410,0.0000,0.00490,-0.00690,0.4600,0.000 +F,13,1.7846,47.410,0.0000,0.00400,-0.00570,0.4200,0.000 +F,13,1.8013,47.410,0.0000,0.00310,-0.00440,0.3800,0.000 +F,13,1.8180,47.410,0.0000,0.00200,-0.00280,0.3500,0.000 +F,13,1.8346,47.410,0.0000,0.00080,-0.00110,0.3200,0.000 +F,13,1.8513,47.410,0.0000,-0.00050,0.00070,0.3100,0.000 +F,13,1.8679,47.410,0.0000,-0.00170,0.00240,0.3000,0.000 +F,13,1.8845,47.410,0.0000,-0.00290,0.00400,0.2900,0.000 +F,13,1.9012,47.410,0.0000,-0.00390,0.00550,0.2900,0.000 +F,13,1.9178,47.410,0.0000,-0.00470,0.00660,0.2900,0.000 +F,13,1.9345,47.410,0.0000,-0.00530,0.00750,0.2800,0.000 +F,13,1.9512,47.410,0.0000,-0.00570,0.00800,0.2700,0.000 +F,13,1.9678,47.410,0.0000,-0.00590,0.00820,0.2600,0.000 +F,13,1.9845,47.410,0.0000,-0.00580,0.00820,0.2500,0.000 +F,13,2.0012,47.410,0.0000,-0.00570,0.00800,0.2300,0.000 +F,13,2.0179,47.410,0.0000,-0.00540,0.00760,0.2100,0.000 +F,13,2.0345,47.410,0.0000,-0.00500,0.00700,0.1800,0.000 +F,13,2.0512,47.410,0.0000,-0.00450,0.00640,0.1600,0.000 +F,13,2.0678,47.410,0.0000,-0.00400,0.00570,0.1300,0.000 +F,13,2.0846,47.410,0.0000,-0.00350,0.00490,0.1000,0.000 +F,13,2.1013,47.410,0.0000,-0.00290,0.00410,0.0800,0.000 +F,13,2.1180,47.410,0.0000,-0.00240,0.00340,0.0600,0.000 +F,13,2.1346,47.410,0.0000,-0.00190,0.00270,0.0500,0.000 +F,13,2.1512,47.410,0.0000,-0.00150,0.00200,0.0400,0.000 +F,13,2.1680,47.410,0.0000,-0.00110,0.00150,0.0300,0.000 +F,13,2.1846,47.410,0.0000,-0.00070,0.00100,0.0200,0.000 +F,13,2.2013,47.410,0.0000,0.00000,0.00000,0.0000,0.000 +F,13,2.2509,47.410,0.0000,0.00000,0.00000,0.0000,0.000 +E,14,dragC,47.410,47.410,47.410,304.980,0.5665,0.3165,304.980 +T,14,0.0000,47.210 +T,14,0.3165,60.880 +T,14,0.3498,94.210 +T,14,0.3665,112.550 +T,14,0.3998,151.210 +T,14,0.4164,170.880 +T,14,0.4331,190.210 +T,14,0.4498,209.880 +T,14,0.4664,228.880 +T,14,0.4831,247.210 +T,14,0.4998,264.550 +T,14,0.5165,280.550 +T,14,0.5331,294.550 +T,14,0.5498,304.880 +T,14,0.5665,304.880 +F,14,0.0185,47.410,0.0000,0.00000,0.00000,0.0000,0.000 +F,14,0.0350,47.410,0.0631,0.00000,0.00000,0.0000,0.632 +F,14,0.0516,47.410,0.2000,0.00000,0.00000,0.0000,2.281 +F,14,0.0683,47.410,0.3525,0.00000,0.00000,0.0000,4.423 +F,14,0.0849,47.410,0.4950,0.00000,0.00000,0.0000,6.704 +F,14,0.1017,47.410,0.6156,0.00000,0.00000,0.0000,8.880 +F,14,0.1183,47.410,0.7125,0.00120,-0.00200,0.0600,10.811 +F,14,0.1351,47.410,0.7881,0.00210,-0.00350,0.1100,12.355 +F,14,0.1517,47.410,0.8456,0.00330,-0.00540,0.1700,13.549 +F,14,0.1682,47.410,0.8887,0.00440,-0.00740,0.2300,14.391 +F,14,0.1850,47.410,0.9206,0.00550,-0.00920,0.2900,14.917 +F,14,0.2016,47.410,0.9431,0.00640,-0.01060,0.3300,15.198 +F,14,0.2183,47.410,0.9600,0.00680,-0.01140,0.3500,15.269 +F,14,0.2349,47.410,0.9719,0.00690,-0.01140,0.3600,15.233 +F,14,0.2516,47.410,1.0000,0.00650,-0.01080,0.3400,15.093 +F,14,0.2683,47.410,1.0000,0.00580,-0.00960,0.3000,14.953 +F,14,0.2849,47.410,1.0000,0.00470,-0.00790,0.2400,14.742 +F,14,0.3016,47.410,1.0000,0.00350,-0.00580,0.1800,14.567 +F,14,0.3186,47.410,1.0000,0.00220,-0.00370,0.1100,14.426 +F,14,0.3350,47.410,1.0000,0.00090,-0.00150,0.0500,14.286 +F,14,0.3532,48.830,1.0000,0.00000,0.00000,0.0000,14.180 +F,14,0.3687,51.650,1.0000,-0.00140,0.00230,-0.0700,14.286 +F,14,0.3849,57.900,1.0000,-0.00220,0.00360,-0.1100,14.602 +F,14,0.4019,68.770,1.0000,-0.00230,0.00390,-0.1500,14.777 +F,14,0.4190,80.980,1.0000,-0.00090,0.00150,-0.2400,14.777 +F,14,0.4353,95.540,1.0000,0.00350,-0.00590,-0.4700,14.812 +F,14,0.4519,113.470,1.0000,0.01180,-0.01960,-0.8800,14.882 +F,14,0.4687,133.050,1.0000,0.02380,-0.03950,-1.4900,14.953 +F,14,0.4851,153.300,1.0000,0.03890,-0.06410,-2.2800,15.093 +F,14,0.5018,173.680,1.0000,0.05630,-0.09170,-3.2100,15.269 +F,14,0.5185,193.770,1.0000,0.07500,-0.12080,-4.2500,15.444 +F,14,0.5354,213.270,1.0000,0.09410,-0.14990,-5.3400,15.619 +F,14,0.5518,231.980,1.0000,0.11270,-0.17780,-6.4400,15.830 +F,14,0.5695,249.680,1.0000,0.12990,-0.20380,-7.4900,16.041 +F,14,0.5848,266.000,1.0000,0.14510,-0.22690,-8.4400,16.216 +F,14,0.6014,279.370,1.0000,0.15750,-0.24640,-9.2300,15.795 +F,14,0.6180,289.340,1.0000,0.16650,-0.26120,-9.8100,14.356 +F,14,0.6373,296.590,1.0000,0.17120,-0.26940,-10.1100,12.320 +F,14,0.6515,301.690,1.0000,0.16990,-0.26810,-10.0100,10.003 +F,14,0.6683,305.150,0.9331,0.16090,-0.25460,-9.4100,7.652 +F,14,0.6848,307.350,0.7950,0.14290,-0.22860,-8.2800,5.476 +F,14,0.7015,308.630,0.6425,0.11690,-0.19260,-6.7300,3.545 +F,14,0.7182,309.250,0.5006,0.08470,-0.15000,-4.9200,1.931 +F,14,0.7349,309.410,0.3813,0.04890,-0.10420,-3.0300,0.667 +F,14,0.7515,309.270,0.2844,0.01170,-0.05790,-1.2000,-0.281 +F,14,0.7682,308.950,0.2094,-0.02490,-0.01310,0.4700,-0.948 +F,14,0.7848,308.520,0.1525,-0.05920,0.02860,1.9200,-1.334 +F,14,0.8016,308.050,0.1100,-0.09000,0.06610,3.1300,-1.509 +F,14,0.8181,307.580,0.0787,-0.11660,0.09860,4.1000,-1.544 +F,14,0.8348,307.140,0.0556,-0.13840,0.12580,4.8500,-1.439 +F,14,0.8514,306.730,0.0394,-0.15510,0.14760,5.4000,-1.264 +F,14,0.8682,306.380,0.0281,-0.16650,0.16390,5.7700,-1.053 +F,14,0.8848,306.070,0.0000,-0.17280,0.17510,5.9800,-0.842 +F,14,0.9015,305.820,0.0000,-0.17440,0.18150,6.0500,-0.632 +F,14,0.9181,305.610,0.0000,-0.17200,0.18380,6.0000,-0.421 +F,14,0.9348,305.440,0.0000,-0.16630,0.18210,5.8500,0.000 +F,14,0.9515,305.310,0.0000,-0.15790,0.17720,5.6200,0.000 +F,14,0.9681,305.200,0.0000,-0.14760,0.16950,5.3300,0.000 +F,14,0.9848,305.120,0.0000,-0.13590,0.15970,4.9800,0.000 +F,14,1.0015,305.060,0.0000,-0.12360,0.14830,4.5900,0.000 +F,14,1.0181,304.980,0.0000,-0.11100,0.13610,4.1900,0.000 +F,14,1.0347,304.980,0.0000,-0.09840,0.12310,3.7300,0.000 +F,14,1.0515,304.980,0.0000,-0.08580,0.10950,3.2300,0.000 +F,14,1.0681,304.980,0.0000,-0.07340,0.09540,2.7200,0.000 +F,14,1.0848,304.980,0.0000,-0.06130,0.08120,2.2400,0.000 +F,14,1.1015,304.980,0.0000,-0.04990,0.06740,1.8000,0.000 +F,14,1.1181,304.980,0.0000,-0.03940,0.05430,1.4100,0.000 +F,14,1.1368,304.980,0.0000,-0.03000,0.04240,1.0700,0.000 +F,14,1.1515,304.980,0.0000,-0.02170,0.03170,0.7900,0.000 +F,14,1.1681,304.980,0.0000,-0.01470,0.02240,0.5500,0.000 +F,14,1.1863,304.980,0.0000,-0.00890,0.01470,0.3600,0.000 +F,14,1.2014,304.980,0.0000,-0.00440,0.00840,0.2100,0.000 +F,14,1.2200,304.980,0.0000,-0.00100,0.00370,0.0900,0.000 +F,14,1.2347,304.980,0.0000,0.00150,0.00020,-0.0100,0.000 +F,14,1.2530,304.980,0.0000,0.00300,-0.00210,-0.0800,0.000 +F,14,1.2682,304.980,0.0000,0.00380,-0.00340,-0.1400,0.000 +F,14,1.2849,304.980,0.0000,0.00400,-0.00390,-0.1900,0.000 +F,14,1.3015,304.980,0.0000,0.00370,-0.00380,-0.2200,0.000 +F,14,1.3199,304.980,0.0000,0.00310,-0.00320,-0.2500,0.000 +F,14,1.3348,304.980,0.0000,0.00240,-0.00230,-0.2700,0.000 +F,14,1.3514,304.980,0.0000,0.00400,-0.00480,-0.1700,0.000 +F,14,1.3697,304.980,0.0000,0.00730,-0.00960,-0.0000,0.000 +F,14,1.3847,304.980,0.0000,0.00870,-0.01180,0.1000,0.000 +F,14,1.4032,304.980,0.0000,0.00810,-0.01110,0.1600,0.000 +F,14,1.4181,304.980,0.0000,0.00660,-0.00910,0.2300,0.000 +F,14,1.4365,304.980,0.0000,0.00500,-0.00690,0.2800,0.000 +F,14,1.4514,304.980,0.0000,0.00360,-0.00500,0.3100,0.000 +F,14,1.4697,304.980,0.0000,0.00230,-0.00320,0.3200,0.000 +F,14,1.4864,304.980,0.0000,0.00110,-0.00150,0.3200,0.000 +F,14,1.5014,304.980,0.0000,0.00000,0.00000,0.3200,0.000 +F,14,1.5199,304.980,0.0000,-0.00110,0.00160,0.3200,0.000 +F,14,1.5364,304.980,0.0000,-0.00220,0.00300,0.3100,0.000 +F,14,1.5514,304.980,0.0000,-0.00320,0.00440,0.3100,0.000 +F,14,1.5696,304.980,0.0000,-0.00410,0.00560,0.3100,0.000 +F,14,1.5863,304.980,0.0000,-0.00480,0.00670,0.3100,0.000 +F,14,1.6014,304.980,0.0000,-0.00540,0.00750,0.3000,0.000 +F,14,1.6199,304.980,0.0000,-0.00580,0.00810,0.3000,0.000 +F,14,1.6347,304.980,0.0000,-0.00600,0.00850,0.2900,0.000 +F,14,1.6514,304.980,0.0000,-0.00610,0.00860,0.2700,0.000 +F,14,1.6698,304.980,0.0000,-0.00610,0.00850,0.2600,0.000 +F,14,1.6847,304.980,0.0000,-0.00590,0.00820,0.2400,0.000 +F,14,1.7033,304.980,0.0000,-0.00560,0.00780,0.2200,0.000 +F,14,1.7181,304.980,0.0000,-0.00520,0.00730,0.2000,0.000 +F,14,1.7348,304.980,0.0000,-0.00480,0.00670,0.1800,0.000 +F,14,1.7530,304.980,0.0000,-0.00430,0.00610,0.1600,0.000 +F,14,1.7682,304.980,0.0000,-0.00380,0.00530,0.1300,0.000 +F,14,1.7864,304.980,0.0000,-0.00330,0.00460,0.1100,0.000 +F,14,1.8015,304.980,0.0000,-0.00270,0.00390,0.0900,0.000 +F,14,1.8181,304.980,0.0000,-0.00220,0.00320,0.0700,0.000 +F,14,1.8365,304.980,0.0000,-0.00180,0.00250,0.0500,0.000 +F,14,1.8514,304.980,0.0000,-0.00140,0.00190,0.0400,0.000 +F,14,1.8697,304.980,0.0000,-0.00100,0.00140,0.0300,0.000 +F,14,1.8848,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,14,1.9357,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +E,15,dragC,304.980,304.980,47.410,304.980,1.6165,0.3165,112.840 +T,15,0.0000,304.880 +T,15,0.3165,302.210 +T,15,0.3331,299.550 +T,15,0.3498,296.550 +T,15,0.3665,293.550 +T,15,0.3831,290.210 +T,15,0.3998,287.210 +T,15,0.4164,283.550 +T,15,0.4331,280.210 +T,15,0.4498,276.550 +T,15,0.4665,272.880 +T,15,0.4831,269.210 +T,15,0.4998,265.550 +T,15,0.5165,261.550 +T,15,0.5332,257.880 +T,15,0.5498,253.880 +T,15,0.5665,249.880 +T,15,0.5832,245.880 +T,15,0.5998,241.880 +T,15,0.6164,237.880 +T,15,0.6332,233.880 +T,15,0.6498,229.880 +T,15,0.6665,225.880 +T,15,0.6831,221.880 +T,15,0.6998,217.550 +T,15,0.7165,213.550 +T,15,0.7331,209.550 +T,15,0.7498,205.550 +T,15,0.7665,201.210 +T,15,0.7832,197.210 +T,15,0.7998,193.210 +T,15,0.8165,189.210 +T,15,0.8332,185.210 +T,15,0.8498,181.210 +T,15,0.8665,177.210 +T,15,0.8832,173.210 +T,15,0.8998,169.550 +T,15,0.9165,165.550 +T,15,0.9331,161.880 +T,15,0.9498,157.880 +T,15,0.9665,154.210 +T,15,0.9831,150.550 +T,15,0.9998,146.880 +T,15,1.0165,143.550 +T,15,1.0332,139.880 +T,15,1.0498,136.550 +T,15,1.0665,133.210 +T,15,1.0832,130.210 +T,15,1.0998,127.210 +T,15,1.1165,124.210 +T,15,1.1332,121.550 +T,15,1.1498,118.880 +T,15,1.1665,116.550 +T,15,1.1831,114.550 +T,15,1.1998,112.880 +T,15,1.6165,112.880 +F,15,0.0184,304.980,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,0.0349,304.980,0.0644,0.00000,0.00000,0.0000,0.667 +F,15,0.0516,304.980,0.2012,0.00000,0.00000,0.0000,2.317 +F,15,0.0682,304.980,0.3544,0.00000,0.00000,0.0000,4.458 +F,15,0.0849,304.980,0.4962,0.00000,0.00000,0.0000,6.739 +F,15,0.1016,304.980,0.6162,0.00000,0.00000,0.0000,8.915 +F,15,0.1182,304.980,0.7138,0.00120,-0.00200,-0.0600,10.811 +F,15,0.1349,304.980,0.7888,0.00210,-0.00360,-0.1100,12.390 +F,15,0.1516,304.980,0.8463,0.00330,-0.00550,-0.1700,13.549 +F,15,0.1682,304.980,0.8894,0.00450,-0.00750,-0.2300,14.391 +F,15,0.1849,304.980,0.9206,0.00560,-0.00930,-0.2800,14.917 +F,15,0.2016,304.980,0.9437,0.00640,-0.01070,-0.3200,15.198 +F,15,0.2182,304.980,0.9600,0.00690,-0.01150,-0.3500,15.269 +F,15,0.2349,304.980,0.9719,0.00690,-0.01160,-0.3500,15.233 +F,15,0.2515,304.980,1.0000,0.00660,-0.01100,-0.3300,15.093 +F,15,0.2682,304.980,1.0000,0.00580,-0.00970,-0.2900,14.917 +F,15,0.2848,304.980,1.0000,0.00480,-0.00800,-0.2400,14.742 +F,15,0.3016,304.980,1.0000,0.00350,-0.00590,-0.1800,14.567 +F,15,0.3185,304.980,1.0000,0.00220,-0.00370,-0.1100,14.426 +F,15,0.3352,304.980,1.0000,0.00090,-0.00150,-0.0500,14.286 +F,15,0.3519,304.670,1.0000,0.00000,0.00000,0.0000,14.180 +F,15,0.3686,303.760,1.0000,-0.00140,0.00230,0.0700,14.180 +F,15,0.3851,302.230,1.0000,-0.00230,0.00390,0.1200,14.215 +F,15,0.4019,300.190,1.0000,-0.00300,0.00500,0.1500,14.251 +F,15,0.4184,297.770,1.0000,-0.00310,0.00520,0.1800,14.286 +F,15,0.4352,295.080,1.0000,-0.00240,0.00410,0.2200,14.321 +F,15,0.4518,292.130,1.0000,-0.00070,0.00120,0.3000,14.356 +F,15,0.4684,288.990,1.0000,0.00200,-0.00330,0.4200,14.391 +F,15,0.4851,285.700,1.0000,0.00560,-0.00930,0.5900,14.426 +F,15,0.5018,282.310,1.0000,0.00990,-0.01650,0.7800,14.461 +F,15,0.5188,278.860,1.0000,0.01460,-0.02440,1.0000,14.496 +F,15,0.5352,275.280,1.0000,0.01950,-0.03250,1.2200,14.531 +F,15,0.5519,271.570,1.0000,0.02410,-0.04020,1.4300,14.602 +F,15,0.5686,267.870,1.0000,0.02840,-0.04730,1.6300,14.602 +F,15,0.5852,264.180,1.0000,0.03210,-0.05340,1.8000,14.672 +F,15,0.6018,260.450,1.0000,0.03510,-0.05850,1.9400,14.707 +F,15,0.6185,256.650,1.0000,0.03730,-0.06220,2.0400,14.742 +F,15,0.6352,252.790,1.0000,0.03890,-0.06480,2.1100,14.777 +F,15,0.6519,248.880,1.0000,0.03970,-0.06620,2.1400,14.812 +F,15,0.6685,244.930,1.0000,0.03990,-0.06650,2.1400,14.847 +F,15,0.6851,240.960,1.0000,0.03960,-0.06600,2.1100,14.882 +F,15,0.7022,236.970,1.0000,0.03890,-0.06480,2.0600,14.917 +F,15,0.7185,232.970,1.0000,0.03780,-0.06300,1.9900,14.953 +F,15,0.7352,228.870,1.0000,0.03650,-0.06090,1.9200,15.023 +F,15,0.7519,224.670,1.0000,0.03510,-0.05840,1.8300,15.058 +F,15,0.7685,220.460,1.0000,0.03350,-0.05580,1.7500,15.093 +F,15,0.7854,216.290,1.0000,0.03180,-0.05300,1.6500,15.128 +F,15,0.8020,212.160,1.0000,0.03020,-0.05030,1.5600,15.163 +F,15,0.8186,208.070,1.0000,0.02860,-0.04760,1.4800,15.233 +F,15,0.8352,204.020,1.0000,0.02700,-0.04500,1.3900,15.269 +F,15,0.8520,199.990,1.0000,0.02540,-0.04230,1.3100,15.304 +F,15,0.8686,195.970,1.0000,0.02380,-0.03960,1.2200,15.339 +F,15,0.8853,191.960,1.0000,0.02210,-0.03680,1.1300,15.374 +F,15,0.9020,187.960,1.0000,0.02030,-0.03390,1.0400,15.409 +F,15,0.9186,183.960,1.0000,0.01850,-0.03090,0.9500,15.444 +F,15,0.9353,179.960,1.0000,0.01670,-0.02790,0.8500,15.479 +F,15,0.9519,175.960,1.0000,0.01490,-0.02490,0.7600,15.549 +F,15,0.9691,172.060,1.0000,0.01320,-0.02200,0.6700,15.549 +F,15,0.9853,168.270,1.0000,0.01150,-0.01910,0.5800,15.619 +F,15,1.0019,164.470,1.0000,0.00990,-0.01650,0.5000,15.655 +F,15,1.0186,160.650,1.0000,0.00840,-0.01400,0.4200,15.690 +F,15,1.0353,156.880,1.0000,0.00690,-0.01150,0.3500,15.725 +F,15,1.0519,153.160,1.0000,0.00550,-0.00920,0.2800,15.760 +F,15,1.0686,149.530,1.0000,0.00420,-0.00690,0.2100,15.795 +F,15,1.0852,145.930,1.0000,0.00290,-0.00480,0.1500,15.830 +F,15,1.1020,142.380,1.0000,0.00170,-0.00280,0.0900,15.865 +F,15,1.1186,138.950,1.0000,0.00000,0.00000,0.0000,15.900 +F,15,1.1353,135.640,1.0000,0.00000,0.00000,0.0000,15.900 +F,15,1.1519,132.440,1.0000,-0.00160,0.00260,-0.0800,15.935 +F,15,1.1686,129.320,1.0000,-0.00260,0.00430,-0.1300,15.971 +F,15,1.1852,126.350,1.0000,-0.00360,0.00600,-0.1800,16.006 +F,15,1.2020,123.520,1.0000,-0.00470,0.00780,-0.2300,16.006 +F,15,1.2183,120.810,1.0000,-0.00580,0.00960,-0.2900,16.041 +F,15,1.2350,118.390,1.0000,-0.00690,0.01150,-0.3400,16.041 +F,15,1.2517,116.430,1.0000,-0.00810,0.01360,-0.4000,16.041 +F,15,1.2683,115.010,1.0000,-0.00940,0.01570,-0.4700,16.041 +F,15,1.2850,114.070,1.0000,-0.01080,0.01810,-0.5400,16.041 +F,15,1.3016,113.490,1.0000,-0.01250,0.02080,-0.6200,16.041 +F,15,1.3184,113.160,1.0000,-0.01460,0.02430,-0.7200,16.041 +F,15,1.3351,112.990,1.0000,-0.01710,0.02850,-0.8400,16.041 +F,15,1.3517,112.980,1.0000,-0.02010,0.03360,-0.9900,16.041 +F,15,1.3684,112.980,1.0000,-0.02360,0.03930,-1.1500,16.041 +F,15,1.3850,112.980,1.0000,-0.02720,0.04540,-1.3300,16.041 +F,15,1.4017,112.980,1.0000,-0.03090,0.05150,-1.5000,16.041 +F,15,1.4184,112.980,1.0000,-0.03440,0.05730,-1.6600,16.041 +F,15,1.4350,112.980,1.0000,-0.03740,0.06230,-1.8000,16.041 +F,15,1.4516,112.980,1.0000,-0.03980,0.06640,-1.9100,16.041 +F,15,1.4683,112.980,1.0000,-0.04150,0.06920,-1.9900,16.041 +F,15,1.4850,112.980,1.0000,-0.04240,0.07070,-2.0300,16.041 +F,15,1.5017,112.980,1.0000,-0.04260,0.07100,-2.0400,16.041 +F,15,1.5183,112.980,1.0000,-0.04210,0.07020,-2.0200,16.041 +F,15,1.5350,112.980,1.0000,-0.04100,0.06840,-1.9700,16.041 +F,15,1.5517,112.980,1.0000,-0.03950,0.06580,-1.9000,16.041 +F,15,1.5683,112.980,1.0000,-0.03760,0.06260,-1.8100,16.041 +F,15,1.5850,112.980,1.0000,-0.03540,0.05900,-1.7100,16.041 +F,15,1.6017,112.980,1.0000,-0.03310,0.05520,-1.6000,16.041 +F,15,1.6225,112.980,1.0000,-0.03070,0.05120,-1.4900,16.041 +F,15,1.6351,112.840,1.0000,-0.02830,0.04720,-1.3600,16.041 +F,15,1.6518,112.840,0.9331,-0.02570,0.04280,-1.2000,15.444 +F,15,1.6684,112.840,0.7950,-0.02290,0.03810,-1.0400,13.935 +F,15,1.6851,112.840,0.6425,-0.02000,0.03320,-0.8700,11.864 +F,15,1.7017,112.840,0.5006,-0.01690,0.02810,-0.7200,9.582 +F,15,1.7184,112.840,0.3813,-0.01370,0.02280,-0.5900,7.266 +F,15,1.7351,112.840,0.2844,-0.01040,0.01750,-0.4900,5.125 +F,15,1.7517,112.840,0.2094,-0.00700,0.01210,-0.4200,3.264 +F,15,1.7685,112.840,0.1525,-0.00370,0.00690,-0.3800,1.720 +F,15,1.7851,112.840,0.1100,-0.00060,0.00200,-0.3600,0.491 +F,15,1.8017,112.840,0.0787,0.00220,-0.00230,-0.3500,-0.386 +F,15,1.8184,112.840,0.0556,0.00450,-0.00580,-0.3400,-0.983 +F,15,1.8351,112.840,0.0394,0.00610,-0.00840,-0.3300,-1.334 +F,15,1.8517,112.840,0.0281,0.00710,-0.00990,-0.3100,-1.509 +F,15,1.8683,112.840,0.0000,0.00740,-0.01050,-0.2800,-1.509 +F,15,1.8851,112.840,0.0000,0.00710,-0.01020,-0.2400,-1.404 +F,15,1.9017,112.840,0.0000,0.00630,-0.00910,-0.1800,-1.228 +F,15,1.9184,112.840,0.0000,0.00510,-0.00750,-0.1200,-1.018 +F,15,1.9350,112.840,0.0000,0.00360,-0.00540,-0.0500,-0.807 +F,15,1.9517,112.840,0.0000,0.00200,-0.00310,0.0100,-0.597 +F,15,1.9683,112.840,0.0000,0.00000,0.00000,0.0000,-0.386 +F,15,1.9850,112.840,0.0000,-0.00110,0.00130,0.1300,0.000 +F,15,2.0017,112.840,0.0000,-0.00250,0.00320,0.1800,0.000 +F,15,2.0183,112.840,0.0000,-0.00360,0.00490,0.2200,0.000 +F,15,2.0350,112.840,0.0000,-0.00450,0.00620,0.2500,0.000 +F,15,2.0516,112.840,0.0000,-0.00520,0.00720,0.2600,0.000 +F,15,2.0683,112.840,0.0000,-0.00560,0.00770,0.2700,0.000 +F,15,2.0849,112.840,0.0000,-0.00570,0.00800,0.2700,0.000 +F,15,2.1016,112.840,0.0000,-0.00570,0.00790,0.2600,0.000 +F,15,2.1183,112.840,0.0000,-0.00550,0.00760,0.2500,0.000 +F,15,2.1349,112.840,0.0000,-0.00510,0.00720,0.2300,0.000 +F,15,2.1516,112.840,0.0000,-0.00470,0.00670,0.2100,0.000 +F,15,2.1683,112.840,0.0000,-0.00430,0.00610,0.1900,0.000 +F,15,2.1849,112.840,0.0000,-0.00390,0.00540,0.1600,0.000 +F,15,2.2016,112.840,0.0000,-0.00340,0.00480,0.1400,0.000 +F,15,2.2182,112.840,0.0000,-0.00300,0.00420,0.1200,0.000 +F,15,2.2350,112.840,0.0000,-0.00250,0.00360,0.1000,0.000 +F,15,2.2515,112.840,0.0000,-0.00210,0.00300,0.0800,0.000 +F,15,2.2682,112.840,0.0000,-0.00170,0.00240,0.0600,0.000 +F,15,2.2849,112.840,0.0000,-0.00130,0.00190,0.0500,0.000 +F,15,2.3015,112.840,0.0000,-0.00100,0.00140,0.0400,0.000 +F,15,2.3182,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,2.3349,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,2.3516,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,2.3682,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,2.3849,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,2.4015,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,2.4182,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,2.4349,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,2.4515,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,2.4682,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,2.4849,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,2.5014,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,2.5181,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,15,2.5348,112.840,0.0000,0.00230,-0.00320,0.0900,0.000 +F,15,2.5515,112.840,0.0000,0.00650,-0.00910,0.2700,0.000 +F,15,2.5681,112.840,0.0000,0.00880,-0.01240,0.3700,0.000 +F,15,2.5848,112.840,0.0000,0.00910,-0.01290,0.4100,0.000 +F,15,2.6014,112.840,0.0000,0.00870,-0.01220,0.4400,0.000 +F,15,2.6181,112.840,0.0000,0.00800,-0.01120,0.4500,0.000 +F,15,2.6348,112.840,0.0000,0.00720,-0.01010,0.4300,0.000 +F,15,2.6515,112.840,0.0000,0.00620,-0.00880,0.4000,0.000 +F,15,2.6681,112.840,0.0000,0.00520,-0.00730,0.3700,0.000 +F,15,2.6848,112.840,0.0000,0.00390,-0.00550,0.3400,0.000 +F,15,2.7015,112.840,0.0000,0.00260,-0.00360,0.3200,0.000 +F,15,2.7181,112.840,0.0000,0.00110,-0.00160,0.3000,0.000 +F,15,2.7347,112.840,0.0000,-0.00030,0.00040,0.3000,0.000 +F,15,2.7514,112.840,0.0000,-0.00160,0.00230,0.2900,0.000 +F,15,2.7681,112.840,0.0000,-0.00280,0.00400,0.2900,0.000 +F,15,2.7848,112.840,0.0000,-0.00380,0.00540,0.2800,0.000 +F,15,2.8014,112.840,0.0000,-0.00460,0.00650,0.2800,0.000 +F,15,2.8181,112.840,0.0000,-0.00520,0.00730,0.2700,0.000 +F,15,2.8348,112.840,0.0000,-0.00550,0.00770,0.2600,0.000 +F,15,2.8514,112.840,0.0000,-0.00560,0.00790,0.2400,0.000 +F,15,2.8681,112.840,0.0000,-0.00550,0.00780,0.2300,0.000 +F,15,2.8847,112.840,0.0000,-0.00540,0.00750,0.2100,0.000 +F,15,2.9015,112.840,0.0000,-0.00500,0.00710,0.1900,0.000 +F,15,2.9177,112.840,0.0000,-0.00460,0.00650,0.1600,0.000 +F,15,2.9343,112.840,0.0000,-0.00420,0.00590,0.1400,0.000 +F,15,2.9510,112.840,0.0000,-0.00370,0.00520,0.1200,0.000 +F,15,2.9677,112.840,0.0000,-0.00320,0.00450,0.0900,0.000 +F,15,2.9844,112.840,0.0000,-0.00270,0.00380,0.0700,0.000 +F,15,3.0025,112.840,0.0000,-0.00220,0.00310,0.0600,0.000 +F,15,3.0193,112.840,0.0000,-0.00180,0.00250,0.0400,0.000 +F,15,3.0343,112.840,0.0000,-0.00140,0.00200,0.0300,0.000 +F,15,3.0509,112.840,0.0000,-0.00110,0.00160,0.0200,0.000 +F,15,3.0676,112.840,0.0000,-0.00080,0.00120,0.0100,0.000 +F,15,3.0843,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +E,16,dragC,112.840,112.840,47.410,304.980,0.6002,0.3165,240.930 +T,16,0.0000,112.880 +T,16,0.3165,118.210 +T,16,0.3331,124.550 +T,16,0.3498,131.550 +T,16,0.3665,138.880 +T,16,0.3831,146.880 +T,16,0.3998,154.550 +T,16,0.4165,162.550 +T,16,0.4331,170.880 +T,16,0.4498,178.880 +T,16,0.4665,187.210 +T,16,0.4831,195.210 +T,16,0.4998,202.880 +T,16,0.5164,210.550 +T,16,0.5331,217.880 +T,16,0.5665,231.210 +T,16,0.5831,236.550 +T,16,0.5998,240.880 +T,16,0.6002,240.880 +F,16,0.0058,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,16,0.0181,112.840,0.0000,0.00000,0.00000,0.0000,0.000 +F,16,0.0347,112.840,0.0537,0.00000,0.00000,0.0000,0.562 +F,16,0.0514,112.840,0.1875,0.00000,0.00000,0.0000,2.106 +F,16,0.0681,112.840,0.3406,0.00000,0.00000,0.0000,4.247 +F,16,0.0848,112.840,0.4838,0.00000,0.00000,0.0000,6.529 +F,16,0.1013,112.840,0.6063,0.00070,-0.00120,0.0100,8.740 +F,16,0.1181,112.840,0.7056,0.00100,-0.00160,0.0300,10.670 +F,16,0.1347,112.840,0.7831,0.00140,-0.00230,0.0500,12.250 +F,16,0.1514,112.840,0.8419,0.00190,-0.00320,0.0800,13.478 +F,16,0.1681,112.840,0.8856,0.00240,-0.00410,0.1100,14.321 +F,16,0.1848,112.840,0.9181,0.00290,-0.00490,0.1300,14.882 +F,16,0.2014,112.840,0.9419,0.00330,-0.00550,0.1500,15.163 +F,16,0.2180,112.840,0.9588,0.00350,-0.00590,0.1700,15.269 +F,16,0.2348,112.840,0.9713,0.00350,-0.00590,0.1700,15.233 +F,16,0.2514,112.840,1.0000,0.00330,-0.00560,0.1600,15.128 +F,16,0.2680,112.840,1.0000,0.00300,-0.00500,0.1400,14.953 +F,16,0.2847,112.840,1.0000,0.00250,-0.00410,0.1200,14.777 +F,16,0.3013,112.840,1.0000,0.00180,-0.00310,0.0900,14.602 +F,16,0.3185,112.840,1.0000,0.00120,-0.00200,0.0600,14.426 +F,16,0.3351,112.840,1.0000,0.00000,0.00000,0.0000,14.286 +F,16,0.3518,113.350,1.0000,0.00000,0.00000,0.0000,14.180 +F,16,0.3685,114.960,1.0000,0.00000,0.00000,0.0000,14.251 +F,16,0.3851,117.920,1.0000,-0.00100,0.00170,-0.0500,14.321 +F,16,0.4017,122.240,1.0000,-0.00110,0.00190,-0.0700,14.391 +F,16,0.4188,127.770,1.0000,-0.00040,0.00070,-0.1100,14.496 +F,16,0.4351,134.140,1.0000,0.00160,-0.00260,-0.2200,14.567 +F,16,0.4517,141.070,1.0000,0.00540,-0.00900,-0.4100,14.637 +F,16,0.4685,148.510,1.0000,0.01130,-0.01890,-0.7000,14.742 +F,16,0.4850,156.350,1.0000,0.01920,-0.03200,-1.1000,14.812 +F,16,0.5017,164.370,1.0000,0.02870,-0.04790,-1.5900,14.882 +F,16,0.5183,172.470,1.0000,0.03930,-0.06560,-2.1400,14.988 +F,16,0.5362,180.580,1.0000,0.05030,-0.08390,-2.7300,15.058 +F,16,0.5513,188.570,1.0000,0.06100,-0.10170,-3.3200,15.128 +F,16,0.5685,196.440,1.0000,0.07080,-0.11800,-3.8600,15.198 +F,16,0.5851,203.470,1.0000,0.07910,-0.13180,-4.3300,15.198 +F,16,0.6028,210.230,1.0000,0.08550,-0.14250,-4.6900,15.339 +F,16,0.6180,217.170,1.0000,0.08980,-0.14960,-4.9400,15.374 +F,16,0.6347,223.250,1.0000,0.09170,-0.15280,-5.0400,14.812 +F,16,0.6513,228.120,1.0000,0.09130,-0.15220,-5.0100,13.338 +F,16,0.6706,231.940,1.0000,0.08870,-0.14790,-4.8600,11.372 +F,16,0.6848,234.880,1.0000,0.08410,-0.14010,-4.5800,9.161 +F,16,0.7015,237.100,0.9331,0.07690,-0.12830,-4.1600,6.985 +F,16,0.7181,238.740,0.7950,0.06700,-0.11230,-3.6100,4.914 +F,16,0.7348,239.900,0.6425,0.05460,-0.09250,-2.9400,3.124 +F,16,0.7515,240.710,0.5006,0.04010,-0.06980,-2.1900,1.650 +F,16,0.7681,241.230,0.3813,0.02410,-0.04530,-1.4000,0.491 +F,16,0.7848,241.560,0.2844,0.00760,-0.02020,-0.6100,-0.386 +F,16,0.8014,241.730,0.2094,-0.00890,0.00440,0.1500,-0.948 +F,16,0.8182,241.790,0.1525,-0.02460,0.02760,0.8400,-1.299 +F,16,0.8348,241.790,0.1100,-0.03870,0.04850,1.4400,-1.439 +F,16,0.8514,241.740,0.0787,-0.05100,0.06670,1.9500,-1.439 +F,16,0.8681,241.660,0.0556,-0.06100,0.08170,2.3500,-1.334 +F,16,0.8848,241.570,0.0394,-0.06860,0.09330,2.6500,-1.193 +F,16,0.9015,241.480,0.0281,-0.07400,0.10150,2.8600,-0.983 +F,16,0.9181,241.390,0.0000,-0.07710,0.10650,2.9900,-0.772 +F,16,0.9348,241.310,0.0000,-0.07820,0.10870,3.0400,-0.562 +F,16,0.9514,241.230,0.0000,-0.07760,0.10830,3.0200,-0.386 +F,16,0.9681,241.170,0.0000,-0.07550,0.10580,2.9500,0.000 +F,16,0.9848,241.120,0.0000,-0.07240,0.10160,2.8400,0.000 +F,16,1.0014,240.930,0.0000,-0.06830,0.09620,2.7000,0.000 +F,16,1.0181,240.930,0.0000,-0.06370,0.08980,2.5300,0.000 +F,16,1.0348,240.930,0.0000,-0.05870,0.08300,2.3500,0.000 +F,16,1.0514,240.930,0.0000,-0.05360,0.07580,2.1500,0.000 +F,16,1.0681,240.930,0.0000,-0.04850,0.06870,1.9600,0.000 +F,16,1.0868,240.930,0.0000,-0.04340,0.06150,1.7400,0.000 +F,16,1.1014,240.930,0.0000,-0.03820,0.05420,1.5000,0.000 +F,16,1.1201,240.930,0.0000,-0.03310,0.04690,1.2600,0.000 +F,16,1.1348,240.930,0.0000,-0.02800,0.03970,1.0300,0.000 +F,16,1.1535,240.930,0.0000,-0.02310,0.03280,0.8200,0.000 +F,16,1.1681,240.930,0.0000,-0.01850,0.02630,0.6400,0.000 +F,16,1.1869,240.930,0.0000,-0.01430,0.02040,0.4900,0.000 +F,16,1.2014,240.930,0.0000,-0.01070,0.01520,0.3600,0.000 +F,16,1.2181,240.930,0.0000,-0.00750,0.01070,0.2500,0.000 +F,16,1.2347,240.930,0.0000,-0.00480,0.00690,0.1600,0.000 +F,16,1.2514,240.930,0.0000,-0.00270,0.00390,0.1000,0.000 +F,16,1.2680,240.930,0.0000,-0.00110,0.00160,0.0400,0.000 +F,16,1.2867,240.930,0.0000,0.00000,0.00000,0.0000,0.000 +F,16,1.3014,240.930,0.0000,0.00080,-0.00110,-0.0400,0.000 +F,16,1.3200,240.930,0.0000,0.00120,-0.00170,-0.0700,0.000 +F,16,1.3347,240.930,0.0000,0.00140,-0.00190,-0.0900,0.000 +F,16,1.3532,240.930,0.0000,0.00130,-0.00190,-0.1000,0.000 +F,16,1.3679,240.930,0.0000,0.00110,-0.00160,-0.1200,0.000 +F,16,1.3866,240.930,0.0000,0.00330,-0.00470,-0.0200,0.000 +F,16,1.4012,240.930,0.0000,0.00720,-0.01010,0.1500,0.000 +F,16,1.4199,240.930,0.0000,0.00910,-0.01280,0.2500,0.000 +F,16,1.4345,240.930,0.0000,0.00900,-0.01270,0.3000,0.000 +F,16,1.4531,240.930,0.0000,0.00800,-0.01130,0.3500,0.000 +F,16,1.4697,240.930,0.0000,0.00680,-0.00960,0.3800,0.000 +F,16,1.4845,240.930,0.0000,0.00560,-0.00800,0.3800,0.000 +F,16,1.5032,240.930,0.0000,0.00450,-0.00630,0.3700,0.000 +F,16,1.5199,240.930,0.0000,0.00330,-0.00470,0.3500,0.000 +F,16,1.5345,240.930,0.0000,0.00210,-0.00290,0.3400,0.000 +F,16,1.5532,240.930,0.0000,0.00080,-0.00120,0.3200,0.000 +F,16,1.5698,240.930,0.0000,-0.00050,0.00060,0.3100,0.000 +F,16,1.5845,240.930,0.0000,-0.00170,0.00240,0.3100,0.000 +F,16,1.6031,240.930,0.0000,-0.00280,0.00400,0.3000,0.000 +F,16,1.6178,240.930,0.0000,-0.00380,0.00530,0.3000,0.000 +F,16,1.6364,240.930,0.0000,-0.00460,0.00650,0.2900,0.000 +F,16,1.6531,240.930,0.0000,-0.00520,0.00730,0.2900,0.000 +F,16,1.6699,240.930,0.0000,-0.00560,0.00790,0.2800,0.000 +F,16,1.6864,240.930,0.0000,-0.00580,0.00820,0.2700,0.000 +F,16,1.7032,240.930,0.0000,-0.00580,0.00820,0.2500,0.000 +F,16,1.7198,240.930,0.0000,-0.00570,0.00810,0.2300,0.000 +F,16,1.7365,240.930,0.0000,-0.00550,0.00770,0.2200,0.000 +F,16,1.7531,240.930,0.0000,-0.00510,0.00720,0.1900,0.000 +F,16,1.7697,240.930,0.0000,-0.00470,0.00660,0.1700,0.000 +F,16,1.7864,240.930,0.0000,-0.00420,0.00600,0.1400,0.000 +F,16,1.8031,240.930,0.0000,-0.00370,0.00520,0.1200,0.000 +F,16,1.8197,240.930,0.0000,-0.00320,0.00450,0.0900,0.000 +F,16,1.8364,240.930,0.0000,-0.00270,0.00370,0.0700,0.000 +F,16,1.8531,240.930,0.0000,-0.00220,0.00300,0.0600,0.000 +F,16,1.8697,240.930,0.0000,-0.00170,0.00240,0.0400,0.000 +F,16,1.8863,240.930,0.0000,-0.00130,0.00180,0.0300,0.000 +F,16,1.9011,240.930,0.0000,-0.00090,0.00130,0.0200,0.000 +F,16,1.9196,240.930,0.0000,0.00000,0.00000,0.0000,0.000 +F,16,1.9690,240.930,0.0000,0.00000,0.00000,0.0000,0.000 +E,17,dragC,240.930,240.930,47.410,304.980,1.6831,0.2998,176.880 +T,17,0.0000,240.880 +T,17,0.2998,239.550 +T,17,0.3332,238.550 +T,17,0.3498,235.880 +T,17,0.3831,232.880 +T,17,0.4164,229.880 +T,17,0.4498,226.550 +T,17,0.4832,224.880 +T,17,0.4998,221.550 +T,17,0.5331,218.210 +T,17,0.5498,216.550 +T,17,0.5665,214.550 +T,17,0.5831,212.880 +T,17,0.5998,211.210 +T,17,0.6164,209.550 +T,17,0.6498,207.550 +T,17,0.6664,204.210 +T,17,0.6998,200.880 +T,17,0.7165,198.880 +T,17,0.7331,197.210 +T,17,0.7665,195.550 +T,17,0.7832,193.880 +T,17,0.7998,192.550 +T,17,0.8165,190.880 +T,17,0.8331,187.550 +T,17,0.8665,184.880 +T,17,0.8831,183.210 +T,17,0.9165,180.880 +T,17,0.9331,179.550 +T,17,0.9498,178.550 +T,17,0.9831,176.880 +T,17,1.6831,176.880 +F,17,0.0058,240.930,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,0.0181,240.930,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,0.0346,240.930,0.0544,0.00000,0.00000,0.0000,0.562 +F,17,0.0514,240.930,0.1881,0.00000,0.00000,0.0000,2.141 +F,17,0.0681,240.930,0.3406,0.00000,0.00000,0.0000,4.247 +F,17,0.0847,240.930,0.4844,0.00060,-0.00100,-0.0000,6.529 +F,17,0.1014,240.930,0.6069,0.00080,-0.00130,-0.0100,8.740 +F,17,0.1182,240.930,0.7056,0.00100,-0.00170,-0.0300,10.670 +F,17,0.1347,240.930,0.7831,0.00150,-0.00240,-0.0500,12.250 +F,17,0.1514,240.930,0.8419,0.00200,-0.00330,-0.0800,13.478 +F,17,0.1680,240.930,0.8856,0.00250,-0.00420,-0.1100,14.321 +F,17,0.1848,240.930,0.9181,0.00310,-0.00510,-0.1400,14.882 +F,17,0.2014,240.930,0.9419,0.00350,-0.00580,-0.1600,15.163 +F,17,0.2180,240.930,0.9588,0.00370,-0.00610,-0.1700,15.269 +F,17,0.2347,240.930,0.9713,0.00370,-0.00610,-0.1800,15.233 +F,17,0.2514,240.930,1.0000,0.00350,-0.00580,-0.1700,15.128 +F,17,0.2681,240.930,1.0000,0.00310,-0.00520,-0.1500,14.953 +F,17,0.2846,240.930,1.0000,0.00260,-0.00430,-0.1200,14.777 +F,17,0.3018,240.930,1.0000,0.00190,-0.00320,-0.0900,14.602 +F,17,0.3180,240.930,1.0000,0.00120,-0.00210,-0.0600,14.426 +F,17,0.3351,240.730,1.0000,0.00000,0.00000,0.0000,14.286 +F,17,0.3517,240.320,1.0000,0.00000,0.00000,0.0000,14.145 +F,17,0.3681,239.810,1.0000,-0.00060,0.00110,0.0300,14.180 +F,17,0.3851,239.060,1.0000,-0.00110,0.00190,0.0500,14.180 +F,17,0.4013,238.200,1.0000,-0.00130,0.00220,0.0700,14.180 +F,17,0.4184,237.140,1.0000,-0.00120,0.00200,0.0900,14.215 +F,17,0.4347,235.950,1.0000,-0.00070,0.00120,0.1200,14.215 +F,17,0.4517,234.630,1.0000,0.00020,-0.00040,0.1600,14.251 +F,17,0.4679,233.250,1.0000,0.00150,-0.00260,0.2200,14.251 +F,17,0.4852,231.690,1.0000,0.00320,-0.00530,0.2900,14.286 +F,17,0.5018,230.020,1.0000,0.00500,-0.00840,0.3700,14.286 +F,17,0.5179,228.510,1.0000,0.00710,-0.01180,0.4600,14.286 +F,17,0.5351,226.850,1.0000,0.00910,-0.01520,0.5600,14.356 +F,17,0.5517,225.110,1.0000,0.01110,-0.01850,0.6500,14.356 +F,17,0.5685,223.350,1.0000,0.01290,-0.02160,0.7300,14.356 +F,17,0.5850,221.470,1.0000,0.01450,-0.02420,0.8000,14.391 +F,17,0.6017,219.500,1.0000,0.01590,-0.02650,0.8600,14.426 +F,17,0.6183,217.610,1.0000,0.01690,-0.02820,0.9100,14.426 +F,17,0.6347,215.790,1.0000,0.01780,-0.02960,0.9400,14.426 +F,17,0.6517,213.980,1.0000,0.01830,-0.03060,0.9600,14.461 +F,17,0.6685,212.340,1.0000,0.01870,-0.03110,0.9800,14.461 +F,17,0.6847,210.860,1.0000,0.01880,-0.03130,0.9800,14.496 +F,17,0.7017,209.250,1.0000,0.01860,-0.03100,0.9600,14.496 +F,17,0.7182,207.640,1.0000,0.01810,-0.03020,0.9400,14.496 +F,17,0.7352,206.020,1.0000,0.01730,-0.02890,0.8900,14.531 +F,17,0.7513,204.250,1.0000,0.01620,-0.02710,0.8300,14.567 +F,17,0.7684,202.370,1.0000,0.01500,-0.02500,0.7700,14.567 +F,17,0.7850,200.630,1.0000,0.01360,-0.02270,0.7000,14.567 +F,17,0.8017,199.060,1.0000,0.01230,-0.02050,0.6300,14.602 +F,17,0.8183,197.580,1.0000,0.01110,-0.01840,0.5600,14.602 +F,17,0.8350,196.120,1.0000,0.01000,-0.01660,0.5000,14.637 +F,17,0.8514,194.680,1.0000,0.00890,-0.01480,0.4500,14.637 +F,17,0.8683,193.010,1.0000,0.00790,-0.01310,0.4000,14.672 +F,17,0.8849,191.250,1.0000,0.00680,-0.01130,0.3400,14.672 +F,17,0.9012,189.550,1.0000,0.00570,-0.00960,0.2900,14.707 +F,17,0.9183,187.830,1.0000,0.00470,-0.00790,0.2400,14.707 +F,17,0.9349,186.260,1.0000,0.00390,-0.00640,0.1900,14.707 +F,17,0.9517,184.820,1.0000,0.00310,-0.00520,0.1600,14.742 +F,17,0.9680,183.330,1.0000,0.00260,-0.00430,0.1300,14.742 +F,17,0.9851,181.840,1.0000,0.00210,-0.00350,0.1000,14.777 +F,17,1.0012,180.530,1.0000,0.00160,-0.00270,0.0800,14.777 +F,17,1.0181,179.430,1.0000,0.00120,-0.00190,0.0600,14.777 +F,17,1.0346,178.540,1.0000,0.00060,-0.00110,0.0300,14.777 +F,17,1.0513,177.880,1.0000,0.00000,0.00000,0.0000,14.777 +F,17,1.0701,177.450,1.0000,-0.00070,0.00110,-0.0300,14.777 +F,17,1.0870,177.180,1.0000,-0.00170,0.00280,-0.0800,14.777 +F,17,1.1015,177.020,1.0000,-0.00290,0.00480,-0.1400,14.777 +F,17,1.1200,176.930,1.0000,-0.00440,0.00740,-0.2200,14.777 +F,17,1.1346,176.930,1.0000,-0.00620,0.01030,-0.3100,14.777 +F,17,1.1514,176.930,1.0000,-0.00820,0.01370,-0.4100,14.777 +F,17,1.1703,176.930,1.0000,-0.01030,0.01720,-0.5100,14.777 +F,17,1.1847,176.930,1.0000,-0.01240,0.02060,-0.6100,14.777 +F,17,1.2014,176.930,1.0000,-0.01430,0.02390,-0.7100,14.777 +F,17,1.2202,176.930,1.0000,-0.01600,0.02670,-0.7900,14.777 +F,17,1.2346,176.930,1.0000,-0.01740,0.02900,-0.8600,14.777 +F,17,1.2527,176.930,1.0000,-0.01840,0.03070,-0.9000,14.777 +F,17,1.2705,176.930,1.0000,-0.01900,0.03170,-0.9300,14.777 +F,17,1.2847,176.930,1.0000,-0.01930,0.03210,-0.9400,14.777 +F,17,1.3014,176.930,1.0000,-0.01920,0.03190,-0.9400,14.777 +F,17,1.3180,176.930,1.0000,-0.01880,0.03130,-0.9200,14.777 +F,17,1.3368,176.930,1.0000,-0.01810,0.03020,-0.8900,14.777 +F,17,1.3514,176.930,1.0000,-0.01730,0.02890,-0.8500,14.777 +F,17,1.3680,176.930,1.0000,-0.01640,0.02730,-0.8100,14.777 +F,17,1.3847,176.930,1.0000,-0.01540,0.02570,-0.7600,14.777 +F,17,1.4035,176.930,1.0000,-0.01430,0.02390,-0.7100,14.777 +F,17,1.4181,176.930,1.0000,-0.01330,0.02210,-0.6500,14.777 +F,17,1.4369,176.930,1.0000,-0.01220,0.02030,-0.6000,14.777 +F,17,1.4513,176.930,1.0000,-0.01110,0.01840,-0.5300,14.777 +F,17,1.4679,176.930,1.0000,-0.00990,0.01660,-0.4700,14.777 +F,17,1.4849,176.930,1.0000,-0.00880,0.01470,-0.4000,14.777 +F,17,1.5035,176.930,1.0000,-0.00770,0.01280,-0.3500,14.777 +F,17,1.5180,176.930,1.0000,-0.00660,0.01100,-0.3000,14.777 +F,17,1.5346,176.930,1.0000,-0.00560,0.00930,-0.2500,14.777 +F,17,1.5513,176.930,1.0000,-0.00470,0.00780,-0.2100,14.777 +F,17,1.5679,176.930,1.0000,-0.00390,0.00640,-0.1800,14.777 +F,17,1.5846,176.930,1.0000,-0.00310,0.00520,-0.1500,14.777 +F,17,1.6033,176.930,1.0000,-0.00250,0.00420,-0.1300,14.777 +F,17,1.6180,176.930,1.0000,-0.00200,0.00340,-0.1100,14.777 +F,17,1.6347,176.930,1.0000,-0.00160,0.00270,-0.0900,14.777 +F,17,1.6513,176.930,1.0000,-0.00130,0.00210,-0.0700,14.777 +F,17,1.6680,176.930,1.0000,-0.00100,0.00170,-0.0600,14.777 +F,17,1.6884,176.930,1.0000,-0.00080,0.00140,-0.0400,14.777 +F,17,1.7016,176.880,1.0000,-0.00060,0.00120,-0.0300,14.777 +F,17,1.7181,176.880,0.9331,0.00000,0.00000,0.0000,14.215 +F,17,1.7348,176.880,0.7950,-0.00080,0.00120,-0.0100,12.811 +F,17,1.7515,176.880,0.6425,0.00000,0.00000,0.0000,10.916 +F,17,1.7681,176.880,0.5006,0.00000,0.00000,0.0000,8.810 +F,17,1.7848,176.880,0.3813,0.00000,0.00000,0.0000,6.704 +F,17,1.8014,176.880,0.2844,0.00000,0.00000,0.0000,4.739 +F,17,1.8182,176.880,0.2094,0.00100,-0.00140,0.0100,3.019 +F,17,1.8348,176.880,0.1525,0.00120,-0.00160,0.0100,1.579 +F,17,1.8515,176.880,0.1100,0.00120,-0.00170,0.0100,0.456 +F,17,1.8681,176.880,0.0787,0.00100,-0.00140,0.0000,-0.351 +F,17,1.8847,176.880,0.0556,0.00000,0.00000,0.0000,-0.913 +F,17,1.9014,176.880,0.0394,0.00000,0.00000,0.0000,-1.228 +F,17,1.9181,176.880,0.0281,0.00000,0.00000,0.0000,-1.369 +F,17,1.9348,176.880,0.0000,0.00000,0.00000,0.0000,-1.404 +F,17,1.9515,176.880,0.0000,-0.00070,0.00100,-0.0100,-1.299 +F,17,1.9681,176.880,0.0000,-0.00090,0.00130,-0.0100,-1.123 +F,17,1.9848,176.880,0.0000,-0.00110,0.00160,-0.0100,-0.948 +F,17,2.0014,176.880,0.0000,-0.00120,0.00170,-0.0000,-0.737 +F,17,2.0181,176.880,0.0000,-0.00110,0.00160,0.0100,-0.526 +F,17,2.0348,176.880,0.0000,-0.00100,0.00150,0.0100,-0.351 +F,17,2.0514,176.880,0.0000,-0.00090,0.00130,0.0200,0.000 +F,17,2.0681,176.880,0.0000,-0.00080,0.00110,0.0200,0.000 +F,17,2.0847,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.1014,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.1181,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.1347,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.1514,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.1680,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.1847,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.2014,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.2180,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.2346,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.2513,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.2680,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.2869,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.3013,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.3201,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.3346,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.3533,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.3679,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.3867,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.4013,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.4200,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.4347,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.4534,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.4680,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.4846,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.5033,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.5179,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.5368,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.5529,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.5679,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.5866,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +F,17,2.6012,176.880,0.0000,0.00250,-0.00350,0.1000,0.000 +F,17,2.6197,176.880,0.0000,0.00670,-0.00940,0.2800,0.000 +F,17,2.6346,176.880,0.0000,0.00900,-0.01270,0.3800,0.000 +F,17,2.6511,176.880,0.0000,0.00940,-0.01320,0.4300,0.000 +F,17,2.6678,176.880,0.0000,0.00890,-0.01250,0.4500,0.000 +F,17,2.6845,176.880,0.0000,0.00820,-0.01150,0.4600,0.000 +F,17,2.7012,176.880,0.0000,0.00740,-0.01040,0.4400,0.000 +F,17,2.7179,176.880,0.0000,0.00640,-0.00910,0.4000,0.000 +F,17,2.7346,176.880,0.0000,0.00530,-0.00750,0.3700,0.000 +F,17,2.7512,176.880,0.0000,0.00410,-0.00580,0.3400,0.000 +F,17,2.7679,176.880,0.0000,0.00270,-0.00380,0.3200,0.000 +F,17,2.7846,176.880,0.0000,0.00120,-0.00170,0.3000,0.000 +F,17,2.8012,176.880,0.0000,-0.00020,0.00030,0.3000,0.000 +F,17,2.8178,176.880,0.0000,-0.00160,0.00220,0.2900,0.000 +F,17,2.8345,176.880,0.0000,-0.00280,0.00390,0.2900,0.000 +F,17,2.8512,176.880,0.0000,-0.00380,0.00540,0.2800,0.000 +F,17,2.8678,176.880,0.0000,-0.00460,0.00650,0.2800,0.000 +F,17,2.8845,176.880,0.0000,-0.00520,0.00730,0.2700,0.000 +F,17,2.9012,176.880,0.0000,-0.00550,0.00770,0.2600,0.000 +F,17,2.9178,176.880,0.0000,-0.00560,0.00790,0.2400,0.000 +F,17,2.9345,176.880,0.0000,-0.00560,0.00780,0.2300,0.000 +F,17,2.9512,176.880,0.0000,-0.00540,0.00760,0.2100,0.000 +F,17,2.9678,176.880,0.0000,-0.00510,0.00710,0.1900,0.000 +F,17,2.9842,176.880,0.0000,-0.00470,0.00660,0.1600,0.000 +F,17,3.0008,176.880,0.0000,-0.00420,0.00590,0.1400,0.000 +F,17,3.0175,176.880,0.0000,-0.00370,0.00520,0.1200,0.000 +F,17,3.0342,176.880,0.0000,-0.00320,0.00450,0.0900,0.000 +F,17,3.0509,176.880,0.0000,-0.00270,0.00380,0.0700,0.000 +F,17,3.0675,176.880,0.0000,-0.00220,0.00310,0.0600,0.000 +F,17,3.0842,176.880,0.0000,-0.00180,0.00250,0.0400,0.000 +F,17,3.1009,176.880,0.0000,-0.00140,0.00200,0.0300,0.000 +F,17,3.1190,176.880,0.0000,-0.00110,0.00160,0.0200,0.000 +F,17,3.1356,176.880,0.0000,-0.00080,0.00120,0.0100,0.000 +F,17,3.1523,176.880,0.0000,0.00000,0.00000,0.0000,0.000 +E,18,dragD,51.000,51.000,51.000,223.000,1.4156,0.3153,223.000 +T,18,0.0000,50.960 +T,18,0.3153,53.620 +T,18,0.3320,56.290 +T,18,0.3486,59.290 +T,18,0.3653,62.620 +T,18,0.3820,65.620 +T,18,0.3986,69.290 +T,18,0.4153,72.620 +T,18,0.4320,76.290 +T,18,0.4486,79.960 +T,18,0.4653,83.620 +T,18,0.4820,87.620 +T,18,0.4986,91.290 +T,18,0.5153,95.290 +T,18,0.5320,99.290 +T,18,0.5487,103.290 +T,18,0.5653,107.290 +T,18,0.5820,111.290 +T,18,0.5987,115.290 +T,18,0.6153,119.290 +T,18,0.6320,123.620 +T,18,0.6486,127.620 +T,18,0.6654,131.620 +T,18,0.6820,135.960 +T,18,0.6986,139.960 +T,18,0.7153,143.960 +T,18,0.7320,148.290 +T,18,0.7486,152.290 +T,18,0.7653,156.290 +T,18,0.7820,160.290 +T,18,0.7986,164.290 +T,18,0.8153,168.290 +T,18,0.8320,172.290 +T,18,0.8486,175.960 +T,18,0.8653,179.960 +T,18,0.8820,183.620 +T,18,0.8986,187.290 +T,18,0.9153,190.960 +T,18,0.9320,194.620 +T,18,0.9487,198.290 +T,18,0.9653,201.620 +T,18,0.9820,204.960 +T,18,0.9987,207.960 +T,18,1.0153,210.960 +T,18,1.0320,213.960 +T,18,1.0486,216.620 +T,18,1.0653,218.960 +T,18,1.0820,221.290 +T,18,1.0987,222.960 +T,18,1.4156,222.960 +F,18,0.0334,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,18,0.0667,51.000,0.2200,0.00000,0.00000,0.0000,2.548 +F,18,0.0834,51.000,0.3725,0.00000,0.00000,0.0000,4.713 +F,18,0.1000,51.000,0.5119,0.00000,0.00000,0.0000,7.014 +F,18,0.1166,51.000,0.6294,0.00000,0.00000,0.0000,9.152 +F,18,0.1334,51.000,0.7237,0.00110,-0.00180,0.0600,11.042 +F,18,0.1501,51.000,0.7969,0.00190,-0.00320,0.1100,12.549 +F,18,0.1667,51.000,0.8525,0.00290,-0.00490,0.1600,13.673 +F,18,0.1834,51.000,0.8937,0.00400,-0.00660,0.2200,14.467 +F,18,0.2001,51.000,0.9238,0.00490,-0.00810,0.2700,14.960 +F,18,0.2167,51.000,0.9456,0.00550,-0.00920,0.3100,15.207 +F,18,0.2333,51.000,0.9619,0.00590,-0.00980,0.3300,15.289 +F,18,0.2500,51.000,0.9731,0.00590,-0.00980,0.3200,15.234 +F,18,0.2666,51.000,1.0000,0.00550,-0.00920,0.3000,15.097 +F,18,0.2833,51.000,1.0000,0.00480,-0.00800,0.2700,14.906 +F,18,0.3000,51.000,1.0000,0.00390,-0.00650,0.2200,14.714 +F,18,0.3172,51.000,1.0000,0.00280,-0.00470,0.1600,14.549 +F,18,0.3336,51.000,1.0000,0.00170,-0.00290,0.0900,14.385 +F,18,0.3503,51.200,1.0000,0.00060,-0.00100,0.0300,14.275 +F,18,0.3670,51.910,1.0000,0.00000,0.00000,0.0000,14.193 +F,18,0.3836,53.230,1.0000,-0.00130,0.00220,-0.0700,14.221 +F,18,0.4003,55.110,1.0000,-0.00200,0.00340,-0.1100,14.248 +F,18,0.4170,57.400,1.0000,-0.00230,0.00390,-0.1300,14.275 +F,18,0.4336,60.100,1.0000,-0.00190,0.00320,-0.1700,14.303 +F,18,0.4502,63.100,1.0000,-0.00060,0.00090,-0.2500,14.330 +F,18,0.4670,66.310,1.0000,0.00180,-0.00300,-0.3800,14.358 +F,18,0.4836,69.760,1.0000,0.00520,-0.00860,-0.5500,14.385 +F,18,0.5003,73.300,1.0000,0.00940,-0.01560,-0.7600,14.412 +F,18,0.5170,76.880,1.0000,0.01410,-0.02350,-1.0100,14.440 +F,18,0.5338,80.550,1.0000,0.01910,-0.03180,-1.2700,14.467 +F,18,0.5504,84.320,1.0000,0.02400,-0.04010,-1.5300,14.522 +F,18,0.5670,88.170,1.0000,0.02870,-0.04780,-1.7800,14.549 +F,18,0.5836,92.080,1.0000,0.03270,-0.05450,-1.9900,14.577 +F,18,0.6003,96.030,1.0000,0.03610,-0.06010,-2.1700,14.604 +F,18,0.6169,100.010,1.0000,0.03870,-0.06450,-2.3100,14.632 +F,18,0.6336,104.000,1.0000,0.04050,-0.06750,-2.4000,14.659 +F,18,0.6503,108.000,1.0000,0.04160,-0.06940,-2.4500,14.686 +F,18,0.6687,112.000,1.0000,0.04210,-0.07010,-2.4700,14.714 +F,18,0.6836,116.000,1.0000,0.04190,-0.06990,-2.4500,14.741 +F,18,0.7003,120.010,1.0000,0.04130,-0.06880,-2.4000,14.796 +F,18,0.7170,124.110,1.0000,0.04020,-0.06710,-2.3300,14.823 +F,18,0.7336,128.310,1.0000,0.03890,-0.06480,-2.2400,14.851 +F,18,0.7502,132.520,1.0000,0.03720,-0.06200,-2.1400,14.878 +F,18,0.7670,136.690,1.0000,0.03540,-0.05900,-2.0300,14.906 +F,18,0.7836,140.820,1.0000,0.03360,-0.05590,-1.9200,14.960 +F,18,0.8003,144.910,1.0000,0.03170,-0.05280,-1.8100,14.988 +F,18,0.8169,148.960,1.0000,0.02980,-0.04970,-1.6900,15.015 +F,18,0.8336,152.990,1.0000,0.02790,-0.04660,-1.5800,15.043 +F,18,0.8503,157.010,1.0000,0.02600,-0.04340,-1.4700,15.070 +F,18,0.8669,161.020,1.0000,0.02410,-0.04020,-1.3600,15.097 +F,18,0.8836,165.020,1.0000,0.02210,-0.03690,-1.2500,15.125 +F,18,0.9005,169.020,1.0000,0.02020,-0.03360,-1.1300,15.152 +F,18,0.9169,172.910,1.0000,0.01820,-0.03030,-1.0200,15.180 +F,18,0.9336,176.710,1.0000,0.01620,-0.02700,-0.9100,15.207 +F,18,0.9503,180.500,1.0000,0.01430,-0.02380,-0.8000,15.262 +F,18,0.9670,184.230,1.0000,0.01240,-0.02070,-0.6900,15.262 +F,18,0.9835,187.900,1.0000,0.01060,-0.01760,-0.5900,15.317 +F,18,1.0003,191.510,1.0000,0.00880,-0.01460,-0.4900,15.317 +F,18,1.0169,194.980,1.0000,0.00700,-0.01170,-0.3900,15.344 +F,18,1.0336,198.310,1.0000,0.00530,-0.00880,-0.2900,15.371 +F,18,1.0502,201.530,1.0000,0.00360,-0.00600,-0.2000,15.399 +F,18,1.0670,204.660,1.0000,0.00200,-0.00330,-0.1100,15.426 +F,18,1.0836,207.730,1.0000,0.00000,0.00000,0.0000,15.454 +F,18,1.1003,210.660,1.0000,-0.00130,0.00220,0.0700,15.454 +F,18,1.1166,213.470,1.0000,-0.00300,0.00500,0.1700,15.481 +F,18,1.1333,216.070,1.0000,-0.00470,0.00780,0.2600,15.481 +F,18,1.1499,218.180,1.0000,-0.00630,0.01050,0.3500,15.481 +F,18,1.1666,219.730,1.0000,-0.00800,0.01330,0.4300,15.481 +F,18,1.1832,220.770,1.0000,-0.00960,0.01600,0.5200,15.481 +F,18,1.2000,221.410,1.0000,-0.01150,0.01910,0.6200,15.481 +F,18,1.2166,221.780,1.0000,-0.01360,0.02270,0.7400,15.481 +F,18,1.2333,221.970,1.0000,-0.01630,0.02710,0.8800,15.481 +F,18,1.2499,222.000,1.0000,-0.01940,0.03230,1.0500,15.481 +F,18,1.2666,222.000,1.0000,-0.02300,0.03830,1.2300,15.481 +F,18,1.2850,222.000,1.0000,-0.02680,0.04470,1.4300,15.481 +F,18,1.3014,222.000,1.0000,-0.03070,0.05120,1.6400,15.481 +F,18,1.3181,222.000,1.0000,-0.03440,0.05740,1.8300,15.481 +F,18,1.3350,222.000,1.0000,-0.03770,0.06290,2.0000,15.481 +F,18,1.3515,222.000,1.0000,-0.04040,0.06730,2.1300,15.481 +F,18,1.3686,222.000,1.0000,-0.04230,0.07060,2.2300,15.481 +F,18,1.3849,222.000,1.0000,-0.04350,0.07250,2.2900,15.481 +F,18,1.4014,222.000,1.0000,-0.04380,0.07310,2.3100,15.481 +F,18,1.4256,222.000,1.0000,-0.04250,0.07080,2.2400,15.481 +F,18,1.4666,222.100,0.7950,-0.03870,0.06420,2.0400,13.426 +F,18,1.4833,222.200,0.6425,-0.03620,0.05970,1.9000,11.453 +F,18,1.5001,222.300,0.5006,-0.03350,0.05470,1.7200,9.234 +F,18,1.5168,222.410,0.3813,-0.03050,0.04940,1.5000,7.014 +F,18,1.5334,222.510,0.2844,-0.02700,0.04340,1.2800,4.959 +F,18,1.5516,222.600,0.2094,-0.02290,0.03670,1.0900,3.151 +F,18,1.5666,222.680,0.1525,-0.01850,0.02950,0.9400,1.644 +F,18,1.5848,222.750,0.1100,-0.01380,0.02220,0.8100,0.493 +F,18,1.5999,222.810,0.0787,-0.00920,0.01500,0.7200,-0.384 +F,18,1.6178,223.000,0.0556,-0.00500,0.00850,0.6500,-0.959 +F,18,1.6333,223.000,0.0394,-0.00140,0.00300,0.5900,-1.315 +F,18,1.6499,223.000,0.0281,0.00140,-0.00130,0.5200,-1.452 +F,18,1.6666,223.000,0.0000,0.00330,-0.00430,0.4500,-1.452 +F,18,1.6832,223.000,0.0000,0.00430,-0.00600,0.3700,-1.343 +F,18,1.7015,223.000,0.0000,0.00460,-0.00650,0.2800,-1.178 +F,18,1.7182,223.000,0.0000,0.00420,-0.00610,0.1900,-0.986 +F,18,1.7348,223.000,0.0000,0.00330,-0.00500,0.1000,-0.767 +F,18,1.7515,223.000,0.0000,0.00210,-0.00330,0.0000,-0.575 +F,18,1.7681,223.000,0.0000,0.00070,-0.00140,-0.0900,-0.384 +F,18,1.7847,223.000,0.0000,-0.00070,0.00070,-0.1600,0.000 +F,18,1.7999,223.000,0.0000,-0.00210,0.00260,-0.2300,0.000 +F,18,1.8181,223.000,0.0000,-0.00330,0.00430,-0.2800,0.000 +F,18,1.8332,223.000,0.0000,-0.00430,0.00580,-0.3200,0.000 +F,18,1.8514,223.000,0.0000,-0.00510,0.00700,-0.3500,0.000 +F,18,1.8665,223.000,0.0000,-0.00570,0.00780,-0.3600,0.000 +F,18,1.8831,223.000,0.0000,-0.00590,0.00820,-0.3600,0.000 +F,18,1.8998,223.000,0.0000,-0.00600,0.00830,-0.3500,0.000 +F,18,1.9166,223.000,0.0000,-0.00590,0.00820,-0.3300,0.000 +F,18,1.9333,223.000,0.0000,-0.00560,0.00790,-0.3100,0.000 +F,18,1.9499,223.000,0.0000,-0.00530,0.00740,-0.2900,0.000 +F,18,1.9666,223.000,0.0000,-0.00490,0.00690,-0.2600,0.000 +F,18,1.9832,223.000,0.0000,-0.00450,0.00630,-0.2300,0.000 +F,18,1.9999,223.000,0.0000,-0.00400,0.00570,-0.2000,0.000 +F,18,2.0165,223.000,0.0000,-0.00360,0.00500,-0.1700,0.000 +F,18,2.0332,223.000,0.0000,-0.00310,0.00440,-0.1400,0.000 +F,18,2.0498,223.000,0.0000,-0.00260,0.00370,-0.1100,0.000 +F,18,2.0665,223.000,0.0000,-0.00220,0.00310,-0.0900,0.000 +F,18,2.0832,223.000,0.0000,-0.00180,0.00250,-0.0700,0.000 +F,18,2.0998,223.000,0.0000,-0.00140,0.00190,-0.0500,0.000 +F,18,2.1165,223.000,0.0000,-0.00100,0.00140,-0.0400,0.000 +F,18,2.1331,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,18,2.1498,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,18,2.1664,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,18,2.1832,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,18,2.1998,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,18,2.2165,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,18,2.2331,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,18,2.2498,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,18,2.2664,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,18,2.2831,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,18,2.2997,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,18,2.3164,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,18,2.3330,223.000,0.0000,0.00420,-0.00550,0.2300,0.000 +F,18,2.3497,223.000,0.0000,0.01200,-0.01580,0.6000,0.000 +F,18,2.3664,223.000,0.0000,0.01600,-0.02080,0.7900,0.000 +F,18,2.3830,223.000,0.0000,0.01540,-0.01980,0.8600,0.000 +F,18,2.3997,223.000,0.0000,0.01340,-0.01680,0.9200,0.000 +F,18,2.4164,223.000,0.0000,0.01130,-0.01380,0.9300,0.000 +F,18,2.4330,223.000,0.0000,0.00930,-0.01120,0.8900,0.000 +F,18,2.4497,223.000,0.0000,0.00740,-0.00860,0.8300,0.000 +F,18,2.4664,223.000,0.0000,0.00520,-0.00580,0.7800,0.000 +F,18,2.4830,223.000,0.0000,0.00280,-0.00250,0.7400,0.000 +F,18,2.4997,223.000,0.0000,0.00000,0.00120,0.7200,0.000 +F,18,2.5164,223.000,0.0000,-0.00290,0.00510,0.7200,0.000 +F,18,2.5331,223.000,0.0000,-0.00580,0.00900,0.7300,0.000 +F,18,2.5497,223.000,0.0000,-0.00850,0.01270,0.7500,0.000 +F,18,2.5663,223.000,0.0000,-0.01090,0.01590,0.7700,0.000 +F,18,2.5830,223.000,0.0000,-0.01290,0.01850,0.7800,0.000 +F,18,2.5997,223.000,0.0000,-0.01430,0.02040,0.7800,0.000 +F,18,2.6164,223.000,0.0000,-0.01520,0.02160,0.7700,0.000 +F,18,2.6330,223.000,0.0000,-0.01560,0.02210,0.7500,0.000 +F,18,2.6497,223.000,0.0000,-0.01550,0.02200,0.7200,0.000 +F,18,2.6664,223.000,0.0000,-0.01510,0.02130,0.6800,0.000 +F,18,2.6831,223.000,0.0000,-0.01440,0.02030,0.6300,0.000 +F,18,2.6997,223.000,0.0000,-0.01340,0.01890,0.5800,0.000 +F,18,2.7160,223.000,0.0000,-0.01240,0.01740,0.5200,0.000 +F,18,2.7327,223.000,0.0000,-0.01120,0.01570,0.4500,0.000 +F,18,2.7494,223.000,0.0000,-0.00990,0.01380,0.3800,0.000 +F,18,2.7660,223.000,0.0000,-0.00850,0.01200,0.3100,0.000 +F,18,2.7827,223.000,0.0000,-0.00730,0.01020,0.2500,0.000 +F,18,2.8004,223.000,0.0000,-0.00610,0.00850,0.2000,0.000 +F,18,2.8173,223.000,0.0000,-0.00500,0.00690,0.1600,0.000 +F,18,2.8340,223.000,0.0000,-0.00400,0.00560,0.1200,0.000 +F,18,2.8524,223.000,0.0000,-0.00310,0.00440,0.0900,0.000 +F,18,2.8678,223.000,0.0000,-0.00240,0.00330,0.0600,0.000 +F,18,2.8838,223.000,0.0000,-0.00180,0.00250,0.0400,0.000 +F,18,2.9002,223.000,0.0000,-0.00130,0.00180,0.0200,0.000 +F,18,2.9169,223.000,0.0000,-0.00090,0.00120,0.0100,0.000 +F,18,2.9336,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,19,dragD,223.000,223.000,51.000,223.000,0.6002,0.3164,51.000 +T,19,0.0000,222.960 +T,19,0.3164,215.620 +T,19,0.3331,206.960 +T,19,0.3497,197.620 +T,19,0.3664,187.620 +T,19,0.3831,177.290 +T,19,0.3998,166.620 +T,19,0.4164,155.960 +T,19,0.4331,144.960 +T,19,0.4498,133.960 +T,19,0.4664,122.960 +T,19,0.4831,112.290 +T,19,0.4997,101.620 +T,19,0.5164,91.290 +T,19,0.5331,81.620 +T,19,0.5497,72.290 +T,19,0.5664,63.960 +T,19,0.5831,56.620 +T,19,0.5998,50.960 +T,19,0.6002,50.960 +F,19,0.0065,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,19,0.0177,223.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,19,0.0514,223.000,0.1356,0.00000,0.00000,0.0000,1.480 +F,19,0.0678,223.000,0.2862,0.00000,0.00000,0.0000,3.452 +F,19,0.0845,223.000,0.4350,0.00000,0.00000,0.0000,5.727 +F,19,0.1011,223.000,0.5656,0.00000,0.00000,0.0000,7.973 +F,19,0.1178,223.000,0.6731,0.00070,-0.00120,-0.0400,10.001 +F,19,0.1345,223.000,0.7575,0.00140,-0.00240,-0.0800,11.727 +F,19,0.1511,223.000,0.8225,0.00230,-0.00390,-0.1300,13.070 +F,19,0.1677,223.000,0.8719,0.00340,-0.00560,-0.1900,14.056 +F,19,0.1845,223.000,0.9075,0.00440,-0.00730,-0.2400,14.714 +F,19,0.2011,223.000,0.9344,0.00520,-0.00860,-0.2900,15.097 +F,19,0.2179,223.000,0.9531,0.00570,-0.00960,-0.3200,15.262 +F,19,0.2344,223.000,0.9669,0.00590,-0.00990,-0.3300,15.262 +F,19,0.2512,223.000,0.9769,0.00580,-0.00960,-0.3200,15.180 +F,19,0.2678,223.000,1.0000,0.00530,-0.00880,-0.2900,15.015 +F,19,0.2844,223.000,1.0000,0.00450,-0.00740,-0.2500,14.823 +F,19,0.3011,223.000,1.0000,0.00350,-0.00580,-0.1900,14.659 +F,19,0.3181,223.000,1.0000,0.00240,-0.00390,-0.1300,14.495 +F,19,0.3348,223.000,1.0000,0.00120,-0.00210,-0.0700,14.330 +F,19,0.3514,222.290,1.0000,0.00000,0.00000,0.0000,14.221 +F,19,0.3681,219.960,1.0000,-0.00080,0.00130,0.0400,14.275 +F,19,0.3848,215.790,1.0000,-0.00160,0.00270,0.0800,14.330 +F,19,0.4017,209.900,1.0000,-0.00200,0.00340,0.1000,14.412 +F,19,0.4180,202.580,1.0000,-0.00120,0.00200,0.1700,14.495 +F,19,0.4348,194.090,1.0000,0.00150,-0.00250,0.3300,14.604 +F,19,0.4516,184.780,1.0000,0.00680,-0.01130,0.6200,14.686 +F,19,0.4682,174.900,1.0000,0.01490,-0.02480,1.0700,14.769 +F,19,0.4848,164.600,1.0000,0.02540,-0.04240,1.6600,14.851 +F,19,0.5014,153.990,1.0000,0.03780,-0.06300,2.3600,14.933 +F,19,0.5184,143.200,1.0000,0.05110,-0.08510,3.1400,15.043 +F,19,0.5349,132.280,1.0000,0.06450,-0.10750,3.9500,15.125 +F,19,0.5515,121.410,1.0000,0.07750,-0.12900,4.7400,15.207 +F,19,0.5682,110.710,1.0000,0.08940,-0.14880,5.4900,15.262 +F,19,0.5848,100.300,1.0000,0.09980,-0.16610,6.1500,15.344 +F,19,0.6026,90.370,1.0000,0.10820,-0.18020,6.6900,15.399 +F,19,0.6177,80.970,1.0000,0.11420,-0.19020,7.0800,15.454 +F,19,0.6344,72.930,1.0000,0.11770,-0.19600,7.3000,14.878 +F,19,0.6510,66.580,1.0000,0.11840,-0.19720,7.3400,13.426 +F,19,0.6703,61.650,1.0000,0.11640,-0.19380,7.1900,11.426 +F,19,0.6845,57.900,1.0000,0.11130,-0.18550,6.8400,9.234 +F,19,0.7011,55.120,0.9331,0.10260,-0.17110,6.2600,7.014 +F,19,0.7178,53.120,0.7950,0.08970,-0.15040,5.4300,4.959 +F,19,0.7345,51.710,0.6425,0.07300,-0.12420,4.4100,3.151 +F,19,0.7512,50.770,0.5006,0.05320,-0.09410,3.2400,1.644 +F,19,0.7679,50.190,0.3813,0.03120,-0.06180,2.0100,0.493 +F,19,0.7845,49.850,0.2844,0.00820,-0.02920,0.7900,-0.384 +F,19,0.8012,49.700,0.2094,-0.01460,0.00240,-0.3600,-0.959 +F,19,0.8178,49.680,0.1525,-0.03610,0.03180,-1.4000,-1.288 +F,19,0.8346,49.740,0.1100,-0.05550,0.05810,-2.2900,-1.452 +F,19,0.8512,49.840,0.0787,-0.07200,0.08090,-3.0200,-1.452 +F,19,0.8679,49.970,0.0556,-0.08540,0.09970,-3.5900,-1.343 +F,19,0.8845,50.110,0.0394,-0.09540,0.11450,-4.0100,-1.178 +F,19,0.9012,50.250,0.0281,-0.10220,0.12540,-4.2900,-0.986 +F,19,0.9178,50.380,0.0000,-0.10590,0.13250,-4.4500,-0.767 +F,19,0.9345,50.500,0.0000,-0.10700,0.13590,-4.5000,-0.575 +F,19,0.9512,50.600,0.0000,-0.10570,0.13620,-4.4600,-0.384 +F,19,0.9678,50.690,0.0000,-0.10260,0.13370,-4.3500,0.000 +F,19,0.9845,50.760,0.0000,-0.09800,0.12910,-4.1800,0.000 +F,19,1.0019,50.820,0.0000,-0.09220,0.12280,-3.9700,0.000 +F,19,1.0177,51.000,0.0000,-0.08580,0.11530,-3.7200,0.000 +F,19,1.0360,51.000,0.0000,-0.07890,0.10700,-3.4400,0.000 +F,19,1.0512,51.000,0.0000,-0.07190,0.09830,-3.1600,0.000 +F,19,1.0678,51.000,0.0000,-0.06490,0.08940,-2.8300,0.000 +F,19,1.0859,51.000,0.0000,-0.05800,0.08040,-2.4400,0.000 +F,19,1.1011,51.000,0.0000,-0.05090,0.07110,-2.0400,0.000 +F,19,1.1191,51.000,0.0000,-0.04390,0.06160,-1.6500,0.000 +F,19,1.1345,51.000,0.0000,-0.03690,0.05210,-1.3100,0.000 +F,19,1.1525,51.000,0.0000,-0.03020,0.04290,-1.0100,0.000 +F,19,1.1692,51.000,0.0000,-0.02400,0.03430,-0.7600,0.000 +F,19,1.1859,51.000,0.0000,-0.01830,0.02640,-0.5500,0.000 +F,19,1.2011,51.000,0.0000,-0.01330,0.01940,-0.3800,0.000 +F,19,1.2190,51.000,0.0000,-0.00910,0.01340,-0.2400,0.000 +F,19,1.2345,51.000,0.0000,-0.00560,0.00850,-0.1400,0.000 +F,19,1.2525,51.000,0.0000,-0.00290,0.00460,-0.0500,0.000 +F,19,1.2677,51.000,0.0000,-0.00090,0.00180,0.0100,0.000 +F,19,1.2860,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,19,1.3024,51.000,0.0000,0.00140,-0.00150,0.1100,0.000 +F,19,1.3193,51.000,0.0000,0.00170,-0.00210,0.1400,0.000 +F,19,1.3343,51.000,0.0000,0.00170,-0.00220,0.1700,0.000 +F,19,1.3528,51.000,0.0000,0.00150,-0.00190,0.1900,0.000 +F,19,1.3690,51.000,0.0000,0.00110,-0.00140,0.2000,0.000 +F,19,1.3858,51.000,0.0000,0.00520,-0.00670,0.4200,0.000 +F,19,1.4022,51.000,0.0000,0.01240,-0.01630,0.7800,0.000 +F,19,1.4189,51.000,0.0000,0.01550,-0.02040,0.9300,0.000 +F,19,1.4355,51.000,0.0000,0.01440,-0.01860,0.9600,0.000 +F,19,1.4522,51.000,0.0000,0.01210,-0.01520,0.9900,0.000 +F,19,1.4688,51.000,0.0000,0.00980,-0.01210,0.9700,0.000 +F,19,1.4842,51.000,0.0000,0.00780,-0.00930,0.9100,0.000 +F,19,1.5009,51.000,0.0000,0.00580,-0.00670,0.8400,0.000 +F,19,1.5187,51.000,0.0000,0.00370,-0.00380,0.7800,0.000 +F,19,1.5356,51.000,0.0000,0.00120,-0.00050,0.7300,0.000 +F,19,1.5521,51.000,0.0000,-0.00150,0.00310,0.7100,0.000 +F,19,1.5689,51.000,0.0000,-0.00430,0.00690,0.7100,0.000 +F,19,1.5855,51.000,0.0000,-0.00710,0.01070,0.7200,0.000 +F,19,1.6020,51.000,0.0000,-0.00970,0.01430,0.7400,0.000 +F,19,1.6188,51.000,0.0000,-0.01200,0.01730,0.7600,0.000 +F,19,1.6353,51.000,0.0000,-0.01380,0.01980,0.7800,0.000 +F,19,1.6520,51.000,0.0000,-0.01510,0.02150,0.7800,0.000 +F,19,1.6688,51.000,0.0000,-0.01580,0.02250,0.7700,0.000 +F,19,1.6854,51.000,0.0000,-0.01610,0.02280,0.7500,0.000 +F,19,1.7020,51.000,0.0000,-0.01590,0.02250,0.7200,0.000 +F,19,1.7176,51.000,0.0000,-0.01540,0.02170,0.6700,0.000 +F,19,1.7343,51.000,0.0000,-0.01450,0.02050,0.6300,0.000 +F,19,1.7509,51.000,0.0000,-0.01350,0.01900,0.5700,0.000 +F,19,1.7676,51.000,0.0000,-0.01230,0.01730,0.5100,0.000 +F,19,1.7855,51.000,0.0000,-0.01110,0.01560,0.4400,0.000 +F,19,1.8021,51.000,0.0000,-0.00980,0.01370,0.3700,0.000 +F,19,1.8187,51.000,0.0000,-0.00840,0.01180,0.3000,0.000 +F,19,1.8355,51.000,0.0000,-0.00700,0.00990,0.2400,0.000 +F,19,1.8523,51.000,0.0000,-0.00580,0.00810,0.1900,0.000 +F,19,1.8689,51.000,0.0000,-0.00450,0.00640,0.1500,0.000 +F,19,1.8855,51.000,0.0000,-0.00340,0.00480,0.1100,0.000 +F,19,1.9021,51.000,0.0000,-0.00250,0.00350,0.0800,0.000 +F,19,1.9194,51.000,0.0000,-0.00170,0.00230,0.0600,0.000 +F,19,1.9362,51.000,0.0000,-0.00100,0.00140,0.0400,0.000 +F,19,1.9522,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,19,1.9682,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +E,20,dragD,51.000,51.000,51.000,223.000,1.3998,0.3164,137.000 +T,20,0.0000,50.960 +T,20,0.3164,52.960 +T,20,0.3331,55.290 +T,20,0.3498,57.960 +T,20,0.3665,60.620 +T,20,0.3831,63.620 +T,20,0.3998,66.620 +T,20,0.4164,69.620 +T,20,0.4332,72.620 +T,20,0.4498,75.960 +T,20,0.4664,78.960 +T,20,0.4831,82.290 +T,20,0.4997,85.620 +T,20,0.5164,88.960 +T,20,0.5331,92.290 +T,20,0.5498,95.290 +T,20,0.5664,98.620 +T,20,0.5831,101.960 +T,20,0.5998,105.290 +T,20,0.6164,108.290 +T,20,0.6331,111.620 +T,20,0.6498,114.620 +T,20,0.6664,117.620 +T,20,0.6831,120.620 +T,20,0.6998,123.620 +T,20,0.7164,126.290 +T,20,0.7331,128.960 +T,20,0.7498,131.290 +T,20,0.7664,133.620 +T,20,0.7831,135.290 +T,20,0.7998,136.960 +T,20,1.3998,136.960 +F,20,0.0182,51.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,0.0347,51.000,0.0606,0.00000,0.00000,0.0000,0.630 +F,20,0.0514,51.000,0.1969,0.00000,0.00000,0.0000,2.247 +F,20,0.0680,51.000,0.3500,0.00000,0.00000,0.0000,4.384 +F,20,0.0847,51.000,0.4925,0.00000,0.00000,0.0000,6.686 +F,20,0.1013,51.000,0.6131,0.00000,0.00000,0.0000,8.850 +F,20,0.1180,51.000,0.7112,0.00100,-0.00170,0.0500,10.768 +F,20,0.1347,51.000,0.7869,0.00180,-0.00300,0.1000,12.330 +F,20,0.1513,51.000,0.8450,0.00280,-0.00460,0.1500,13.536 +F,20,0.1682,51.000,0.8881,0.00380,-0.00630,0.2100,14.385 +F,20,0.1848,51.000,0.9200,0.00470,-0.00790,0.2600,14.906 +F,20,0.2015,51.000,0.9431,0.00540,-0.00910,0.3000,15.180 +F,20,0.2181,51.000,0.9600,0.00580,-0.00970,0.3200,15.289 +F,20,0.2348,51.000,0.9719,0.00590,-0.00980,0.3300,15.234 +F,20,0.2514,51.000,1.0000,0.00560,-0.00930,0.3100,15.097 +F,20,0.2681,51.000,1.0000,0.00490,-0.00820,0.2700,14.933 +F,20,0.2847,51.000,1.0000,0.00400,-0.00670,0.2200,14.741 +F,20,0.3015,51.000,1.0000,0.00300,-0.00500,0.1700,14.577 +F,20,0.3185,51.000,1.0000,0.00190,-0.00310,0.1000,14.412 +F,20,0.3352,51.000,1.0000,0.00080,-0.00130,0.0400,14.303 +F,20,0.3518,51.200,1.0000,0.00000,0.00000,0.0000,14.193 +F,20,0.3686,51.810,1.0000,-0.00120,0.00190,-0.0600,14.193 +F,20,0.3851,52.930,1.0000,-0.00190,0.00320,-0.1100,14.221 +F,20,0.4018,54.490,1.0000,-0.00250,0.00420,-0.1300,14.221 +F,20,0.4185,56.420,1.0000,-0.00270,0.00450,-0.1500,14.248 +F,20,0.4352,58.680,1.0000,-0.00230,0.00380,-0.1900,14.275 +F,20,0.4518,61.220,1.0000,-0.00110,0.00180,-0.2500,14.303 +F,20,0.4685,63.940,1.0000,0.00100,-0.00160,-0.3500,14.330 +F,20,0.4850,66.900,1.0000,0.00390,-0.00640,-0.4900,14.358 +F,20,0.5016,70.040,1.0000,0.00750,-0.01240,-0.6700,14.385 +F,20,0.5183,73.220,1.0000,0.01150,-0.01920,-0.8700,14.412 +F,20,0.5350,76.390,1.0000,0.01580,-0.02640,-1.0900,14.440 +F,20,0.5516,79.630,1.0000,0.02020,-0.03360,-1.3100,14.467 +F,20,0.5686,82.930,1.0000,0.02420,-0.04040,-1.5200,14.495 +F,20,0.5850,86.200,1.0000,0.02790,-0.04650,-1.7100,14.495 +F,20,0.6016,89.410,1.0000,0.03090,-0.05160,-1.8700,14.522 +F,20,0.6183,92.660,1.0000,0.03330,-0.05550,-1.9900,14.549 +F,20,0.6350,95.960,1.0000,0.03490,-0.05820,-2.0700,14.577 +F,20,0.6517,99.220,1.0000,0.03580,-0.05970,-2.1100,14.604 +F,20,0.6684,102.430,1.0000,0.03610,-0.06010,-2.1100,14.632 +F,20,0.6851,105.570,1.0000,0.03570,-0.05950,-2.0800,14.659 +F,20,0.7017,108.660,1.0000,0.03490,-0.05810,-2.0200,14.686 +F,20,0.7183,111.720,1.0000,0.03360,-0.05610,-1.9400,14.686 +F,20,0.7350,114.750,1.0000,0.03210,-0.05340,-1.8400,14.714 +F,20,0.7517,117.760,1.0000,0.03020,-0.05040,-1.7300,14.741 +F,20,0.7683,120.770,1.0000,0.02820,-0.04690,-1.6100,14.769 +F,20,0.7850,123.670,1.0000,0.02600,-0.04330,-1.4800,14.769 +F,20,0.8017,126.360,1.0000,0.02370,-0.03950,-1.3400,14.796 +F,20,0.8179,128.850,1.0000,0.02140,-0.03560,-1.2100,14.823 +F,20,0.8346,131.170,1.0000,0.01910,-0.03180,-1.0700,14.823 +F,20,0.8512,133.150,1.0000,0.01670,-0.02780,-0.9400,14.823 +F,20,0.8679,134.650,1.0000,0.01420,-0.02370,-0.8000,14.823 +F,20,0.8846,135.680,1.0000,0.01160,-0.01930,-0.6500,14.823 +F,20,0.9013,136.340,1.0000,0.00870,-0.01460,-0.4900,14.823 +F,20,0.9179,136.730,1.0000,0.00560,-0.00930,-0.3100,14.823 +F,20,0.9346,136.940,1.0000,0.00190,-0.00320,-0.1100,14.823 +F,20,0.9512,137.000,1.0000,-0.00220,0.00360,0.1200,14.823 +F,20,0.9679,137.000,1.0000,-0.00660,0.01100,0.3600,14.823 +F,20,0.9846,137.000,1.0000,-0.01130,0.01890,0.6200,14.823 +F,20,1.0013,137.000,1.0000,-0.01610,0.02680,0.8700,14.823 +F,20,1.0179,137.000,1.0000,-0.02060,0.03440,1.1100,14.823 +F,20,1.0346,137.000,1.0000,-0.02470,0.04120,1.3300,14.823 +F,20,1.0512,137.000,1.0000,-0.02830,0.04710,1.5100,14.823 +F,20,1.0679,137.000,1.0000,-0.03110,0.05180,1.6600,14.823 +F,20,1.0846,137.000,1.0000,-0.03310,0.05520,1.7600,14.823 +F,20,1.1012,137.000,1.0000,-0.03440,0.05730,1.8300,14.823 +F,20,1.1179,137.000,1.0000,-0.03490,0.05820,1.8500,14.823 +F,20,1.1346,137.000,1.0000,-0.03480,0.05800,1.8500,14.823 +F,20,1.1512,137.000,1.0000,-0.03420,0.05690,1.8200,14.823 +F,20,1.1679,137.000,1.0000,-0.03310,0.05520,1.7600,14.823 +F,20,1.1846,137.000,1.0000,-0.03170,0.05280,1.6900,14.823 +F,20,1.2012,137.000,1.0000,-0.03010,0.05010,1.6000,14.823 +F,20,1.2179,137.000,1.0000,-0.02830,0.04720,1.5100,14.823 +F,20,1.2346,137.000,1.0000,-0.02640,0.04410,1.4200,14.823 +F,20,1.2512,137.000,1.0000,-0.02460,0.04090,1.3200,14.823 +F,20,1.2679,137.000,1.0000,-0.02260,0.03770,1.2000,14.823 +F,20,1.2846,137.000,1.0000,-0.02070,0.03450,1.0700,14.823 +F,20,1.3012,137.000,1.0000,-0.01870,0.03110,0.9400,14.823 +F,20,1.3179,137.000,1.0000,-0.01660,0.02770,0.8100,14.823 +F,20,1.3346,137.000,1.0000,-0.01460,0.02430,0.7000,14.823 +F,20,1.3512,137.000,1.0000,-0.01260,0.02100,0.6000,14.823 +F,20,1.3679,137.000,1.0000,-0.01070,0.01790,0.5100,14.823 +F,20,1.3846,137.000,1.0000,-0.00900,0.01500,0.4300,14.823 +F,20,1.4054,137.000,1.0000,-0.00740,0.01240,0.3700,14.823 +F,20,1.4179,137.000,1.0000,-0.00610,0.01010,0.3100,14.823 +F,20,1.4346,137.000,0.9331,-0.00480,0.00810,0.2600,14.275 +F,20,1.4513,137.000,0.7950,-0.00380,0.00630,0.2100,12.878 +F,20,1.4679,137.000,0.6425,-0.00280,0.00470,0.1700,10.960 +F,20,1.4845,137.000,0.5006,-0.00210,0.00340,0.1300,8.850 +F,20,1.5013,137.000,0.3813,-0.00140,0.00240,0.1000,6.713 +F,20,1.5178,137.000,0.2844,-0.00090,0.00150,0.0700,4.740 +F,20,1.5346,137.000,0.2094,0.00000,0.00000,0.0000,3.014 +F,20,1.5512,137.000,0.1525,0.00000,0.00000,0.0000,1.589 +F,20,1.5678,137.000,0.1100,0.00000,0.00000,0.0000,0.466 +F,20,1.5846,137.000,0.0787,0.00000,0.00000,0.0000,-0.356 +F,20,1.6012,137.000,0.0556,0.00070,-0.00100,0.0100,-0.932 +F,20,1.6179,137.000,0.0394,0.00070,-0.00120,0.0000,-1.260 +F,20,1.6346,137.000,0.0281,0.00070,-0.00110,-0.0000,-1.397 +F,20,1.6512,137.000,0.0000,0.00060,-0.00100,-0.0100,-1.397 +F,20,1.6679,137.000,0.0000,0.00000,0.00000,0.0000,-1.288 +F,20,1.6846,137.000,0.0000,0.00000,0.00000,0.0000,-1.123 +F,20,1.7012,137.000,0.0000,0.00000,0.00000,0.0000,-0.932 +F,20,1.7179,137.000,0.0000,0.00000,0.00000,0.0000,-0.740 +F,20,1.7345,137.000,0.0000,0.00000,0.00000,0.0000,-0.548 +F,20,1.7512,137.000,0.0000,-0.00080,0.00110,-0.0600,-0.356 +F,20,1.7678,137.000,0.0000,-0.00100,0.00140,-0.0700,0.000 +F,20,1.7845,137.000,0.0000,-0.00110,0.00160,-0.0700,0.000 +F,20,1.8013,137.000,0.0000,-0.00080,0.00180,-0.0700,0.000 +F,20,1.8178,137.000,0.0000,-0.00070,0.00190,-0.0700,0.000 +F,20,1.8361,137.000,0.0000,-0.00050,0.00190,-0.0700,0.000 +F,20,1.8526,137.000,0.0000,-0.00050,0.00190,-0.0700,0.000 +F,20,1.8694,137.000,0.0000,-0.00040,0.00180,-0.0600,0.000 +F,20,1.8845,137.000,0.0000,-0.00040,0.00170,-0.0600,0.000 +F,20,1.9012,137.000,0.0000,-0.00030,0.00160,-0.0500,0.000 +F,20,1.9191,137.000,0.0000,-0.00030,0.00140,-0.0500,0.000 +F,20,1.9345,137.000,0.0000,-0.00020,0.00130,-0.0400,0.000 +F,20,1.9524,137.000,0.0000,-0.00010,0.00110,-0.0400,0.000 +F,20,1.9678,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,1.9854,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.0011,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.0178,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.0358,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.0526,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.0691,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.0859,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.1024,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.1178,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.1357,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.1519,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.1677,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.1844,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.2010,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.2177,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.2344,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.2510,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.2677,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.2842,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.3009,137.000,0.0000,0.00000,0.00000,0.0000,0.000 +F,20,2.3176,137.000,0.0000,0.00450,-0.00590,0.2200,0.000 +F,20,2.3342,137.000,0.0000,0.01230,-0.01620,0.5900,0.000 +F,20,2.3509,137.000,0.0000,0.01620,-0.02120,0.7800,0.000 +F,20,2.3676,137.000,0.0000,0.01570,-0.02020,0.8500,0.000 +F,20,2.3843,137.000,0.0000,0.01360,-0.01720,0.9100,0.000 +F,20,2.4009,137.000,0.0000,0.01150,-0.01420,0.9200,0.000 +F,20,2.4176,137.000,0.0000,0.00950,-0.01150,0.8900,0.000 +F,20,2.4343,137.000,0.0000,0.00760,-0.00880,0.8300,0.000 +F,20,2.4509,137.000,0.0000,0.00540,-0.00600,0.7800,0.000 +F,20,2.4676,137.000,0.0000,0.00290,-0.00260,0.7400,0.000 +F,20,2.4842,137.000,0.0000,0.00010,0.00110,0.7200,0.000 +F,20,2.5010,137.000,0.0000,-0.00280,0.00500,0.7200,0.000 +F,20,2.5176,137.000,0.0000,-0.00570,0.00890,0.7300,0.000 +F,20,2.5343,137.000,0.0000,-0.00850,0.01260,0.7500,0.000 +F,20,2.5509,137.000,0.0000,-0.01090,0.01580,0.7600,0.000 +F,20,2.5677,137.000,0.0000,-0.01280,0.01850,0.7800,0.000 +F,20,2.5844,137.000,0.0000,-0.01420,0.02040,0.7800,0.000 +F,20,2.6010,137.000,0.0000,-0.01520,0.02160,0.7700,0.000 +F,20,2.6177,137.000,0.0000,-0.01560,0.02210,0.7500,0.000 +F,20,2.6343,137.000,0.0000,-0.01550,0.02200,0.7200,0.000 +F,20,2.6510,137.000,0.0000,-0.01510,0.02130,0.6800,0.000 +F,20,2.6677,137.000,0.0000,-0.01440,0.02030,0.6300,0.000 +F,20,2.6844,137.000,0.0000,-0.01340,0.01890,0.5800,0.000 +F,20,2.7006,137.000,0.0000,-0.01240,0.01740,0.5200,0.000 +F,20,2.7172,137.000,0.0000,-0.01120,0.01570,0.4500,0.000 +F,20,2.7339,137.000,0.0000,-0.00990,0.01380,0.3800,0.000 +F,20,2.7506,137.000,0.0000,-0.00860,0.01200,0.3100,0.000 +F,20,2.7673,137.000,0.0000,-0.00730,0.01020,0.2500,0.000 +F,20,2.7839,137.000,0.0000,-0.00610,0.00850,0.2000,0.000 +F,20,2.8006,137.000,0.0000,-0.00500,0.00700,0.1600,0.000 +F,20,2.8173,137.000,0.0000,-0.00400,0.00560,0.1200,0.000 +F,20,2.8339,137.000,0.0000,-0.00310,0.00440,0.0900,0.000 +F,20,2.8506,137.000,0.0000,-0.00240,0.00330,0.0600,0.000 +F,20,2.8672,137.000,0.0000,-0.00180,0.00250,0.0400,0.000 +F,20,2.8840,137.000,0.0000,-0.00130,0.00180,0.0200,0.000 +F,20,2.9007,137.000,0.0000,-0.00090,0.00120,0.0100,0.000 +F,20,2.9173,137.000,0.0000,0.00000,0.00000,0.0000,0.000 diff --git a/maven/core-unittests/src/test/resources/tab-glass-motion/native-ios27.csv b/maven/core-unittests/src/test/resources/tab-glass-motion/native-ios27.csv new file mode 100644 index 00000000000..36b17ffaeb1 --- /dev/null +++ b/maven/core-unittests/src/test/resources/tab-glass-motion/native-ios27.csv @@ -0,0 +1,1321 @@ +# Native iOS 27 UITabBarController selection motion, one row per display frame. +# Captured by scripts/probe-ios-tab-motion.sh and exported by +# scripts/fidelity-app/tools/tab-motion/tabmotion.py fixture. +# set,tap,travelPt,liftRegime,tSec,position,liftPt,scaleX,scaleY,leadPt,platterOpacity,contentScale,barGrowPt +# Every tap is a quick ~66 ms touch: a longer press grows the bar pulse further. +# liftRegime: fast = the lift curve every 3-tab tap uses; slow = the slower rise UIKit +# sometimes plays on the 5-tab bar (position/deformation still hold, lift is not asserted). +light-grey,1,-172.00,fast,0.0000,-0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.0167,0.02378,0.870,1.0000,1.0000,0.000,0.9460,1.0087,0.548 +light-grey,1,-172.00,fast,0.0335,0.09238,3.010,1.0055,0.9926,-0.300,0.8120,1.0301,2.137 +light-grey,1,-172.00,fast,0.0513,0.18634,5.460,1.0194,0.9738,-1.090,0.6590,1.0546,4.247 +light-grey,1,-172.00,fast,0.0667,0.29128,7.750,1.0385,0.9479,-2.200,0.5160,1.0775,6.549 +light-grey,1,-172.00,fast,0.0833,0.39750,9.710,1.0605,0.9181,-3.530,0.3930,1.0971,8.193 +light-grey,1,-172.00,fast,0.1000,0.49866,11.290,1.0838,0.8867,-4.990,0.2940,1.1129,8.768 +light-grey,1,-172.00,fast,0.1166,0.59110,12.530,1.1065,0.8554,-6.480,0.2170,1.1253,8.576 +light-grey,1,-172.00,fast,0.1333,0.67285,13.470,1.1198,0.8366,-7.380,0.1580,1.1347,7.809 +light-grey,1,-172.00,fast,0.1499,0.74320,14.180,1.1155,0.8384,-7.090,0.1140,1.1418,6.713 +light-grey,1,-172.00,fast,0.1667,0.80238,14.690,1.0991,0.8547,-6.400,0.0820,1.1469,5.480 +light-grey,1,-172.00,fast,0.1833,0.85122,15.070,1.0825,0.8728,-5.780,0.0580,1.1507,4.220 +light-grey,1,-172.00,fast,0.1999,0.89070,15.340,1.0721,0.8832,-4.930,0.0410,1.1534,3.014 +light-grey,1,-172.00,fast,0.2166,0.92198,15.540,1.0680,0.8853,-3.860,0.0290,1.1554,1.973 +light-grey,1,-172.00,fast,0.2333,0.94634,16.000,1.0676,0.8825,-2.700,0.0200,1.1568,1.069 +light-grey,1,-172.00,fast,0.2538,0.96488,16.000,1.0682,0.8795,-1.620,0.0140,1.1600,0.384 +light-grey,1,-172.00,fast,0.2666,0.97872,16.000,1.0672,0.8800,-0.750,0.0100,1.1600,0.000 +light-grey,1,-172.00,fast,0.2832,0.98872,14.930,1.0627,0.8867,-0.170,0.0360,1.1493,-0.493 +light-grey,1,-172.00,fast,0.2999,0.99576,12.720,1.0538,0.9005,0.100,0.1020,1.1272,-0.712 +light-grey,1,-172.00,fast,0.3166,1.00052,10.280,1.0408,0.9203,0.070,0.1890,1.1028,-0.822 +light-grey,1,-172.00,fast,0.3332,1.00355,8.010,1.0245,0.9444,-0.200,0.2840,1.0801,-0.822 +light-grey,1,-172.00,fast,0.3499,1.00529,6.100,1.0062,0.9706,-0.640,0.3780,1.0610,-0.767 +light-grey,1,-172.00,fast,0.3666,1.00610,4.550,0.9872,0.9974,-1.170,0.4660,1.0455,-0.685 +light-grey,1,-172.00,fast,0.3832,1.00628,3.350,0.9688,1.0232,-1.730,0.5470,1.0335,-0.575 +light-grey,1,-172.00,fast,0.3999,1.00605,2.440,0.9521,1.0468,-2.240,0.6190,1.0244,-0.466 +light-grey,1,-172.00,fast,0.4166,1.00558,1.760,0.9378,1.0676,-2.700,0.6820,1.0176,-0.329 +light-grey,1,-172.00,fast,0.4332,1.00500,1.260,0.9264,1.0849,-3.060,0.7360,1.0126,0.000 +light-grey,1,-172.00,fast,0.4498,1.00430,0.890,0.9180,1.0984,-3.320,0.7820,1.0089,0.000 +light-grey,1,-172.00,fast,0.4665,1.00366,0.630,0.9126,1.1079,-3.500,0.8210,1.0063,0.000 +light-grey,1,-172.00,fast,0.4832,1.00302,0.450,0.9100,1.1136,-3.580,0.8530,1.0045,0.000 +light-grey,1,-172.00,fast,0.5000,1.00244,0.000,0.9098,1.1160,-3.590,0.8800,1.0031,0.000 +light-grey,1,-172.00,fast,0.5165,1.00198,0.000,0.9115,1.1154,-3.530,0.9030,1.0000,0.000 +light-grey,1,-172.00,fast,0.5332,1.00151,0.000,0.9148,1.1125,-3.420,0.9210,1.0000,0.000 +light-grey,1,-172.00,fast,0.5498,1.00116,0.000,0.9194,1.1078,-3.260,0.9360,1.0000,0.000 +light-grey,1,-172.00,fast,0.5665,1.00000,0.000,0.9247,1.1017,-3.080,0.9490,1.0000,0.000 +light-grey,1,-172.00,fast,0.5832,1.00000,0.000,0.9305,1.0946,-2.870,0.9590,1.0000,0.000 +light-grey,1,-172.00,fast,0.6001,1.00000,0.000,0.9366,1.0870,-2.650,0.9670,1.0000,0.000 +light-grey,1,-172.00,fast,0.6165,1.00000,0.000,0.9428,1.0791,-2.380,0.9730,1.0000,0.000 +light-grey,1,-172.00,fast,0.6331,1.00000,0.000,0.9491,1.0709,-2.070,0.9790,1.0000,0.000 +light-grey,1,-172.00,fast,0.6498,1.00000,0.000,0.9555,1.0624,-1.740,0.9830,1.0000,0.000 +light-grey,1,-172.00,fast,0.6665,1.00000,0.000,0.9619,1.0537,-1.430,0.9860,1.0000,0.000 +light-grey,1,-172.00,fast,0.6832,1.00000,0.000,0.9681,1.0452,-1.150,0.9890,1.0000,0.000 +light-grey,1,-172.00,fast,0.6998,1.00000,0.000,0.9741,1.0371,-0.900,0.9910,1.0000,0.000 +light-grey,1,-172.00,fast,0.7165,1.00000,0.000,0.9796,1.0294,-0.680,0.9930,1.0000,0.000 +light-grey,1,-172.00,fast,0.7338,1.00000,0.000,0.9845,1.0225,-0.510,0.9950,1.0000,0.000 +light-grey,1,-172.00,fast,0.7505,1.00000,0.000,0.9888,1.0164,-0.360,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.7665,1.00000,0.000,0.9925,1.0113,-0.240,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.7831,1.00000,0.000,0.9954,1.0070,-0.150,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.7999,1.00000,0.000,0.9978,1.0037,-0.070,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.8165,1.00000,0.000,0.9995,1.0012,-0.010,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.8331,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.8499,1.00000,0.000,1.0014,0.9984,0.070,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.8672,1.00000,0.000,1.0017,0.9979,0.110,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.8831,1.00000,0.000,1.0017,0.9979,0.130,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.8999,1.00000,0.000,1.0015,0.9981,0.150,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.9165,1.00000,0.000,1.0012,0.9985,0.160,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.9331,1.00000,0.000,1.0007,0.9991,0.170,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.9498,1.00000,0.000,1.0003,0.9997,0.180,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.9670,1.00000,0.000,0.9998,1.0003,0.180,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,0.9832,1.00000,0.000,0.9994,1.0009,0.180,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.0001,1.00000,0.000,1.0035,0.9955,0.390,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.0164,1.00000,0.000,1.0110,0.9856,0.740,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.0330,1.00000,0.000,1.0146,0.9809,0.910,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.0497,1.00000,0.000,1.0139,0.9822,0.950,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.0665,1.00000,0.000,1.0118,0.9854,0.990,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.0830,1.00000,0.000,1.0096,0.9883,0.980,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.0997,1.00000,0.000,1.0077,0.9909,0.930,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.1164,1.00000,0.000,1.0059,0.9934,0.860,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.1331,1.00000,0.000,1.0038,0.9962,0.800,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.1497,1.00000,0.000,1.0014,0.9993,0.760,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.1664,1.00000,0.000,0.9988,1.0029,0.730,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.1830,1.00000,0.000,0.9960,1.0066,0.730,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.1997,1.00000,0.000,0.9932,1.0103,0.740,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.2164,1.00000,0.000,0.9906,1.0138,0.750,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.2330,1.00000,0.000,0.9884,1.0169,0.770,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.2497,1.00000,0.000,0.9866,1.0193,0.780,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.2664,1.00000,0.000,0.9853,1.0210,0.780,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.2830,1.00000,0.000,0.9845,1.0221,0.770,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.2997,1.00000,0.000,0.9842,1.0224,0.750,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.3164,1.00000,0.000,0.9843,1.0222,0.710,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.3330,1.00000,0.000,0.9848,1.0214,0.670,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.3497,1.00000,0.000,0.9856,1.0203,0.620,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.3663,1.00000,0.000,0.9866,1.0188,0.570,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.3829,1.00000,0.000,0.9877,1.0172,0.510,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.3997,1.00000,0.000,0.9890,1.0155,0.440,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.4163,1.00000,0.000,0.9903,1.0136,0.370,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.4329,1.00000,0.000,0.9917,1.0117,0.300,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.4496,1.00000,0.000,0.9930,1.0098,0.240,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.4663,1.00000,0.000,0.9943,1.0080,0.190,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.4829,1.00000,0.000,0.9955,1.0063,0.150,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.4997,1.00000,0.000,0.9966,1.0048,0.110,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.5163,1.00000,0.000,0.9975,1.0034,0.080,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.5330,1.00000,0.000,0.9983,1.0023,0.060,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.5493,1.00000,0.000,0.9990,1.0014,0.040,1.0000,1.0000,0.000 +light-grey,1,-172.00,fast,1.5676,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.0000,0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.0167,0.02477,0.900,1.0000,1.0000,0.000,0.9430,1.0090,0.575 +light-grey,2,86.00,fast,0.0334,0.09407,3.060,1.0055,0.9926,0.300,0.8090,1.0306,2.165 +light-grey,2,86.00,fast,0.0512,0.18837,5.510,1.0194,0.9738,1.090,0.6560,1.0551,4.302 +light-grey,2,86.00,fast,0.0667,0.29337,7.790,1.0385,0.9479,2.200,0.5130,1.0779,6.576 +light-grey,2,86.00,fast,0.0833,0.39953,9.750,1.0605,0.9181,3.530,0.3910,1.0975,8.220 +light-grey,2,86.00,fast,0.0999,0.50070,11.320,1.0837,0.8867,4.990,0.2920,1.1132,8.823 +light-grey,2,86.00,fast,0.1166,0.59291,12.550,1.1048,0.8569,6.370,0.2160,1.1255,8.604 +light-grey,2,86.00,fast,0.1333,0.67442,13.490,1.1171,0.8390,7.200,0.1570,1.1349,7.836 +light-grey,2,86.00,fast,0.1499,0.74453,14.190,1.1152,0.8398,7.070,0.1130,1.1419,6.740 +light-grey,2,86.00,fast,0.1666,0.80349,14.700,1.1044,0.8525,6.480,0.0810,1.1470,5.480 +light-grey,2,86.00,fast,0.1833,0.85209,15.080,1.0932,0.8666,5.840,0.0580,1.1508,4.220 +light-grey,2,86.00,fast,0.1999,0.89140,15.350,1.0847,0.8768,5.030,0.0410,1.1535,3.014 +light-grey,2,86.00,fast,0.2189,0.92256,15.540,1.0789,0.8831,4.120,0.0290,1.1554,1.973 +light-grey,2,86.00,fast,0.2332,0.94674,16.000,1.0747,0.8873,3.190,0.0200,1.1568,1.069 +light-grey,2,86.00,fast,0.2499,0.96523,14.930,1.0704,0.8922,2.330,0.0430,1.1471,0.384 +light-grey,2,86.00,fast,0.2666,0.97895,12.720,1.0646,0.8998,1.590,0.1070,1.1257,0.000 +light-grey,2,86.00,fast,0.2832,0.98884,10.280,1.0569,0.9107,1.040,0.1930,1.1017,-0.493 +light-grey,2,86.00,fast,0.3000,0.99593,8.010,1.0470,0.9249,0.690,0.2860,1.0794,-0.712 +light-grey,2,86.00,fast,0.3166,1.00058,6.100,1.0355,0.9419,0.540,0.3790,1.0605,-0.822 +light-grey,2,86.00,fast,0.3332,1.00360,4.550,1.0227,0.9607,0.550,0.4670,1.0452,-0.822 +light-grey,2,86.00,fast,0.3499,1.00535,3.350,1.0095,0.9802,0.670,0.5480,1.0333,-0.795 +light-grey,2,86.00,fast,0.3665,1.00605,2.440,0.9966,0.9992,0.870,0.6200,1.0242,-0.685 +light-grey,2,86.00,fast,0.3832,1.00628,1.760,0.9847,1.0169,1.090,0.6820,1.0175,-0.575 +light-grey,2,86.00,fast,0.3998,1.00605,1.260,0.9743,1.0324,1.320,0.7360,1.0125,-0.466 +light-grey,2,86.00,fast,0.4166,1.00558,0.890,0.9657,1.0454,1.520,0.7820,1.0089,-0.329 +light-grey,2,86.00,fast,0.4333,1.00500,0.630,0.9590,1.0554,1.690,0.8210,1.0063,0.000 +light-grey,2,86.00,fast,0.4499,1.00430,0.450,0.9543,1.0626,1.800,0.8530,1.0044,0.000 +light-grey,2,86.00,fast,0.4666,1.00360,0.000,0.9515,1.0670,1.870,0.8800,1.0000,0.000 +light-grey,2,86.00,fast,0.4832,1.00302,0.000,0.9504,1.0690,1.900,0.9030,1.0000,0.000 +light-grey,2,86.00,fast,0.4999,1.00244,0.000,0.9507,1.0690,1.880,0.9210,1.0000,0.000 +light-grey,2,86.00,fast,0.5166,1.00000,0.000,0.9521,1.0673,1.830,0.9360,1.0000,0.000 +light-grey,2,86.00,fast,0.5332,1.00000,0.000,0.9544,1.0642,1.750,0.9490,1.0000,0.000 +light-grey,2,86.00,fast,0.5500,1.00000,0.000,0.9573,1.0603,1.660,0.9590,1.0000,0.000 +light-grey,2,86.00,fast,0.5665,1.00000,0.000,0.9605,1.0558,1.550,0.9670,1.0000,0.000 +light-grey,2,86.00,fast,0.5832,1.00000,0.000,0.9640,1.0510,1.430,0.9730,1.0000,0.000 +light-grey,2,86.00,fast,0.5998,1.00000,0.000,0.9676,1.0460,1.280,0.9790,1.0000,0.000 +light-grey,2,86.00,fast,0.6165,1.00000,0.000,0.9712,1.0409,1.120,0.9830,1.0000,0.000 +light-grey,2,86.00,fast,0.6331,1.00000,0.000,0.9749,1.0356,0.940,0.9860,1.0000,0.000 +light-grey,2,86.00,fast,0.6499,1.00000,0.000,0.9786,1.0304,0.780,0.9890,1.0000,0.000 +light-grey,2,86.00,fast,0.6665,1.00000,0.000,0.9822,1.0253,0.620,0.9910,1.0000,0.000 +light-grey,2,86.00,fast,0.6832,1.00000,0.000,0.9856,1.0205,0.490,0.9930,1.0000,0.000 +light-grey,2,86.00,fast,0.6998,1.00000,0.000,0.9887,1.0161,0.380,0.9950,1.0000,0.000 +light-grey,2,86.00,fast,0.7165,1.00000,0.000,0.9915,1.0122,0.280,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.7331,1.00000,0.000,0.9939,1.0087,0.200,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.7498,1.00000,0.000,0.9960,1.0058,0.140,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.7665,1.00000,0.000,0.9976,1.0034,0.090,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.7831,1.00000,0.000,0.9989,1.0016,0.050,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.7997,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.8165,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.8331,1.00000,0.000,1.0009,0.9987,-0.030,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.8511,1.00000,0.000,1.0011,0.9985,-0.050,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.8664,1.00000,0.000,1.0011,0.9984,-0.060,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.8851,1.00000,0.000,1.0010,0.9986,-0.070,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.9014,1.00000,0.000,1.0008,0.9989,-0.080,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.9182,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.9347,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.9498,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.9663,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,0.9846,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.0012,1.00000,0.000,1.0038,0.9951,0.120,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.0181,1.00000,0.000,1.0115,0.9850,0.500,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.0346,1.00000,0.000,1.0153,0.9801,0.690,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.0512,1.00000,0.000,1.0147,0.9812,0.770,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.0680,1.00000,0.000,1.0126,0.9843,0.850,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.0853,1.00000,0.000,1.0104,0.9874,0.870,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.1016,1.00000,0.000,1.0085,0.9900,0.850,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.1184,1.00000,0.000,1.0065,0.9926,0.800,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.1348,1.00000,0.000,1.0044,0.9954,0.760,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.1517,1.00000,0.000,1.0021,0.9985,0.720,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.1680,1.00000,0.000,0.9994,1.0020,0.710,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.1847,1.00000,0.000,0.9966,1.0058,0.710,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.2018,1.00000,0.000,0.9938,1.0095,0.730,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.2184,1.00000,0.000,0.9912,1.0130,0.740,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.2354,1.00000,0.000,0.9889,1.0161,0.760,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.2517,1.00000,0.000,0.9871,1.0186,0.780,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.2688,1.00000,0.000,0.9857,1.0204,0.780,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.2850,1.00000,0.000,0.9848,1.0216,0.770,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.3017,1.00000,0.000,0.9845,1.0220,0.750,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.3185,1.00000,0.000,0.9845,1.0219,0.720,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.3350,1.00000,0.000,0.9850,1.0212,0.680,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.3515,1.00000,0.000,0.9857,1.0202,0.630,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.3681,1.00000,0.000,0.9866,1.0188,0.580,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.3847,1.00000,0.000,0.9877,1.0173,0.520,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.4013,1.00000,0.000,0.9889,1.0156,0.450,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.4180,1.00000,0.000,0.9901,1.0138,0.380,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.4346,1.00000,0.000,0.9915,1.0120,0.310,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.4515,1.00000,0.000,0.9928,1.0101,0.250,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.4684,1.00000,0.000,0.9941,1.0083,0.190,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.4846,1.00000,0.000,0.9953,1.0066,0.150,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.5014,1.00000,0.000,0.9964,1.0051,0.110,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.5171,1.00000,0.000,0.9973,1.0037,0.080,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.5339,1.00000,0.000,0.9982,1.0025,0.060,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.5508,1.00000,0.000,0.9989,1.0016,0.040,1.0000,1.0000,0.000 +light-grey,2,86.00,fast,1.5677,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.0000,0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.0166,0.02337,0.860,1.0000,1.0000,0.000,0.9460,1.0086,0.548 +light-grey,3,86.00,fast,0.0333,0.09174,2.990,1.0055,0.9926,0.300,0.8130,1.0299,2.110 +light-grey,3,86.00,fast,0.0512,0.18558,5.440,1.0194,0.9738,1.090,0.6600,1.0544,4.247 +light-grey,3,86.00,fast,0.0666,0.29047,7.740,1.0385,0.9479,2.200,0.5170,1.0774,6.521 +light-grey,3,86.00,fast,0.0832,0.39674,9.700,1.0605,0.9181,3.530,0.3940,1.0970,8.165 +light-grey,3,86.00,fast,0.0999,0.49791,11.280,1.0837,0.8867,4.990,0.2950,1.1128,8.768 +light-grey,3,86.00,fast,0.1167,0.59047,12.520,1.1047,0.8570,6.360,0.2170,1.1252,8.549 +light-grey,3,86.00,fast,0.1333,0.67221,13.470,1.1166,0.8398,7.160,0.1580,1.1347,7.809 +light-grey,3,86.00,fast,0.1500,0.74267,14.170,1.1143,0.8413,7.580,0.1140,1.1417,6.713 +light-grey,3,86.00,fast,0.1666,0.80198,14.690,1.1038,0.8535,7.810,0.0820,1.1469,5.480 +light-grey,3,86.00,fast,0.1832,0.85093,15.070,1.0936,0.8659,7.620,0.0580,1.1507,4.220 +light-grey,3,86.00,fast,0.2000,0.89047,15.340,1.0863,0.8741,7.030,0.0410,1.1534,3.014 +light-grey,3,86.00,fast,0.2190,0.92174,15.540,1.0817,0.8783,6.160,0.0290,1.1554,1.973 +light-grey,3,86.00,fast,0.2333,0.94616,16.000,1.0784,0.8810,5.180,0.0200,1.1567,1.096 +light-grey,3,86.00,fast,0.2667,0.97860,12.720,1.0690,0.8924,3.290,0.1070,1.1256,0.000 +light-grey,3,86.00,fast,0.2832,0.98860,10.280,1.0609,0.9038,2.560,0.1930,1.1017,-0.493 +light-grey,3,86.00,fast,0.2998,0.99570,8.010,1.0503,0.9192,2.030,0.2860,1.0794,-0.712 +light-grey,3,86.00,fast,0.3166,1.00047,6.100,1.0376,0.9378,1.700,0.3790,1.0604,-0.822 +light-grey,3,86.00,fast,0.3333,1.00349,4.550,1.0237,0.9584,1.550,0.4670,1.0452,-0.822 +light-grey,3,86.00,fast,0.3498,1.00523,3.350,1.0092,0.9797,1.530,0.5480,1.0333,-0.767 +light-grey,3,86.00,fast,0.3666,1.00605,2.440,0.9952,1.0006,1.600,0.6200,1.0242,-0.685 +light-grey,3,86.00,fast,0.3831,1.00628,1.760,0.9823,1.0197,1.720,0.6820,1.0175,-0.575 +light-grey,3,86.00,fast,0.3999,1.00605,1.260,0.9712,1.0364,1.840,0.7360,1.0125,-0.466 +light-grey,3,86.00,fast,0.4166,1.00558,0.890,0.9621,1.0500,1.950,0.7820,1.0089,-0.329 +light-grey,3,86.00,fast,0.4332,1.00500,0.630,0.9553,1.0604,2.030,0.8210,1.0063,0.000 +light-grey,3,86.00,fast,0.4498,1.00430,0.450,0.9507,1.0675,2.070,0.8530,1.0044,0.000 +light-grey,3,86.00,fast,0.4664,1.00360,0.000,0.9482,1.0716,2.070,0.8800,1.0000,0.000 +light-grey,3,86.00,fast,0.4831,1.00302,0.000,0.9475,1.0730,2.040,0.9030,1.0000,0.000 +light-grey,3,86.00,fast,0.4998,1.00244,0.000,0.9483,1.0723,1.970,0.9210,1.0000,0.000 +light-grey,3,86.00,fast,0.5165,1.00000,0.000,0.9503,1.0698,1.870,0.9360,1.0000,0.000 +light-grey,3,86.00,fast,0.5332,1.00000,0.000,0.9532,1.0660,1.750,0.9490,1.0000,0.000 +light-grey,3,86.00,fast,0.5498,1.00000,0.000,0.9566,1.0613,1.630,0.9590,1.0000,0.000 +light-grey,3,86.00,fast,0.5665,1.00000,0.000,0.9603,1.0561,1.470,0.9670,1.0000,0.000 +light-grey,3,86.00,fast,0.5832,1.00000,0.000,0.9643,1.0506,1.280,0.9730,1.0000,0.000 +light-grey,3,86.00,fast,0.5998,1.00000,0.000,0.9686,1.0447,1.080,0.9790,1.0000,0.000 +light-grey,3,86.00,fast,0.6164,1.00000,0.000,0.9729,1.0386,0.890,0.9830,1.0000,0.000 +light-grey,3,86.00,fast,0.6333,1.00000,0.000,0.9771,1.0326,0.710,0.9860,1.0000,0.000 +light-grey,3,86.00,fast,0.6498,1.00000,0.000,0.9812,1.0269,0.560,0.9890,1.0000,0.000 +light-grey,3,86.00,fast,0.6665,1.00000,0.000,0.9850,1.0215,0.430,0.9910,1.0000,0.000 +light-grey,3,86.00,fast,0.6831,1.00000,0.000,0.9884,1.0166,0.320,0.9930,1.0000,0.000 +light-grey,3,86.00,fast,0.6998,1.00000,0.000,0.9915,1.0122,0.230,0.9950,1.0000,0.000 +light-grey,3,86.00,fast,0.7164,1.00000,0.000,0.9941,1.0085,0.160,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.7331,1.00000,0.000,0.9962,1.0055,0.100,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.7498,1.00000,0.000,0.9979,1.0031,0.050,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.7665,1.00000,0.000,0.9992,1.0012,0.020,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.7831,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.7998,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.8164,1.00000,0.000,1.0010,0.9986,-0.060,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.8331,1.00000,0.000,1.0011,0.9985,-0.080,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.8497,1.00000,0.000,1.0010,0.9986,-0.090,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.8663,1.00000,0.000,1.0009,0.9988,-0.100,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.8830,1.00000,0.000,1.0006,0.9991,-0.100,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.8997,1.00000,0.000,1.0003,0.9995,-0.110,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.9163,1.00000,0.000,1.0001,0.9999,-0.110,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.9330,1.00000,0.000,0.9998,1.0003,-0.110,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.9497,1.00000,0.000,0.9996,1.0006,-0.110,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.9662,1.00000,0.000,0.9994,1.0009,-0.100,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.9828,1.00000,0.000,0.9992,1.0011,-0.100,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,0.9996,1.00000,0.000,1.0036,0.9954,0.120,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.0162,1.00000,0.000,1.0114,0.9851,0.500,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.0329,1.00000,0.000,1.0152,0.9802,0.700,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.0496,1.00000,0.000,1.0147,0.9812,0.780,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.0662,1.00000,0.000,1.0126,0.9843,0.850,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.0828,1.00000,0.000,1.0105,0.9873,0.870,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.0995,1.00000,0.000,1.0086,0.9899,0.850,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.1162,1.00000,0.000,1.0067,0.9924,0.800,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.1329,1.00000,0.000,1.0046,0.9951,0.760,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.1495,1.00000,0.000,1.0022,0.9983,0.720,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.1662,1.00000,0.000,0.9996,1.0018,0.710,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.1828,1.00000,0.000,0.9967,1.0056,0.710,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.1996,1.00000,0.000,0.9939,1.0094,0.730,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.2162,1.00000,0.000,0.9913,1.0130,0.750,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.2346,1.00000,0.000,0.9890,1.0161,0.760,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.2513,1.00000,0.000,0.9871,1.0186,0.780,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.2679,1.00000,0.000,0.9857,1.0205,0.780,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.2847,1.00000,0.000,0.9848,1.0216,0.770,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.3013,1.00000,0.000,0.9844,1.0221,0.750,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.3180,1.00000,0.000,0.9845,1.0220,0.720,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.3330,1.00000,0.000,0.9849,1.0213,0.680,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.3517,1.00000,0.000,0.9856,1.0203,0.630,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.3678,1.00000,0.000,0.9865,1.0189,0.580,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.3845,1.00000,0.000,0.9876,1.0174,0.530,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.4012,1.00000,0.000,0.9888,1.0157,0.470,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.4181,1.00000,0.000,0.9901,1.0139,0.400,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.4344,1.00000,0.000,0.9914,1.0121,0.340,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.4496,1.00000,0.000,0.9927,1.0102,0.280,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.4663,1.00000,0.000,0.9940,1.0084,0.220,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.4829,1.00000,0.000,0.9952,1.0067,0.180,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.4995,1.00000,0.000,0.9963,1.0052,0.140,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.5160,1.00000,0.000,0.9973,1.0038,0.100,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.5326,1.00000,0.000,0.9981,1.0026,0.080,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.5493,1.00000,0.000,0.9988,1.0017,0.050,1.0000,1.0000,0.000 +light-grey,3,86.00,fast,1.5660,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.0000,-0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.0169,0.02384,0.870,1.0000,1.0000,0.000,0.9450,1.0087,0.548 +light-grey,5,-86.00,fast,0.0335,0.09267,3.020,1.0055,0.9926,-0.300,0.8110,1.0302,2.137 +light-grey,5,-86.00,fast,0.0513,0.18663,5.460,1.0194,0.9738,-1.090,0.6580,1.0546,4.247 +light-grey,5,-86.00,fast,0.0667,0.29151,7.760,1.0385,0.9479,-2.200,0.5150,1.0776,6.549 +light-grey,5,-86.00,fast,0.0834,0.39779,9.710,1.0605,0.9181,-3.530,0.3930,1.0971,8.193 +light-grey,5,-86.00,fast,0.1000,0.49895,11.300,1.0837,0.8867,-4.990,0.2940,1.1130,8.768 +light-grey,5,-86.00,fast,0.1167,0.59140,12.530,1.1047,0.8570,-6.360,0.2170,1.1253,8.576 +light-grey,5,-86.00,fast,0.1332,0.67302,13.470,1.1166,0.8398,-7.160,0.1580,1.1347,7.809 +light-grey,5,-86.00,fast,0.1500,0.74337,14.180,1.1143,0.8413,-7.580,0.1140,1.1418,6.713 +light-grey,5,-86.00,fast,0.1666,0.80256,14.690,1.1038,0.8535,-7.810,0.0820,1.1469,5.480 +light-grey,5,-86.00,fast,0.1835,0.85128,15.070,1.0937,0.8658,-7.620,0.0580,1.1507,4.220 +light-grey,5,-86.00,fast,0.2001,0.89081,15.340,1.0864,0.8740,-7.020,0.0410,1.1534,3.014 +light-grey,5,-86.00,fast,0.2187,0.92209,15.540,1.0818,0.8782,-6.150,0.0290,1.1554,1.973 +light-grey,5,-86.00,fast,0.2332,0.94640,16.000,1.0785,0.8809,-5.170,0.0200,1.1568,1.069 +light-grey,5,-86.00,fast,0.2505,0.96488,14.930,1.0747,0.8849,-4.180,0.0430,1.1471,0.384 +light-grey,5,-86.00,fast,0.2666,0.97872,12.720,1.0691,0.8924,-3.280,0.1070,1.1257,0.000 +light-grey,5,-86.00,fast,0.2833,0.98872,10.280,1.0609,0.9038,-2.550,0.1930,1.1017,-0.493 +light-grey,5,-86.00,fast,0.2999,0.99581,8.010,1.0503,0.9192,-2.020,0.2860,1.0794,-0.712 +light-grey,5,-86.00,fast,0.3166,1.00058,6.100,1.0376,0.9378,-1.700,0.3790,1.0604,-0.822 +light-grey,5,-86.00,fast,0.3332,1.00349,4.550,1.0237,0.9585,-1.550,0.4670,1.0452,-0.822 +light-grey,5,-86.00,fast,0.3500,1.00523,3.350,1.0092,0.9798,-1.530,0.5480,1.0333,-0.795 +light-grey,5,-86.00,fast,0.3666,1.00605,2.440,0.9952,1.0006,-1.600,0.6200,1.0242,-0.685 +light-grey,5,-86.00,fast,0.3832,1.00628,1.760,0.9823,1.0197,-1.720,0.6820,1.0175,-0.575 +light-grey,5,-86.00,fast,0.3999,1.00605,1.260,0.9712,1.0364,-1.840,0.7360,1.0125,-0.466 +light-grey,5,-86.00,fast,0.4166,1.00558,0.890,0.9621,1.0500,-1.950,0.7820,1.0089,-0.329 +light-grey,5,-86.00,fast,0.4337,1.00500,0.630,0.9553,1.0603,-2.030,0.8210,1.0063,0.000 +light-grey,5,-86.00,fast,0.4500,1.00430,0.450,0.9507,1.0675,-2.070,0.8530,1.0044,0.000 +light-grey,5,-86.00,fast,0.4666,1.00360,0.000,0.9482,1.0716,-2.070,0.8800,1.0000,0.000 +light-grey,5,-86.00,fast,0.4832,1.00302,0.000,0.9475,1.0730,-2.040,0.9030,1.0000,0.000 +light-grey,5,-86.00,fast,0.4999,1.00244,0.000,0.9484,1.0722,-1.960,0.9210,1.0000,0.000 +light-grey,5,-86.00,fast,0.5165,1.00000,0.000,0.9504,1.0697,-1.870,0.9360,1.0000,0.000 +light-grey,5,-86.00,fast,0.5332,1.00000,0.000,0.9532,1.0659,-1.750,0.9490,1.0000,0.000 +light-grey,5,-86.00,fast,0.5500,1.00000,0.000,0.9566,1.0612,-1.620,0.9590,1.0000,0.000 +light-grey,5,-86.00,fast,0.5668,1.00000,0.000,0.9604,1.0561,-1.470,0.9670,1.0000,0.000 +light-grey,5,-86.00,fast,0.5833,1.00000,0.000,0.9644,1.0505,-1.280,0.9730,1.0000,0.000 +light-grey,5,-86.00,fast,0.6000,1.00000,0.000,0.9686,1.0446,-1.080,0.9790,1.0000,0.000 +light-grey,5,-86.00,fast,0.6166,1.00000,0.000,0.9729,1.0385,-0.890,0.9830,1.0000,0.000 +light-grey,5,-86.00,fast,0.6333,1.00000,0.000,0.9772,1.0325,-0.710,0.9860,1.0000,0.000 +light-grey,5,-86.00,fast,0.6499,1.00000,0.000,0.9812,1.0268,-0.560,0.9890,1.0000,0.000 +light-grey,5,-86.00,fast,0.6665,1.00000,0.000,0.9850,1.0214,-0.430,0.9910,1.0000,0.000 +light-grey,5,-86.00,fast,0.6833,1.00000,0.000,0.9885,1.0165,-0.320,0.9930,1.0000,0.000 +light-grey,5,-86.00,fast,0.6999,1.00000,0.000,0.9915,1.0122,-0.230,0.9950,1.0000,0.000 +light-grey,5,-86.00,fast,0.7165,1.00000,0.000,0.9941,1.0085,-0.160,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.7332,1.00000,0.000,0.9962,1.0055,-0.100,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.7499,1.00000,0.000,0.9979,1.0030,-0.050,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.7665,1.00000,0.000,0.9992,1.0012,-0.020,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.7832,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.7999,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.8165,1.00000,0.000,1.0010,0.9986,0.060,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.8332,1.00000,0.000,1.0011,0.9985,0.080,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.8499,1.00000,0.000,1.0010,0.9986,0.090,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.8665,1.00000,0.000,1.0008,0.9988,0.100,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.8832,1.00000,0.000,1.0006,0.9991,0.100,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.9015,1.00000,0.000,1.0003,0.9995,0.110,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.9165,1.00000,0.000,1.0001,0.9999,0.110,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.9359,1.00000,0.000,0.9998,1.0003,0.110,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.9524,1.00000,0.000,0.9996,1.0006,0.110,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.9686,1.00000,0.000,0.9994,1.0009,0.100,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,0.9831,1.00000,0.000,0.9992,1.0011,0.100,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.0015,1.00000,0.000,1.0036,0.9954,0.300,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.0185,1.00000,0.000,1.0113,0.9852,0.670,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.0349,1.00000,0.000,1.0152,0.9802,0.840,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.0523,1.00000,0.000,1.0146,0.9813,0.900,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.0681,1.00000,0.000,1.0126,0.9843,0.950,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.0850,1.00000,0.000,1.0105,0.9872,0.950,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.1012,1.00000,0.000,1.0086,0.9898,0.910,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.1184,1.00000,0.000,1.0067,0.9924,0.850,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.1349,1.00000,0.000,1.0046,0.9952,0.790,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.1514,1.00000,0.000,1.0022,0.9984,0.750,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.1680,1.00000,0.000,0.9995,1.0019,0.730,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.1856,1.00000,0.000,0.9966,1.0058,0.730,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.2014,1.00000,0.000,0.9938,1.0096,0.740,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.2179,1.00000,0.000,0.9911,1.0132,0.750,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.2345,1.00000,0.000,0.9888,1.0163,0.770,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.2515,1.00000,0.000,0.9869,1.0188,0.780,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.2682,1.00000,0.000,0.9856,1.0206,0.780,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.2850,1.00000,0.000,0.9847,1.0217,0.770,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.3020,1.00000,0.000,0.9844,1.0222,0.750,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.3195,1.00000,0.000,0.9844,1.0220,0.720,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.3355,1.00000,0.000,0.9849,1.0213,0.670,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.3515,1.00000,0.000,0.9856,1.0202,0.630,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.3681,1.00000,0.000,0.9866,1.0188,0.570,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.3850,1.00000,0.000,0.9877,1.0173,0.510,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.4013,1.00000,0.000,0.9889,1.0155,0.440,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.4182,1.00000,0.000,0.9902,1.0137,0.370,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.4347,1.00000,0.000,0.9916,1.0118,0.310,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.4517,1.00000,0.000,0.9929,1.0099,0.250,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.4685,1.00000,0.000,0.9942,1.0081,0.190,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.4851,1.00000,0.000,0.9954,1.0064,0.150,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.5020,1.00000,0.000,0.9965,1.0049,0.110,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.5165,1.00000,0.000,0.9975,1.0035,0.080,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.5339,1.00000,0.000,0.9983,1.0024,0.060,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.5506,1.00000,0.000,0.9989,1.0015,0.040,1.0000,1.0000,0.000 +light-grey,5,-86.00,fast,1.5673,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.0000,-0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.0167,0.02052,0.760,1.0000,1.0000,0.000,0.9530,1.0076,0.466 +dark-grey,1,-172.00,fast,0.0333,0.08680,2.850,1.0055,0.9926,-0.300,0.8220,1.0285,2.000 +dark-grey,1,-172.00,fast,0.0514,0.17953,5.290,1.0194,0.9738,-1.090,0.6690,1.0529,4.110 +dark-grey,1,-172.00,fast,0.0666,0.28407,7.610,1.0385,0.9479,-2.200,0.5250,1.0761,6.384 +dark-grey,1,-172.00,fast,0.0833,0.39047,9.590,1.0605,0.9181,-3.530,0.4010,1.0959,8.056 +dark-grey,1,-172.00,fast,0.0999,0.49215,11.200,1.0838,0.8867,-4.990,0.3000,1.1120,8.658 +dark-grey,1,-172.00,fast,0.1166,0.58523,12.460,1.1065,0.8554,-6.480,0.2210,1.1246,8.467 +dark-grey,1,-172.00,fast,0.1332,0.66767,13.420,1.1199,0.8366,-7.380,0.1610,1.1342,7.727 +dark-grey,1,-172.00,fast,0.1499,0.73884,14.130,1.1156,0.8384,-7.090,0.1170,1.1413,6.658 +dark-grey,1,-172.00,fast,0.1665,0.79878,14.660,1.0990,0.8547,-6.420,0.0840,1.1466,5.425 +dark-grey,1,-172.00,fast,0.1832,0.84826,15.050,1.0820,0.8732,-5.820,0.0590,1.1505,4.192 +dark-grey,1,-172.00,fast,0.1999,0.88831,15.330,1.0712,0.8840,-4.990,0.0420,1.1533,3.014 +dark-grey,1,-172.00,fast,0.2165,0.92012,15.530,1.0669,0.8863,-3.930,0.0300,1.1553,1.973 +dark-grey,1,-172.00,fast,0.2332,0.94488,16.000,1.0665,0.8837,-2.780,0.0210,1.1567,1.096 +dark-grey,1,-172.00,fast,0.2524,0.96378,16.000,1.0671,0.8805,-1.690,0.0150,1.1600,0.384 +dark-grey,1,-172.00,fast,0.2666,0.97791,16.000,1.0663,0.8807,-0.810,0.0100,1.1600,0.000 +dark-grey,1,-172.00,fast,0.2832,0.98814,14.930,1.0620,0.8871,-0.220,0.0360,1.1493,-0.493 +dark-grey,1,-172.00,fast,0.2999,0.99535,12.720,1.0533,0.9006,0.060,0.1020,1.1272,-0.685 +dark-grey,1,-172.00,fast,0.3165,1.00023,10.280,1.0405,0.9203,0.050,0.1890,1.1028,-0.795 +dark-grey,1,-172.00,fast,0.3332,1.00337,8.010,1.0244,0.9441,-0.210,0.2840,1.0801,-0.822 +dark-grey,1,-172.00,fast,0.3499,1.00517,6.100,1.0062,0.9703,-0.650,0.3780,1.0610,-0.767 +dark-grey,1,-172.00,fast,0.3666,1.00605,4.550,0.9872,0.9970,-1.180,0.4660,1.0455,-0.685 +dark-grey,1,-172.00,fast,0.3832,1.00628,3.350,0.9688,1.0228,-1.730,0.5470,1.0335,-0.575 +dark-grey,1,-172.00,fast,0.3999,1.00610,2.440,0.9520,1.0466,-2.250,0.6190,1.0244,-0.466 +dark-grey,1,-172.00,fast,0.4189,1.00564,1.760,0.9376,1.0675,-2.700,0.6820,1.0176,-0.329 +dark-grey,1,-172.00,fast,0.4332,1.00500,1.260,0.9261,1.0849,-3.070,0.7360,1.0126,0.000 +dark-grey,1,-172.00,fast,0.4499,1.00436,0.890,0.9176,1.0986,-3.340,0.7820,1.0089,0.000 +dark-grey,1,-172.00,fast,0.4666,1.00366,0.630,0.9121,1.1083,-3.520,0.8210,1.0063,0.000 +dark-grey,1,-172.00,fast,0.4832,1.00308,0.450,0.9094,1.1142,-3.610,0.8530,1.0045,0.000 +dark-grey,1,-172.00,fast,0.4999,1.00250,0.000,0.9090,1.1166,-3.620,0.8800,1.0031,0.000 +dark-grey,1,-172.00,fast,0.5166,1.00198,0.000,0.9108,1.1162,-3.560,0.9030,1.0000,0.000 +dark-grey,1,-172.00,fast,0.5332,1.00157,0.000,0.9141,1.1134,-3.450,0.9210,1.0000,0.000 +dark-grey,1,-172.00,fast,0.5499,1.00116,0.000,0.9186,1.1087,-3.300,0.9360,1.0000,0.000 +dark-grey,1,-172.00,fast,0.5666,1.00087,0.000,0.9239,1.1026,-3.110,0.9490,1.0000,0.000 +dark-grey,1,-172.00,fast,0.5832,1.00000,0.000,0.9298,1.0955,-2.900,0.9590,1.0000,0.000 +dark-grey,1,-172.00,fast,0.5999,1.00000,0.000,0.9359,1.0879,-2.680,0.9670,1.0000,0.000 +dark-grey,1,-172.00,fast,0.6165,1.00000,0.000,0.9422,1.0800,-2.410,0.9730,1.0000,0.000 +dark-grey,1,-172.00,fast,0.6332,1.00000,0.000,0.9485,1.0717,-2.090,0.9790,1.0000,0.000 +dark-grey,1,-172.00,fast,0.6499,1.00000,0.000,0.9549,1.0631,-1.760,0.9830,1.0000,0.000 +dark-grey,1,-172.00,fast,0.6665,1.00000,0.000,0.9614,1.0544,-1.440,0.9860,1.0000,0.000 +dark-grey,1,-172.00,fast,0.6832,1.00000,0.000,0.9677,1.0458,-1.150,0.9890,1.0000,0.000 +dark-grey,1,-172.00,fast,0.6998,1.00000,0.000,0.9737,1.0376,-0.900,0.9910,1.0000,0.000 +dark-grey,1,-172.00,fast,0.7165,1.00000,0.000,0.9792,1.0299,-0.690,0.9930,1.0000,0.000 +dark-grey,1,-172.00,fast,0.7332,1.00000,0.000,0.9843,1.0229,-0.510,0.9950,1.0000,0.000 +dark-grey,1,-172.00,fast,0.7498,1.00000,0.000,0.9886,1.0167,-0.360,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.7665,1.00000,0.000,0.9923,1.0115,-0.240,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.7832,1.00000,0.000,0.9953,1.0072,-0.150,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.7998,1.00000,0.000,0.9977,1.0038,-0.070,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.8165,1.00000,0.000,0.9994,1.0013,-0.010,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.8331,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.8498,1.00000,0.000,1.0013,0.9985,0.080,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.8665,1.00000,0.000,1.0017,0.9980,0.110,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.8842,1.00000,0.000,1.0017,0.9979,0.130,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.9011,1.00000,0.000,1.0015,0.9981,0.150,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.9165,1.00000,0.000,1.0011,0.9986,0.170,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.9347,1.00000,0.000,1.0007,0.9991,0.180,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.9515,1.00000,0.000,1.0002,0.9997,0.180,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.9683,1.00000,0.000,0.9998,1.0004,0.180,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.9831,1.00000,0.000,0.9993,1.0009,0.180,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,0.9997,1.00000,0.000,1.0035,0.9955,0.390,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.0163,1.00000,0.000,1.0110,0.9856,0.750,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.0345,1.00000,0.000,1.0146,0.9810,0.910,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.0512,1.00000,0.000,1.0139,0.9823,0.950,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.0663,1.00000,0.000,1.0117,0.9854,0.990,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.0847,1.00000,0.000,1.0096,0.9884,0.980,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.1007,1.00000,0.000,1.0077,0.9910,0.930,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.1179,1.00000,0.000,1.0059,0.9935,0.860,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.1346,1.00000,0.000,1.0038,0.9962,0.800,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.1512,1.00000,0.000,1.0014,0.9994,0.760,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.1681,1.00000,0.000,0.9988,1.0029,0.730,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.1846,1.00000,0.000,0.9960,1.0066,0.730,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.1996,1.00000,0.000,0.9932,1.0104,0.740,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.2179,1.00000,0.000,0.9906,1.0138,0.750,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.2350,1.00000,0.000,0.9884,1.0169,0.770,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.2514,1.00000,0.000,0.9866,1.0193,0.780,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.2677,1.00000,0.000,0.9853,1.0210,0.780,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.2845,1.00000,0.000,0.9845,1.0221,0.770,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.3010,1.00000,0.000,0.9842,1.0224,0.750,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.3181,1.00000,0.000,0.9843,1.0222,0.710,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.3347,1.00000,0.000,0.9848,1.0214,0.670,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.3510,1.00000,0.000,0.9856,1.0203,0.620,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.3681,1.00000,0.000,0.9866,1.0188,0.570,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.3830,1.00000,0.000,0.9877,1.0172,0.510,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.4011,1.00000,0.000,0.9890,1.0155,0.440,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.4179,1.00000,0.000,0.9903,1.0136,0.370,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.4346,1.00000,0.000,0.9917,1.0117,0.300,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.4497,1.00000,0.000,0.9930,1.0098,0.240,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.4679,1.00000,0.000,0.9943,1.0080,0.190,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.4846,1.00000,0.000,0.9955,1.0063,0.150,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.5010,1.00000,0.000,0.9966,1.0048,0.110,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.5180,1.00000,0.000,0.9975,1.0034,0.080,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.5347,1.00000,0.000,0.9983,1.0023,0.060,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.5493,1.00000,0.000,0.9990,1.0014,0.040,1.0000,1.0000,0.000 +dark-grey,1,-172.00,fast,1.5669,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.0000,0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.0167,0.02081,0.770,1.0000,1.0000,0.000,0.9520,1.0077,0.493 +dark-grey,2,86.00,fast,0.0334,0.08733,2.860,1.0055,0.9926,0.300,0.8210,1.0286,2.000 +dark-grey,2,86.00,fast,0.0514,0.18012,5.310,1.0194,0.9738,1.090,0.6680,1.0531,4.110 +dark-grey,2,86.00,fast,0.0667,0.28477,7.620,1.0385,0.9479,2.200,0.5240,1.0762,6.412 +dark-grey,2,86.00,fast,0.0833,0.39105,9.600,1.0605,0.9181,3.530,0.4000,1.0960,8.056 +dark-grey,2,86.00,fast,0.0999,0.49279,11.210,1.0837,0.8867,4.990,0.3000,1.1121,8.658 +dark-grey,2,86.00,fast,0.1166,0.58581,12.460,1.1049,0.8568,6.370,0.2210,1.1246,8.467 +dark-grey,2,86.00,fast,0.1332,0.66814,13.420,1.1172,0.8389,7.200,0.1610,1.1342,7.727 +dark-grey,2,86.00,fast,0.1499,0.73919,14.140,1.1153,0.8397,7.070,0.1160,1.1414,6.658 +dark-grey,2,86.00,fast,0.1666,0.79907,14.670,1.1044,0.8525,6.500,0.0830,1.1467,5.453 +dark-grey,2,86.00,fast,0.1834,0.84849,15.050,1.0929,0.8671,5.860,0.0590,1.1505,4.192 +dark-grey,2,86.00,fast,0.1999,0.88849,15.330,1.0842,0.8776,5.070,0.0420,1.1533,3.014 +dark-grey,2,86.00,fast,0.2189,0.92023,15.530,1.0783,0.8841,4.160,0.0300,1.1553,1.973 +dark-grey,2,86.00,fast,0.2332,0.94500,16.000,1.0740,0.8884,3.230,0.0210,1.1567,1.096 +dark-grey,2,86.00,fast,0.2499,0.96384,14.930,1.0697,0.8932,2.370,0.0430,1.1470,0.384 +dark-grey,2,86.00,fast,0.2666,0.97802,12.720,1.0641,0.9006,1.630,0.1070,1.1256,0.000 +dark-grey,2,86.00,fast,0.2833,0.98826,10.280,1.0565,0.9114,1.070,0.1930,1.1016,-0.493 +dark-grey,2,86.00,fast,0.2999,0.99547,8.010,1.0468,0.9254,0.710,0.2860,1.0794,-0.712 +dark-grey,2,86.00,fast,0.3166,1.00023,6.100,1.0353,0.9422,0.550,0.3790,1.0604,-0.795 +dark-grey,2,86.00,fast,0.3333,1.00337,4.550,1.0227,0.9608,0.550,0.4670,1.0452,-0.822 +dark-grey,2,86.00,fast,0.3499,1.00523,3.350,1.0095,0.9802,0.670,0.5480,1.0333,-0.767 +dark-grey,2,86.00,fast,0.3666,1.00605,2.440,0.9967,0.9992,0.870,0.6200,1.0242,-0.685 +dark-grey,2,86.00,fast,0.3832,1.00628,1.760,0.9847,1.0169,1.100,0.6820,1.0175,-0.575 +dark-grey,2,86.00,fast,0.4000,1.00605,1.260,0.9742,1.0325,1.320,0.7360,1.0125,-0.466 +dark-grey,2,86.00,fast,0.4166,1.00558,0.890,0.9656,1.0455,1.530,0.7820,1.0089,-0.329 +dark-grey,2,86.00,fast,0.4333,1.00500,0.630,0.9588,1.0557,1.690,0.8210,1.0063,0.000 +dark-grey,2,86.00,fast,0.4499,1.00430,0.450,0.9541,1.0629,1.810,0.8530,1.0044,0.000 +dark-grey,2,86.00,fast,0.4666,1.00372,0.000,0.9512,1.0675,1.890,0.8800,1.0000,0.000 +dark-grey,2,86.00,fast,0.4832,1.00302,0.000,0.9500,1.0695,1.910,0.9030,1.0000,0.000 +dark-grey,2,86.00,fast,0.4999,1.00244,0.000,0.9503,1.0696,1.900,0.9210,1.0000,0.000 +dark-grey,2,86.00,fast,0.5165,1.00000,0.000,0.9517,1.0679,1.850,0.9360,1.0000,0.000 +dark-grey,2,86.00,fast,0.5333,1.00000,0.000,0.9539,1.0649,1.770,0.9490,1.0000,0.000 +dark-grey,2,86.00,fast,0.5499,1.00000,0.000,0.9568,1.0610,1.680,0.9590,1.0000,0.000 +dark-grey,2,86.00,fast,0.5666,1.00000,0.000,0.9601,1.0565,1.570,0.9670,1.0000,0.000 +dark-grey,2,86.00,fast,0.5832,1.00000,0.000,0.9635,1.0516,1.450,0.9730,1.0000,0.000 +dark-grey,2,86.00,fast,0.5999,1.00000,0.000,0.9671,1.0466,1.300,0.9790,1.0000,0.000 +dark-grey,2,86.00,fast,0.6165,1.00000,0.000,0.9708,1.0414,1.130,0.9830,1.0000,0.000 +dark-grey,2,86.00,fast,0.6332,1.00000,0.000,0.9746,1.0362,0.960,0.9860,1.0000,0.000 +dark-grey,2,86.00,fast,0.6499,1.00000,0.000,0.9783,1.0309,0.790,0.9890,1.0000,0.000 +dark-grey,2,86.00,fast,0.6665,1.00000,0.000,0.9819,1.0258,0.630,0.9910,1.0000,0.000 +dark-grey,2,86.00,fast,0.6832,1.00000,0.000,0.9853,1.0209,0.500,0.9930,1.0000,0.000 +dark-grey,2,86.00,fast,0.6999,1.00000,0.000,0.9885,1.0165,0.380,0.9950,1.0000,0.000 +dark-grey,2,86.00,fast,0.7165,1.00000,0.000,0.9913,1.0124,0.280,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.7331,1.00000,0.000,0.9938,1.0089,0.210,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.7498,1.00000,0.000,0.9959,1.0060,0.140,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.7665,1.00000,0.000,0.9975,1.0036,0.090,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.7831,1.00000,0.000,0.9989,1.0017,0.050,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.7998,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.8179,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.8332,1.00000,0.000,1.0009,0.9987,-0.030,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.8506,1.00000,0.000,1.0011,0.9985,-0.050,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.8679,1.00000,0.000,1.0011,0.9984,-0.060,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.8832,1.00000,0.000,1.0010,0.9986,-0.070,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.8998,1.00000,0.000,1.0008,0.9989,-0.080,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.9182,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.9331,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.9498,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.9664,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.9845,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,0.9997,1.00000,0.000,1.0038,0.9951,0.120,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.0176,1.00000,0.000,1.0115,0.9850,0.500,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.0344,1.00000,0.000,1.0153,0.9801,0.690,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.0510,1.00000,0.000,1.0147,0.9812,0.770,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.0676,1.00000,0.000,1.0126,0.9843,0.840,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.0844,1.00000,0.000,1.0104,0.9874,0.870,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.0997,1.00000,0.000,1.0084,0.9900,0.850,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.1177,1.00000,0.000,1.0065,0.9926,0.800,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.1343,1.00000,0.000,1.0044,0.9954,0.760,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.1511,1.00000,0.000,1.0021,0.9985,0.720,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.1677,1.00000,0.000,0.9994,1.0020,0.710,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.1843,1.00000,0.000,0.9966,1.0058,0.710,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.2011,1.00000,0.000,0.9938,1.0095,0.730,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.2177,1.00000,0.000,0.9912,1.0130,0.740,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.2345,1.00000,0.000,0.9889,1.0161,0.760,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.2510,1.00000,0.000,0.9871,1.0186,0.780,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.2676,1.00000,0.000,0.9857,1.0204,0.780,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.2830,1.00000,0.000,0.9848,1.0216,0.770,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.3010,1.00000,0.000,0.9845,1.0220,0.750,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.3183,1.00000,0.000,0.9845,1.0219,0.720,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.3344,1.00000,0.000,0.9850,1.0212,0.680,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.3510,1.00000,0.000,0.9857,1.0202,0.630,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.3677,1.00000,0.000,0.9866,1.0188,0.580,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.3843,1.00000,0.000,0.9877,1.0173,0.520,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.4018,1.00000,0.000,0.9889,1.0156,0.450,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.4346,1.00000,0.000,0.9915,1.0120,0.310,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.4509,1.00000,0.000,0.9928,1.0101,0.250,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.4677,1.00000,0.000,0.9941,1.0083,0.190,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.4843,1.00000,0.000,0.9953,1.0066,0.150,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.5010,1.00000,0.000,0.9964,1.0051,0.110,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.5170,1.00000,0.000,0.9973,1.0037,0.080,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.5335,1.00000,0.000,0.9982,1.0025,0.060,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.5503,1.00000,0.000,0.9989,1.0016,0.040,1.0000,1.0000,0.000 +dark-grey,2,86.00,fast,1.5669,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.0000,0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.0167,0.02105,0.780,1.0000,1.0000,0.000,0.9520,1.0078,0.493 +dark-grey,3,86.00,fast,0.0333,0.08779,2.880,1.0055,0.9926,0.300,0.8200,1.0288,2.028 +dark-grey,3,86.00,fast,0.0512,0.18070,5.320,1.0194,0.9738,1.090,0.6670,1.0532,4.137 +dark-grey,3,86.00,fast,0.0668,0.28523,7.630,1.0385,0.9479,2.200,0.5230,1.0763,6.412 +dark-grey,3,86.00,fast,0.0833,0.39163,9.610,1.0605,0.9181,3.530,0.3990,1.0961,8.083 +dark-grey,3,86.00,fast,0.0999,0.49326,11.210,1.0837,0.8867,4.990,0.2990,1.1121,8.658 +dark-grey,3,86.00,fast,0.1166,0.58616,12.470,1.1047,0.8570,6.360,0.2210,1.1247,8.467 +dark-grey,3,86.00,fast,0.1332,0.66860,13.430,1.1166,0.8398,7.160,0.1610,1.1343,7.727 +dark-grey,3,86.00,fast,0.1499,0.73953,14.140,1.1143,0.8412,7.590,0.1160,1.1414,6.658 +dark-grey,3,86.00,fast,0.1665,0.79942,14.670,1.1036,0.8535,7.830,0.0830,1.1467,5.453 +dark-grey,3,86.00,fast,0.1833,0.84872,15.050,1.0932,0.8661,7.650,0.0590,1.1505,4.192 +dark-grey,3,86.00,fast,0.1998,0.88872,15.330,1.0858,0.8744,7.070,0.0420,1.1533,3.014 +dark-grey,3,86.00,fast,0.2189,0.92047,15.530,1.0812,0.8788,6.200,0.0300,1.1553,1.973 +dark-grey,3,86.00,fast,0.2333,0.94512,16.000,1.0779,0.8814,5.220,0.0210,1.1567,1.096 +dark-grey,3,86.00,fast,0.2499,0.96395,14.930,1.0742,0.8852,4.220,0.0430,1.1470,0.384 +dark-grey,3,86.00,fast,0.2666,0.97802,12.720,1.0686,0.8925,3.320,0.1070,1.1256,0.000 +dark-grey,3,86.00,fast,0.2832,0.98826,10.280,1.0607,0.9038,2.580,0.1930,1.1016,-0.493 +dark-grey,3,86.00,fast,0.3000,0.99547,8.010,1.0502,0.9191,2.040,0.2860,1.0794,-0.712 +dark-grey,3,86.00,fast,0.3166,1.00035,6.100,1.0376,0.9375,1.710,0.3790,1.0604,-0.795 +dark-grey,3,86.00,fast,0.3332,1.00337,4.550,1.0237,0.9581,1.550,0.4670,1.0452,-0.822 +dark-grey,3,86.00,fast,0.3499,1.00523,3.350,1.0093,0.9794,1.530,0.5480,1.0333,-0.767 +dark-grey,3,86.00,fast,0.3665,1.00605,2.440,0.9953,1.0003,1.600,0.6200,1.0242,-0.685 +dark-grey,3,86.00,fast,0.3832,1.00628,1.760,0.9824,1.0195,1.720,0.6820,1.0175,-0.575 +dark-grey,3,86.00,fast,0.3999,1.00605,1.260,0.9712,1.0362,1.840,0.7360,1.0125,-0.466 +dark-grey,3,86.00,fast,0.4166,1.00558,0.890,0.9621,1.0500,1.950,0.7820,1.0089,-0.329 +dark-grey,3,86.00,fast,0.4332,1.00500,0.630,0.9552,1.0604,2.040,0.8210,1.0063,0.000 +dark-grey,3,86.00,fast,0.4499,1.00430,0.450,0.9506,1.0676,2.080,0.8530,1.0044,0.000 +dark-grey,3,86.00,fast,0.4666,1.00372,0.000,0.9480,1.0718,2.080,0.8800,1.0000,0.000 +dark-grey,3,86.00,fast,0.4832,1.00302,0.000,0.9473,1.0733,2.050,0.9030,1.0000,0.000 +dark-grey,3,86.00,fast,0.4998,1.00244,0.000,0.9481,1.0726,1.980,0.9210,1.0000,0.000 +dark-grey,3,86.00,fast,0.5165,1.00000,0.000,0.9500,1.0701,1.880,0.9360,1.0000,0.000 +dark-grey,3,86.00,fast,0.5332,1.00000,0.000,0.9529,1.0664,1.770,0.9490,1.0000,0.000 +dark-grey,3,86.00,fast,0.5499,1.00000,0.000,0.9563,1.0617,1.640,0.9590,1.0000,0.000 +dark-grey,3,86.00,fast,0.5665,1.00000,0.000,0.9600,1.0565,1.480,0.9670,1.0000,0.000 +dark-grey,3,86.00,fast,0.5832,1.00000,0.000,0.9641,1.0510,1.290,0.9730,1.0000,0.000 +dark-grey,3,86.00,fast,0.5999,1.00000,0.000,0.9683,1.0451,1.090,0.9790,1.0000,0.000 +dark-grey,3,86.00,fast,0.6181,1.00000,0.000,0.9726,1.0390,0.900,0.9830,1.0000,0.000 +dark-grey,3,86.00,fast,0.6332,1.00000,0.000,0.9769,1.0330,0.720,0.9860,1.0000,0.000 +dark-grey,3,86.00,fast,0.6517,1.00000,0.000,0.9810,1.0272,0.560,0.9890,1.0000,0.000 +dark-grey,3,86.00,fast,0.6683,1.00000,0.000,0.9848,1.0217,0.430,0.9910,1.0000,0.000 +dark-grey,3,86.00,fast,0.6832,1.00000,0.000,0.9883,1.0168,0.320,0.9930,1.0000,0.000 +dark-grey,3,86.00,fast,0.6999,1.00000,0.000,0.9913,1.0124,0.230,0.9950,1.0000,0.000 +dark-grey,3,86.00,fast,0.7165,1.00000,0.000,0.9940,1.0087,0.160,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.7347,1.00000,0.000,0.9961,1.0056,0.100,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.7498,1.00000,0.000,0.9979,1.0032,0.050,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.7679,1.00000,0.000,0.9992,1.0013,0.010,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.7847,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.7998,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.8180,1.00000,0.000,1.0010,0.9986,-0.060,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.8331,1.00000,0.000,1.0011,0.9985,-0.080,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.8512,1.00000,0.000,1.0010,0.9986,-0.090,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.8664,1.00000,0.000,1.0009,0.9988,-0.100,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.8847,1.00000,0.000,1.0006,0.9991,-0.100,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.8998,1.00000,0.000,1.0003,0.9995,-0.110,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.9182,1.00000,0.000,1.0001,0.9999,-0.110,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.9332,1.00000,0.000,0.9998,1.0003,-0.110,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.9512,1.00000,0.000,0.9996,1.0006,-0.110,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.9677,1.00000,0.000,0.9994,1.0009,-0.100,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.9830,1.00000,0.000,0.9992,1.0011,-0.100,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,0.9997,1.00000,0.000,1.0036,0.9954,0.120,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.0163,1.00000,0.000,1.0114,0.9851,0.500,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.0330,1.00000,0.000,1.0152,0.9802,0.700,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.0496,1.00000,0.000,1.0147,0.9812,0.780,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.0663,1.00000,0.000,1.0126,0.9843,0.850,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.0830,1.00000,0.000,1.0105,0.9873,0.870,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.0997,1.00000,0.000,1.0086,0.9899,0.850,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.1163,1.00000,0.000,1.0067,0.9924,0.800,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.1330,1.00000,0.000,1.0046,0.9951,0.760,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.1497,1.00000,0.000,1.0022,0.9983,0.720,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.1663,1.00000,0.000,0.9996,1.0018,0.710,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.1830,1.00000,0.000,0.9967,1.0056,0.710,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.1996,1.00000,0.000,0.9939,1.0094,0.730,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.2163,1.00000,0.000,0.9913,1.0130,0.750,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.2330,1.00000,0.000,0.9890,1.0161,0.760,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.2497,1.00000,0.000,0.9871,1.0186,0.780,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.2663,1.00000,0.000,0.9857,1.0205,0.780,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.2830,1.00000,0.000,0.9848,1.0216,0.770,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.2997,1.00000,0.000,0.9844,1.0221,0.750,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.3163,1.00000,0.000,0.9845,1.0220,0.720,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.3330,1.00000,0.000,0.9849,1.0213,0.680,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.3497,1.00000,0.000,0.9856,1.0203,0.630,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.3663,1.00000,0.000,0.9865,1.0189,0.580,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.3830,1.00000,0.000,0.9876,1.0174,0.530,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.3997,1.00000,0.000,0.9888,1.0157,0.470,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.4163,1.00000,0.000,0.9901,1.0139,0.400,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.4330,1.00000,0.000,0.9914,1.0121,0.340,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.4496,1.00000,0.000,0.9927,1.0102,0.280,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.4663,1.00000,0.000,0.9940,1.0084,0.220,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.4830,1.00000,0.000,0.9952,1.0067,0.180,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.4996,1.00000,0.000,0.9963,1.0052,0.140,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.5159,1.00000,0.000,0.9973,1.0038,0.100,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.5326,1.00000,0.000,0.9981,1.0026,0.080,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.5493,1.00000,0.000,0.9988,1.0017,0.050,1.0000,1.0000,0.000 +dark-grey,3,86.00,fast,1.5660,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.0000,-0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.0166,0.02233,0.820,1.0000,1.0000,0.000,0.9490,1.0082,0.521 +dark-grey,4,-86.00,fast,0.0333,0.09000,2.940,1.0055,0.9926,-0.300,0.8160,1.0294,2.082 +dark-grey,4,-86.00,fast,0.0513,0.18349,5.390,1.0194,0.9738,-1.090,0.6630,1.0539,4.192 +dark-grey,4,-86.00,fast,0.0667,0.28826,7.690,1.0385,0.9479,-2.200,0.5190,1.0769,6.466 +dark-grey,4,-86.00,fast,0.0833,0.39453,9.660,1.0605,0.9181,-3.530,0.3960,1.0966,8.138 +dark-grey,4,-86.00,fast,0.0999,0.49593,11.250,1.0837,0.8867,-4.990,0.2970,1.1125,8.713 +dark-grey,4,-86.00,fast,0.1166,0.58860,12.500,1.1049,0.8568,-6.370,0.2190,1.1250,8.521 +dark-grey,4,-86.00,fast,0.1333,0.67070,13.450,1.1171,0.8390,-7.200,0.1590,1.1345,7.782 +dark-grey,4,-86.00,fast,0.1499,0.74140,14.160,1.1153,0.8398,-7.070,0.1150,1.1416,6.686 +dark-grey,4,-86.00,fast,0.1666,0.80081,14.680,1.1044,0.8525,-6.490,0.0820,1.1468,5.453 +dark-grey,4,-86.00,fast,0.1833,0.85000,15.060,1.0930,0.8669,-5.850,0.0590,1.1506,4.192 +dark-grey,4,-86.00,fast,0.1999,0.88965,15.340,1.0844,0.8773,-5.050,0.0420,1.1534,3.014 +dark-grey,4,-86.00,fast,0.2188,0.92116,15.530,1.0785,0.8837,-4.140,0.0290,1.1553,1.973 +dark-grey,4,-86.00,fast,0.2332,0.94570,16.000,1.0743,0.8879,-3.220,0.0200,1.1567,1.096 +dark-grey,4,-86.00,fast,0.2499,0.96442,14.930,1.0700,0.8928,-2.350,0.0430,1.1470,0.384 +dark-grey,4,-86.00,fast,0.2666,0.97837,12.720,1.0643,0.9003,-1.610,0.1070,1.1256,0.000 +dark-grey,4,-86.00,fast,0.2832,0.98849,10.280,1.0566,0.9111,-1.060,0.1930,1.1017,-0.493 +dark-grey,4,-86.00,fast,0.3000,0.99558,8.010,1.0469,0.9252,-0.710,0.2860,1.0794,-0.712 +dark-grey,4,-86.00,fast,0.3166,1.00047,6.100,1.0354,0.9421,-0.550,0.3790,1.0604,-0.822 +dark-grey,4,-86.00,fast,0.3332,1.00349,4.550,1.0227,0.9607,-0.550,0.4670,1.0452,-0.822 +dark-grey,4,-86.00,fast,0.3499,1.00523,3.350,1.0095,0.9802,-0.670,0.5480,1.0333,-0.767 +dark-grey,4,-86.00,fast,0.3666,1.00605,2.440,0.9967,0.9992,-0.870,0.6200,1.0242,-0.685 +dark-grey,4,-86.00,fast,0.3832,1.00628,1.760,0.9847,1.0169,-1.090,0.6820,1.0175,-0.575 +dark-grey,4,-86.00,fast,0.3999,1.00605,1.260,0.9743,1.0325,-1.320,0.7360,1.0125,-0.466 +dark-grey,4,-86.00,fast,0.4165,1.00558,0.890,0.9656,1.0455,-1.520,0.7820,1.0089,-0.329 +dark-grey,4,-86.00,fast,0.4333,1.00500,0.630,0.9589,1.0556,-1.690,0.8210,1.0063,0.000 +dark-grey,4,-86.00,fast,0.4499,1.00430,0.450,0.9542,1.0628,-1.810,0.8530,1.0044,0.000 +dark-grey,4,-86.00,fast,0.4665,1.00372,0.000,0.9514,1.0673,-1.880,0.8800,1.0000,0.000 +dark-grey,4,-86.00,fast,0.4831,1.00302,0.000,0.9502,1.0693,-1.910,0.9030,1.0000,0.000 +dark-grey,4,-86.00,fast,0.4999,1.00244,0.000,0.9504,1.0693,-1.890,0.9210,1.0000,0.000 +dark-grey,4,-86.00,fast,0.5165,1.00000,0.000,0.9518,1.0676,-1.840,0.9360,1.0000,0.000 +dark-grey,4,-86.00,fast,0.5332,1.00000,0.000,0.9541,1.0646,-1.770,0.9490,1.0000,0.000 +dark-grey,4,-86.00,fast,0.5499,1.00000,0.000,0.9570,1.0607,-1.670,0.9590,1.0000,0.000 +dark-grey,4,-86.00,fast,0.5665,1.00000,0.000,0.9602,1.0562,-1.560,0.9670,1.0000,0.000 +dark-grey,4,-86.00,fast,0.5835,1.00000,0.000,0.9637,1.0514,-1.440,0.9730,1.0000,0.000 +dark-grey,4,-86.00,fast,0.5998,1.00000,0.000,0.9673,1.0464,-1.300,0.9790,1.0000,0.000 +dark-grey,4,-86.00,fast,0.6166,1.00000,0.000,0.9710,1.0412,-1.130,0.9830,1.0000,0.000 +dark-grey,4,-86.00,fast,0.6332,1.00000,0.000,0.9747,1.0359,-0.950,0.9860,1.0000,0.000 +dark-grey,4,-86.00,fast,0.6498,1.00000,0.000,0.9784,1.0307,-0.780,0.9890,1.0000,0.000 +dark-grey,4,-86.00,fast,0.6665,1.00000,0.000,0.9820,1.0256,-0.630,0.9910,1.0000,0.000 +dark-grey,4,-86.00,fast,0.6832,1.00000,0.000,0.9854,1.0208,-0.490,0.9930,1.0000,0.000 +dark-grey,4,-86.00,fast,0.6997,1.00000,0.000,0.9886,1.0163,-0.380,0.9950,1.0000,0.000 +dark-grey,4,-86.00,fast,0.7164,1.00000,0.000,0.9914,1.0123,-0.280,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.7331,1.00000,0.000,0.9938,1.0088,-0.200,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.7498,1.00000,0.000,0.9959,1.0059,-0.140,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.7664,1.00000,0.000,0.9976,1.0035,-0.090,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.7831,1.00000,0.000,0.9989,1.0016,-0.050,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.7998,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.8164,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.8331,1.00000,0.000,1.0009,0.9987,0.030,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.8498,1.00000,0.000,1.0011,0.9985,0.050,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.8665,1.00000,0.000,1.0011,0.9984,0.060,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.8831,1.00000,0.000,1.0010,0.9986,0.070,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.8998,1.00000,0.000,1.0008,0.9989,0.080,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.9181,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.9349,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.9498,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,0.9677,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.0012,1.00000,0.000,0.9992,1.0011,0.100,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.0343,1.00000,0.000,0.9989,1.0016,0.080,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.0497,1.00000,0.000,0.9955,1.0063,0.020,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.0677,1.00000,0.000,0.9921,1.0111,0.030,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.0845,1.00000,0.000,0.9897,1.0145,0.050,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.1013,1.00000,0.000,0.9883,1.0165,0.060,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.1176,1.00000,0.000,0.9875,1.0176,0.080,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.1344,1.00000,0.000,0.9870,1.0184,0.120,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.1497,1.00000,0.000,0.9864,1.0191,0.170,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.1663,1.00000,0.000,0.9859,1.0199,0.230,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.1843,1.00000,0.000,0.9852,1.0208,0.310,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.2011,1.00000,0.000,0.9846,1.0217,0.390,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.2164,1.00000,0.000,0.9840,1.0225,0.460,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.2344,1.00000,0.000,0.9836,1.0231,0.530,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.2511,1.00000,0.000,0.9835,1.0233,0.580,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.2677,1.00000,0.000,0.9836,1.0231,0.610,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.2830,1.00000,0.000,0.9840,1.0226,0.630,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.3013,1.00000,0.000,0.9846,1.0217,0.630,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.3510,1.00000,0.000,0.9873,1.0179,0.570,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.3843,1.00000,0.000,0.9894,1.0150,0.490,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.4010,1.00000,0.000,0.9904,1.0135,0.450,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.4176,1.00000,0.000,0.9914,1.0121,0.410,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.4347,1.00000,0.000,0.9924,1.0108,0.370,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.4497,1.00000,0.000,0.9933,1.0095,0.330,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.4677,1.00000,0.000,0.9942,1.0082,0.280,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.4849,1.00000,0.000,0.9950,1.0070,0.230,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.5010,1.00000,0.000,0.9959,1.0058,0.190,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.5164,1.00000,0.000,0.9967,1.0047,0.150,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.5338,1.00000,0.000,0.9974,1.0037,0.120,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.5504,1.00000,0.000,0.9980,1.0028,0.090,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.5672,1.00000,0.000,0.9986,1.0020,0.060,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.5837,1.00000,0.000,0.9990,1.0014,0.050,1.0000,1.0000,0.000 +dark-grey,4,-86.00,fast,1.6004,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.0000,-0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.0167,0.02221,0.820,1.0000,1.0000,0.000,0.9490,1.0082,0.521 +dark-grey,5,-86.00,fast,0.0334,0.08977,2.930,1.0055,0.9926,-0.300,0.8170,1.0293,2.055 +dark-grey,5,-86.00,fast,0.0513,0.18314,5.380,1.0194,0.9738,-1.090,0.6640,1.0538,4.192 +dark-grey,5,-86.00,fast,0.0668,0.28791,7.680,1.0385,0.9479,-2.200,0.5200,1.0768,6.466 +dark-grey,5,-86.00,fast,0.0833,0.39419,9.650,1.0605,0.9181,-3.530,0.3970,1.0965,8.138 +dark-grey,5,-86.00,fast,0.1000,0.49558,11.250,1.0837,0.8867,-4.990,0.2970,1.1125,8.713 +dark-grey,5,-86.00,fast,0.1167,0.58837,12.500,1.1047,0.8570,-6.360,0.2190,1.1250,8.521 +dark-grey,5,-86.00,fast,0.1334,0.67047,13.450,1.1166,0.8398,-7.160,0.1600,1.1345,7.782 +dark-grey,5,-86.00,fast,0.1500,0.74116,14.160,1.1143,0.8413,-7.580,0.1150,1.1416,6.686 +dark-grey,5,-86.00,fast,0.1669,0.80070,14.680,1.1037,0.8535,-7.820,0.0830,1.1468,5.453 +dark-grey,5,-86.00,fast,0.1833,0.84977,15.060,1.0934,0.8660,-7.640,0.0590,1.1506,4.192 +dark-grey,5,-86.00,fast,0.2000,0.88953,15.340,1.0860,0.8743,-7.050,0.0420,1.1534,3.014 +dark-grey,5,-86.00,fast,0.2191,0.92105,15.530,1.0814,0.8785,-6.180,0.0290,1.1553,1.973 +dark-grey,5,-86.00,fast,0.2334,0.94570,16.000,1.0782,0.8812,-5.200,0.0210,1.1567,1.096 +dark-grey,5,-86.00,fast,0.2499,0.96442,14.930,1.0744,0.8851,-4.210,0.0430,1.1470,0.384 +dark-grey,5,-86.00,fast,0.2666,0.97837,12.720,1.0688,0.8924,-3.300,0.1070,1.1256,0.000 +dark-grey,5,-86.00,fast,0.2834,0.98849,10.280,1.0608,0.9038,-2.570,0.1930,1.1017,-0.493 +dark-grey,5,-86.00,fast,0.2999,0.99558,8.010,1.0502,0.9191,-2.030,0.2860,1.0794,-0.712 +dark-grey,5,-86.00,fast,0.3167,1.00035,6.100,1.0376,0.9377,-1.700,0.3790,1.0604,-0.822 +dark-grey,5,-86.00,fast,0.3333,1.00349,4.550,1.0237,0.9582,-1.550,0.4670,1.0452,-0.822 +dark-grey,5,-86.00,fast,0.3499,1.00523,3.350,1.0093,0.9796,-1.530,0.5480,1.0333,-0.767 +dark-grey,5,-86.00,fast,0.3666,1.00605,2.440,0.9952,1.0004,-1.600,0.6200,1.0242,-0.685 +dark-grey,5,-86.00,fast,0.3833,1.00628,1.760,0.9824,1.0196,-1.720,0.6820,1.0175,-0.575 +dark-grey,5,-86.00,fast,0.4000,1.00605,1.260,0.9712,1.0363,-1.840,0.7360,1.0125,-0.466 +dark-grey,5,-86.00,fast,0.4167,1.00558,0.890,0.9621,1.0500,-1.950,0.7820,1.0089,-0.329 +dark-grey,5,-86.00,fast,0.4333,1.00500,0.630,0.9553,1.0604,-2.030,0.8210,1.0063,0.000 +dark-grey,5,-86.00,fast,0.4500,1.00430,0.450,0.9506,1.0676,-2.080,0.8530,1.0044,0.000 +dark-grey,5,-86.00,fast,0.4666,1.00372,0.000,0.9481,1.0717,-2.080,0.8800,1.0000,0.000 +dark-grey,5,-86.00,fast,0.4833,1.00302,0.000,0.9474,1.0732,-2.040,0.9030,1.0000,0.000 +dark-grey,5,-86.00,fast,0.4999,1.00244,0.000,0.9482,1.0724,-1.970,0.9210,1.0000,0.000 +dark-grey,5,-86.00,fast,0.5166,1.00000,0.000,0.9502,1.0699,-1.880,0.9360,1.0000,0.000 +dark-grey,5,-86.00,fast,0.5333,1.00000,0.000,0.9530,1.0662,-1.760,0.9490,1.0000,0.000 +dark-grey,5,-86.00,fast,0.5500,1.00000,0.000,0.9564,1.0615,-1.630,0.9590,1.0000,0.000 +dark-grey,5,-86.00,fast,0.5666,1.00000,0.000,0.9602,1.0563,-1.470,0.9670,1.0000,0.000 +dark-grey,5,-86.00,fast,0.5833,1.00000,0.000,0.9642,1.0508,-1.280,0.9730,1.0000,0.000 +dark-grey,5,-86.00,fast,0.6000,1.00000,0.000,0.9684,1.0449,-1.080,0.9790,1.0000,0.000 +dark-grey,5,-86.00,fast,0.6166,1.00000,0.000,0.9727,1.0388,-0.890,0.9830,1.0000,0.000 +dark-grey,5,-86.00,fast,0.6334,1.00000,0.000,0.9770,1.0328,-0.720,0.9860,1.0000,0.000 +dark-grey,5,-86.00,fast,0.6500,1.00000,0.000,0.9811,1.0270,-0.560,0.9890,1.0000,0.000 +dark-grey,5,-86.00,fast,0.6666,1.00000,0.000,0.9849,1.0216,-0.430,0.9910,1.0000,0.000 +dark-grey,5,-86.00,fast,0.6833,1.00000,0.000,0.9884,1.0167,-0.320,0.9930,1.0000,0.000 +dark-grey,5,-86.00,fast,0.7000,1.00000,0.000,0.9914,1.0123,-0.230,0.9950,1.0000,0.000 +dark-grey,5,-86.00,fast,0.7166,1.00000,0.000,0.9940,1.0086,-0.160,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.7333,1.00000,0.000,0.9962,1.0056,-0.100,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.7499,1.00000,0.000,0.9979,1.0031,-0.050,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.7665,1.00000,0.000,0.9992,1.0013,-0.020,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.7848,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.7998,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.8180,1.00000,0.000,1.0010,0.9986,0.060,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.8347,1.00000,0.000,1.0011,0.9985,0.080,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.8515,1.00000,0.000,1.0010,0.9986,0.090,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.8687,1.00000,0.000,1.0009,0.9988,0.100,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.8832,1.00000,0.000,1.0006,0.9991,0.100,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.9013,1.00000,0.000,1.0003,0.9995,0.110,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.9165,1.00000,0.000,1.0001,0.9999,0.110,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.9514,1.00000,0.000,0.9996,1.0006,0.110,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.9678,1.00000,0.000,0.9994,1.0009,0.100,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,0.9844,1.00000,0.000,0.9992,1.0011,0.100,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.0013,1.00000,0.000,1.0036,0.9954,0.300,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.0178,1.00000,0.000,1.0113,0.9852,0.670,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.0345,1.00000,0.000,1.0152,0.9802,0.840,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.0512,1.00000,0.000,1.0146,0.9813,0.900,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.0678,1.00000,0.000,1.0126,0.9843,0.950,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.0848,1.00000,0.000,1.0105,0.9872,0.950,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.1013,1.00000,0.000,1.0086,0.9898,0.910,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.1179,1.00000,0.000,1.0067,0.9924,0.850,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.1347,1.00000,0.000,1.0046,0.9952,0.790,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.1512,1.00000,0.000,1.0022,0.9984,0.750,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.1664,1.00000,0.000,0.9995,1.0020,0.730,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.1844,1.00000,0.000,0.9966,1.0058,0.730,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.2011,1.00000,0.000,0.9938,1.0096,0.740,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.2179,1.00000,0.000,0.9911,1.0132,0.750,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.2345,1.00000,0.000,0.9888,1.0163,0.770,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.2513,1.00000,0.000,0.9869,1.0188,0.780,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.2664,1.00000,0.000,0.9856,1.0206,0.780,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.2844,1.00000,0.000,0.9847,1.0217,0.770,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.3015,1.00000,0.000,0.9844,1.0222,0.750,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.3177,1.00000,0.000,0.9844,1.0220,0.720,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.3344,1.00000,0.000,0.9849,1.0213,0.670,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.3511,1.00000,0.000,0.9856,1.0202,0.630,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.3679,1.00000,0.000,0.9866,1.0188,0.570,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.3844,1.00000,0.000,0.9877,1.0173,0.510,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.4003,1.00000,0.000,0.9889,1.0155,0.440,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.4179,1.00000,0.000,0.9902,1.0137,0.370,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.4345,1.00000,0.000,0.9916,1.0118,0.310,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.4512,1.00000,0.000,0.9929,1.0099,0.250,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.4678,1.00000,0.000,0.9942,1.0081,0.190,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.4845,1.00000,0.000,0.9954,1.0064,0.150,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.5011,1.00000,0.000,0.9965,1.0049,0.110,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.5161,1.00000,0.000,0.9975,1.0035,0.080,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.5337,1.00000,0.000,0.9983,1.0024,0.060,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.5494,1.00000,0.000,0.9989,1.0015,0.040,1.0000,1.0000,0.000 +dark-grey,5,-86.00,fast,1.5661,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.0000,-0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.0167,0.02209,0.160,1.0000,1.0000,0.000,0.9490,1.0081,0.527 +light-grey-5b,3,-128.09,slow,0.0337,0.08962,0.700,1.0055,0.9926,-0.280,0.8170,1.0293,2.071 +light-grey-5b,3,-128.09,slow,0.0515,0.18292,1.500,1.0194,0.9738,-0.990,0.6640,1.0538,4.177 +light-grey-5b,3,-128.09,slow,0.0670,0.28769,2.470,1.0385,0.9479,-2.000,0.5200,1.0768,6.458 +light-grey-5b,3,-128.09,slow,0.0833,0.39394,3.550,1.0605,0.9181,-3.210,0.3970,1.0965,8.108 +light-grey-5b,3,-128.09,slow,0.1000,0.49543,4.680,1.0838,0.8867,-4.540,0.2970,1.1125,8.705 +light-grey-5b,3,-128.09,slow,0.1167,0.58818,5.820,1.1059,0.8558,-5.860,0.2190,1.1249,8.494 +light-grey-5b,3,-128.09,slow,0.1334,0.67031,6.940,1.1191,0.8369,-6.660,0.1600,1.1344,7.757 +light-grey-5b,3,-128.09,slow,0.1500,0.74104,8.000,1.1160,0.8382,-6.480,0.1150,1.1415,6.704 +light-grey-5b,3,-128.09,slow,0.1667,0.80061,9.000,1.1022,0.8531,-5.900,0.0830,1.1468,5.441 +light-grey-5b,3,-128.09,slow,0.1833,0.84972,9.930,1.0880,0.8700,-5.330,0.0590,1.1506,4.212 +light-grey-5b,3,-128.09,slow,0.2001,0.88953,10.780,1.0782,0.8811,-4.580,0.0420,1.1533,3.019 +light-grey-5b,3,-128.09,slow,0.2167,0.92107,11.550,1.0729,0.8858,-3.680,0.0290,1.1553,1.966 +light-grey-5b,3,-128.09,slow,0.2357,0.94559,12.240,1.0705,0.8868,-2.740,0.0210,1.1600,1.088 +light-grey-5b,3,-128.09,slow,0.2500,0.96432,12.850,1.0687,0.8876,-1.860,0.0140,1.1600,0.386 +light-grey-5b,3,-128.09,slow,0.2666,0.97830,12.370,1.0654,0.8913,-1.140,0.0390,1.1493,0.000 +light-grey-5b,3,-128.09,slow,0.2833,0.98845,10.710,1.0595,0.8998,-0.600,0.1040,1.1272,-0.491 +light-grey-5b,3,-128.09,slow,0.3000,0.99563,8.740,1.0504,0.9134,-0.300,0.1910,1.1028,-0.702 +light-grey-5b,3,-128.09,slow,0.3166,1.00039,6.870,1.0384,0.9315,-0.210,0.2850,1.0801,-0.807 +light-grey-5b,3,-128.09,slow,0.3334,1.00351,5.250,1.0242,0.9528,-0.310,0.3780,1.0610,-0.807 +light-grey-5b,3,-128.09,slow,0.3500,1.00523,3.940,1.0087,0.9758,-0.550,0.4670,1.0455,-0.772 +light-grey-5b,3,-128.09,slow,0.3666,1.00609,2.910,0.9930,0.9991,-0.860,0.5470,1.0335,-0.702 +light-grey-5b,3,-128.09,slow,0.3833,1.00632,2.120,0.9779,1.0213,-1.210,0.6190,1.0244,-0.562 +light-grey-5b,3,-128.09,slow,0.3999,1.00609,1.530,0.9644,1.0415,-1.540,0.6820,1.0176,-0.456 +light-grey-5b,3,-128.09,slow,0.4166,1.00562,1.100,0.9529,1.0590,-1.840,0.7360,1.0126,0.000 +light-grey-5b,3,-128.09,slow,0.4334,1.00500,0.780,0.9437,1.0731,-2.080,0.7820,1.0089,0.000 +light-grey-5b,3,-128.09,slow,0.4500,1.00437,0.550,0.9370,1.0836,-2.260,0.8210,1.0063,0.000 +light-grey-5b,3,-128.09,slow,0.4666,1.00367,0.390,0.9327,1.0906,-2.380,0.8530,1.0045,0.000 +light-grey-5b,3,-128.09,slow,0.4833,1.00304,0.000,0.9307,1.0945,-2.440,0.8800,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.4999,1.00250,0.000,0.9305,1.0955,-2.440,0.9030,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.5166,1.00203,0.000,0.9319,1.0941,-2.400,0.9210,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.5332,1.00156,0.000,0.9346,1.0909,-2.320,0.9360,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.5498,1.00000,0.000,0.9383,1.0862,-2.210,0.9490,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.5665,1.00000,0.000,0.9425,1.0806,-2.080,0.9590,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.5832,1.00000,0.000,0.9472,1.0743,-1.930,0.9670,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.6000,1.00000,0.000,0.9520,1.0677,-1.770,0.9730,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.6165,1.00000,0.000,0.9570,1.0609,-1.590,0.9790,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.6333,1.00000,0.000,0.9620,1.0539,-1.380,0.9830,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.6499,1.00000,0.000,0.9670,1.0468,-1.170,0.9860,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.6665,1.00000,0.000,0.9720,1.0398,-0.960,0.9890,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.6832,1.00000,0.000,0.9768,1.0331,-0.770,0.9910,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.6998,1.00000,0.000,0.9813,1.0268,-0.600,0.9930,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.7165,1.00000,0.000,0.9854,1.0210,-0.460,0.9950,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.7332,1.00000,0.000,0.9890,1.0158,-0.340,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.7499,1.00000,0.000,0.9922,1.0113,-0.240,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.7665,1.00000,0.000,0.9948,1.0075,-0.160,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.7832,1.00000,0.000,0.9970,1.0045,-0.100,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.7999,1.00000,0.000,0.9986,1.0021,-0.050,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.8165,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.8331,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.8497,1.00000,0.000,1.0012,0.9985,0.050,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.8666,1.00000,0.000,1.0014,0.9981,0.070,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.8831,1.00000,0.000,1.0014,0.9981,0.090,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.8999,1.00000,0.000,1.0012,0.9983,0.100,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.9187,1.00000,0.000,1.0010,0.9987,0.110,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.9350,1.00000,0.000,1.0007,0.9991,0.110,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.9499,1.00000,0.000,1.0003,0.9996,0.110,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.9680,1.00000,0.000,1.0000,1.0000,0.110,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,0.9831,1.00000,0.000,0.9997,1.0005,0.110,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.0021,1.00000,0.000,1.0018,0.9974,0.210,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.0164,1.00000,0.000,1.0057,0.9920,0.370,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.0355,1.00000,0.000,1.0077,0.9892,0.450,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.0517,1.00000,0.000,1.0079,0.9889,0.480,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.0685,1.00000,0.000,1.0073,0.9897,0.490,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.0848,1.00000,0.000,1.0066,0.9907,0.480,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.0997,1.00000,0.000,1.0058,0.9918,0.450,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.1185,1.00000,0.000,1.0050,0.9930,0.410,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.1343,1.00000,0.000,1.0040,0.9944,0.380,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.1499,1.00000,0.000,1.0029,0.9960,0.340,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.1683,1.00000,0.000,1.0016,0.9978,0.320,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.1850,1.00000,0.000,1.0003,0.9996,0.310,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.1996,1.00000,0.000,0.9989,1.0015,0.300,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.2181,1.00000,0.000,0.9977,1.0032,0.290,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.2347,1.00000,0.000,0.9966,1.0048,0.290,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.2691,1.00000,0.000,0.9950,1.0070,0.280,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.2850,1.00000,0.000,0.9946,1.0076,0.270,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.3016,1.00000,0.000,0.9943,1.0080,0.260,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.3183,1.00000,0.000,0.9943,1.0080,0.240,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.3349,1.00000,0.000,0.9944,1.0079,0.220,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.3514,1.00000,0.000,0.9947,1.0075,0.200,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.3664,1.00000,0.000,0.9950,1.0070,0.180,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.3853,1.00000,0.000,0.9955,1.0064,0.150,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.4022,1.00000,0.000,0.9960,1.0057,0.120,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.4185,1.00000,0.000,0.9965,1.0050,0.100,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.4352,1.00000,0.000,0.9970,1.0042,0.080,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.4518,1.00000,0.000,0.9975,1.0035,0.060,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.4683,1.00000,0.000,0.9980,1.0028,0.050,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.4849,1.00000,0.000,0.9985,1.0021,0.030,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.4998,1.00000,0.000,0.9989,1.0016,0.020,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.5184,1.00000,0.000,0.9992,1.0011,0.020,1.0000,1.0000,0.000 +light-grey-5b,3,-128.09,slow,1.5327,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.0000,0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.0167,0.02374,0.180,1.0000,1.0000,0.000,0.9460,1.0087,0.562 +light-grey-5b,4,64.04,slow,0.0334,0.09244,0.720,1.0055,0.9926,0.280,0.8120,1.0301,2.141 +light-grey-5b,4,64.04,slow,0.0511,0.18629,1.530,1.0194,0.9738,0.990,0.6590,1.0546,4.247 +light-grey-5b,4,64.04,slow,0.0668,0.29122,2.510,1.0385,0.9479,2.000,0.5150,1.0775,6.529 +light-grey-5b,4,64.04,slow,0.0835,0.39756,3.590,1.0605,0.9181,3.210,0.3930,1.0971,8.178 +light-grey-5b,4,64.04,slow,0.0999,0.49875,4.720,1.0835,0.8867,4.520,0.2940,1.1129,8.775 +light-grey-5b,4,64.04,slow,0.1168,0.59119,5.860,1.1037,0.8579,5.730,0.2170,1.1253,8.564 +light-grey-5b,4,64.04,slow,0.1333,0.67286,6.970,1.1152,0.8411,6.420,0.1580,1.1347,7.792 +light-grey-5b,4,64.04,slow,0.1499,0.74329,8.040,1.1142,0.8415,6.360,0.1140,1.1418,6.704 +light-grey-5b,4,64.04,slow,0.1666,0.80247,9.040,1.1056,0.8525,5.910,0.0820,1.1469,5.476 +light-grey-5b,4,64.04,slow,0.1833,0.85134,9.960,1.0962,0.8647,5.340,0.0580,1.1507,4.212 +light-grey-5b,4,64.04,slow,0.2000,0.89069,10.810,1.0884,0.8745,4.650,0.0410,1.1534,3.019 +light-grey-5b,4,64.04,slow,0.2166,0.92208,11.580,1.0824,0.8816,3.870,0.0290,1.1554,1.966 +light-grey-5b,4,64.04,slow,0.2333,0.94644,12.260,1.0772,0.8875,3.090,0.0200,1.1600,1.088 +light-grey-5b,4,64.04,slow,0.2500,0.96487,11.870,1.0716,0.8944,2.370,0.0430,1.1493,0.386 +light-grey-5b,4,64.04,slow,0.2667,0.97876,10.310,1.0647,0.9035,1.740,0.1070,1.1272,0.000 +light-grey-5b,4,64.04,slow,0.2833,0.98876,8.430,1.0562,0.9153,1.250,0.1930,1.1028,-0.491 +light-grey-5b,4,64.04,slow,0.3000,0.99578,6.630,1.0463,0.9294,0.910,0.2860,1.0801,-0.702 +light-grey-5b,4,64.04,slow,0.3165,1.00062,5.070,1.0352,0.9454,0.720,0.3790,1.0610,-0.807 +light-grey-5b,4,64.04,slow,0.3333,1.00359,3.800,1.0234,0.9624,0.650,0.4670,1.0455,-0.842 +light-grey-5b,4,64.04,slow,0.3499,1.00531,2.810,1.0116,0.9795,0.670,0.5480,1.0335,-0.772 +light-grey-5b,4,64.04,slow,0.3667,1.00609,2.050,1.0003,0.9960,0.750,0.6200,1.0244,-0.702 +light-grey-5b,4,64.04,slow,0.3833,1.00640,1.480,0.9900,1.0110,0.860,0.6820,1.0176,-0.562 +light-grey-5b,4,64.04,slow,0.3999,1.00609,1.060,0.9811,1.0240,0.980,0.7360,1.0126,-0.456 +light-grey-5b,4,64.04,slow,0.4166,1.00562,0.760,0.9739,1.0348,1.090,0.7820,1.0089,0.000 +light-grey-5b,4,64.04,slow,0.4334,1.00500,0.540,0.9683,1.0430,1.180,0.8210,1.0063,0.000 +light-grey-5b,4,64.04,slow,0.4500,1.00437,0.380,0.9644,1.0489,1.250,0.8530,1.0045,0.000 +light-grey-5b,4,64.04,slow,0.4667,1.00375,0.000,0.9621,1.0524,1.280,0.8800,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.4835,1.00000,0.000,0.9612,1.0540,1.290,0.9030,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.4999,1.00000,0.000,0.9615,1.0539,1.270,0.9210,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.5166,1.00000,0.000,0.9627,1.0524,1.220,0.9360,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.5333,1.00000,0.000,0.9645,1.0499,1.170,0.9490,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.5500,1.00000,0.000,0.9669,1.0467,1.100,0.9590,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.5666,1.00000,0.000,0.9695,1.0430,1.020,0.9670,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.5845,1.00000,0.000,0.9724,1.0390,0.920,0.9730,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.5999,1.00000,0.000,0.9754,1.0348,0.800,0.9790,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.6166,1.00000,0.000,0.9785,1.0305,0.680,0.9830,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.6333,1.00000,0.000,0.9816,1.0261,0.560,0.9860,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.6516,1.00000,0.000,0.9846,1.0219,0.450,0.9890,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.6690,1.00000,0.000,0.9874,1.0179,0.350,0.9910,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.6833,1.00000,0.000,0.9901,1.0142,0.270,0.9930,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.7011,1.00000,0.000,0.9924,1.0108,0.200,0.9950,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.7167,1.00000,0.000,0.9945,1.0079,0.150,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.7349,1.00000,0.000,0.9963,1.0054,0.100,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.7501,1.00000,0.000,0.9977,1.0033,0.060,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.7686,1.00000,0.000,0.9988,1.0017,0.040,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.7832,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.8025,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.8181,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.8351,1.00000,0.000,1.0009,0.9988,-0.030,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.8498,1.00000,0.000,1.0009,0.9987,-0.040,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.8688,1.00000,0.000,1.0009,0.9987,-0.050,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.8832,1.00000,0.000,1.0008,0.9989,-0.050,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.9021,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.9190,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.9355,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.9520,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.9683,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,0.9852,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.0018,1.00000,0.000,1.0020,0.9972,0.050,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.0164,1.00000,0.000,1.0060,0.9915,0.230,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.0353,1.00000,0.000,1.0083,0.9883,0.330,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.0517,1.00000,0.000,1.0086,0.9879,0.380,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.0682,1.00000,0.000,1.0081,0.9886,0.410,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.0849,1.00000,0.000,1.0074,0.9896,0.420,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.1020,1.00000,0.000,1.0066,0.9907,0.410,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.1188,1.00000,0.000,1.0057,0.9919,0.390,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.1350,1.00000,0.000,1.0047,0.9934,0.360,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.1516,1.00000,0.000,1.0035,0.9950,0.330,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.1686,1.00000,0.000,1.0022,0.9969,0.310,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.1852,1.00000,0.000,1.0009,0.9988,0.300,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.2024,1.00000,0.000,0.9995,1.0007,0.290,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.2192,1.00000,0.000,0.9982,1.0025,0.290,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.2355,1.00000,0.000,0.9971,1.0041,0.290,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.2520,1.00000,0.000,0.9961,1.0055,0.280,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.2691,1.00000,0.000,0.9954,1.0065,0.280,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.2858,1.00000,0.000,0.9948,1.0073,0.270,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.3018,1.00000,0.000,0.9945,1.0077,0.260,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.3188,1.00000,0.000,0.9944,1.0079,0.240,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.3352,1.00000,0.000,0.9945,1.0078,0.230,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.3518,1.00000,0.000,0.9947,1.0075,0.210,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.3688,1.00000,0.000,0.9950,1.0071,0.190,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.3850,1.00000,0.000,0.9954,1.0065,0.160,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.4023,1.00000,0.000,0.9958,1.0059,0.140,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.4183,1.00000,0.000,0.9963,1.0052,0.110,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.4350,1.00000,0.000,0.9969,1.0044,0.090,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.4518,1.00000,0.000,0.9974,1.0037,0.070,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.4683,1.00000,0.000,0.9979,1.0030,0.060,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.4851,1.00000,0.000,0.9983,1.0024,0.040,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.5016,1.00000,0.000,0.9987,1.0018,0.030,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.5160,1.00000,0.000,0.9991,1.0013,0.020,1.0000,1.0000,0.000 +light-grey-5b,4,64.04,slow,1.5344,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.0000,0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.0168,0.02326,0.850,1.0000,1.0000,0.000,0.9470,1.0085,0.527 +light-grey-5b,5,128.10,fast,0.0333,0.09157,2.990,1.0055,0.9926,0.280,0.8130,1.0299,2.106 +light-grey-5b,5,128.10,fast,0.0513,0.18532,5.430,1.0194,0.9738,0.990,0.6600,1.0543,4.212 +light-grey-5b,5,128.10,fast,0.0669,0.29016,7.730,1.0385,0.9479,2.000,0.5170,1.0773,6.529 +light-grey-5b,5,128.10,fast,0.0834,0.39641,9.690,1.0605,0.9181,3.210,0.3940,1.0969,8.178 +light-grey-5b,5,128.10,fast,0.1000,0.49766,11.280,1.0838,0.8867,4.540,0.2950,1.1128,8.740 +light-grey-5b,5,128.10,fast,0.1166,0.59016,12.520,1.1059,0.8558,5.860,0.2180,1.1252,8.564 +light-grey-5b,5,128.10,fast,0.1334,0.67205,13.460,1.1188,0.8372,6.650,0.1590,1.1346,7.792 +light-grey-5b,5,128.10,fast,0.1499,0.74247,14.170,1.1155,0.8386,6.440,0.1140,1.1417,6.704 +light-grey-5b,5,128.10,fast,0.1665,0.80180,14.690,1.1019,0.8533,5.830,0.0820,1.1469,5.476 +light-grey-5b,5,128.10,fast,0.1833,0.85074,15.070,1.0884,0.8694,5.220,0.0580,1.1507,4.212 +light-grey-5b,5,128.10,fast,0.1999,0.89032,15.340,1.0794,0.8794,4.430,0.0410,1.1534,3.019 +light-grey-5b,5,128.10,fast,0.2166,0.92162,15.540,1.0746,0.8835,3.500,0.0290,1.1554,1.966 +light-grey-5b,5,128.10,fast,0.2357,0.94606,16.000,1.0724,0.8843,2.550,0.0200,1.1600,1.088 +light-grey-5b,5,128.10,fast,0.2498,0.96472,16.000,1.0704,0.8854,1.690,0.0140,1.1600,0.386 +light-grey-5b,5,128.10,fast,0.2665,0.97853,14.930,1.0668,0.8897,0.990,0.0390,1.1493,0.000 +light-grey-5b,5,128.10,fast,0.2833,0.98860,12.720,1.0603,0.8991,0.490,0.1040,1.1272,-0.491 +light-grey-5b,5,128.10,fast,0.2998,0.99571,10.280,1.0506,0.9135,0.220,0.1910,1.1028,-0.702 +light-grey-5b,5,128.10,fast,0.3166,1.00047,8.010,1.0380,0.9323,0.170,0.2850,1.0801,-0.807 +light-grey-5b,5,128.10,fast,0.3333,1.00351,6.100,1.0235,0.9541,0.300,0.3780,1.0610,-0.842 +light-grey-5b,5,128.10,fast,0.3499,1.00523,4.550,1.0078,0.9774,0.550,0.4670,1.0455,-0.772 +light-grey-5b,5,128.10,fast,0.3666,1.00609,3.350,0.9920,1.0007,0.880,0.5470,1.0335,-0.702 +light-grey-5b,5,128.10,fast,0.3832,1.00625,2.440,0.9771,1.0229,1.220,0.6190,1.0244,-0.562 +light-grey-5b,5,128.10,fast,0.4000,1.00601,1.760,0.9638,1.0428,1.550,0.6820,1.0176,-0.456 +light-grey-5b,5,128.10,fast,0.4167,1.00554,1.260,0.9526,1.0598,1.840,0.7360,1.0126,0.000 +light-grey-5b,5,128.10,fast,0.4333,1.00492,0.890,0.9438,1.0734,2.070,0.7820,1.0089,0.000 +light-grey-5b,5,128.10,fast,0.4499,1.00429,0.630,0.9375,1.0833,2.240,0.8210,1.0063,0.000 +light-grey-5b,5,128.10,fast,0.4666,1.00359,0.450,0.9336,1.0898,2.350,0.8530,1.0045,0.000 +light-grey-5b,5,128.10,fast,0.4833,1.00297,0.000,0.9318,1.0931,2.400,0.8800,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.4999,1.00242,0.000,0.9319,1.0938,2.390,0.9030,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.5167,1.00195,0.000,0.9335,1.0921,2.350,0.9210,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.5333,1.00148,0.000,0.9363,1.0887,2.260,0.9360,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.5500,1.00000,0.000,0.9400,1.0840,2.150,0.9490,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.5666,1.00000,0.000,0.9442,1.0784,2.020,0.9590,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.5832,1.00000,0.000,0.9487,1.0722,1.880,0.9670,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.5998,1.00000,0.000,0.9534,1.0658,1.720,0.9730,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.6165,1.00000,0.000,0.9582,1.0592,1.550,0.9790,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.6353,1.00000,0.000,0.9631,1.0524,1.340,0.9830,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.6500,1.00000,0.000,0.9680,1.0455,1.130,0.9860,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.6667,1.00000,0.000,0.9728,1.0387,0.930,0.9890,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.6833,1.00000,0.000,0.9774,1.0322,0.750,0.9910,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.7020,1.00000,0.000,0.9818,1.0260,0.590,0.9930,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.7167,1.00000,0.000,0.9858,1.0204,0.450,0.9950,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.7352,1.00000,0.000,0.9893,1.0154,0.330,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.7499,1.00000,0.000,0.9924,1.0110,0.240,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.7685,1.00000,0.000,0.9949,1.0074,0.160,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.7832,1.00000,0.000,0.9970,1.0044,0.100,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.8018,1.00000,0.000,0.9986,1.0021,0.050,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.8166,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.8353,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.8499,1.00000,0.000,1.0011,0.9985,-0.050,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.8685,1.00000,0.000,1.0013,0.9982,-0.070,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.8832,1.00000,0.000,1.0013,0.9982,-0.090,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.9018,1.00000,0.000,1.0012,0.9984,-0.100,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.9165,1.00000,0.000,1.0009,0.9987,-0.100,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.9332,1.00000,0.000,1.0006,0.9991,-0.110,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.9519,1.00000,0.000,1.0003,0.9996,-0.110,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.9665,1.00000,0.000,1.0000,1.0000,-0.110,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.9852,1.00000,0.000,0.9997,1.0005,-0.110,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,0.9997,1.00000,0.000,1.0019,0.9973,-0.000,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.0184,1.00000,0.000,1.0059,0.9917,0.180,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.0349,1.00000,0.000,1.0080,0.9887,0.280,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.0497,1.00000,0.000,1.0082,0.9884,0.340,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.0685,1.00000,0.000,1.0076,0.9893,0.380,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.0846,1.00000,0.000,1.0068,0.9904,0.390,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.0997,1.00000,0.000,1.0060,0.9916,0.390,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.1182,1.00000,0.000,1.0051,0.9929,0.370,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.1330,1.00000,0.000,1.0041,0.9943,0.350,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.1516,1.00000,0.000,1.0029,0.9959,0.320,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.1686,1.00000,0.000,1.0017,0.9976,0.310,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.1854,1.00000,0.000,1.0004,0.9995,0.300,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.2017,1.00000,0.000,0.9991,1.0013,0.290,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.2186,1.00000,0.000,0.9978,1.0030,0.290,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.2349,1.00000,0.000,0.9968,1.0046,0.290,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.2497,1.00000,0.000,0.9959,1.0058,0.290,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.2683,1.00000,0.000,0.9952,1.0068,0.280,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.2834,1.00000,0.000,0.9947,1.0075,0.270,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.3018,1.00000,0.000,0.9944,1.0079,0.260,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.3183,1.00000,0.000,0.9943,1.0080,0.250,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.3330,1.00000,0.000,0.9944,1.0079,0.230,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.3517,1.00000,0.000,0.9946,1.0076,0.220,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.3679,1.00000,0.000,0.9949,1.0072,0.190,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.3830,1.00000,0.000,0.9953,1.0066,0.170,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.4020,1.00000,0.000,0.9958,1.0060,0.140,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.4188,1.00000,0.000,0.9963,1.0053,0.120,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.4350,1.00000,0.000,0.9968,1.0045,0.090,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.4514,1.00000,0.000,0.9973,1.0038,0.070,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.4664,1.00000,0.000,0.9978,1.0031,0.060,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.4854,1.00000,0.000,0.9983,1.0024,0.040,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.5017,1.00000,0.000,0.9987,1.0018,0.030,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.5182,1.00000,0.000,0.9991,1.0013,0.020,1.0000,1.0000,0.000 +light-grey-5b,5,128.10,fast,1.5327,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.0000,-0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.0167,0.02451,0.900,1.0000,1.0000,0.000,0.9440,1.0090,0.562 +light-grey-5b,6,-64.05,fast,0.0334,0.09368,3.050,1.0055,0.9926,-0.280,0.8100,1.0305,2.141 +light-grey-5b,6,-64.05,fast,0.0514,0.18782,5.490,1.0194,0.9738,-0.990,0.6570,1.0549,4.282 +light-grey-5b,6,-64.05,fast,0.0668,0.29290,7.780,1.0385,0.9479,-2.000,0.5140,1.0778,6.564 +light-grey-5b,6,-64.05,fast,0.0836,0.39906,9.740,1.0605,0.9181,-3.210,0.3920,1.0974,8.213 +light-grey-5b,6,-64.05,fast,0.0999,0.50008,11.310,1.0835,0.8867,-4.530,0.2930,1.1131,8.810 +light-grey-5b,6,-64.05,fast,0.1166,0.59235,12.550,1.1038,0.8578,-5.730,0.2160,1.1255,8.599 +light-grey-5b,6,-64.05,fast,0.1333,0.67400,13.480,1.1155,0.8406,-6.450,0.1570,1.1348,7.827 +light-grey-5b,6,-64.05,fast,0.1501,0.74411,14.180,1.1149,0.8405,-6.410,0.1140,1.1418,6.739 +light-grey-5b,6,-64.05,fast,0.1666,0.80328,14.700,1.1062,0.8517,-5.980,0.0810,1.1470,5.476 +light-grey-5b,6,-64.05,fast,0.1833,0.85183,15.080,1.0962,0.8650,-5.460,0.0580,1.1508,4.212 +light-grey-5b,6,-64.05,fast,0.1999,0.89118,15.350,1.0878,0.8758,-4.800,0.0410,1.1535,3.019 +light-grey-5b,6,-64.05,fast,0.2167,0.92240,15.540,1.0813,0.8836,-4.050,0.0290,1.1554,1.966 +light-grey-5b,6,-64.05,fast,0.2333,0.94660,16.000,1.0760,0.8898,-3.270,0.0200,1.1600,1.088 +light-grey-5b,6,-64.05,fast,0.2500,0.96518,14.930,1.0705,0.8965,-2.530,0.0430,1.1493,0.386 +light-grey-5b,6,-64.05,fast,0.2665,0.97892,12.720,1.0640,0.9051,-1.880,0.1070,1.1272,0.000 +light-grey-5b,6,-64.05,fast,0.2833,0.98891,10.280,1.0560,0.9162,-1.360,0.1930,1.1028,-0.491 +light-grey-5b,6,-64.05,fast,0.2999,0.99578,8.010,1.0465,0.9296,-0.990,0.2860,1.0801,-0.702 +light-grey-5b,6,-64.05,fast,0.3167,1.00062,6.100,1.0358,0.9449,-0.760,0.3790,1.0610,-0.807 +light-grey-5b,6,-64.05,fast,0.3333,1.00359,4.550,1.0243,0.9614,-0.670,0.4670,1.0455,-0.842 +light-grey-5b,6,-64.05,fast,0.3499,1.00531,3.350,1.0127,0.9783,-0.670,0.5480,1.0335,-0.772 +light-grey-5b,6,-64.05,fast,0.3665,1.00609,2.440,1.0014,0.9947,-0.740,0.6200,1.0244,-0.702 +light-grey-5b,6,-64.05,fast,0.3833,1.00625,1.760,0.9910,1.0099,-0.850,0.6820,1.0176,-0.597 +light-grey-5b,6,-64.05,fast,0.3999,1.00609,1.260,0.9818,1.0232,-0.970,0.7360,1.0126,-0.456 +light-grey-5b,6,-64.05,fast,0.4166,1.00562,0.890,0.9742,1.0344,-1.090,0.7820,1.0089,0.000 +light-grey-5b,6,-64.05,fast,0.4333,1.00500,0.630,0.9683,1.0431,-1.190,0.8210,1.0063,0.000 +light-grey-5b,6,-64.05,fast,0.4499,1.00437,0.450,0.9641,1.0494,-1.270,0.8530,1.0045,0.000 +light-grey-5b,6,-64.05,fast,0.4666,1.00359,0.000,0.9614,1.0535,-1.310,0.8800,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.4832,1.00000,0.000,0.9602,1.0554,-1.330,0.9030,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.4999,1.00000,0.000,0.9602,1.0556,-1.310,0.9210,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.5165,1.00000,0.000,0.9613,1.0543,-1.280,0.9360,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.5332,1.00000,0.000,0.9631,1.0520,-1.220,0.9490,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.5499,1.00000,0.000,0.9654,1.0488,-1.150,0.9590,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.5665,1.00000,0.000,0.9681,1.0450,-1.070,0.9670,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.5853,1.00000,0.000,0.9711,1.0408,-0.970,0.9730,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.5998,1.00000,0.000,0.9743,1.0364,-0.840,0.9790,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.6185,1.00000,0.000,0.9775,1.0319,-0.720,0.9830,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.6332,1.00000,0.000,0.9807,1.0274,-0.590,0.9860,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.6522,1.00000,0.000,0.9838,1.0230,-0.480,0.9890,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.6684,1.00000,0.000,0.9868,1.0187,-0.380,0.9910,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.6832,1.00000,0.000,0.9896,1.0148,-0.290,0.9930,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.6999,1.00000,0.000,0.9921,1.0113,-0.220,0.9950,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.7188,1.00000,0.000,0.9942,1.0082,-0.160,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.7332,1.00000,0.000,0.9961,1.0056,-0.110,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.7520,1.00000,0.000,0.9976,1.0035,-0.070,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.7665,1.00000,0.000,0.9988,1.0018,-0.040,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.7831,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.8000,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.8184,1.00000,0.000,1.0008,0.9990,0.020,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.8332,1.00000,0.000,1.0010,0.9987,0.030,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.8498,1.00000,0.000,1.0010,0.9986,0.040,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.8685,1.00000,0.000,1.0010,0.9986,0.050,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.8832,1.00000,0.000,1.0008,0.9988,0.050,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.9020,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.9165,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.9352,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.9499,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.9684,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.9831,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,0.9997,1.00000,0.000,1.0019,0.9973,0.170,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.0187,1.00000,0.000,1.0060,0.9915,0.340,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.0353,1.00000,0.000,1.0082,0.9884,0.430,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.0498,1.00000,0.000,1.0085,0.9879,0.460,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.0683,1.00000,0.000,1.0081,0.9886,0.480,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.0849,1.00000,0.000,1.0074,0.9895,0.480,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.1018,1.00000,0.000,1.0067,0.9906,0.450,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.1182,1.00000,0.000,1.0058,0.9918,0.420,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.1351,1.00000,0.000,1.0048,0.9932,0.380,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.1515,1.00000,0.000,1.0036,0.9949,0.350,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.1664,1.00000,0.000,1.0023,0.9968,0.320,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.1847,1.00000,0.000,1.0009,0.9988,0.310,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.2018,1.00000,0.000,0.9994,1.0008,0.300,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.2182,1.00000,0.000,0.9981,1.0026,0.290,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.2330,1.00000,0.000,0.9970,1.0043,0.290,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.2515,1.00000,0.000,0.9960,1.0057,0.280,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.2664,1.00000,0.000,0.9952,1.0067,0.280,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.2853,1.00000,0.000,0.9947,1.0075,0.270,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.3016,1.00000,0.000,0.9944,1.0079,0.260,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.3185,1.00000,0.000,0.9943,1.0080,0.240,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.3349,1.00000,0.000,0.9944,1.0079,0.230,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.3521,1.00000,0.000,0.9946,1.0076,0.210,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.3689,1.00000,0.000,0.9949,1.0071,0.180,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.3851,1.00000,0.000,0.9954,1.0065,0.150,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.4018,1.00000,0.000,0.9959,1.0058,0.130,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.4182,1.00000,0.000,0.9964,1.0051,0.100,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.4331,1.00000,0.000,0.9969,1.0043,0.080,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.4509,1.00000,0.000,0.9975,1.0036,0.060,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.4683,1.00000,0.000,0.9980,1.0029,0.050,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.4831,1.00000,0.000,0.9984,1.0022,0.030,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.5016,1.00000,0.000,0.9988,1.0017,0.020,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.5175,1.00000,0.000,0.9992,1.0012,0.020,1.0000,1.0000,0.000 +light-grey-5b,6,-64.05,fast,1.5328,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.0000,-0.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.0169,0.02165,0.160,1.0000,1.0000,0.000,0.9500,1.0080,0.491 +light-grey-5b,7,-193.52,slow,0.0335,0.08883,0.690,1.0055,0.9926,-0.280,0.8180,1.0291,2.036 +light-grey-5b,7,-193.52,slow,0.0512,0.18200,1.490,1.0194,0.9738,-1.010,0.6650,1.0535,4.142 +light-grey-5b,7,-193.52,slow,0.0668,0.28669,2.460,1.0385,0.9479,-2.050,0.5210,1.0766,6.458 +light-grey-5b,7,-193.52,slow,0.0833,0.39303,3.540,1.0605,0.9181,-3.300,0.3980,1.0963,8.108 +light-grey-5b,7,-193.52,slow,0.1000,0.49452,4.670,1.0838,0.8867,-4.670,0.2980,1.1123,8.705 +light-grey-5b,7,-193.52,slow,0.1167,0.58738,5.810,1.1067,0.8553,-6.070,0.2200,1.1248,8.494 +light-grey-5b,7,-193.52,slow,0.1334,0.66959,6.930,1.1200,0.8367,-6.910,0.1600,1.1344,7.757 +light-grey-5b,7,-193.52,slow,0.1500,0.74044,7.990,1.1152,0.8388,-6.600,0.1160,1.1415,6.669 +light-grey-5b,7,-193.52,slow,0.1666,0.80012,9.000,1.0975,0.8556,-5.960,0.0830,1.1467,5.441 +light-grey-5b,7,-193.52,slow,0.1834,0.84932,9.920,1.0793,0.8747,-5.430,0.0590,1.1506,4.212 +light-grey-5b,7,-193.52,slow,0.2000,0.88916,10.780,1.0680,0.8856,-4.660,0.0420,1.1533,3.019 +light-grey-5b,7,-193.52,slow,0.2167,0.92078,11.550,1.0637,0.8873,-3.660,0.0290,1.1553,1.966 +light-grey-5b,7,-193.52,slow,0.2334,0.94543,12.230,1.0639,0.8835,-2.550,0.0210,1.1600,1.088 +light-grey-5b,7,-193.52,slow,0.2521,0.96419,12.850,1.0655,0.8787,-1.490,0.0140,1.1600,0.386 +light-grey-5b,7,-193.52,slow,0.2668,0.97819,13.380,1.0657,0.8776,-0.640,0.0100,1.1600,0.000 +light-grey-5b,7,-193.52,slow,0.2833,0.98837,12.820,1.0621,0.8830,-0.050,0.0360,1.1493,-0.491 +light-grey-5b,7,-193.52,slow,0.3000,0.99556,11.080,1.0538,0.8962,0.220,0.1020,1.1272,-0.702 +light-grey-5b,7,-193.52,slow,0.3166,1.00036,9.020,1.0409,0.9160,0.190,0.1890,1.1028,-0.807 +light-grey-5b,7,-193.52,slow,0.3333,1.00346,7.080,1.0242,0.9405,-0.090,0.2840,1.0801,-0.807 +light-grey-5b,7,-193.52,slow,0.3500,1.00522,5.410,1.0050,0.9676,-0.550,0.3780,1.0610,-0.772 +light-grey-5b,7,-193.52,slow,0.3667,1.00610,4.050,0.9847,0.9956,-1.110,0.4660,1.0455,-0.702 +light-grey-5b,7,-193.52,slow,0.3834,1.00630,2.990,0.9649,1.0228,-1.690,0.5470,1.0335,-0.562 +light-grey-5b,7,-193.52,slow,0.4000,1.00610,2.180,0.9467,1.0480,-2.230,0.6190,1.0244,-0.456 +light-grey-5b,7,-193.52,slow,0.4168,1.00563,1.580,0.9309,1.0703,-2.710,0.6820,1.0176,0.000 +light-grey-5b,7,-193.52,slow,0.4333,1.00501,1.130,0.9181,1.0891,-3.100,0.7360,1.0126,0.000 +light-grey-5b,7,-193.52,slow,0.4500,1.00434,0.800,0.9086,1.1041,-3.390,0.7820,1.0089,0.000 +light-grey-5b,7,-193.52,slow,0.4666,1.00367,0.570,0.9024,1.1151,-3.580,0.8210,1.0063,0.000 +light-grey-5b,7,-193.52,slow,0.4833,1.00305,0.400,0.8992,1.1221,-3.680,0.8530,1.0045,0.000 +light-grey-5b,7,-193.52,slow,0.5000,1.00248,0.000,0.8986,1.1255,-3.700,0.8800,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.5166,1.00196,0.000,0.9004,1.1257,-3.650,0.9030,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.5332,1.00155,0.000,0.9039,1.1233,-3.550,0.9210,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.5500,1.00119,0.000,0.9088,1.1187,-3.400,0.9360,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.5666,1.00088,0.000,0.9146,1.1125,-3.210,0.9490,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.5833,1.00000,0.000,0.9212,1.1052,-3.000,0.9590,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.5999,1.00000,0.000,0.9280,1.0971,-2.770,0.9670,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.6167,1.00000,0.000,0.9350,1.0885,-2.490,0.9730,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.6353,1.00000,0.000,0.9421,1.0796,-2.160,0.9790,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.6501,1.00000,0.000,0.9493,1.0703,-1.820,0.9830,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.6666,1.00000,0.000,0.9565,1.0608,-1.490,0.9860,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.6857,1.00000,0.000,0.9636,1.0514,-1.190,0.9890,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.6999,1.00000,0.000,0.9703,1.0424,-0.930,0.9910,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.7187,1.00000,0.000,0.9764,1.0339,-0.700,0.9930,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.7350,1.00000,0.000,0.9820,1.0261,-0.520,0.9950,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.7498,1.00000,0.000,0.9869,1.0193,-0.360,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.7688,1.00000,0.000,0.9911,1.0135,-0.240,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.7832,1.00000,0.000,0.9945,1.0087,-0.140,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.8023,1.00000,0.000,0.9971,1.0048,-0.060,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.8188,1.00000,0.000,0.9991,1.0020,0.000,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.8332,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.8513,1.00000,0.000,1.0014,0.9987,0.090,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.8688,1.00000,0.000,1.0018,0.9980,0.120,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.8832,1.00000,0.000,1.0018,0.9978,0.140,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.9022,1.00000,0.000,1.0017,0.9980,0.160,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.9179,1.00000,0.000,1.0013,0.9984,0.170,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.9332,1.00000,0.000,1.0009,0.9989,0.180,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.9518,1.00000,0.000,1.0004,0.9996,0.180,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.9665,1.00000,0.000,0.9999,1.0002,0.180,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,0.9851,1.00000,0.000,0.9994,1.0008,0.180,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.0017,1.00000,0.000,1.0014,0.9980,0.280,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.0183,1.00000,0.000,1.0051,0.9928,0.440,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.0331,1.00000,0.000,1.0070,0.9902,0.510,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.0517,1.00000,0.000,1.0070,0.9901,0.520,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.0664,1.00000,0.000,1.0065,0.9909,0.530,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.0851,1.00000,0.000,1.0058,0.9919,0.510,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.1018,1.00000,0.000,1.0050,0.9929,0.470,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.1186,1.00000,0.000,1.0042,0.9940,0.430,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.1353,1.00000,0.000,1.0033,0.9953,0.390,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.1518,1.00000,0.000,1.0022,0.9969,0.360,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.1685,1.00000,0.000,1.0010,0.9986,0.330,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.1846,1.00000,0.000,0.9997,1.0004,0.310,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.1999,1.00000,0.000,0.9985,1.0021,0.300,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.2184,1.00000,0.000,0.9973,1.0038,0.300,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.2351,1.00000,0.000,0.9963,1.0052,0.290,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.2519,1.00000,0.000,0.9955,1.0064,0.290,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.2684,1.00000,0.000,0.9948,1.0073,0.280,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.2852,1.00000,0.000,0.9944,1.0078,0.270,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.3017,1.00000,0.000,0.9942,1.0081,0.260,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.3183,1.00000,0.000,0.9942,1.0081,0.250,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.3331,1.00000,0.000,0.9944,1.0079,0.230,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.3521,1.00000,0.000,0.9947,1.0075,0.210,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.3684,1.00000,0.000,0.9950,1.0070,0.180,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.3831,1.00000,0.000,0.9955,1.0063,0.160,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.4022,1.00000,0.000,0.9960,1.0056,0.130,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.4184,1.00000,0.000,0.9966,1.0048,0.100,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.4342,1.00000,0.000,0.9971,1.0041,0.080,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.4498,1.00000,0.000,0.9976,1.0033,0.060,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.4685,1.00000,0.000,0.9981,1.0026,0.050,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.4850,1.00000,0.000,0.9986,1.0020,0.040,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.5018,1.00000,0.000,0.9990,1.0015,0.030,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.5178,1.00000,0.000,0.9993,1.0010,0.020,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.5331,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 +light-grey-5b,7,-193.52,slow,1.5511,1.00000,0.000,1.0000,1.0000,0.000,1.0000,1.0000,0.000 diff --git a/maven/core-unittests/src/test/resources/tab-glass-motion/native-vibrancy.csv b/maven/core-unittests/src/test/resources/tab-glass-motion/native-vibrancy.csv new file mode 100644 index 00000000000..cc7a3cb9184 --- /dev/null +++ b/maven/core-unittests/src/test/resources/tab-glass-motion/native-vibrancy.csv @@ -0,0 +1,651 @@ +# Native iOS 27 vibrancy matrices read from UIKit (vibrantColorMatrix inputColorMatrix) +# for the selected tab-bar item, by tint. Captured with the motion probe (PROBE_TINTSWEEP). +# appearance,tint,m00,m01,m02,off0,m10,m11,m12,off1,m20,m21,m22,off2 +dark,E7EEE7,0.49060,0.00546,0.00530,0.90588,0.00530,0.49076,0.00530,0.93333,0.00530,0.00546,0.49060,0.90588 +dark,615EF3,0.29153,0.09508,0.24578,0.38039,0.09811,0.28849,0.24578,0.36863,0.09811,0.09508,0.43920,0.95294 +dark,5F30E4,0.25630,0.07631,0.36248,0.37255,0.15104,0.18158,0.36248,0.18824,0.15104,0.07631,0.46775,0.89412 +dark,9B482E,0.59204,0.20608,0.13167,0.60784,0.44366,0.35447,0.13167,0.28235,0.44366,0.20608,0.28005,0.18039 +dark,15CAE7,0.07118,0.24749,0.28302,0.08235,0.02573,0.29294,0.28302,0.79216,0.02573,0.24749,0.32847,0.90588 +dark,500720,1.28923,0.10898,0.49819,0.31373,1.24548,0.15273,0.49819,0.02745,1.24548,0.10898,0.54194,0.12549 +dark,1E1261,0.38576,0.17579,0.94729,0.11765,0.29298,0.26857,0.94729,0.07059,0.29298,0.17579,1.04007,0.38039 +dark,7B0FED,0.23703,0.02505,0.39575,0.48235,0.20539,0.05669,0.39575,0.05882,0.20539,0.02505,0.42739,0.92941 +dark,A7E164,0.35586,0.18006,0.08003,0.65490,0.13364,0.40228,0.08003,0.88235,0.13364,0.18006,0.30225,0.39216 +dark,7796FF,0.31291,0.10031,0.17052,0.46667,0.07958,0.33364,0.17052,0.58824,0.07958,0.10031,0.40386,1.00000 +dark,022BEA,0.00874,0.09602,0.52253,0.00784,0.00447,0.10029,0.52253,0.16863,0.00447,0.09602,0.52681,0.91765 +dark,8ED02A,0.32260,0.32466,0.06556,0.55686,0.22164,0.42562,0.06556,0.81569,0.22164,0.32466,0.16652,0.16471 +dark,82A175,0.44351,0.09927,0.07214,0.50980,0.08016,0.46263,0.07214,0.63137,0.08016,0.09927,0.43550,0.45882 +dark,930F23,0.78089,0.07448,0.17378,0.57647,0.72987,0.12550,0.17378,0.05882,0.72987,0.07448,0.22480,0.13725 +dark,37CD37,0.24088,0.39782,0.10673,0.21569,0.10673,0.53196,0.10673,0.80392,0.10673,0.39782,0.24088,0.21569 +dark,94C522,0.33865,0.33591,0.05797,0.58039,0.25236,0.42221,0.05797,0.77255,0.25236,0.33591,0.14427,0.13333 +dark,08006D,0.08539,0.00000,1.16346,0.03137,0.08539,0.00000,1.16346,0.00000,0.08539,0.00000,1.16346,0.42745 +dark,6B1AF0,0.22863,0.04239,0.39132,0.41961,0.17446,0.09656,0.39132,0.10196,0.17446,0.04239,0.44549,0.94118 +dark,C0CBD6,0.46892,0.02148,0.02264,0.75294,0.02032,0.47008,0.02264,0.79608,0.02032,0.02148,0.47124,0.83922 +dark,25658A,0.24684,0.30786,0.42064,0.14510,0.11278,0.44192,0.42064,0.39608,0.11278,0.30786,0.55470,0.54118 +dark,AC2C9F,0.41523,0.07350,0.26560,0.67451,0.28732,0.20141,0.26560,0.17255,0.28732,0.07350,0.39351,0.62353 +dark,AA07D1,0.30518,0.01188,0.35461,0.66667,0.28844,0.02862,0.35461,0.02745,0.28844,0.01188,0.37135,0.81961 +dark,3C447E,0.40437,0.18844,0.34917,0.23529,0.16627,0.42654,0.34917,0.26667,0.16627,0.18844,0.58727,0.49412 +dark,33051E,1.71238,0.16307,0.97845,0.20000,1.66336,0.21209,0.97845,0.01961,1.66336,0.16307,1.02747,0.11765 +dark,EEF95A,0.33360,0.15995,0.05781,0.93333,0.15288,0.34067,0.05781,0.97647,0.15288,0.15995,0.23854,0.35294 +dark,60E561,0.30964,0.23862,0.10107,0.37647,0.10003,0.44822,0.10107,0.89804,0.10003,0.23862,0.31068,0.38039 +dark,43D6C4,0.22270,0.21130,0.19353,0.26275,0.06615,0.36784,0.19353,0.83922,0.06615,0.21130,0.35007,0.76863 +dark,3BCAD7,0.19751,0.20647,0.21975,0.23137,0.06030,0.34368,0.21975,0.79216,0.06030,0.20647,0.35696,0.84314 +dark,6C008A,0.44842,0.00000,0.57298,0.42353,0.44842,0.00000,0.57298,0.00000,0.44842,0.00000,0.57298,0.54118 +dark,9B0A6B,0.55195,0.03353,0.35875,0.60784,0.51969,0.06579,0.35875,0.03922,0.51969,0.03353,0.39101,0.41961 +dark,5FC933,0.30061,0.36760,0.09327,0.37255,0.17374,0.49446,0.09327,0.78824,0.17374,0.36760,0.22014,0.20000 +dark,154A6D,0.21779,0.42798,0.63041,0.08235,0.12145,0.52431,0.63041,0.29020,0.12145,0.42798,0.72674,0.42745 +dark,E28404,0.42196,0.24129,0.00731,0.88627,0.41312,0.25014,0.00731,0.51765,0.41312,0.24129,0.01616,0.01569 +dark,A897C5,0.43893,0.05004,0.06529,0.65882,0.05568,0.43329,0.06529,0.59216,0.05568,0.05004,0.44854,0.77255 +dark,25262E,0.58943,0.19232,0.23281,0.14510,0.18726,0.59449,0.23281,0.14902,0.18726,0.19232,0.63498,0.18039 +dark,6A7C07,0.50653,0.55953,0.03159,0.41569,0.47830,0.58775,0.03159,0.48627,0.47830,0.55953,0.05981,0.02745 +dark,BCBEE8,0.44146,0.03668,0.04478,0.73725,0.03629,0.44185,0.04478,0.74510,0.03629,0.03668,0.44996,0.90980 +dark,41F745,0.21882,0.33152,0.09261,0.25490,0.08724,0.46310,0.09261,0.96863,0.08724,0.33152,0.22419,0.27059 +dark,C55D4E,0.48135,0.13378,0.11220,0.77255,0.28338,0.33175,0.11220,0.36471,0.28338,0.13378,0.31017,0.30588 +dark,9F747F,0.46471,0.07290,0.07981,0.62353,0.09993,0.43768,0.07981,0.45490,0.09993,0.07290,0.44459,0.49804 +dark,615164,0.49548,0.07556,0.09328,0.38039,0.09048,0.48056,0.09328,0.31765,0.09048,0.07556,0.49828,0.39216 +dark,C6F728,0.28877,0.25923,0.04198,0.77647,0.20780,0.34020,0.04198,0.96863,0.20780,0.25923,0.12295,0.15686 +dark,D71835,0.54669,0.05480,0.12101,0.84314,0.49088,0.11061,0.12101,0.09412,0.49088,0.05480,0.17682,0.20784 +dark,371382,0.36824,0.10196,0.69765,0.21569,0.29516,0.17504,0.69765,0.07451,0.29516,0.10196,0.77073,0.50980 +dark,7AC883,0.38920,0.13804,0.09042,0.47843,0.08420,0.44304,0.09042,0.78431,0.08420,0.13804,0.39542,0.51373 +dark,D7FB96,0.38254,0.09776,0.05842,0.84314,0.08374,0.39656,0.05842,0.98431,0.08374,0.09776,0.35723,0.58824 +dark,592340,0.71657,0.20447,0.37389,0.34902,0.51994,0.40110,0.37389,0.13725,0.51994,0.20447,0.57052,0.25098 +dark,74F525,0.24326,0.35431,0.05351,0.45490,0.16775,0.42982,0.05351,0.96078,0.16775,0.35431,0.12902,0.14510 +dark,8F6C68,0.47947,0.08748,0.08424,0.56078,0.11583,0.45112,0.08424,0.42353,0.11583,0.08748,0.44788,0.40784 +dark,082389,0.07708,0.20949,0.82000,0.03137,0.04788,0.23869,0.82000,0.13725,0.04788,0.20949,0.84919,0.53725 +dark,D2E47F,0.38421,0.11476,0.06392,0.82353,0.10570,0.39327,0.06392,0.89412,0.10570,0.11476,0.34243,0.49804 +dark,1E175A,0.42660,0.22910,0.89647,0.11765,0.29882,0.35688,0.89647,0.09020,0.29882,0.22910,1.02425,0.35294 +dark,90BC43,0.37329,0.25471,0.09077,0.56471,0.19510,0.43290,0.09077,0.73725,0.19510,0.25471,0.26897,0.26275 +dark,2FB946,0.23517,0.42568,0.16107,0.18431,0.10815,0.55271,0.16107,0.72549,0.10815,0.42568,0.28809,0.27451 +dark,E6A947,0.38871,0.17220,0.07235,0.90196,0.23436,0.32655,0.07235,0.66275,0.23436,0.17220,0.22669,0.27843 +dark,1109F3,0.05365,0.01860,0.50211,0.06667,0.03513,0.03712,0.50211,0.03529,0.03513,0.01860,0.52063,0.95294 +dark,B79F11,0.40482,0.31137,0.03329,0.71765,0.35837,0.35782,0.03329,0.62353,0.35837,0.31137,0.07974,0.06667 +dark,0A26F6,0.04003,0.07490,0.48485,0.03922,0.01971,0.09522,0.48485,0.14902,0.01971,0.07490,0.50518,0.96471 +dark,229FA3,0.16902,0.30268,0.31030,0.13333,0.06472,0.40697,0.31030,0.62353,0.06472,0.30268,0.41459,0.63922 +dark,452526,0.80680,0.28886,0.29667,0.27059,0.53869,0.55698,0.29667,0.14510,0.53869,0.28886,0.56478,0.14902 +dark,E7BC16,0.34639,0.24316,0.02845,0.90588,0.29878,0.29078,0.02845,0.73725,0.29878,0.24316,0.07607,0.08627 +dark,42AEB4,0.26284,0.20961,0.21684,0.25882,0.07951,0.39294,0.21684,0.68235,0.07951,0.20961,0.40017,0.70588 +dark,2BF227,0.15483,0.41790,0.06735,0.16863,0.07426,0.49848,0.06735,0.94902,0.07426,0.41790,0.14793,0.15294 +dark,D50FFF,0.26048,0.01627,0.27663,0.83529,0.23106,0.04568,0.27663,0.05882,0.23106,0.01627,0.30604,1.00000 +dark,07C3C2,0.02931,0.31660,0.31498,0.02745,0.01137,0.33455,0.31498,0.76471,0.01137,0.31660,0.33293,0.76078 +dark,062429,0.28991,1.30046,1.48108,0.02353,0.21674,1.37363,1.48108,0.14118,0.21674,1.30046,1.55425,0.16078 +dark,2E3B83,0.34279,0.21447,0.47621,0.18039,0.16722,0.39005,0.47621,0.23137,0.16722,0.21447,0.65178,0.51373 +dark,D5A9C6,0.44630,0.03934,0.04610,0.83529,0.04959,0.43606,0.04610,0.66275,0.04959,0.03934,0.44281,0.77647 +dark,EAE1EC,0.48533,0.00830,0.00871,0.91765,0.00863,0.48500,0.00871,0.88235,0.00863,0.00830,0.48540,0.92549 +dark,2A0F9E,0.22729,0.06422,0.67646,0.16471,0.17982,0.11169,0.67646,0.05882,0.17982,0.06422,0.72392,0.61961 +dark,2CF60B,0.10800,0.47884,0.02141,0.17255,0.08565,0.50120,0.02141,0.96471,0.08565,0.47884,0.04377,0.04314 +dark,7539FE,0.25425,0.06920,0.30836,0.45882,0.14204,0.18140,0.30836,0.22353,0.14204,0.06920,0.42057,0.99608 +dark,F88205,0.40512,0.20708,0.00796,0.97255,0.39504,0.21716,0.00796,0.50980,0.39504,0.20708,0.01805,0.01961 +dark,BC9A49,0.42187,0.18653,0.08842,0.73725,0.22772,0.38068,0.08842,0.60392,0.22772,0.18653,0.28257,0.28627 +dark,6756AF,0.38305,0.11467,0.23334,0.40392,0.13734,0.36039,0.23334,0.33725,0.13734,0.11467,0.47906,0.68627 +dark,E2FF7B,0.35484,0.12825,0.06186,0.88627,0.11366,0.36942,0.06186,1.00000,0.11366,0.12825,0.30304,0.48235 +dark,A7CF80,0.40245,0.11561,0.07149,0.65490,0.09327,0.42479,0.07149,0.81176,0.09327,0.11561,0.38067,0.50196 +dark,65DC66,0.33049,0.21988,0.10194,0.39608,0.10094,0.44942,0.10194,0.86275,0.10094,0.21988,0.33149,0.40000 +dark,6DC470,0.37623,0.17652,0.10087,0.42745,0.09817,0.45458,0.10087,0.76863,0.09817,0.17652,0.37893,0.43922 +dark,A26B45,0.49227,0.18448,0.11896,0.63529,0.27930,0.39744,0.11896,0.41961,0.27930,0.18448,0.33193,0.27059 +dark,44FEB3,0.19661,0.23438,0.16517,0.26667,0.06275,0.36824,0.16517,0.99608,0.06275,0.23438,0.29903,0.70196 +dark,14208D,0.17363,0.16434,0.72413,0.07843,0.10271,0.23526,0.72413,0.12549,0.10271,0.16434,0.79505,0.55294 +dark,5639E6,0.25370,0.08602,0.34712,0.33725,0.12979,0.20994,0.34712,0.22353,0.12979,0.08602,0.47103,0.90196 +dark,F18C6D,0.41406,0.10916,0.08499,0.94510,0.18791,0.33530,0.08499,0.54902,0.18791,0.10916,0.31113,0.42745 +dark,D3C3FC,0.42857,0.03851,0.04976,0.82745,0.04166,0.42541,0.04976,0.76471,0.04166,0.03851,0.43667,0.98824 +dark,A1E7A4,0.40707,0.08406,0.05968,0.63137,0.05859,0.43254,0.05968,0.90588,0.05859,0.08406,0.40816,0.64314 +dark,26108E,0.25297,0.08279,0.73477,0.14902,0.19663,0.13913,0.73477,0.06275,0.19663,0.08279,0.79111,0.55686 +dark,158FB5,0.10213,0.30041,0.38024,0.08235,0.04412,0.35842,0.38024,0.56078,0.04412,0.30041,0.43825,0.70980 +dark,9E0945,0.66585,0.03631,0.27835,0.61961,0.63737,0.06479,0.27835,0.03529,0.63737,0.03631,0.30683,0.27059 +dark,CFE861,0.35382,0.16226,0.06784,0.81176,0.14477,0.37131,0.06784,0.90980,0.14477,0.16226,0.27689,0.38039 +dark,0C8879,0.08603,0.47505,0.42265,0.04706,0.04192,0.51916,0.42265,0.53333,0.04192,0.47505,0.46677,0.47451 +dark,48183B,0.82893,0.22076,0.54269,0.28235,0.66227,0.38742,0.54269,0.09412,0.66227,0.22076,0.70936,0.23137 +dark,E437BC,0.36474,0.05889,0.20130,0.89412,0.24413,0.17950,0.20130,0.21569,0.24413,0.05889,0.32191,0.73725 +dark,276566,0.32998,0.35948,0.36303,0.15294,0.13881,0.55065,0.36303,0.39608,0.13881,0.35948,0.55421,0.40000 +dark,F3835B,0.41662,0.12365,0.08590,0.95294,0.22937,0.31090,0.08590,0.51373,0.22937,0.12365,0.27314,0.35686 +dark,05F112,0.02106,0.51498,0.03846,0.01961,0.01068,0.52535,0.03846,0.94510,0.01068,0.51498,0.04884,0.07059 +dark,5B738B,0.42547,0.12402,0.14990,0.35686,0.09814,0.45136,0.14990,0.45098,0.09814,0.12402,0.47724,0.54510 +dark,B151C9,0.37358,0.07875,0.19542,0.69412,0.17209,0.28025,0.19542,0.31765,0.17209,0.07875,0.39692,0.78824 +dark,722CD2,0.29939,0.07512,0.35853,0.44706,0.19463,0.17988,0.35853,0.17255,0.19463,0.07512,0.46330,0.82353 +dark,C642E6,0.33009,0.06220,0.21677,0.77647,0.18661,0.20568,0.21677,0.25882,0.18661,0.06220,0.36025,0.90196 +dark,E86403,0.46387,0.19716,0.00591,0.90980,0.45740,0.20362,0.00591,0.39216,0.45740,0.19716,0.01238,0.01176 +dark,C0AFED,0.42099,0.04720,0.06393,0.75294,0.05179,0.41640,0.06393,0.68627,0.05179,0.04720,0.43312,0.92941 +dark,A76832,0.51173,0.22546,0.10839,0.65490,0.36203,0.37516,0.10839,0.40784,0.36203,0.22546,0.25809,0.19608 +dark,3F6D7C,0.38058,0.21894,0.24907,0.24706,0.12654,0.47297,0.24907,0.42745,0.12654,0.21894,0.50310,0.48627 +dark,C72C9E,0.40773,0.06571,0.23595,0.78039,0.29718,0.17626,0.23595,0.17255,0.29718,0.06571,0.34650,0.61961 +dark,A48608,0.46722,0.36182,0.02160,0.64314,0.44283,0.38621,0.02160,0.52549,0.44283,0.36182,0.04599,0.03137 +dark,B22A13,0.65299,0.14148,0.06400,0.69804,0.59962,0.19485,0.06400,0.16471,0.59962,0.14148,0.11737,0.07451 +dark,E1AFD7,0.43890,0.03890,0.04779,0.88235,0.05001,0.42779,0.04779,0.68627,0.05001,0.03890,0.43668,0.84314 +dark,8CF90E,0.23407,0.36630,0.02060,0.54902,0.20595,0.39442,0.02060,0.97647,0.20595,0.36630,0.04871,0.05490 +dark,6F20DB,0.27018,0.05683,0.38891,0.43529,0.19712,0.12989,0.38891,0.12549,0.19712,0.05683,0.46197,0.85882 +dark,1158AB,0.10208,0.27109,0.52677,0.06667,0.05237,0.32080,0.52677,0.34510,0.05237,0.27109,0.57648,0.67059 +dark,47F04C,0.24109,0.31494,0.09973,0.27843,0.09317,0.46286,0.09973,0.94118,0.09317,0.31494,0.24765,0.29804 +dark,E1FC2C,0.29131,0.22849,0.03990,0.88235,0.20401,0.31579,0.03990,0.98824,0.20401,0.22849,0.12720,0.17255 +dark,71E094,0.33637,0.16680,0.11020,0.44314,0.08414,0.41903,0.11020,0.87843,0.08414,0.16680,0.36244,0.58039 +dark,54839F,0.36621,0.15917,0.19319,0.32941,0.10206,0.42332,0.19319,0.51373,0.10206,0.15917,0.45734,0.62353 +dark,C36A9B,0.42663,0.08417,0.12308,0.76471,0.15484,0.35596,0.12308,0.41569,0.15484,0.08417,0.39487,0.60784 +dark,488BFE,0.21562,0.14265,0.26067,0.28235,0.07389,0.28438,0.26067,0.54510,0.07389,0.14265,0.40240,0.99608 +dark,66D23A,0.30076,0.33490,0.09250,0.40000,0.16267,0.47300,0.09250,0.82353,0.16267,0.33490,0.23059,0.22745 +dark,02C10E,0.01192,0.65028,0.04717,0.00784,0.00674,0.65547,0.04717,0.75686,0.00674,0.65028,0.05235,0.05490 +dark,16CD3E,0.10768,0.50337,0.15224,0.08627,0.05402,0.55702,0.15224,0.80392,0.05402,0.50337,0.20590,0.24314 +dark,FB2F55,0.45271,0.06724,0.12160,0.98431,0.35908,0.16086,0.12160,0.18431,0.35908,0.06724,0.21523,0.33333 +dark,21EAD3,0.10652,0.25534,0.23024,0.12941,0.03601,0.32585,0.23024,0.91765,0.03601,0.25534,0.30076,0.82745 +dark,CE897E,0.43815,0.08800,0.08094,0.80784,0.13233,0.39383,0.08094,0.53725,0.13233,0.08800,0.38676,0.49412 +dark,F2FC41,0.31026,0.18879,0.04870,0.94902,0.18130,0.31776,0.04870,0.98824,0.18130,0.18879,0.17766,0.25490 +dark,ADDEF3,0.40192,0.05897,0.06455,0.67843,0.04596,0.41494,0.06455,0.87059,0.04596,0.05897,0.42052,0.95294 +dark,A23762,0.52070,0.11915,0.21230,0.63529,0.35095,0.28890,0.21230,0.21569,0.35095,0.11915,0.38206,0.38431 +dark,D60F85,0.43330,0.02791,0.24751,0.83922,0.39825,0.06296,0.24751,0.05882,0.39825,0.02791,0.28256,0.52157 +dark,420B12,1.54397,0.24344,0.39835,0.25882,1.46063,0.32677,0.39835,0.04314,1.46063,0.24344,0.48169,0.07059 +dark,634F74,0.47701,0.10892,0.15993,0.38824,0.13649,0.44943,0.15993,0.30980,0.13649,0.10892,0.50044,0.45490 +dark,0691A4,0.03366,0.37140,0.42006,0.02353,0.01537,0.38969,0.42006,0.56863,0.01537,0.37140,0.43836,0.64314 +dark,B57DFF,0.34884,0.07164,0.14615,0.70980,0.10374,0.31674,0.14615,0.49020,0.10374,0.07164,0.39125,1.00000 +dark,35FF3E,0.17860,0.35932,0.08736,0.20784,0.07468,0.46324,0.08736,1.00000,0.07468,0.35932,0.19128,0.24314 +dark,8065DF,0.34345,0.09232,0.20383,0.50196,0.11700,0.31877,0.20383,0.39608,0.11700,0.09232,0.43029,0.87451 +dark,0BC0D3,0.04238,0.28469,0.31286,0.04314,0.01631,0.31075,0.31286,0.75294,0.01631,0.28469,0.33893,0.82745 +dark,51686F,0.45885,0.12067,0.12880,0.31765,0.09399,0.48554,0.12880,0.40784,0.09399,0.12067,0.49366,0.43529 +dark,6E4577,0.47987,0.11916,0.20550,0.43137,0.18996,0.40907,0.20550,0.27059,0.18996,0.11916,0.49542,0.46667 +dark,B15CA1,0.42481,0.08572,0.15001,0.69412,0.16492,0.34561,0.15001,0.36078,0.16492,0.08572,0.40990,0.63137 +dark,A1636F,0.47199,0.10118,0.11344,0.63137,0.16454,0.40863,0.11344,0.38824,0.16454,0.10118,0.42089,0.43529 +dark,633144,0.62635,0.18753,0.26024,0.38824,0.37888,0.43500,0.26024,0.19216,0.37888,0.18753,0.50771,0.26667 +dark,7A432D,0.64323,0.25197,0.16923,0.47843,0.45880,0.43639,0.16923,0.26275,0.45880,0.25197,0.35366,0.17647 +dark,84C631,0.33829,0.32183,0.07965,0.51765,0.21456,0.44557,0.07965,0.77647,0.21456,0.32183,0.20338,0.19216 +dark,DED740,0.34639,0.19587,0.05831,0.87059,0.20225,0.34002,0.05831,0.84314,0.20225,0.19587,0.20245,0.25098 +dark,66CE09,0.25685,0.47462,0.02074,0.40000,0.23501,0.49647,0.02074,0.80784,0.23501,0.47462,0.04258,0.03529 +dark,3166B7,0.23270,0.20570,0.36905,0.19216,0.09882,0.33958,0.36905,0.40000,0.09882,0.20570,0.50293,0.71765 +dark,B83BAF,0.39483,0.07519,0.22304,0.72157,0.23451,0.23552,0.22304,0.23137,0.23451,0.07519,0.38336,0.68627 +dark,6024F6,0.22028,0.05517,0.37696,0.37647,0.14711,0.12834,0.37696,0.14118,0.14711,0.05517,0.45013,0.96471 +dark,360C13,1.67644,0.34785,0.55076,0.21176,1.56533,0.45896,0.55076,0.04706,1.56533,0.34785,0.66188,0.07451 +dark,F64B61,0.44102,0.08798,0.11379,0.96471,0.28858,0.24042,0.11379,0.29412,0.28858,0.08798,0.26623,0.38039 +dark,5E3A68,0.50917,0.14211,0.25482,0.36863,0.23032,0.42096,0.25482,0.22745,0.23032,0.14211,0.53367,0.40784 +dark,585090,0.42074,0.12997,0.23394,0.34510,0.14297,0.40775,0.23394,0.31373,0.14297,0.12997,0.51172,0.56471 +dark,301F44,0.65005,0.27261,0.59798,0.18824,0.42211,0.50055,0.59798,0.12157,0.42211,0.27261,0.82593,0.26667 +dark,EC2731,0.50393,0.06962,0.08747,0.92549,0.42131,0.15225,0.08747,0.15294,0.42131,0.06962,0.17010,0.19216 +dark,A7C8EF,0.40068,0.06145,0.07343,0.65490,0.05131,0.41082,0.07343,0.78431,0.05131,0.06145,0.42281,0.93725 +dark,DAB5DC,0.44965,0.03179,0.03864,0.85490,0.03829,0.44315,0.03864,0.70980,0.03829,0.03179,0.45000,0.86275 +dark,6BBF06,0.29119,0.49175,0.01545,0.41961,0.27549,0.50746,0.01545,0.74902,0.27549,0.49175,0.03115,0.02353 +dark,14665C,0.20443,0.54261,0.48941,0.07843,0.10639,0.64065,0.48941,0.40000,0.10639,0.54261,0.58745,0.36078 +dark,D0E8B8,0.43845,0.04674,0.03707,0.81569,0.04190,0.44329,0.03707,0.90980,0.04190,0.04674,0.43362,0.72157 +dark,BDCF63,0.38140,0.15582,0.07452,0.74118,0.14227,0.39495,0.07452,0.81176,0.14227,0.15582,0.31365,0.38824 +dark,543007,1.08508,0.59624,0.08695,0.32941,1.04342,0.63790,0.08695,0.18824,1.04342,0.59624,0.12862,0.02745 +dark,A52BCF,0.33560,0.06039,0.29073,0.64706,0.23174,0.16426,0.29073,0.16863,0.23174,0.06039,0.39459,0.81176 +dark,61AE84,0.37457,0.17191,0.13041,0.38039,0.09583,0.45064,0.13041,0.68235,0.09583,0.17191,0.40915,0.51765 +dark,8F3B51,0.55754,0.14492,0.19896,0.56078,0.35125,0.35122,0.19896,0.23137,0.35125,0.14492,0.40525,0.31765 +dark,CF44A8,0.39838,0.07691,0.19001,0.81176,0.23412,0.24116,0.19001,0.26667,0.23412,0.07691,0.35427,0.65882 +dark,BDDD5C,0.35935,0.17681,0.07360,0.74118,0.15120,0.38495,0.07360,0.86667,0.15120,0.17681,0.28175,0.36078 +dark,CF695E,0.45680,0.11654,0.10433,0.81176,0.22975,0.34359,0.10433,0.41176,0.22975,0.11654,0.33138,0.36863 +dark,24AE9A,0.16929,0.31824,0.28166,0.14118,0.06584,0.42169,0.28166,0.68235,0.06584,0.31824,0.38511,0.60392 +dark,F03305,0.50792,0.10572,0.01036,0.94118,0.49750,0.11614,0.01036,0.20000,0.49750,0.10572,0.02078,0.01961 +dark,B61976,0.48857,0.05768,0.27224,0.71373,0.41989,0.12636,0.27224,0.09804,0.41989,0.05768,0.34092,0.46275 +dark,8B99AC,0.45109,0.05176,0.05818,0.54510,0.04702,0.45583,0.05818,0.60000,0.04702,0.05176,0.46225,0.67451 +dark,6ECF5D,0.34609,0.22854,0.10268,0.43137,0.12145,0.45318,0.10268,0.81176,0.12145,0.22854,0.32732,0.36471 +dark,27C7FE,0.11662,0.20330,0.25949,0.15294,0.03984,0.28008,0.25949,0.78039,0.03984,0.20330,0.33627,0.99608 +dark,6D3DCA,0.32297,0.09625,0.31872,0.42745,0.17198,0.24724,0.31872,0.23922,0.17198,0.09625,0.46971,0.79216 +dark,0B3A37,0.26941,0.92051,0.87289,0.04314,0.17458,1.01533,0.87289,0.22745,0.17458,0.92051,0.96772,0.21569 +dark,7983E3,0.35297,0.09360,0.16218,0.47451,0.08645,0.36012,0.16218,0.51373,0.08645,0.09360,0.42870,0.89020 +dark,CD1964,0.49687,0.05316,0.21263,0.80392,0.43590,0.11413,0.21263,0.09804,0.43590,0.05316,0.27361,0.39216 +dark,C00532,0.61833,0.01576,0.15763,0.75294,0.60531,0.02878,0.15763,0.01961,0.60531,0.01576,0.17065,0.19608 +dark,84808D,0.48280,0.02803,0.03087,0.51765,0.02890,0.48193,0.03087,0.50196,0.02890,0.02803,0.48477,0.55294 +dark,AED433,0.33678,0.26378,0.06346,0.68235,0.21650,0.38406,0.06346,0.83137,0.21650,0.26378,0.18374,0.20000 +dark,E32717,0.53611,0.08340,0.04919,0.89020,0.48545,0.13406,0.04919,0.15294,0.48545,0.08340,0.09985,0.09020 +dark,C551C5,0.38128,0.07224,0.17570,0.77255,0.17570,0.27782,0.17570,0.31765,0.17570,0.07224,0.38128,0.77255 +dark,F156FD,0.32660,0.05590,0.16444,0.94510,0.15664,0.22586,0.16444,0.33725,0.15664,0.05590,0.33440,0.99216 +dark,1DDBFD,0.08633,0.21914,0.25317,0.11373,0.02902,0.27646,0.25317,0.85882,0.02902,0.21914,0.31048,0.99216 +dark,D791CC,0.41919,0.05529,0.07779,0.84314,0.08198,0.39250,0.07779,0.56863,0.08198,0.05529,0.41500,0.80000 +dark,9FBB92,0.44487,0.06409,0.05004,0.62353,0.05449,0.45446,0.05004,0.73333,0.05449,0.06409,0.44041,0.57255 +dark,F78A91,0.41685,0.07682,0.08071,0.96863,0.13749,0.35617,0.08071,0.54118,0.13749,0.07682,0.36007,0.56863 +dark,970D07,0.82078,0.06867,0.03697,0.59216,0.79760,0.09185,0.03697,0.05098,0.79760,0.06867,0.06015,0.02745 +dark,7D1550,0.67423,0.09916,0.37774,0.49020,0.59023,0.18316,0.37774,0.08235,0.59023,0.09916,0.46174,0.31373 +dark,D1C71B,0.34081,0.26300,0.03568,0.81961,0.27621,0.32759,0.03568,0.78039,0.27621,0.26300,0.10028,0.10588 +dark,A1CB19,0.32723,0.33496,0.04125,0.63137,0.26566,0.39653,0.04125,0.79608,0.26566,0.33496,0.10283,0.09804 +dark,A32572,0.50596,0.08909,0.27448,0.63922,0.39246,0.20258,0.27448,0.14510,0.39246,0.08909,0.38798,0.44706 +dark,DBF480,0.36945,0.11939,0.06263,0.85882,0.10716,0.38168,0.06263,0.95686,0.10716,0.11939,0.32493,0.50196 +dark,7C1732,0.79242,0.12978,0.28213,0.48627,0.69967,0.22252,0.28213,0.09020,0.69967,0.12978,0.37487,0.19608 +dark,EF497D,0.42381,0.08280,0.14178,0.93725,0.27109,0.23552,0.14178,0.28627,0.27109,0.08280,0.29450,0.49020 +dark,3A19D5,0.19092,0.05700,0.48564,0.22745,0.13224,0.11568,0.48564,0.09804,0.13224,0.05700,0.54432,0.83529 +dark,E93C68,0.44980,0.08267,0.14330,0.91373,0.32105,0.21143,0.14330,0.23529,0.32105,0.08267,0.27206,0.40784 +dark,1AB64F,0.14239,0.49674,0.21562,0.10196,0.07096,0.56817,0.21562,0.71373,0.07096,0.49674,0.28705,0.30980 +dark,3FBAE2,0.20401,0.19081,0.23184,0.24706,0.06463,0.33019,0.23184,0.72941,0.06463,0.19081,0.37122,0.88627 +dark,47D5E9,0.21248,0.18035,0.19728,0.27843,0.06012,0.33271,0.19728,0.83529,0.06012,0.18035,0.34964,0.91373 +dark,86D6BA,0.37803,0.10371,0.09014,0.52549,0.06494,0.41680,0.09014,0.83922,0.06494,0.10371,0.40323,0.72941 +dark,469441,0.38090,0.34105,0.14979,0.27451,0.16131,0.56065,0.14979,0.58039,0.16131,0.34105,0.36938,0.25490 +dark,7AF63A,0.26881,0.30433,0.07175,0.47843,0.15093,0.42221,0.07175,0.96471,0.15093,0.30433,0.18964,0.22745 +dark,9EB78C,0.44316,0.07024,0.05374,0.61961,0.06064,0.45275,0.05374,0.71765,0.06064,0.07024,0.43625,0.54902 +dark,8B618E,0.45642,0.08016,0.11735,0.54510,0.11487,0.42171,0.11735,0.38039,0.11487,0.08016,0.45889,0.55686 +dark,7A617F,0.47279,0.07227,0.09463,0.47843,0.09090,0.45416,0.09463,0.38039,0.09090,0.07227,0.47652,0.49804 +dark,64141F,0.99781,0.17956,0.27832,0.39216,0.89781,0.27956,0.27832,0.07843,0.89781,0.17956,0.37832,0.12157 +dark,048A84,0.02807,0.46829,0.44793,0.01569,0.01357,0.48278,0.44793,0.54118,0.01357,0.46829,0.46242,0.51765 +dark,D90D13,0.57616,0.03272,0.04782,0.85098,0.54621,0.06268,0.04782,0.05098,0.54621,0.03272,0.07778,0.07451 +dark,34718E,0.30101,0.25624,0.32200,0.20392,0.11791,0.43934,0.32200,0.44314,0.11791,0.25624,0.50510,0.55686 +dark,252C52,0.48375,0.30698,0.57209,0.14510,0.25814,0.53259,0.57209,0.17255,0.25814,0.30698,0.79770,0.32157 +dark,78BFF7,0.31322,0.11191,0.14472,0.47059,0.07031,0.35482,0.14472,0.74902,0.07031,0.11191,0.38763,0.96863 +dark,F5B56B,0.38717,0.12471,0.07372,0.96078,0.16880,0.34307,0.07372,0.70980,0.16880,0.12471,0.29209,0.41961 +dark,ADAFFD,0.39789,0.05664,0.08189,0.67843,0.05599,0.39854,0.08189,0.68627,0.05599,0.05664,0.42378,0.99216 +dark,44263D,0.67016,0.21836,0.35052,0.26667,0.39074,0.49777,0.35052,0.14902,0.39074,0.21836,0.62993,0.23922 +dark,E56DE3,0.37006,0.06286,0.13091,0.89804,0.13207,0.30085,0.13091,0.42745,0.13207,0.06286,0.36890,0.89020 +dark,D984C7,0.40824,0.06332,0.09546,0.85098,0.10409,0.36747,0.09546,0.51765,0.10409,0.06332,0.39961,0.78039 +dark,4DBC4E,0.32719,0.29885,0.12399,0.30196,0.12240,0.50363,0.12399,0.73725,0.12240,0.29885,0.32878,0.30588 +dark,A6945E,0.44060,0.14040,0.08917,0.65098,0.15747,0.42353,0.08917,0.58039,0.15747,0.14040,0.37230,0.36863 +dark,DABD31,0.36397,0.21812,0.05655,0.85490,0.25159,0.33051,0.05655,0.74118,0.25159,0.21812,0.16893,0.19216 +dark,ECA528,0.38041,0.20672,0.05011,0.92549,0.29567,0.29146,0.05011,0.64706,0.29567,0.20672,0.13486,0.15686 +dark,2ADDF8,0.12435,0.20877,0.23428,0.16471,0.03968,0.29345,0.23428,0.86667,0.03968,0.20877,0.31896,0.97255 +dark,ED9904,0.38168,0.24095,0.00630,0.92941,0.37324,0.24939,0.00630,0.60000,0.37324,0.24095,0.01474,0.01569 +dark,269E6D,0.21636,0.39959,0.27567,0.14902,0.09610,0.51985,0.27567,0.61961,0.09610,0.39959,0.39592,0.42745 +dark,299BFD,0.12985,0.18458,0.30129,0.16078,0.04883,0.26561,0.30129,0.60784,0.04883,0.18458,0.38232,0.99216 +dark,A79049,0.44070,0.19154,0.09710,0.65490,0.22213,0.41010,0.09710,0.56471,0.22213,0.19154,0.31566,0.28627 +dark,70B7A7,0.38096,0.12246,0.11175,0.43922,0.07495,0.42847,0.11175,0.71765,0.07495,0.12246,0.41776,0.65490 +dark,BB3CA5,0.40654,0.07897,0.21716,0.73333,0.24611,0.23939,0.21716,0.23529,0.24611,0.07897,0.37758,0.64706 +dark,E18EE0,0.40304,0.05521,0.08710,0.88235,0.08748,0.37077,0.08710,0.55686,0.08748,0.05521,0.40265,0.87843 +dark,9CEAA2,0.39627,0.09441,0.06536,0.61176,0.06294,0.42775,0.06536,0.91765,0.06294,0.09441,0.39870,0.63529 +dark,71CC7F,0.36810,0.16454,0.10244,0.44314,0.09114,0.44150,0.10244,0.80000,0.09114,0.16454,0.37940,0.49804 +dark,2BB9BB,0.17440,0.25567,0.25844,0.16863,0.05943,0.37064,0.25844,0.72549,0.05943,0.25567,0.37341,0.73333 +dark,0CBACA,0.04875,0.29527,0.32067,0.04706,0.01905,0.32498,0.32067,0.72941,0.01905,0.29527,0.35038,0.79216 +dark,C663BC,0.39965,0.07482,0.14209,0.77647,0.14965,0.32482,0.14209,0.38824,0.14965,0.07482,0.39209,0.73725 +dark,C74F5A,0.48213,0.11260,0.12828,0.78039,0.28364,0.31109,0.12828,0.30980,0.28364,0.11260,0.32677,0.35294 +dark,5A2DE8,0.24162,0.07232,0.37284,0.35294,0.14463,0.16930,0.37284,0.17647,0.14463,0.07232,0.46982,0.90980 +dark,900B71,0.54249,0.03852,0.39573,0.56471,0.50430,0.07672,0.39573,0.04314,0.50430,0.03852,0.43393,0.44314 +dark,1D5197,0.19494,0.27627,0.51502,0.11373,0.09891,0.37229,0.51502,0.31765,0.09891,0.27627,0.61104,0.59216 +dark,0BD820,0.05331,0.54680,0.08101,0.04314,0.02785,0.57226,0.08101,0.84706,0.02785,0.54680,0.10647,0.12549 +dark,9C2BAB,0.39435,0.07404,0.29445,0.61176,0.26862,0.19977,0.29445,0.16863,0.26862,0.07404,0.42018,0.67059 +dark,298C35,0.29988,0.52397,0.19836,0.16078,0.15345,0.67040,0.19836,0.54902,0.15345,0.52397,0.34479,0.20784 +dark,A02B0D,0.71926,0.18238,0.05514,0.62745,0.67863,0.22301,0.05514,0.16863,0.67863,0.18238,0.09576,0.05098 +dark,4931D8,0.24574,0.08881,0.39151,0.28627,0.13231,0.20224,0.39151,0.19216,0.13231,0.08881,0.50493,0.84706 +dark,7D70FA,0.32103,0.08694,0.19406,0.49020,0.09703,0.31094,0.19406,0.43922,0.09703,0.08694,0.41806,0.98039 +dark,ADEBC9,0.41443,0.06296,0.05385,0.67843,0.04635,0.43105,0.05385,0.92157,0.04635,0.06296,0.42194,0.78824 +dark,B3ACAA,0.48754,0.01218,0.01204,0.70196,0.01268,0.48704,0.01204,0.67451,0.01268,0.01218,0.48690,0.66667 +dark,45FCF9,0.18595,0.17911,0.17698,0.27059,0.04904,0.31602,0.17698,0.98824,0.04904,0.17911,0.31389,0.97647 +dark,2517D2,0.14609,0.05677,0.51834,0.14510,0.09133,0.11153,0.51834,0.09020,0.09133,0.05677,0.57310,0.82353 +dark,B201C1,0.33011,0.00184,0.35512,0.69804,0.32752,0.00443,0.35512,0.00392,0.32752,0.00184,0.35771,0.75686 +dark,2DED0C,0.11869,0.49175,0.02490,0.17647,0.09337,0.51706,0.02490,0.92941,0.09337,0.49175,0.05021,0.04706 +dark,B90538,0.62738,0.01659,0.18582,0.72549,0.61387,0.03010,0.18582,0.01961,0.61387,0.01659,0.19933,0.21961 +dark,D7D74A,0.35568,0.18358,0.06319,0.84314,0.18358,0.35568,0.06319,0.84314,0.18358,0.18358,0.23528,0.29020 +dark,7D53C0,0.36852,0.10118,0.23405,0.49020,0.15238,0.31732,0.23405,0.32549,0.15238,0.10118,0.45020,0.75294 +dark,55A468,0.37532,0.22415,0.14214,0.33333,0.11617,0.48329,0.14214,0.64314,0.11617,0.22415,0.40129,0.40784 +dark,C7D995,0.41633,0.07962,0.05467,0.78039,0.07302,0.42294,0.05467,0.85098,0.07302,0.07962,0.39799,0.58431 +dark,8C1EF1,0.26112,0.04262,0.34235,0.54902,0.19888,0.10486,0.34235,0.11765,0.19888,0.04262,0.40459,0.94510 +dark,9D3A9B,0.42728,0.08961,0.23947,0.61569,0.24256,0.27432,0.23947,0.22745,0.24256,0.08961,0.42419,0.60784 +dark,4F5B1F,0.59927,0.49409,0.16832,0.30980,0.42894,0.66442,0.16832,0.35686,0.42894,0.49409,0.33865,0.12157 +dark,EA08F6,0.26653,0.00856,0.26310,0.91765,0.25027,0.02482,0.26310,0.03137,0.25027,0.00856,0.27936,0.96471 +dark,12A037,0.12661,0.62541,0.21498,0.07059,0.07036,0.68166,0.21498,0.62745,0.07036,0.62541,0.27123,0.21569 +dark,655EB7,0.37606,0.11097,0.21604,0.39608,0.11923,0.36780,0.21604,0.36863,0.11923,0.11097,0.47287,0.71765 +dark,6F78F1,0.32031,0.09732,0.19546,0.43529,0.09002,0.32761,0.19546,0.47059,0.09002,0.09732,0.42575,0.94510 +dark,72E25E,0.32441,0.23084,0.09601,0.44706,0.11644,0.43880,0.09601,0.88627,0.11644,0.23084,0.30398,0.36863 +dark,8FCE58,0.36146,0.21301,0.09100,0.56078,0.14787,0.42661,0.09100,0.80784,0.14787,0.21301,0.30459,0.34510 +dark,9FF585,0.36141,0.13865,0.07527,0.62353,0.08998,0.41008,0.07527,0.96078,0.08998,0.13865,0.34669,0.52157 +dark,F8CEAF,0.42199,0.05745,0.04881,0.97255,0.06917,0.41028,0.04881,0.80784,0.06917,0.05745,0.40163,0.68627 +dark,2FF771,0.15900,0.33558,0.15353,0.18431,0.06386,0.43073,0.15353,0.96863,0.06386,0.33558,0.24867,0.44314 +dark,CC196F,0.47956,0.05126,0.22760,0.80000,0.41829,0.11253,0.22760,0.09804,0.41829,0.05126,0.28887,0.43529 +dark,44C58C,0.26266,0.26093,0.18544,0.26667,0.09007,0.43352,0.18544,0.77255,0.09007,0.26093,0.35802,0.54902 +dark,1D7905,0.24925,0.95376,0.03941,0.11373,0.22859,0.97442,0.03941,0.47451,0.22859,0.95376,0.06007,0.01961 +dark,F1BEE6,0.43840,0.03485,0.04219,0.94510,0.04421,0.42905,0.04219,0.74510,0.04421,0.03485,0.43638,0.90196 +dark,7ED750,0.33330,0.25127,0.09350,0.49412,0.14726,0.43732,0.09350,0.84314,0.14726,0.25127,0.27954,0.31373 +dark,D450A4,0.40379,0.08117,0.16640,0.83137,0.21511,0.26985,0.16640,0.31373,0.21511,0.08117,0.35508,0.64314 +dark,4A8443,0.42342,0.30258,0.15358,0.29020,0.16963,0.55637,0.15358,0.51765,0.16963,0.30258,0.40737,0.26275 +dark,881B4D,0.65176,0.10969,0.31281,0.53333,0.55250,0.20895,0.31281,0.10588,0.55250,0.10969,0.41208,0.30196 +dark,5B0068,0.60756,0.00000,0.69435,0.35686,0.60756,0.00000,0.69435,0.00000,0.60756,0.00000,0.69435,0.40784 +dark,4C412A,0.64478,0.31514,0.20363,0.29804,0.36847,0.59145,0.20363,0.25490,0.36847,0.31514,0.47994,0.16471 +dark,B48A31,0.44631,0.23782,0.08444,0.70588,0.31020,0.37393,0.08444,0.54118,0.31020,0.23782,0.22055,0.19216 +dark,F5EF2A,0.30338,0.21233,0.03731,0.96078,0.21766,0.29805,0.03731,0.93725,0.21766,0.21233,0.12303,0.16471 +dark,9B0960,0.58767,0.03244,0.34600,0.60784,0.55864,0.06147,0.34600,0.03529,0.55864,0.03244,0.37503,0.37647 +dark,D5622E,0.48095,0.17160,0.08055,0.83529,0.37297,0.27958,0.08055,0.38431,0.37297,0.17160,0.18853,0.18039 +dark,E16A13,0.46434,0.19886,0.03565,0.88235,0.42212,0.24109,0.03565,0.41569,0.42212,0.19886,0.07787,0.07451 +dark,D3FD50,0.31816,0.19192,0.06069,0.82745,0.16006,0.35002,0.06069,0.99216,0.16006,0.19192,0.21879,0.31373 +dark,9FA19F,0.49708,0.00333,0.00329,0.62353,0.00329,0.49712,0.00329,0.63137,0.00329,0.00333,0.49708,0.62353 +dark,CF269C,0.40576,0.05764,0.23662,0.81176,0.31397,0.14942,0.23662,0.14902,0.31397,0.05764,0.32840,0.61176 +dark,EF22B8,0.35482,0.04036,0.21841,0.93725,0.28369,0.11149,0.21841,0.13333,0.28369,0.04036,0.28954,0.72157 +dark,264736,0.50714,0.44756,0.34040,0.14902,0.23954,0.71516,0.34040,0.27843,0.23954,0.44756,0.60800,0.21176 +dark,C9EC3E,0.32042,0.22198,0.05832,0.78824,0.18906,0.35334,0.05832,0.92549,0.18906,0.22198,0.18967,0.24314 +dark,E306C1,0.33048,0.00839,0.26974,0.89020,0.31726,0.02160,0.26974,0.02353,0.31726,0.00839,0.28296,0.75686 +dark,F37996,0.41068,0.08052,0.09982,0.95294,0.16171,0.32949,0.09982,0.47451,0.16171,0.08052,0.34879,0.58824 +dark,3AEA05,0.13515,0.50215,0.01073,0.22745,0.12446,0.51283,0.01073,0.91765,0.12446,0.50215,0.02141,0.01961 +dark,694D94,0.42541,0.12120,0.23296,0.41176,0.16528,0.38134,0.23296,0.30196,0.16528,0.12120,0.49310,0.58039 +dark,C098AC,0.45278,0.04509,0.05102,0.75294,0.05695,0.44092,0.05102,0.59608,0.05695,0.04509,0.44685,0.67451 +dark,C82C83,0.44657,0.07404,0.22045,0.78431,0.33657,0.18404,0.22045,0.17255,0.33657,0.07404,0.33045,0.51373 +dark,6066C0,0.35835,0.11512,0.21670,0.37647,0.10835,0.36512,0.21670,0.40000,0.10835,0.11512,0.46670,0.75294 +dark,38A89E,0.25118,0.25353,0.23844,0.21961,0.08451,0.42020,0.23844,0.65882,0.08451,0.25353,0.40511,0.61961 +dark,8CF0A9,0.36199,0.12055,0.08489,0.54902,0.07032,0.41222,0.08489,0.94118,0.07032,0.12055,0.37656,0.66275 +dark,E4C32F,0.35332,0.21403,0.05159,0.89412,0.25025,0.31710,0.05159,0.76471,0.25025,0.21403,0.15466,0.18431 +dark,344830,0.55017,0.30024,0.20016,0.20392,0.21684,0.63357,0.20016,0.28235,0.21684,0.30024,0.53349,0.18824 +dark,435B5C,0.47345,0.14848,0.15011,0.26275,0.10932,0.51261,0.15011,0.35686,0.10932,0.14848,0.51424,0.36078 +dark,62CD12,0.26329,0.45892,0.04030,0.38431,0.21939,0.50283,0.04030,0.80392,0.21939,0.45892,0.08420,0.07059 +dark,ED2E33,0.49702,0.07763,0.08607,0.92941,0.39997,0.17468,0.08607,0.18039,0.39997,0.07763,0.18312,0.20000 +light,E7EEE7,0.52100,0.02529,0.03571,0.32387,0.03622,0.51095,0.03622,0.34993,0.03571,0.02529,0.52100,0.32387 +light,615EF3,0.29366,0.10215,0.00761,-0.02303,0.09939,0.29469,0.00755,-0.03300,0.14182,0.14451,0.20419,0.46242 +light,5F30E4,0.20113,0.12402,0.01618,0.03122,0.08299,0.21263,0.01400,-0.12139,0.13229,0.17115,0.12759,0.46309 +light,9B482E,0.19800,0.09079,0.10369,0.21536,0.03957,0.22080,0.08270,-0.06072,0.03642,0.06666,0.22451,-0.14720 +light,15CAE7,0.19693,0.03431,0.01554,-0.16442,0.25081,0.10226,0.02572,0.41336,0.26673,0.06041,0.07281,0.50593 +light,500720,0.10032,0.08016,0.07208,0.06117,0.04424,0.10644,0.05637,-0.17961,0.04846,0.06868,0.10550,-0.09715 +light,1E1261,0.15224,0.06263,0.04175,-0.13897,0.05695,0.15277,0.03999,-0.17913,0.07343,0.07735,0.14435,0.08526 +light,7B0FED,0.12469,0.16917,0.01269,0.17580,0.06646,0.15248,0.00906,-0.16918,0.12111,0.22019,0.04816,0.53996 +light,A7E164,0.31695,0.03229,0.16685,0.13881,0.10775,0.25895,0.18978,0.32587,0.07969,0.02717,0.36258,-0.07728 +light,7796FF,0.37117,0.10642,0.00000,-0.01092,0.14926,0.34857,0.00000,0.09040,0.18796,0.14512,0.23333,0.43359 +light,022BEA,0.09267,0.07407,0.00734,-0.16623,0.10250,0.09016,0.00851,-0.03254,0.16819,0.14093,0.01823,0.59029 +light,8ED02A,0.18995,0.03701,0.16775,0.16215,0.10379,0.14413,0.19563,0.37214,0.06658,0.02769,0.22645,-0.15601 +light,82A175,0.40870,0.03410,0.05006,0.01694,0.04900,0.40020,0.05409,0.12808,0.04382,0.03295,0.41173,-0.02967 +light,930F23,0.10072,0.11044,0.10124,0.26407,0.03338,0.12520,0.06800,-0.16775,0.03585,0.07967,0.12405,-0.10233 +light,37CD37,0.21663,0.02062,0.08248,-0.10405,0.12239,0.16475,0.12239,0.39439,0.08248,0.02062,0.21663,-0.10405 +light,94C522,0.17037,0.04557,0.17365,0.19079,0.09430,0.13741,0.19477,0.34607,0.06029,0.03268,0.21083,-0.17047 +light,08006D,0.06613,0.06827,0.03909,-0.14212,0.06412,0.06619,0.03790,-0.16821,0.09152,0.09449,0.05410,0.18734 +light,6B1AF0,0.15026,0.14869,0.00974,0.11092,0.07459,0.16958,0.00756,-0.14978,0.13140,0.20332,0.06748,0.53898 +light,C0CBD6,0.50058,0.04291,0.03383,0.17563,0.05326,0.49256,0.03466,0.21560,0.05454,0.04502,0.48409,0.25556 +light,25658A,0.21634,0.05813,0.04416,-0.17353,0.10032,0.20493,0.05384,0.03699,0.11075,0.07823,0.19349,0.15870 +light,AC2C9F,0.19316,0.16589,0.07548,0.23998,0.04569,0.24407,0.05285,-0.17007,0.06327,0.16084,0.20109,0.19833 +light,AA07D1,0.09067,0.21569,0.04001,0.32030,0.04557,0.14971,0.02466,-0.19250,0.08071,0.23548,0.06042,0.44299 +light,3C447E,0.29194,0.05163,0.03562,-0.14390,0.05521,0.29104,0.03652,-0.11611,0.06512,0.06245,0.28118,0.08536 +light,33051E,0.10360,0.06689,0.06020,-0.03068,0.04637,0.10585,0.05115,-0.18377,0.05083,0.06230,0.10509,-0.10057 +light,EEF95A,0.20986,0.01028,0.28281,0.43038,0.02979,0.19124,0.28912,0.46632,0.02039,0.00720,0.37863,-0.05328 +light,60E561,0.30509,0.01561,0.09488,-0.03911,0.13166,0.23114,0.13083,0.40441,0.09575,0.01566,0.30476,-0.03578 +light,43D6C4,0.31053,0.03358,0.04833,-0.12969,0.22429,0.20546,0.07039,0.33908,0.21568,0.04704,0.22423,0.28168 +light,3BCAD7,0.29362,0.04229,0.03192,-0.13646,0.22764,0.19877,0.04646,0.31929,0.23412,0.06331,0.18499,0.36072 +light,6C008A,0.07978,0.13840,0.06350,0.14184,0.05605,0.09722,0.04461,-0.19788,0.08638,0.14984,0.06875,0.23621 +light,9B0A6B,0.09484,0.15333,0.09262,0.26705,0.04045,0.13136,0.05987,-0.19246,0.05526,0.13538,0.11404,0.11494 +light,5FC933,0.21625,0.03017,0.11396,0.01217,0.11645,0.16617,0.14848,0.35713,0.07815,0.02637,0.22650,-0.13102 +light,154A6D,0.16819,0.05559,0.04484,-0.18627,0.08566,0.16259,0.05345,-0.01151,0.09478,0.07331,0.15546,0.10390 +light,E28404,0.03488,0.11039,0.22526,0.51575,0.02094,0.09766,0.18124,0.21780,0.01401,0.05944,0.13014,-0.18791 +light,A897C5,0.44251,0.07085,0.03951,0.10595,0.05688,0.45125,0.03792,0.04610,0.06333,0.07570,0.42547,0.20805 +light,25262E,0.41568,0.01345,0.01295,-0.29698,0.01355,0.41567,0.01300,-0.29320,0.01393,0.01386,0.41552,-0.26292 +light,6A7C07,0.10370,0.06636,0.12562,0.12000,0.07924,0.09789,0.13189,0.17726,0.05478,0.04816,0.11940,-0.19488 +light,BCBEE8,0.49653,0.08863,0.03136,0.12074,0.09177,0.49420,0.03150,0.12763,0.10043,0.09743,0.43965,0.27230 +light,41F745,0.22655,0.00400,0.09297,-0.06861,0.14898,0.13785,0.14585,0.53595,0.09616,0.00405,0.22571,-0.05533 +light,C55D4E,0.23695,0.10888,0.11896,0.30776,0.03001,0.28180,0.09159,-0.03869,0.02872,0.08021,0.28561,-0.08866 +light,9F747F,0.40067,0.05197,0.04785,0.12304,0.03216,0.41135,0.04288,-0.03149,0.03312,0.04795,0.40894,0.00804 +light,615164,0.42500,0.02202,0.01962,-0.08624,0.01909,0.42602,0.01873,-0.14619,0.02017,0.02221,0.42478,-0.07500 +light,C6F728,0.14650,0.00920,0.24715,0.37363,0.07261,0.09116,0.27389,0.53097,0.04267,0.00599,0.24192,-0.13372 +light,D71835,0.08197,0.15107,0.13211,0.47798,0.01553,0.14549,0.07842,-0.14533,0.01714,0.09900,0.14239,-0.05069 +light,371382,0.14441,0.08417,0.04458,-0.05748,0.06305,0.14747,0.03941,-0.17542,0.08859,0.10454,0.12845,0.18823 +light,7AC883,0.38628,0.03361,0.07578,-0.01723,0.09809,0.34556,0.09145,0.24920,0.08322,0.03441,0.38259,0.01351 +light,D7FB96,0.38857,0.00898,0.23564,0.20995,0.09664,0.30847,0.25369,0.32552,0.07735,0.00774,0.50185,0.00129 +light,592340,0.24285,0.06126,0.05318,-0.00827,0.03897,0.24827,0.04483,-0.19482,0.04286,0.05681,0.24595,-0.09464 +light,74F525,0.17361,0.00706,0.15385,0.12039,0.13221,0.08502,0.20735,0.53621,0.07721,0.00555,0.19660,-0.13427 +light,8F6C68,0.39565,0.04202,0.04317,0.07994,0.02920,0.40196,0.03937,-0.04701,0.02888,0.03791,0.40257,-0.06152 +light,082389,0.10359,0.06626,0.03554,-0.17402,0.08203,0.10226,0.03919,-0.08623,0.11088,0.09876,0.08217,0.24544 +light,D2E47F,0.35116,0.04359,0.20665,0.22212,0.07546,0.32379,0.21465,0.28021,0.05968,0.03581,0.44828,-0.04573 +light,1E175A,0.18216,0.05607,0.03988,-0.16047,0.05305,0.18247,0.03890,-0.18422,0.06583,0.06788,0.17605,0.04318 +light,90BC43,0.26018,0.04949,0.13886,0.11619,0.09102,0.23313,0.15417,0.25893,0.06616,0.03994,0.29025,-0.13360 +light,2FB946,0.21234,0.02871,0.07588,-0.13261,0.12429,0.16886,0.11055,0.32179,0.09181,0.03090,0.20868,-0.05688 +light,E6A947,0.18437,0.10328,0.22098,0.39333,0.02625,0.24464,0.19318,0.19867,0.02018,0.06942,0.30288,-0.11405 +light,1109F3,0.09823,0.08239,0.00402,-0.11798,0.07737,0.09849,0.00390,-0.14446,0.14595,0.15085,0.02588,0.63026 +light,B79F11,0.10685,0.08053,0.19965,0.33062,0.05709,0.12257,0.18871,0.25516,0.03751,0.05001,0.17043,-0.19128 +light,0A26F6,0.10748,0.07720,0.00320,-0.14867,0.09637,0.10568,0.00354,-0.05657,0.16478,0.14595,0.02638,0.62760 +light,229FA3,0.21827,0.04951,0.04745,-0.18190,0.16328,0.17522,0.06797,0.21706,0.16486,0.07161,0.17292,0.22983 +light,452526,0.30056,0.03803,0.03785,-0.10585,0.02924,0.30239,0.03411,-0.22065,0.02934,0.03439,0.30235,-0.21706 +light,E7BC16,0.07429,0.07445,0.25892,0.49822,0.02431,0.11549,0.23601,0.36145,0.01520,0.04244,0.19519,-0.16656 +light,42AEB4,0.30364,0.05156,0.04774,-0.14412,0.16078,0.25224,0.06380,0.20552,0.16303,0.06987,0.24803,0.22495 +light,2BF227,0.16720,0.00531,0.08826,-0.09214,0.14447,0.08944,0.14719,0.56792,0.08546,0.00524,0.16765,-0.10541 +light,D50FFF,0.07616,0.26711,0.00000,0.49202,0.02697,0.18352,0.00000,-0.15166,0.05094,0.29109,0.02941,0.62856 +light,07C3C2,0.15448,0.03303,0.03358,-0.19364,0.23449,0.07468,0.05768,0.39785,0.23397,0.05661,0.07550,0.39471 +light,062429,0.12660,0.04699,0.04592,-0.19598,0.05957,0.12556,0.05120,-0.09515,0.06059,0.05329,0.12525,-0.07835 +light,2E3B83,0.23839,0.05891,0.03727,-0.15417,0.06553,0.23703,0.03888,-0.11006,0.08055,0.07554,0.22337,0.13426 +light,D5A9C6,0.44580,0.10050,0.06661,0.22239,0.04447,0.48777,0.06035,0.07016,0.04751,0.09728,0.46119,0.17049 +light,EAE1EC,0.51888,0.06026,0.03817,0.30034,0.04141,0.53585,0.03746,0.26763,0.04236,0.06051,0.51502,0.30761 +light,2A0F9E,0.12389,0.08611,0.03480,-0.08010,0.06948,0.12575,0.03164,-0.16804,0.10627,0.11974,0.09586,0.29773 +light,2CF60B,0.10917,0.00370,0.10039,-0.04071,0.14546,0.02856,0.16821,0.62248,0.07723,0.00329,0.11166,-0.14905 +light,7539FE,0.21474,0.14711,0.00074,0.09623,0.08599,0.23559,0.00062,-0.09868,0.14029,0.20129,0.11322,0.54128 +light,F88205,0.01670,0.11821,0.23642,0.60121,0.00507,0.10056,0.18096,0.22322,0.00342,0.06110,0.13229,-0.17721 +light,BC9A49,0.25111,0.08586,0.15472,0.24556,0.05259,0.27342,0.14285,0.13507,0.04217,0.06357,0.30871,-0.12818 +light,6756AF,0.31721,0.07949,0.03763,-0.03040,0.06810,0.32143,0.03584,-0.08811,0.08587,0.09547,0.29091,0.21402 +light,E2FF7B,0.30048,0.00000,0.26991,0.31589,0.06287,0.24118,0.28619,0.40976,0.04660,0.00000,0.45329,-0.01754 +light,A7CF80,0.39039,0.04430,0.11720,0.10301,0.08891,0.35768,0.12831,0.23686,0.07371,0.04020,0.41555,-0.02750 +light,65DC66,0.32155,0.02091,0.09140,-0.03778,0.12275,0.25744,0.12196,0.36059,0.09226,0.02097,0.32121,-0.03443 +light,6DC470,0.35627,0.03160,0.07660,-0.03701,0.09690,0.31722,0.09490,0.25961,0.07885,0.03186,0.35529,-0.02679 +light,A26B45,0.26422,0.08157,0.10252,0.18698,0.04450,0.28378,0.08900,0.00234,0.03983,0.06338,0.29262,-0.12524 +light,44FEB3,0.29669,0.00087,0.06618,-0.09707,0.25659,0.13523,0.10428,0.49997,0.21878,0.00117,0.22278,0.25923 +light,14208D,0.14145,0.06693,0.03421,-0.16416,0.07361,0.14077,0.03571,-0.12459,0.10156,0.09637,0.12019,0.23482 +light,5639E6,0.21899,0.11139,0.01406,-0.00718,0.08699,0.22583,0.01287,-0.10215,0.13522,0.15843,0.14392,0.46440 +light,F18C6D,0.24256,0.13491,0.17127,0.39635,0.01308,0.33358,0.13640,0.06597,0.01205,0.09900,0.35183,-0.03544 +light,D3C3FC,0.51236,0.17108,0.00855,0.13545,0.12115,0.55211,0.00826,0.08318,0.13650,0.18613,0.39621,0.26939 +light,A1E7A4,0.44895,0.02565,0.09725,0.05952,0.11737,0.37845,0.11362,0.29645,0.10119,0.02583,0.44644,0.06968 +light,26108E,0.12868,0.07968,0.03767,-0.09701,0.06691,0.13003,0.03484,-0.16904,0.09802,0.10796,0.10738,0.24350 +light,158FB5,0.17568,0.05632,0.03721,-0.18686,0.16969,0.13923,0.05366,0.19821,0.18589,0.08897,0.11680,0.31815 +light,9E0945,0.08270,0.13750,0.10396,0.29545,0.03466,0.11637,0.06646,-0.18219,0.04253,0.10787,0.11004,0.01014 +light,CFE861,0.27869,0.03337,0.22921,0.27050,0.07340,0.24422,0.24162,0.35056,0.05305,0.02542,0.38369,-0.08177 +light,0C8879,0.14145,0.04766,0.05367,-0.19573,0.14254,0.11392,0.07860,0.19828,0.13707,0.06712,0.11970,0.15062 +light,48183B,0.21444,0.06030,0.05116,-0.04355,0.04076,0.21812,0.04365,-0.20841,0.04587,0.05790,0.21580,-0.08820 +light,E437BC,0.15344,0.24315,0.08145,0.41608,0.02107,0.27667,0.05228,-0.13433,0.03011,0.22301,0.19532,0.28882 +light,276566,0.25391,0.04473,0.04444,-0.19014,0.07597,0.24534,0.05381,0.02097,0.07618,0.05431,0.24514,0.02437 +light,F3835B,0.20046,0.13660,0.18066,0.43522,0.01025,0.29312,0.14003,0.07033,0.00918,0.09491,0.31276,-0.05999 +light,05F112,0.09070,0.00450,0.07615,-0.15174,0.15323,0.01895,0.14527,0.62764,0.08434,0.00472,0.09033,-0.10880 +light,5B738B,0.37636,0.04185,0.03468,-0.09603,0.05243,0.37209,0.03708,-0.01062,0.05583,0.04766,0.36683,0.07479 +light,B151C9,0.27832,0.17139,0.05319,0.19122,0.05976,0.33479,0.04137,-0.11827,0.08110,0.18091,0.25764,0.26859 +light,722CD2,0.19763,0.13898,0.02964,0.08081,0.07525,0.21738,0.02402,-0.14410,0.11703,0.17513,0.14211,0.38925 +light,C642E6,0.21323,0.23127,0.03059,0.30138,0.04943,0.30736,0.02168,-0.11964,0.07468,0.24761,0.17623,0.40344 +light,E86403,0.02530,0.12692,0.20635,0.55124,0.01373,0.09898,0.15042,0.12903,0.00998,0.06724,0.11578,-0.18123 +light,C0AFED,0.47254,0.13123,0.02953,0.11964,0.09941,0.49544,0.02840,0.06302,0.11375,0.14444,0.40170,0.26953 +light,A76832,0.20348,0.09228,0.12528,0.23387,0.04575,0.22820,0.10657,0.02732,0.03887,0.06669,0.24024,-0.14973 +light,3F6D7C,0.31383,0.04547,0.04080,-0.15305,0.06845,0.30608,0.04670,0.00622,0.07127,0.05420,0.30266,0.05815 +light,C72C9E,0.16260,0.19612,0.09016,0.33150,0.03428,0.23972,0.05938,-0.16083,0.04735,0.17841,0.19257,0.20127 +light,A48608,0.08921,0.08619,0.17594,0.29180,0.06018,0.10441,0.16334,0.19756,0.04069,0.05410,0.13482,-0.19824 +light,B22A13,0.09512,0.11550,0.12797,0.35945,0.02864,0.13259,0.08778,-0.08430,0.02642,0.07309,0.13435,-0.15935 +light,E1AFD7,0.44282,0.14382,0.07191,0.22380,0.04831,0.51773,0.06442,0.05581,0.05281,0.14082,0.45930,0.19020 +light,8CF90E,0.11825,0.00470,0.18890,0.23717,0.11501,0.03411,0.24103,0.58632,0.06139,0.00320,0.15675,-0.16644 +light,6F20DB,0.16713,0.14568,0.02352,0.09897,0.07376,0.18729,0.01844,-0.15401,0.12183,0.18866,0.10352,0.44482 +light,1158AB,0.14519,0.06700,0.03370,-0.17922,0.12040,0.13419,0.04250,0.04800,0.14954,0.10493,0.10249,0.31363 +light,47F04C,0.24374,0.00781,0.09322,-0.06633,0.14549,0.15978,0.14154,0.49437,0.09729,0.00793,0.24256,-0.04974 +light,E1FC2C,0.12655,0.00392,0.27602,0.47587,0.04145,0.09145,0.29154,0.56379,0.02445,0.00244,0.25924,-0.11358 +light,71E094,0.37147,0.02603,0.08985,-0.04422,0.15521,0.28612,0.11695,0.32015,0.13058,0.02851,0.35063,0.07067 +light,54839F,0.34034,0.05525,0.04277,-0.10895,0.08675,0.32706,0.04870,0.05121,0.09305,0.06747,0.31639,0.14662 +light,C36A9B,0.32343,0.12822,0.08605,0.22701,0.04142,0.37465,0.06903,-0.06942,0.04704,0.11682,0.35020,0.09378 +light,488BFE,0.27873,0.08684,0.00075,-0.08397,0.16507,0.24637,0.00090,0.13275,0.21326,0.13518,0.14290,0.50475 +light,66D23A,0.23041,0.02715,0.11886,0.02359,0.12024,0.17346,0.15481,0.37502,0.08093,0.02380,0.24230,-0.11959 +light,02C10E,0.08183,0.01878,0.07301,-0.16577,0.13361,0.03792,0.12727,0.45807,0.08022,0.01966,0.08160,-0.12658 +light,16CD3E,0.14556,0.01972,0.07612,-0.15513,0.15262,0.08641,0.12642,0.43848,0.10517,0.02257,0.14078,-0.02538 +light,FB2F55,0.09704,0.17776,0.14529,0.56422,0.00204,0.19972,0.08671,-0.10416,0.00230,0.11945,0.19125,0.02034 +light,21EAD3,0.22997,0.01508,0.03160,-0.14725,0.27075,0.09612,0.05366,0.49711,0.25802,0.02441,0.12165,0.42338 +light,CE897E,0.34574,0.09612,0.10508,0.26089,0.03394,0.38756,0.08936,0.02640,0.03299,0.07944,0.39267,-0.01099 +light,F2FC41,0.14879,0.00457,0.28973,0.50592,0.02022,0.13364,0.29556,0.53881,0.01276,0.00295,0.31552,-0.07632 +light,ADDEF3,0.54655,0.07670,0.02789,0.02729,0.21241,0.44145,0.03108,0.18565,0.22176,0.08924,0.38842,0.25352 +light,A23762,0.22217,0.11273,0.08849,0.21190,0.03897,0.25356,0.06579,-0.14262,0.04437,0.09543,0.24466,-0.00015 +light,D60F85,0.07121,0.21170,0.10761,0.44869,0.02082,0.15692,0.06195,-0.18087,0.02992,0.17514,0.12407,0.19244 +light,420B12,0.13341,0.06466,0.06280,-0.00205,0.04150,0.13691,0.05204,-0.18731,0.04259,0.05499,0.13674,-0.16373 +light,634F74,0.37592,0.03994,0.03154,-0.05917,0.03340,0.37820,0.02976,-0.13156,0.03710,0.04186,0.37358,0.00236 +light,0691A4,0.13229,0.05036,0.04166,-0.20079,0.17472,0.09548,0.06385,0.23458,0.18301,0.08085,0.08518,0.29410 +light,B57DFF,0.35536,0.19371,0.00000,0.16074,0.09610,0.41392,0.00000,-0.01983,0.12898,0.22658,0.24510,0.39934 +light,35FF3E,0.19864,0.00000,0.09049,-0.08129,0.15683,0.10392,0.14984,0.58940,0.09748,0.00000,0.19706,-0.05140 +light,8065DF,0.32413,0.11843,0.02461,0.03479,0.09078,0.33654,0.02287,-0.05412,0.12189,0.14781,0.25717,0.34764 +light,0BC0D3,0.16708,0.03641,0.02543,-0.18578,0.23697,0.08725,0.04273,0.38599,0.24704,0.06378,0.07061,0.44601 +light,51686F,0.39698,0.02787,0.02657,-0.13377,0.03431,0.39464,0.02839,-0.04950,0.03498,0.03035,0.39381,-0.02385 +light,6E4577,0.33492,0.05773,0.04221,-0.00349,0.03995,0.34116,0.03747,-0.14799,0.04611,0.05915,0.33317,0.02823 +light,B15CA1,0.31839,0.12226,0.07050,0.18297,0.04699,0.35809,0.05663,-0.10093,0.05634,0.11773,0.32778,0.12953 +light,A1636F,0.34935,0.06954,0.06419,0.14829,0.03566,0.36663,0.05462,-0.06867,0.03686,0.06118,0.36393,-0.02668 +light,633144,0.28860,0.05430,0.04929,-0.00395,0.03531,0.29411,0.04233,-0.17959,0.03752,0.04954,0.29245,-0.11285 +light,7A432D,0.23330,0.06908,0.07716,0.09889,0.04174,0.24343,0.06591,-0.08833,0.03889,0.05497,0.24583,-0.16322 +light,84C631,0.21209,0.04094,0.14797,0.11664,0.10342,0.17166,0.17321,0.32817,0.06940,0.03216,0.23998,-0.14939 +light,DED740,0.18705,0.05200,0.24831,0.38323,0.04227,0.19538,0.24467,0.36082,0.02869,0.03478,0.31020,-0.12269 +light,66CE09,0.11261,0.02907,0.14593,0.11239,0.11720,0.05938,0.18845,0.44281,0.06712,0.02150,0.12976,-0.18308 +light,3166B7,0.23072,0.07192,0.03385,-0.14433,0.11372,0.21834,0.03975,0.02819,0.13952,0.10363,0.18265,0.29185 +light,B83BAF,0.22403,0.17586,0.07178,0.24989,0.04557,0.28612,0.05134,-0.15165,0.06240,0.17226,0.23064,0.22098 +light,6024F6,0.17063,0.13424,0.00552,0.06608,0.08080,0.18446,0.00457,-0.12866,0.13911,0.19161,0.08105,0.55294 +light,360C13,0.15782,0.05646,0.05484,-0.05735,0.04036,0.15990,0.04738,-0.20058,0.04141,0.05007,0.15974,-0.17671 +light,F64B61,0.16062,0.16369,0.14368,0.49672,0.00539,0.26026,0.09464,-0.06617,0.00575,0.11500,0.25339,0.00625 +light,5E3A68,0.32144,0.05211,0.03994,-0.04487,0.03820,0.32558,0.03582,-0.17215,0.04381,0.05361,0.31994,-0.00951 +light,585090,0.33169,0.05649,0.03583,-0.07892,0.05265,0.33295,0.03500,-0.10688,0.06271,0.06572,0.31946,0.11682 +light,301F44,0.26794,0.04328,0.03613,-0.15911,0.03775,0.26879,0.03410,-0.21908,0.04264,0.04614,0.26646,-0.08856 +light,EC2731,0.09569,0.14845,0.14158,0.53977,0.00782,0.17152,0.08477,-0.11117,0.00808,0.09191,0.17029,-0.07813 +light,A7C8EF,0.50158,0.09513,0.02767,0.03051,0.16411,0.45194,0.02984,0.13842,0.17818,0.11136,0.38177,0.26595 +light,DAB5DC,0.46903,0.11533,0.05455,0.21599,0.05316,0.51767,0.05028,0.08869,0.05791,0.11582,0.46614,0.22287 +light,6BBF06,0.10416,0.03825,0.14882,0.12838,0.10898,0.06283,0.18335,0.39386,0.06377,0.02758,0.12300,-0.19083 +light,14665C,0.17212,0.04823,0.05138,-0.19329,0.09616,0.16065,0.06670,0.07649,0.09347,0.06086,0.16287,0.04359 +light,D0E8B8,0.48337,0.04248,0.13115,0.15869,0.09132,0.44124,0.13795,0.23930,0.08232,0.04028,0.52090,0.07807 +light,BDCF63,0.31312,0.05381,0.17488,0.19936,0.07699,0.29512,0.18197,0.25768,0.05899,0.04290,0.37856,-0.09222 +light,543007,0.10133,0.07222,0.08653,0.06933,0.05333,0.10622,0.07734,-0.04866,0.04611,0.05582,0.10854,-0.18302 +light,A52BCF,0.18503,0.19118,0.04329,0.22756,0.05759,0.23951,0.03071,-0.15919,0.08928,0.21030,0.15148,0.36071 +light,61AE84,0.35787,0.04057,0.06160,-0.07964,0.09644,0.32818,0.07508,0.18266,0.08700,0.04460,0.34646,0.03959 +light,8F3B51,0.25464,0.08461,0.07511,0.14642,0.03814,0.27305,0.05926,-0.13908,0.04082,0.07143,0.26971,-0.06431 +light,CF44A8,0.21406,0.19406,0.09028,0.31336,0.03483,0.29992,0.06312,-0.13120,0.04561,0.17768,0.24691,0.18862 +light,BDDD5C,0.28812,0.04120,0.19752,0.21433,0.08574,0.25231,0.21176,0.31685,0.06250,0.03220,0.36251,-0.09643 +light,CF695E,0.26285,0.11187,0.12007,0.31697,0.02789,0.31422,0.09356,-0.02392,0.02704,0.08451,0.31776,-0.06068 +light,24AE9A,0.22059,0.04333,0.05402,-0.17676,0.17269,0.16732,0.07964,0.26270,0.16464,0.06089,0.17938,0.19901 +light,F03305,0.02087,0.14214,0.17420,0.60397,0.00646,0.09829,0.10768,-0.01243,0.00549,0.07466,0.10191,-0.16246 +light,B61976,0.12229,0.16891,0.10061,0.32192,0.03435,0.17691,0.06446,-0.17768,0.04576,0.14417,0.15456,0.11826 +light,8B99AC,0.44685,0.03761,0.03061,0.03003,0.04430,0.44302,0.03169,0.08099,0.04636,0.04076,0.43724,0.15015 +light,6ECF5D,0.31283,0.02920,0.09853,-0.00919,0.11163,0.26159,0.12472,0.31382,0.08409,0.02784,0.31858,-0.06580 +light,27C7FE,0.23870,0.04198,0.00075,-0.12849,0.25006,0.14160,0.00116,0.38758,0.28035,0.07268,0.07807,0.56498 +light,6D3DCA,0.24108,0.11971,0.03271,0.03395,0.07821,0.25492,0.02839,-0.12231,0.11311,0.15030,0.19205,0.33669 +light,0B3A37,0.15185,0.04604,0.04674,-0.20150,0.06710,0.14900,0.05500,-0.04366,0.06646,0.05366,0.14930,-0.05373 +light,7983E3,0.36996,0.09572,0.02162,-0.01279,0.10620,0.36479,0.02219,0.02055,0.13261,0.12271,0.29423,0.34065 +light,CD1964,0.09912,0.17544,0.11823,0.41113,0.02322,0.16777,0.07197,-0.16491,0.02943,0.13540,0.15222,0.07510 +light,C00532,0.05094,0.15048,0.12339,0.42813,0.02206,0.10055,0.07177,-0.17477,0.02587,0.10268,0.09721,-0.02969 +light,84808D,0.46878,0.01536,0.01379,0.01972,0.01472,0.46910,0.01364,0.00449,0.01522,0.01572,0.46801,0.05399 +light,AED433,0.19941,0.04201,0.19928,0.24165,0.08614,0.16601,0.21694,0.36229,0.05644,0.02996,0.26243,-0.14883 +light,E32717,0.06863,0.13859,0.14886,0.53412,0.01096,0.13520,0.09080,-0.08401,0.01036,0.07994,0.13652,-0.13662 +light,C551C5,0.26816,0.18773,0.06258,0.25409,0.04652,0.34513,0.04652,-0.12052,0.06258,0.18773,0.26816,0.25409 +light,F156FD,0.19387,0.28857,0.00342,0.45925,0.01643,0.36835,0.00235,-0.04988,0.02448,0.29555,0.17346,0.49866 +light,1DDBFD,0.22006,0.02592,0.00144,-0.13370,0.27163,0.10058,0.00240,0.48421,0.29111,0.04637,0.05989,0.59479 +light,D791CC,0.39353,0.15489,0.07181,0.22290,0.04794,0.46903,0.06112,-0.00946,0.05501,0.15127,0.40734,0.18639 +light,9FBB92,0.44119,0.03600,0.05770,0.08864,0.05425,0.42880,0.06160,0.18867,0.04922,0.03487,0.44626,0.04220 +light,F78A91,0.28963,0.15038,0.14139,0.38722,0.00805,0.39708,0.11069,0.02536,0.00819,0.11983,0.39201,0.04860 +light,970D07,0.06933,0.10738,0.11004,0.30540,0.03046,0.09406,0.07264,-0.14618,0.02978,0.06930,0.09419,-0.16582 +light,7D1550,0.14428,0.10850,0.08114,0.15628,0.04378,0.16280,0.05893,-0.18316,0.05314,0.09565,0.15553,0.00941 +light,D1C71B,0.11330,0.05930,0.24143,0.40557,0.04766,0.12262,0.23623,0.37388,0.02960,0.03604,0.21133,-0.17109 +light,A1CB19,0.14139,0.04415,0.19529,0.25054,0.08787,0.11019,0.21501,0.38301,0.05372,0.02972,0.19302,-0.17842 +light,A32572,0.17227,0.13927,0.09008,0.23761,0.04106,0.21078,0.06292,-0.16966,0.05188,0.12294,0.19301,0.07922 +light,DBF480,0.33165,0.02119,0.24466,0.26132,0.07301,0.28460,0.25756,0.34168,0.05604,0.01712,0.45998,-0.03118 +light,7C1732,0.14642,0.09506,0.08400,0.16080,0.03937,0.16247,0.06161,-0.17326,0.04320,0.07650,0.16034,-0.08396 +light,EF497D,0.16910,0.18630,0.13307,0.44879,0.01087,0.27641,0.08835,-0.08937,0.01260,0.14330,0.25508,0.07921 +light,3A19D5,0.14602,0.10197,0.01862,-0.03916,0.07813,0.14990,0.01666,-0.14665,0.13059,0.15246,0.08653,0.46572 +light,E93C68,0.14826,0.17286,0.13386,0.45874,0.01259,0.24034,0.08640,-0.10404,0.01435,0.12717,0.22723,0.03910 +light,1AB64F,0.16465,0.02972,0.07165,-0.16406,0.14498,0.11765,0.11143,0.33967,0.11081,0.03532,0.15659,0.00708 +light,3FBAE2,0.29493,0.05590,0.02349,-0.12726,0.21571,0.21690,0.03258,0.26422,0.23527,0.08455,0.17492,0.39153 +light,47D5E9,0.32563,0.03955,0.02072,-0.10747,0.24875,0.20914,0.02974,0.34767,0.25938,0.05921,0.18337,0.41177 +light,86D6BA,0.43827,0.04242,0.07139,-0.02659,0.15094,0.36423,0.08607,0.23798,0.14193,0.04809,0.39402,0.14538 +light,469441,0.28145,0.03577,0.06352,-0.10623,0.07670,0.26395,0.07877,0.16098,0.06090,0.03522,0.28214,-0.12336 +light,7AF63A,0.21948,0.00687,0.15048,0.10160,0.13500,0.12702,0.19997,0.50271,0.08434,0.00571,0.24282,-0.10542 +light,9EB78C,0.43327,0.03768,0.06018,0.08848,0.05383,0.42247,0.06382,0.17753,0.04855,0.03603,0.44007,0.02437 +light,8B618E,0.38399,0.05781,0.04134,0.06195,0.03792,0.39320,0.03694,-0.08766,0.04276,0.05825,0.38321,0.07264 +light,7A617F,0.41198,0.03575,0.02896,0.00173,0.02810,0.41527,0.02704,-0.09002,0.03049,0.03622,0.41124,0.02009 +light,64141F,0.15102,0.07735,0.07373,0.09006,0.03952,0.15992,0.05711,-0.17812,0.04110,0.06232,0.15940,-0.14125 +light,048A84,0.11762,0.04807,0.05053,-0.20053,0.15648,0.08743,0.07668,0.22059,0.15409,0.07183,0.09000,0.20173 +light,D90D13,0.05181,0.13921,0.13576,0.52420,0.01241,0.10900,0.07708,-0.14751,0.01269,0.08081,0.10876,-0.12776 +light,34718E,0.26301,0.05590,0.04448,-0.15947,0.09579,0.25011,0.05332,0.04392,0.10334,0.07229,0.24062,0.14061 +light,252C52,0.26857,0.04158,0.03409,-0.19914,0.04399,0.26819,0.03491,-0.17454,0.04958,0.04799,0.26496,-0.04096 +light,78BFF7,0.41734,0.08269,0.01034,-0.03978,0.20745,0.34126,0.01229,0.18802,0.23350,0.11069,0.25675,0.36769 +light,F5B56B,0.23374,0.11376,0.22752,0.38576,0.01341,0.31757,0.19840,0.18044,0.01113,0.08236,0.38309,-0.05698 +light,ADAFFD,0.46870,0.12371,0.00309,0.08293,0.12739,0.46618,0.00311,0.08959,0.15050,0.14683,0.34557,0.34926 +light,44263D,0.31281,0.03875,0.03464,-0.11954,0.03029,0.31456,0.03143,-0.22726,0.03267,0.03791,0.31331,-0.14467 +light,E56DE3,0.28142,0.24387,0.04677,0.32598,0.03266,0.42140,0.03517,-0.06178,0.04325,0.24286,0.28457,0.31952 +light,D984C7,0.35762,0.17307,0.07880,0.24149,0.04384,0.44605,0.06461,-0.03686,0.05143,0.16647,0.37994,0.18255 +light,4DBC4E,0.28232,0.02918,0.07710,-0.08665,0.10346,0.24373,0.10288,0.28719,0.07777,0.02927,0.28212,-0.08328 +light,A6945E,0.33696,0.06471,0.09737,0.15195,0.05152,0.34508,0.09320,0.09059,0.04462,0.05364,0.36385,-0.09348 +light,DABD31,0.15464,0.07538,0.23526,0.38962,0.03967,0.18314,0.22084,0.29753,0.02716,0.04844,0.26359,-0.14704 +light,ECA528,0.10600,0.10067,0.24048,0.47835,0.01818,0.17086,0.20571,0.25232,0.01277,0.06048,0.22923,-0.14562 +light,2ADDF8,0.25392,0.02701,0.00556,-0.12179,0.27124,0.12797,0.00891,0.45854,0.28662,0.04575,0.09410,0.54608 +light,ED9904,0.02615,0.10038,0.24700,0.55588,0.01469,0.09168,0.20483,0.28880,0.00932,0.05284,0.13847,-0.18494 +light,269E6D,0.21334,0.04161,0.06263,-0.16856,0.13121,0.17890,0.08828,0.22121,0.11564,0.05169,0.19806,0.06206 +light,299BFD,0.22371,0.06668,0.00133,-0.13094,0.19764,0.17338,0.00185,0.23498,0.24488,0.11443,0.08332,0.54953 +light,A79049,0.27751,0.07436,0.12192,0.18112,0.05574,0.28887,0.11527,0.10483,0.04582,0.05779,0.31332,-0.13066 +light,70B7A7,0.39752,0.04607,0.05631,-0.06069,0.10921,0.36100,0.06721,0.18023,0.10522,0.05298,0.37076,0.12594 +light,BB3CA5,0.22076,0.17301,0.07985,0.25970,0.04300,0.28373,0.05691,-0.14834,0.05733,0.16440,0.23631,0.18902 +light,E18EE0,0.37002,0.20515,0.05628,0.25091,0.04505,0.48523,0.04655,-0.01996,0.05435,0.20472,0.37172,0.24765 +light,9CEAA2,0.44103,0.02284,0.10117,0.04673,0.12813,0.36051,0.12037,0.30863,0.10927,0.02318,0.43598,0.06687 +light,71CC7F,0.36627,0.03208,0.08051,-0.03572,0.11140,0.31697,0.10042,0.27121,0.09271,0.03330,0.36053,0.01150 +light,2BB9BB,0.24895,0.04424,0.04297,-0.16753,0.19782,0.18029,0.06345,0.28394,0.19871,0.06561,0.17871,0.29029 +light,0CBACA,0.16711,0.03902,0.02997,-0.18904,0.22696,0.09415,0.04950,0.35881,0.23519,0.06678,0.08100,0.40919 +light,C663BC,0.31038,0.16525,0.07097,0.22988,0.04718,0.37913,0.05546,-0.09354,0.05905,0.16160,0.31940,0.19721 +light,C74F5A,0.23598,0.11783,0.11047,0.31611,0.02758,0.28518,0.08127,-0.08423,0.02849,0.08954,0.28244,-0.04753 +light,5A2DE8,0.19277,0.12191,0.01335,0.02491,0.08329,0.20299,0.01161,-0.12142,0.13521,0.17208,0.11583,0.48668 +light,900B71,0.10487,0.14657,0.08530,0.22796,0.04445,0.13591,0.05687,-0.19409,0.06150,0.13519,0.11687,0.12958 +light,1D5197,0.18042,0.06498,0.03884,-0.17051,0.09985,0.17290,0.04595,-0.00105,0.12065,0.09289,0.15155,0.22707 +light,0BD820,0.10887,0.01333,0.07622,-0.15528,0.14768,0.04907,0.13497,0.51534,0.08999,0.01438,0.10770,-0.08658 +light,9C2BAB,0.19966,0.15830,0.06272,0.19108,0.05360,0.24051,0.04548,-0.17096,0.07662,0.16408,0.19074,0.23914 +light,298C35,0.21374,0.03617,0.06353,-0.15266,0.08982,0.19470,0.08478,0.17972,0.07004,0.03764,0.21254,-0.11237 +light,A02B0D,0.08891,0.10774,0.12299,0.30781,0.03467,0.11799,0.08832,-0.07235,0.03118,0.06958,0.12005,-0.16983 +light,4931D8,0.20324,0.10166,0.01925,-0.03787,0.08324,0.20765,0.01784,-0.11657,0.12897,0.14598,0.14106,0.43104 +light,7D70FA,0.33344,0.12039,0.00421,0.03216,0.10570,0.34027,0.00407,-0.01082,0.14544,0.15999,0.22959,0.44537 +light,ADEBC9,0.50642,0.03374,0.09110,0.04717,0.15838,0.40671,0.10430,0.25218,0.14739,0.03595,0.46515,0.13975 +light,B3ACAA,0.48737,0.01366,0.01399,0.18695,0.01231,0.48830,0.01376,0.16014,0.01225,0.01338,0.48856,0.15249 +light,45FCF9,0.32640,0.00306,0.00611,-0.06499,0.29653,0.14169,0.00957,0.54045,0.29478,0.00475,0.14641,0.53052 +light,2517D2,0.13701,0.08753,0.01698,-0.09643,0.07831,0.13810,0.01616,-0.14238,0.13098,0.13940,0.08180,0.47135 +light,B201C1,0.06956,0.22092,0.05393,0.35363,0.03960,0.13320,0.03188,-0.20076,0.06929,0.22857,0.05838,0.40062 +light,2DED0C,0.11202,0.00743,0.10033,-0.04331,0.14219,0.03750,0.16454,0.58517,0.07717,0.00661,0.11461,-0.15133 +light,B90538,0.05552,0.15004,0.11943,0.40050,0.02482,0.10217,0.07057,-0.17796,0.02969,0.10605,0.09793,-0.01406 +light,D7D74A,0.22352,0.05143,0.23271,0.33548,0.05143,0.22352,0.23271,0.33548,0.03600,0.03600,0.33499,-0.11679 +light,7D53C0,0.29952,0.11032,0.04041,0.03995,0.07416,0.31427,0.03594,-0.09888,0.09808,0.12977,0.26368,0.26142 +light,55A468,0.32877,0.03727,0.06185,-0.09456,0.08581,0.30508,0.07622,0.17604,0.07352,0.03935,0.32445,-0.02948 +light,C7D995,0.41888,0.05127,0.14302,0.16722,0.07855,0.39662,0.14869,0.22711,0.06724,0.04563,0.47059,0.00086 +light,8C1EF1,0.15539,0.18224,0.01134,0.20005,0.06721,0.19373,0.00818,-0.15147,0.11696,0.22884,0.07648,0.52282 +light,9D3A9B,0.25009,0.13143,0.06671,0.16745,0.04967,0.28456,0.05068,-0.15746,0.06506,0.13079,0.25110,0.16089 +light,4F5B1F,0.22414,0.05014,0.06848,-0.03295,0.05574,0.22227,0.07094,0.00791,0.04607,0.04293,0.22897,-0.19641 +light,EA08F6,0.04048,0.28487,0.01038,0.58192,0.01303,0.16947,0.00558,-0.15671,0.02481,0.29186,0.02689,0.62114 +light,12A037,0.13813,0.03282,0.06910,-0.16946,0.12447,0.10614,0.10504,0.29180,0.09298,0.03727,0.13471,-0.04927 +light,655EB7,0.32979,0.07627,0.03411,-0.04409,0.07152,0.33160,0.03344,-0.06793,0.08976,0.09384,0.29880,0.23525 +light,6F78F1,0.33920,0.10210,0.01059,-0.01659,0.11158,0.33490,0.01085,0.01326,0.14759,0.13836,0.24464,0.41451 +light,72E25E,0.30569,0.02010,0.11159,0.00968,0.12739,0.23417,0.14546,0.37926,0.09243,0.01901,0.31351,-0.05632 +light,8FCE58,0.30301,0.03912,0.13333,0.08533,0.10357,0.25890,0.15443,0.29094,0.07706,0.03371,0.32849,-0.09417 +light,9FF585,0.38404,0.01173,0.14312,0.08464,0.13601,0.28560,0.17285,0.36633,0.10554,0.01099,0.40556,-0.00053 +light,F8CEAF,0.37039,0.12297,0.20076,0.27843,0.01610,0.46552,0.18400,0.14223,0.01502,0.10512,0.52445,0.04169 +light,2FF771,0.21531,0.00462,0.08204,-0.11765,0.19974,0.10282,0.13636,0.52970,0.14643,0.00563,0.19511,0.09597 +light,CC196F,0.10169,0.18226,0.11411,0.40194,0.02465,0.17246,0.06961,-0.16868,0.03223,0.14533,0.15226,0.10547 +light,44C58C,0.28766,0.03569,0.07077,-0.12745,0.16103,0.22253,0.09903,0.28996,0.14072,0.04365,0.25913,0.10553 +light,1D7905,0.09083,0.04161,0.07762,-0.09634,0.09290,0.07575,0.10277,0.20309,0.06424,0.03809,0.09172,-0.17445 +light,F1BEE6,0.43787,0.20281,0.07800,0.22641,0.03919,0.57615,0.06998,0.05977,0.04271,0.19831,0.47047,0.19047 +light,7ED750,0.28078,0.02937,0.12851,0.05545,0.11686,0.22228,0.15853,0.34546,0.08329,0.02583,0.29904,-0.09444 +light,D450A4,0.23470,0.18729,0.09739,0.31200,0.03301,0.32303,0.06986,-0.11217,0.04129,0.16804,0.27606,0.15776 +light,4A8443,0.30663,0.03591,0.05489,-0.10724,0.06216,0.29603,0.06457,0.09489,0.05172,0.03515,0.30751,-0.13163 +light,881B4D,0.15644,0.10955,0.08553,0.18181,0.04124,0.17828,0.06169,-0.17532,0.04855,0.09302,0.17189,-0.01150 +light,5B0068,0.07419,0.11536,0.06831,0.09900,0.05468,0.08502,0.05035,-0.19005,0.07698,0.11970,0.07088,0.14029 +light,4C412A,0.31134,0.03718,0.04168,-0.09216,0.03386,0.31226,0.04030,-0.13152,0.03143,0.03336,0.31371,-0.21380 +light,B48A31,0.19379,0.08998,0.15842,0.26370,0.05211,0.21740,0.14312,0.12854,0.04031,0.06288,0.24682,-0.15785 +light,F5EF2A,0.09930,0.02174,0.28948,0.55026,0.01343,0.10720,0.28601,0.53062,0.00807,0.01292,0.25766,-0.11395 +light,9B0960,0.08950,0.14876,0.09615,0.27343,0.03894,0.12482,0.06191,-0.19037,0.05177,0.12735,0.11134,0.08601 +light,D5622E,0.14096,0.12328,0.16411,0.40694,0.02488,0.20097,0.12379,0.03469,0.02121,0.07929,0.21353,-0.13364 +light,E16A13,0.06732,0.12465,0.19743,0.49295,0.01888,0.13597,0.14849,0.11236,0.01433,0.07115,0.15492,-0.16589 +light,D3FD50,0.22268,0.00294,0.25686,0.34498,0.07040,0.16130,0.28001,0.48045,0.04643,0.00211,0.34275,-0.07756 +light,9FA19F,0.49650,0.00266,0.00272,0.12165,0.00273,0.49646,0.00273,0.12945,0.00272,0.00266,0.49650,0.12165 +light,CF269C,0.13767,0.20741,0.09463,0.37206,0.02910,0.22333,0.06001,-0.16342,0.04081,0.18451,0.17597,0.21047 +light,EF22B8,0.08991,0.25946,0.08335,0.50453,0.01099,0.22292,0.04876,-0.14934,0.01669,0.23057,0.14520,0.32910 +light,264736,0.30414,0.03097,0.03384,-0.21993,0.04064,0.30207,0.03765,-0.10193,0.03852,0.03267,0.30329,-0.16272 +light,C9EC3E,0.19855,0.02364,0.24017,0.32587,0.07236,0.15681,0.25860,0.43772,0.04671,0.01644,0.29832,-0.11833 +light,E306C1,0.04280,0.26305,0.06550,0.51885,0.01602,0.15566,0.03547,-0.18361,0.02749,0.24450,0.07409,0.41078 +light,F37996,0.26442,0.17255,0.13521,0.38076,0.01167,0.37925,0.10208,-0.01849,0.01257,0.14033,0.35893,0.07641 +light,3AEA05,0.09964,0.00948,0.11289,0.00543,0.13898,0.02550,0.17637,0.57680,0.07390,0.00788,0.10446,-0.16662 +light,694D94,0.31934,0.07026,0.04223,-0.02007,0.05460,0.32493,0.03895,-0.11652,0.06628,0.07865,0.30741,0.12805 +light,C098AC,0.43683,0.06702,0.05401,0.19508,0.03733,0.45686,0.04918,0.05272,0.03916,0.06402,0.44743,0.12390 +light,C82C83,0.15653,0.17852,0.10491,0.34436,0.03058,0.22731,0.06894,-0.15428,0.03948,0.15144,0.19900,0.12380 +light,6066C0,0.32949,0.07649,0.03149,-0.06100,0.08085,0.32779,0.03203,-0.04067,0.10123,0.09741,0.29011,0.26420 +light,38A89E,0.27605,0.04782,0.05332,-0.15758,0.14878,0.23171,0.07252,0.20582,0.14526,0.06351,0.23747,0.17337 +light,8CF0A9,0.42786,0.01776,0.10185,0.00155,0.17067,0.31393,0.12763,0.32894,0.14619,0.01907,0.40099,0.09649 +light,E4C32F,0.13570,0.07251,0.25136,0.43455,0.03040,0.17062,0.23419,0.32949,0.02040,0.04534,0.26024,-0.14166 +light,344830,0.35984,0.02390,0.02703,-0.20685,0.02824,0.35879,0.02879,-0.13346,0.02616,0.02359,0.36001,-0.22153 +light,435B5C,0.39191,0.02424,0.02409,-0.17750,0.02986,0.39017,0.02589,-0.08905,0.02994,0.02612,0.39009,-0.08537 +light,62CD12,0.13492,0.02899,0.13739,0.08302,0.11860,0.08167,0.17904,0.42461,0.07039,0.02242,0.15016,-0.17238 +light,ED2E33,0.10955,0.14518,0.14171,0.53297,0.00765,0.18587,0.08669,-0.09982,0.00778,0.09030,0.18518,-0.08325 +dark,000000,0.31250,0.00000,0.00000,-0.25000,0.00000,0.31250,0.00000,-0.25000,0.00000,0.00000,0.31250,-0.25000 +dark,FFFFFF,0.31250,0.00000,0.00000,0.93750,0.00000,0.31250,0.00000,0.93750,0.00000,0.00000,0.31250,0.93750 +dark,808080,0.30864,0.00000,0.00000,0.34181,0.00000,0.30864,0.00000,0.34181,0.00000,0.00000,0.30864,0.34181 +dark,404040,0.30959,0.00000,0.00000,0.04759,0.00000,0.30959,0.00000,0.04759,0.00000,0.00000,0.30959,0.04759 +dark,C0C0C0,0.30962,0.00000,0.00000,0.63818,0.00000,0.30962,0.00000,0.63818,0.00000,0.00000,0.30962,0.63818 +dark,000000,0.31250,0.00000,0.00000,-0.25000,0.00000,0.31250,0.00000,-0.25000,0.00000,0.00000,0.31250,-0.25000 +dark,111111,0.31153,0.00000,0.00000,-0.17030,0.00000,0.31153,0.00000,-0.17030,0.00000,0.00000,0.31153,-0.17030 +dark,222222,0.31070,0.00000,0.00000,-0.09114,0.00000,0.31070,0.00000,-0.09114,0.00000,0.00000,0.31070,-0.09114 +dark,333333,0.31002,0.00000,0.00000,-0.01240,0.00000,0.31002,0.00000,-0.01240,0.00000,0.00000,0.31002,-0.01240 +dark,444444,0.30947,0.00000,0.00000,0.06602,0.00000,0.30947,0.00000,0.06602,0.00000,0.00000,0.30947,0.06602 +dark,555555,0.30907,0.00000,0.00000,0.14423,0.00000,0.30907,0.00000,0.14423,0.00000,0.00000,0.30907,0.14423 +dark,666666,0.30879,0.00000,0.00000,0.22233,0.00000,0.30879,0.00000,0.22233,0.00000,0.00000,0.30879,0.22233 +dark,777777,0.30866,0.00000,0.00000,0.30043,0.00000,0.30866,0.00000,0.30043,0.00000,0.00000,0.30866,0.30043 +dark,888888,0.30866,0.00000,0.00000,0.37862,0.00000,0.30866,0.00000,0.37862,0.00000,0.00000,0.30866,0.37862 +dark,999999,0.30879,0.00000,0.00000,0.45702,0.00000,0.30879,0.00000,0.45702,0.00000,0.00000,0.30879,0.45702 +dark,AAAAAA,0.30907,0.00000,0.00000,0.53571,0.00000,0.30907,0.00000,0.53571,0.00000,0.00000,0.30907,0.53571 +dark,BBBBBB,0.30947,0.00000,0.00000,0.61482,0.00000,0.30947,0.00000,0.61482,0.00000,0.00000,0.30947,0.61482 +dark,CCCCCC,0.31002,0.00000,0.00000,0.69444,0.00000,0.31002,0.00000,0.69444,0.00000,0.00000,0.31002,0.69444 +dark,DDDDDD,0.31070,0.00000,0.00000,0.77469,0.00000,0.31070,0.00000,0.77469,0.00000,0.00000,0.31070,0.77469 +dark,EEEEEE,0.31153,0.00000,0.00000,0.85567,0.00000,0.31153,0.00000,0.85567,0.00000,0.00000,0.31153,0.85567 +dark,FFFFFF,0.31250,0.00000,0.00000,0.93750,0.00000,0.31250,0.00000,0.93750,0.00000,0.00000,0.31250,0.93750 +dark,000000,0.31250,0.00000,0.00000,-0.25000,0.00000,0.31250,0.00000,-0.25000,0.00000,0.00000,0.31250,-0.25000 +dark,000000,0.31250,0.00000,0.00000,-0.25000,0.00000,0.31250,0.00000,-0.25000,0.00000,0.00000,0.31250,-0.25000 +dark,000000,0.31250,0.00000,0.00000,-0.25000,0.00000,0.31250,0.00000,-0.25000,0.00000,0.00000,0.31250,-0.25000 +light,000000,0.31250,0.00000,0.00000,-0.25000,0.00000,0.31250,0.00000,-0.25000,0.00000,0.00000,0.31250,-0.25000 +light,FFFFFF,0.31250,0.00000,0.00000,0.93750,0.00000,0.31250,0.00000,0.93750,0.00000,0.00000,0.31250,0.93750 +light,808080,0.30864,0.00000,0.00000,0.34181,0.00000,0.30864,0.00000,0.34181,0.00000,0.00000,0.30864,0.34181 +light,404040,0.30959,0.00000,0.00000,0.04759,0.00000,0.30959,0.00000,0.04759,0.00000,0.00000,0.30959,0.04759 +light,C0C0C0,0.30962,0.00000,0.00000,0.63818,0.00000,0.30962,0.00000,0.63818,0.00000,0.00000,0.30962,0.63818 +light,000000,0.31250,0.00000,0.00000,-0.25000,0.00000,0.31250,0.00000,-0.25000,0.00000,0.00000,0.31250,-0.25000 +light,111111,0.31153,0.00000,0.00000,-0.17030,0.00000,0.31153,0.00000,-0.17030,0.00000,0.00000,0.31153,-0.17030 +light,222222,0.31070,0.00000,0.00000,-0.09114,0.00000,0.31070,0.00000,-0.09114,0.00000,0.00000,0.31070,-0.09114 +light,333333,0.31002,0.00000,0.00000,-0.01240,0.00000,0.31002,0.00000,-0.01240,0.00000,0.00000,0.31002,-0.01240 +light,444444,0.30947,0.00000,0.00000,0.06602,0.00000,0.30947,0.00000,0.06602,0.00000,0.00000,0.30947,0.06602 +light,555555,0.30907,0.00000,0.00000,0.14423,0.00000,0.30907,0.00000,0.14423,0.00000,0.00000,0.30907,0.14423 +light,666666,0.30879,0.00000,0.00000,0.22233,0.00000,0.30879,0.00000,0.22233,0.00000,0.00000,0.30879,0.22233 +light,777777,0.30866,0.00000,0.00000,0.30043,0.00000,0.30866,0.00000,0.30043,0.00000,0.00000,0.30866,0.30043 +light,888888,0.30866,0.00000,0.00000,0.37862,0.00000,0.30866,0.00000,0.37862,0.00000,0.00000,0.30866,0.37862 +light,999999,0.30879,0.00000,0.00000,0.45702,0.00000,0.30879,0.00000,0.45702,0.00000,0.00000,0.30879,0.45702 +light,AAAAAA,0.30907,0.00000,0.00000,0.53571,0.00000,0.30907,0.00000,0.53571,0.00000,0.00000,0.30907,0.53571 +light,BBBBBB,0.30947,0.00000,0.00000,0.61482,0.00000,0.30947,0.00000,0.61482,0.00000,0.00000,0.30947,0.61482 +light,CCCCCC,0.31002,0.00000,0.00000,0.69444,0.00000,0.31002,0.00000,0.69444,0.00000,0.00000,0.31002,0.69444 +light,DDDDDD,0.31070,0.00000,0.00000,0.77469,0.00000,0.31070,0.00000,0.77469,0.00000,0.00000,0.31070,0.77469 +light,EEEEEE,0.31153,0.00000,0.00000,0.85567,0.00000,0.31153,0.00000,0.85567,0.00000,0.00000,0.31153,0.85567 +light,FFFFFF,0.31250,0.00000,0.00000,0.93750,0.00000,0.31250,0.00000,0.93750,0.00000,0.00000,0.31250,0.93750 +light,000000,0.31250,0.00000,0.00000,-0.25000,0.00000,0.31250,0.00000,-0.25000,0.00000,0.00000,0.31250,-0.25000 +light,000000,0.31250,0.00000,0.00000,-0.25000,0.00000,0.31250,0.00000,-0.25000,0.00000,0.00000,0.31250,-0.25000 +light,000000,0.31250,0.00000,0.00000,-0.25000,0.00000,0.31250,0.00000,-0.25000,0.00000,0.00000,0.31250,-0.25000 diff --git a/maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java b/maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java index f9c6cbbe515..cc9a6c20417 100644 --- a/maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java +++ b/maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java @@ -7018,11 +7018,16 @@ public void apply(Element style, String property, LexicalUnit value) { // `updateResources()` only has to look in one place; emission to // the .res is gated on whether the resulting theme key is one of // the accent-bearing color outputs (fg/bg color today). - if (value instanceof ScaledUnit) { - String varName = ((ScaledUnit) value).bindingVarName; - if (varName != null && varName.length() > 0) { - style.bindings.put(property, varName); - } + String varName = value instanceof ScaledUnit ? ((ScaledUnit) value).bindingVarName : null; + if (varName != null && varName.length() > 0) { + style.bindings.put(property, varName); + } else if ("color".equals(property) || "background-color".equals(property)) { + // A later literal for the same property wins the cascade, so it must + // also drop an earlier var() binding: otherwise the runtime + // applyThemeBindings pass re-applies the variable and the override + // silently loses (a theme generation file overriding a + // `color: var(--accent-color-dark)` rule from the shared sheet). + style.bindings.remove(property); } switch (property) { diff --git a/maven/css-compiler/src/test/java/com/codename1/designer/css/CSSDarkModeMediaQueryTest.java b/maven/css-compiler/src/test/java/com/codename1/designer/css/CSSDarkModeMediaQueryTest.java index 75925381246..36f60860202 100644 --- a/maven/css-compiler/src/test/java/com/codename1/designer/css/CSSDarkModeMediaQueryTest.java +++ b/maven/css-compiler/src/test/java/com/codename1/designer/css/CSSDarkModeMediaQueryTest.java @@ -107,6 +107,48 @@ void testAtMediaInsideHeaderCommentIsIgnored() throws Exception { } } + /** + * A later literal colour must cancel an earlier var() binding for the same + * property. Theme generation files are concatenated after the shared sheet + * and override its rules; without this the runtime applyThemeBindings pass + * re-applied the shared sheet's `var(--accent-color-dark)` over the override. + */ + @Test + void testLaterLiteralColourDropsVarBinding() throws Exception { + Path cssFile = Files.createTempFile("cn1-binding-override", ".css"); + Path resFile = Files.createTempFile("cn1-binding-override", ".res"); + try { + String css = "#Constants { --accent-color: #007aff; --accent-color-dark: #0a84ff; }" + + "Tab.pressed { color: var(--accent-color, #007aff); }" + + "Tab.selected { color: var(--accent-color, #007aff); }" + + "@media (prefers-color-scheme: dark) {" + + " Tab.pressed { color: var(--accent-color-dark, #0a84ff); }" + + "}" + + "Tab.pressed { color: #0065d1; }" + + "@media (prefers-color-scheme: dark) {" + + " Tab.pressed { color: #5eefff; }" + + "}"; + Files.write(cssFile, css.getBytes(StandardCharsets.UTF_8)); + + CSSTheme theme = CSSTheme.load(cssFile.toUri().toURL()); + theme.resourceFile = resFile.toFile(); + theme.res = new com.codename1.ui.util.EditableResourcesForCSS(resFile.toFile()); + theme.res.setTheme("Theme", new Hashtable()); + theme.updateResources(); + + Hashtable themeProps = theme.res.getTheme("Theme"); + assertEquals("0065D1", themeProps.get("Tab.press#fgColor"), "overridden light colour"); + assertEquals("5EEFFF", themeProps.get("$DarkTab.press#fgColor"), "overridden dark colour"); + assertNull(themeProps.get("@cn1-bind:Tab.press#fgColor"), "light binding survived the override"); + assertNull(themeProps.get("@cn1-bind:$DarkTab.press#fgColor"), "dark binding survived the override"); + // An untouched var() colour keeps its binding. + assertNotNull(themeProps.get("@cn1-bind:Tab.sel#fgColor"), "unrelated binding lost"); + } finally { + deleteIfExists(cssFile); + deleteIfExists(resFile); + } + } + private static void deleteIfExists(Path path) { try { Files.deleteIfExists(path); @@ -119,4 +161,16 @@ private static void assertEquals(Object expected, Object actual, String message) throw new AssertionError(message + " expected=" + expected + " actual=" + actual); } } + + private static void assertNull(Object actual, String message) { + if (actual != null) { + throw new AssertionError(message + " actual=" + actual); + } + } + + private static void assertNotNull(Object actual, String message) { + if (actual == null) { + throw new AssertionError(message); + } + } } diff --git a/maven/javase/src/test/java/com/codename1/impl/javase/JavaSEPortRegionClipTest.java b/maven/javase/src/test/java/com/codename1/impl/javase/JavaSEPortRegionClipTest.java new file mode 100644 index 00000000000..6b320575239 --- /dev/null +++ b/maven/javase/src/test/java/com/codename1/impl/javase/JavaSEPortRegionClipTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if + * you need additional information or have any questions. + */ +package com.codename1.impl.javase; + +import com.codename1.ui.plaf.GlassLensBlend; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.awt.image.BufferedImage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * The region ops (lensRegion, glassLensRegion, colorMatrixRegion) read and write + * the buffer directly. They must still honour the graphics clip, as every other + * paint does and as the Metal and JavaScript implementations do: pixels outside + * the clip stay exactly as they were, and the clipped part still changes. + */ +public class JavaSEPortRegionClipTest { + private static final int SIZE = 64; + private static final int CLIP_X = 20; + private static final int CLIP_Y = 18; + private static final int CLIP_W = 22; + private static final int CLIP_H = 24; + + private JavaSEPort port; + private BufferedImage image; + private int[] before; + private Object graphics; + private JavaSEPort originalInstance; + + @BeforeEach + void setUp() { + // new JavaSEPort() replaces the global JavaSEPort.instance, which later test + // classes (CodenameOneExtensionTest) drive the live Display through; put the + // original back afterwards, as JavaSEPortFontMappingTest does. + originalInstance = JavaSEPort.instance; + port = new JavaSEPort(); + image = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_ARGB); + for (int y = 0; y < SIZE; y++) { + for (int x = 0; x < SIZE; x++) { + // A gradient, so a lens that shifts or magnifies changes pixels too. + image.setRGB(x, y, 0xff000000 | ((x * 4) << 16) | ((y * 4) << 8) | 0x40); + } + } + before = image.getRGB(0, 0, SIZE, SIZE, null, 0, SIZE); + graphics = port.getNativeGraphics(image); + port.setClip(graphics, CLIP_X, CLIP_Y, CLIP_W, CLIP_H); + } + + @AfterEach + void tearDown() { + JavaSEPort.instance = originalInstance; + } + + @Test + void colorMatrixRegionStaysInsideTheClip() { + float[] invert = {-1, 0, 0, 1, 0, -1, 0, 1, 0, 0, -1, 1}; + assertTrue(port.colorMatrixRegion(graphics, 0, 0, SIZE, SIZE, invert, null, 0, 1f)); + assertClipped("colorMatrixRegion"); + } + + @Test + void glassLensRegionStaysInsideTheClip() { + float[] optics = new float[GlassLensBlend.COUNT]; + optics[GlassLensBlend.BEVEL] = 8; + optics[GlassLensBlend.MAX_SHIFT] = 6; + optics[GlassLensBlend.BRIGHTNESS] = 0.25f; + assertTrue(port.glassLensRegion(graphics, 8, 8, SIZE - 16, SIZE - 16, -1, optics, 1f)); + assertClipped("glassLensRegion"); + } + + @Test + void lensRegionStaysInsideTheClip() { + assertTrue(port.lensRegion(graphics, 0, 0, SIZE, SIZE, 0, 1.6f, 0f, 0x0000ff, 1f)); + assertClipped("lensRegion"); + } + + private void assertClipped(String op) { + int[] after = image.getRGB(0, 0, SIZE, SIZE, null, 0, SIZE); + int changedInside = 0; + for (int y = 0; y < SIZE; y++) { + for (int x = 0; x < SIZE; x++) { + boolean inside = x >= CLIP_X && x < CLIP_X + CLIP_W && y >= CLIP_Y && y < CLIP_Y + CLIP_H; + int i = y * SIZE + x; + if (inside) { + if (after[i] != before[i]) { + changedInside++; + } + } else { + assertEquals(before[i], after[i], op + " wrote outside the clip at " + x + "," + y); + } + } + } + assertTrue(changedInside > 0, op + " must still change the pixels inside the clip"); + } +} diff --git a/native-themes/ios-modern/gen27.css b/native-themes/ios-modern/gen27.css index 2b752bddb0e..254cbd6cb6e 100644 --- a/native-themes/ios-modern/gen27.css +++ b/native-themes/ios-modern/gen27.css @@ -82,3 +82,79 @@ TabsContainerGlassRecipe: "pill27"; glassRecipeDefault: "panel27"; } + +/* THE iOS 27 TAB BAR, MEASURED FROM A REAL UITabBarController. + * + * The motion probe (scripts/fidelity-app/ios-native-ref/motion-probe) logs every + * layer of a controller-managed tab bar on every display frame of a tap-driven + * selection, and screenshots it over backdrops whose pixels are known. What it + * found, and what the rules below encode: + * + * - The selection is a glass LENS that lifts, travels on a spring, squashes on + * arrival and wobbles once more a second later, while the WHOLE BAR pulses in + * scale. Tabs plays those curves from TabGlassMotion when the preset is ios27. + * - The accent colour lives in a second copy of the tab content that is only + * visible through the lens, so the blue travels WITH the glass. Tabs paints + * Tab.pressed / TabIcon.pressed through the lens and Tab / TabIcon everywhere + * else, which is why the unselected and selected colours below are the real + * ones and not the lens-keyed dark glyph of the iOS 26 rules. The selected + colours are the ACCENT, bound through var() like the shared rules, so an app's + own --accent-color recolours the tab bar in both generations. UIKit draws the + accent through a vibrancy blend over the glass, which shifts it with the + content behind the bar (the default accent reads about #006ad8 light and + #4ad8ff dark on the simulator); there is no such blend mode here, so the + accent is drawn as-is. + * - The bar is tinted clear glass (GlassRecipe.liquidPill27) with no fill of its + * own; the old white wash and dark slab were the fill plus a mis-fitted recipe. + * - The lens overshoots the bar ends by up to ~9.5 pt and the bar grows by ~4.4 pt + * a side, so the bar reserves motion SLACK inside its own bounds: the padding + * grows by the slack and the margin shrinks by the same amount, leaving the + * visible pill exactly where it was, and tabsGlassSlack*Mm tells Tabs how much + * of the padding is slack. Everything the motion draws then stays inside the + * component, and inside its repaint region. */ +#Constants { + tabsMorphPreset: "ios27"; + tabsGlassSlackXMm: "2"; + tabsGlassSlackYMm: "1"; + /* The native lens rests 4 pt inside the 62 pt bar (54 pt tall). */ + tabSelInsetMm: "0.66"; + /* The resting selection platter darkens the glass under it: over a flat 50% + grey the bar reads 195 light / 134 dark and the platter 168 / 99, a black + overlay of 14% / 26%. */ + tabSelPillColorInt: 0; + tabSelPillAlphaInt: 35; + tabSelPillColorDarkInt: 0; + tabSelPillAlphaDarkInt: 67; + /* The bar's specular rim along its top and bottom edges: white on the + outermost row reaching 228 over a 192 interior (light) and 176 over 134 + (dark), i.e. about 57% and 35%. */ + tabGlassSpecularAlphaInt: 145; + tabGlassSpecularDarkAlphaInt: 90; +} + +TabsContainer { + background-color: transparent; + backdrop-filter: blur(24px); + padding: 1.5mm 3.3mm 1.5mm 3.3mm; + margin: 0 5mm 0 5mm; +} +/* The native tab titles, measured on the resting bar: "Search" is 32.3 pt of ink + wide and 7.0 pt tall at the cap, against 28.7 x 6.7 for the shared rule's + regular 1.65mm -- a larger and heavier face. */ +Tab { color: #000000; font-family: "native:MainBold"; font-size: 1.72mm; } +UnselectedTab { color: #000000; } +Tab.selected { color: var(--accent-color, #007aff); } +Tab.pressed { color: var(--accent-color, #007aff); } +SelectedTab { color: var(--accent-color, #007aff); } +TabIcon { color: #000000; } +TabIcon.selected { color: var(--accent-color, #007aff); } +TabIcon.pressed { color: var(--accent-color, #007aff); } + +@media (prefers-color-scheme: dark) { + TabsContainer { background-color: transparent; backdrop-filter: blur(24px); } + Tab { color: #ffffff; } + UnselectedTab { color: #ffffff; } + TabIcon { color: #ffffff; } + TabIcon.selected { color: var(--accent-color-dark, #0a84ff); } + TabIcon.pressed { color: var(--accent-color-dark, #0a84ff); } +} diff --git a/scripts/check-fidelity-spec.py b/scripts/check-fidelity-spec.py index 3a1251f1dd0..748f915ae53 100755 --- a/scripts/check-fidelity-spec.py +++ b/scripts/check-fidelity-spec.py @@ -27,7 +27,7 @@ DESKTOP_KEYS = {"native_win", "native_mac", "native_gnome"} KNOWN_KEYS = MOBILE_KEYS | DESKTOP_KEYS | { "id", "cn1_uiid", "text", "backdrop", "material", "states", "platforms", "frames", - "tile_width_mm", "tile_height_mm", "tile_width_px", "tile_height_px", + "golden_sets", "motion_checks", "tile_width_mm", "tile_height_mm", "tile_width_px", "tile_height_px", } # Use fixture runner IDs, not host names: getNativeKind("linux") resolves a key, # but a platforms: linux filter does not match the workflow's "gnome" runner. @@ -37,6 +37,9 @@ KNOWN_STATES = {"normal", "pressed", "disabled", "selected", "hover", "focus"} KNOWN_MATERIALS = {"normal", "glass", "lens"} KNOWN_BACKDROPS = {"photo", "gradient", "grouped"} +# motion_checks selects MorphFrameValidator's property checks for a frames row. +KNOWN_MOTION_CHECKS = {"monotonic", "distinct"} +GOLDENS_DIR = REPO / "scripts/fidelity-app/goldens" TILE_KEYS = {"tile_width_mm", "tile_height_mm", "tile_width_px", "tile_height_px"} DEFAULT_KEYS = TILE_KEYS | {"bg", "appearances"} @@ -200,6 +203,16 @@ def main(): if "frames" in r: check_frames(r["frames"], where) + for key in ("golden_sets", "motion_checks"): + if key in r and "frames" not in r: + yield_error(f"{where}: '{key}' only applies to a frames row") + for gs in [unquote(g.strip()) for g in r.get("golden_sets", "").split(",") if g.strip()]: + # A misspelled set would silently exempt the row from every run. + if not (GOLDENS_DIR / gs).is_dir(): + yield_error(f"{where}: unknown golden set '{gs}' (no goldens/{gs} directory)") + if "motion_checks" in r and unquote(r["motion_checks"]) not in KNOWN_MOTION_CHECKS: + yield_error(f"{where}: unknown motion_checks '{r['motion_checks']}' " + f"(known: {sorted(KNOWN_MOTION_CHECKS)})") has_desktop = bool(DESKTOP_KEYS & set(r)) has_mobile = bool(MOBILE_KEYS & set(r)) diff --git a/scripts/common/java/MorphFrameValidator.java b/scripts/common/java/MorphFrameValidator.java index 899c1f43998..28f9fed3f2b 100644 --- a/scripts/common/java/MorphFrameValidator.java +++ b/scripts/common/java/MorphFrameValidator.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Codename One in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; @@ -63,6 +85,7 @@ public static void main(String[] args) throws Exception { Path outJson = null; Path stripDir = null; Path specPath = null; + String goldenSet = null; boolean seedMissing = false; int maxChannelDelta = 8; double maxMismatchPercent = 0.5d; @@ -74,6 +97,7 @@ public static void main(String[] args) throws Exception { case "--out-json" -> outJson = Path.of(args[++i]); case "--strip-dir" -> stripDir = Path.of(args[++i]); case "--spec" -> specPath = Path.of(args[++i]); + case "--golden-set" -> goldenSet = args[++i]; case "--seed-missing" -> seedMissing = true; case "--max-channel-delta" -> maxChannelDelta = Integer.parseInt(args[++i]); case "--max-mismatch-percent" -> maxMismatchPercent = Double.parseDouble(args[++i]); @@ -92,7 +116,24 @@ public static void main(String[] args) throws Exception { // The DECLARED frame set per component id (fidelity-tests.yaml `frames:`), // so a capture that died mid-sequence cannot validate green on just the // frames it happened to deliver. - Map> declaredFrames = loadDeclaredFrames(specPath); + Map goldenSets = new TreeMap<>(); + Map motionChecks = new TreeMap<>(); + Map> declaredFrames = loadDeclaredFrames(specPath, goldenSets, motionChecks); + // A frames row naming golden sets belongs to those generations only: the + // device skips it in any other run, so it must not be required here either. + if (goldenSet != null) { + for (Map.Entry e : goldenSets.entrySet()) { + boolean member = false; + for (String g : e.getValue().split(",")) { + if (g.trim().equals(goldenSet)) { + member = true; + } + } + if (!member) { + declaredFrames.remove(e.getKey()); + } + } + } // Group delivered frames by (id, appearance), ordered by progress value. Map> groups = new TreeMap<>(); @@ -192,6 +233,10 @@ public static void main(String[] args) throws Exception { changedFraction.put(t, (double) count / changed.length); rightmost.put(t, rx); } + // motion_checks: distinct -- a motion that is not a monotonic sweep (the + // iOS 27 selection pulses the whole bar outward and back and overshoots) + // keeps the stuck-frame and did-it-move checks but not monotonic travel. + boolean monotonic = !"distinct".equals(motionChecks.get(componentId)); if (ts.size() < 3) { groupPropertyFailures.add(name + ": only " + ts.size() + " frame(s) delivered; need at least 3"); } else { @@ -226,7 +271,7 @@ public static void main(String[] args) throws Exception { // snaps back toward the source tab is a whole cell (~30%+) and fails. int settleAllowancePx = Math.max(overshootPx, base.getWidth() * 15 / 100); int maxSeen = -1; - for (int i = 1; i < ts.size(); i++) { + for (int i = 1; monotonic && i < ts.size(); i++) { int t = ts.get(i); int rx = rightmost.get(t); boolean settleWindow = t >= 86; @@ -330,7 +375,8 @@ public static void main(String[] args) throws Exception { /// Loads the per-component `frames:` declarations from fidelity-tests.yaml /// (the same flat subset ProcessScreenshots reads). Returns an empty map when /// no spec is given/readable -- delivered-set completeness is then not checked. - private static Map> loadDeclaredFrames(Path specPath) { + private static Map> loadDeclaredFrames(Path specPath, Map goldenSets, + Map motionChecks) { Map> out = new TreeMap<>(); if (specPath == null || !Files.isRegularFile(specPath)) { if (specPath != null) { @@ -367,6 +413,10 @@ private static Map> loadDeclaredFrames(Path specPath) { String value = unquote(line.substring(idx + 1).trim()); if ("id".equals(key)) { id = value; + } else if ("golden_sets".equals(key) && id != null) { + goldenSets.put(id, value); + } else if ("motion_checks".equals(key) && id != null) { + motionChecks.put(id, value); } else if ("frames".equals(key) && id != null) { List vals = new ArrayList<>(); for (String part : value.split(",")) { diff --git a/scripts/fidelity-app/baseline/ios-27-metal-fidelity-baseline.json b/scripts/fidelity-app/baseline/ios-27-metal-fidelity-baseline.json index d5ad9a5a1d6..6b9719975a0 100644 --- a/scripts/fidelity-app/baseline/ios-27-metal-fidelity-baseline.json +++ b/scripts/fidelity-app/baseline/ios-27-metal-fidelity-baseline.json @@ -281,34 +281,34 @@ "width_ratio": 1.0282 }, "TabOne_normal_dark": { - "center_offset": 5.1, - "height_ratio": 0.9422, - "width_ratio": 0.9932 + "center_offset": 5.59, + "height_ratio": 1.0636, + "width_ratio": 0.863 }, "TabOne_normal_light": { - "center_offset": 4.61, - "height_ratio": 0.948, - "width_ratio": 0.9932 + "center_offset": 6.08, + "height_ratio": 1.0694, + "width_ratio": 0.863 }, "TabsGeom_normal_dark": { - "center_offset": 0.71, - "height_ratio": 1.0058, - "width_ratio": 0.9704 + "center_offset": 5.59, + "height_ratio": 1.0636, + "width_ratio": 0.9897 }, "TabsGeom_normal_light": { - "center_offset": 1.12, - "height_ratio": 1.0116, - "width_ratio": 0.9704 + "center_offset": 6.08, + "height_ratio": 1.0694, + "width_ratio": 0.9897 }, "Tabs_normal_dark": { - "center_offset": 1.12, - "height_ratio": 1.0116, - "width_ratio": 0.9679 + "center_offset": 5.1, + "height_ratio": 1.0578, + "width_ratio": 0.9871 }, "Tabs_normal_light": { - "center_offset": 1.0, - "height_ratio": 1.0116, - "width_ratio": 0.9691 + "center_offset": 5.52, + "height_ratio": 1.0636, + "width_ratio": 0.9884 }, "TextField_disabled_dark": { "center_offset": 0.0, @@ -398,12 +398,12 @@ "Switch_normal_light": 95.51, "Switch_selected_dark": 95.9, "Switch_selected_light": 92.13, - "TabOne_normal_dark": 97.51, - "TabOne_normal_light": 96.19, - "TabsGeom_normal_dark": 90.72, - "TabsGeom_normal_light": 89.35, - "Tabs_normal_dark": 73.45, - "Tabs_normal_light": 81.7, + "TabOne_normal_dark": 97.36, + "TabOne_normal_light": 96.9, + "TabsGeom_normal_dark": 93.67, + "TabsGeom_normal_light": 92.99, + "Tabs_normal_dark": 85.89, + "Tabs_normal_light": 86.12, "TextField_disabled_dark": 96.35, "TextField_disabled_light": 89.39, "TextField_normal_dark": 96.0, diff --git a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityApp.java b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityApp.java index 3465b36893a..91e320311ec 100644 --- a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityApp.java +++ b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityApp.java @@ -41,6 +41,11 @@ public void runApp() { // freezing the UI thread (mirrors the hellocodenameone runner). Thread runner = new Thread(new Runnable() { public void run() { + String showcase = FidelityDeviceRunner.requestedTabsShowcase(); + if (showcase != null) { + new FidelityDeviceRunner().runTabsShowcase(showcase); + return; + } new FidelityDeviceRunner().runSuite(); } }, "CN1SS-Fidelity-Runner"); diff --git a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityDeviceRunner.java b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityDeviceRunner.java index 33c36411f02..7c2e759dd12 100644 --- a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityDeviceRunner.java +++ b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/FidelityDeviceRunner.java @@ -86,6 +86,80 @@ public void runSuite() { } } + /// File in the app home that switches the app from the capture suite to a live + /// tab-bar screen for side-by-side screen recordings against the native bar + /// (scripts/record-ios-tabs-side-by-side.sh). Its content is the appearance. + public static final String TABS_SHOWCASE_FILE = "tabs-showcase.txt"; + + /// The appearance the showcase was asked for, or null when the suite should run. + public static String requestedTabsShowcase() { + try { + com.codename1.io.FileSystemStorage fs = com.codename1.io.FileSystemStorage.getInstance(); + String path = fs.getAppHomePath() + TABS_SHOWCASE_FILE; + Log.p("CN1SS:INFO:fidelity tabs showcase flag " + path); + if (!fs.exists(path)) { + return null; + } + InputStream in = fs.openInputStream(path); + try { + String v = Util.readToString(in).trim(); + return "dark".equals(v) ? "dark" : "light"; + } finally { + Util.cleanup(in); + } + } catch (Throwable t) { + return null; + } + } + + /// Shows the iOS tab bar the Tabs goldens are built from -- same tabs, theme and + /// backdrop -- as a live, touchable screen, and stays up. Nothing is captured; + /// the screen is recorded from outside while real touches drive it. + public void runTabsShowcase(final String appearance) { + platform = resolvePlatform(); + spec = loadSpec(); + if (spec == null) { + println("CN1SS:ERR:fidelity spec failed to load from " + SPEC_RESOURCE); + return; + } + installNativeTheme(); + applyAppearance(appearance); + ComponentSpec tabsSpec = null; + List components = spec.getComponents(); + for (int i = 0; i < components.size(); i++) { + ComponentSpec c = (ComponentSpec) components.get(i); + if ("Tabs".equals(c.getId())) { + tabsSpec = c; + } + } + if (tabsSpec == null) { + println("CN1SS:ERR:fidelity no Tabs row in the spec"); + return; + } + final ComponentSpec ts = tabsSpec; + runOnEdtSync(new Runnable() { + public void run() { + Form f = new Form(new BorderLayout()); + f.getTitleArea().setHidden(true); + Image backdrop = getGlassBackdrop(); + com.codename1.ui.plaf.Style fs = f.getAllStyles(); + if (backdrop != null) { + fs.setBgImage(backdrop); + fs.setBackgroundType(com.codename1.ui.plaf.Style.BACKGROUND_IMAGE_SCALED); + fs.setBgTransparency(255); + } + f.getContentPane().getAllStyles().setBgTransparency(0); + com.codename1.ui.Tabs tabs = (com.codename1.ui.Tabs) Cn1WidgetRenderer.build(ts, "normal", appearance); + tabs.setTabPlacement(Component.BOTTOM); + tabs.getAllStyles().setBgTransparency(0); + tabs.getContentPane().getAllStyles().setBgTransparency(0); + f.add(BorderLayout.CENTER, tabs); + f.show(); + println("CN1SS:INFO:fidelity tabs showcase up (" + appearance + ")"); + } + }); + } + private void runSuiteImpl() { platform = resolvePlatform(); // Default OFF: native references are captured LOCALLY by the standalone @@ -126,6 +200,10 @@ private void runSuiteImpl() { println("CN1SS:INFO:fidelity skip " + c.getId() + " (CN1 renderer not implemented yet)"); continue; } + if (!c.appliesToGeneration(goldenGenerationPrefix())) { + println("CN1SS:INFO:fidelity skip " + c.getId() + " (not captured for " + goldenGenerationPrefix() + ")"); + continue; + } for (int a = 0; a < appearances.size(); a++) { String appearance = (String) appearances.get(a); // Animation-frame test: capture the component once per declared @@ -363,7 +441,11 @@ public void run() { runOnEdtSync(new Runnable() { public void run() { Component comp = compHolder[0]; - if (comp instanceof com.codename1.ui.Tabs) { + if (comp instanceof com.codename1.ui.Tabs && "TabsGlassMotion".equals(c.getId())) { + // Time-based iOS 27 motion: the frame value is tens of ms. + com.codename1.ui.Tabs tabs = (com.codename1.ui.Tabs) comp; + tabs.setGlassMotionTestTime(0, Math.max(0, tabs.getTabCount() - 1), value * 10); + } else if (comp instanceof com.codename1.ui.Tabs) { com.codename1.ui.Tabs tabs = (com.codename1.ui.Tabs) comp; int last = Math.max(0, tabs.getTabCount() - 1); tabs.setMorphTestState(0, last, value); @@ -626,9 +708,18 @@ private Container newTile(Component comp, String compId, int w, int h, String ap // the same widget over a flat backdrop (geometry-isolation), so it lays out // identically. boolean centered = "ios".equals(platform) && ("Tabs".equals(compId) || "TabsGeom".equals(compId) - || "TabsMorph".equals(compId) || "TabOne".equals(compId)); + || "TabsMorph".equals(compId) || "TabsGlassMotion".equals(compId) || "TabOne".equals(compId)); Container tile; - if (centered) { + int glassSlackY = centered ? glassTabsSlackPx() : 0; + if (glassSlackY > 0) { + // The iOS 27 bar reserves motion slack INSIDE its bounds (see + // tabsGlassSlackYMm in native-themes/ios-modern/gen27.css): its visible + // pill starts that far below the component's top. In an app the bar is + // bottom-anchored and the slack costs nothing; this tile anchors the bar + // at the TOP like the native reference, so raise it by the slack to put + // the pill where the native one is. + tile = new Container(new RaisedCenterLayout(glassSlackY)); + } else if (centered) { tile = new Container(new FlowLayout(Component.CENTER, Component.TOP)); } else if (fullWidth) { tile = new Container(new BorderLayout()); @@ -662,6 +753,54 @@ private Container newTile(Component comp, String compId, int w, int h, String ap return tile; } + /// Vertical motion slack the iOS 27 tab bar reserves, in pixels (0 for other themes). + private int glassTabsSlackPx() { + UIManager uim = UIManager.getInstance(); + if (!"ios27".equals(uim.getThemeConstant("tabsMorphPreset", ""))) { + return 0; + } + try { + return Display.getInstance().convertToPixels(Float.parseFloat( + uim.getThemeConstant("tabsGlassSlackYMm", "0").trim())); + } catch (NumberFormatException nfe) { + return 0; + } + } + + /// Centres its single child horizontally at its preferred size and places it + /// `raise` pixels ABOVE the top edge (the part above is clipped by the tile). + private static final class RaisedCenterLayout extends com.codename1.ui.layouts.Layout { + private final int raise; + + RaisedCenterLayout(int raise) { + this.raise = raise; + } + + @Override + public void layoutContainer(Container parent) { + for (int i = 0; i < parent.getComponentCount(); i++) { + Component c = parent.getComponentAt(i); + int w = Math.min(c.getPreferredW(), parent.getLayoutWidth()); + c.setWidth(w); + c.setHeight(c.getPreferredH()); + c.setX((parent.getLayoutWidth() - w) / 2); + c.setY(-raise); + } + } + + @Override + public com.codename1.ui.geom.Dimension getPreferredSize(Container parent) { + int w = 0; + int h = 0; + for (int i = 0; i < parent.getComponentCount(); i++) { + Component c = parent.getComponentAt(i); + w = Math.max(w, c.getPreferredW()); + h = Math.max(h, c.getPreferredH() - raise); + } + return new com.codename1.ui.geom.Dimension(w, h); + } + } + /// Full-width widgets that fill the whole tile (both CN1 and the native /// reference stretch them edge-to-edge). iOS only -- on Android the tuned /// preferred-size + 48dp inset path handles layout, so this stays false there @@ -937,6 +1076,16 @@ private void installNativeTheme() { } } + /// Golden-set prefix of the design generation this run renders ("ios-27-" for + /// the iOS 27 theme), or null when the platform has a single generation. + private String goldenGenerationPrefix() { + if (!"ios".equals(platform)) { + return null; + } + String theme = resolveThemeResource(); + return theme != null && theme.indexOf("27") >= 0 ? "ios-27-" : "ios-26-"; + } + private String resolveThemeResource() { String forced = Display.getInstance().getProperty("cn1ss.fidelity.themeResource", null); if (forced != null && forced.length() > 0) { diff --git a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/render/Cn1WidgetRenderer.java b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/render/Cn1WidgetRenderer.java index 50b5ed2e581..73ea7760119 100644 --- a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/render/Cn1WidgetRenderer.java +++ b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/render/Cn1WidgetRenderer.java @@ -111,6 +111,7 @@ public static boolean isSupported(String id) { || "Dialog".equals(id) || "Spinner".equals(id) || "TabsGeom".equals(id) // geometry-isolation: Tabs over a flat backdrop || "TabsMorph".equals(id) // animation-frame validation: same bar, frozen morph + || "TabsGlassMotion".equals(id) // iOS 27 motion frames: same bar, frozen at a time || "SwitchMorph".equals(id) // animation-frame validation: frozen droplet slide || "TabOne".equals(id) // minimal: one tab with a transparent icon slot || "GlassText".equals(id) || "GlassIcon".equals(id) // ladder rungs: glass + one element @@ -342,7 +343,8 @@ private static Component buildImpl(ComponentSpec spec, String state, String appe Image blankIcon = Image.createImage(blankWidth, blankHeight, 0); tabs.addTab("Tab", blankIcon, new Container()); c = tabs; - } else if ("Tabs".equals(id) || "TabsGeom".equals(id) || "TabsMorph".equals(id)) { + } else if ("Tabs".equals(id) || "TabsGeom".equals(id) || "TabsMorph".equals(id) + || "TabsGlassMotion".equals(id)) { // iOS UITabBar: an icon-over-label bar at the TOP, three items // (Featured / Search / More) mirroring the native reference's system // tab items; the first is selected (blue), the rest grey. NOT a @@ -359,9 +361,20 @@ private static Component buildImpl(ComponentSpec spec, String state, String appe // defines them -- hardcoding the iOS blues here made the Android // tile a fake that could never match the honest M3 reference. boolean iosTabs = "ios".equals(com.codename1.ui.Display.getInstance().getPlatformName()); + // The iOS 27 bar (tabsMorphPreset ios27) paints every tab as UNSELECTED + // content outside the selection lens and as SELECTED content inside it + // -- the accent travels with the glass -- so each tab needs both icon + // states, in the theme's own TabIcon / TabIcon.pressed colours. + boolean glass27 = iosTabs && "ios27".equals(com.codename1.ui.plaf.UIManager.getInstance() + .getThemeConstant("tabsMorphPreset", "")); int selColor; int unselColor; - if (iosTabs) { + if (glass27) { + selColor = com.codename1.ui.plaf.UIManager.getInstance() + .getComponentCustomStyle("TabIcon", "press").getFgColor(); + unselColor = com.codename1.ui.plaf.UIManager.getInstance() + .getComponentStyle("TabIcon").getFgColor(); + } else if (iosTabs) { // Both appearances use the native vivid accent: in light the lens // re-tints the deliberately-dark glyph; in dark the lens tint is // disabled (it flooded the dark bar) and the glyph carries it. @@ -390,12 +403,22 @@ private static Component buildImpl(ComponentSpec spec, String state, String appe } catch (NumberFormatException nfe) { tabIconMm = 4.1f; } - Image star = FontImage.createSFOrMaterial(FontImage.MATERIAL_STAR, selS, tabIconMm); - Image search = FontImage.createSFOrMaterial(FontImage.MATERIAL_SEARCH, unS, tabIconMm); - Image more = FontImage.createSFOrMaterial(FontImage.MATERIAL_MORE_HORIZ, unS, tabIconMm); - tabs.addTab("Featured", star, star, new Container()); - tabs.addTab("Search", search, search, new Container()); - tabs.addTab("More", more, more, new Container()); + if (glass27) { + tabs.addTab("Featured", FontImage.createSFOrMaterial(FontImage.MATERIAL_STAR, unS, tabIconMm), + FontImage.createSFOrMaterial(FontImage.MATERIAL_STAR, selS, tabIconMm), new Container()); + tabs.addTab("Search", FontImage.createSFOrMaterial(FontImage.MATERIAL_SEARCH, unS, tabIconMm), + FontImage.createSFOrMaterial(FontImage.MATERIAL_SEARCH, selS, tabIconMm), new Container()); + tabs.addTab("More", FontImage.createSFOrMaterial(FontImage.MATERIAL_MORE_HORIZ, unS, tabIconMm), + FontImage.createSFOrMaterial(FontImage.MATERIAL_MORE_HORIZ, selS, tabIconMm), + new Container()); + } else { + Image star = FontImage.createSFOrMaterial(FontImage.MATERIAL_STAR, selS, tabIconMm); + Image search = FontImage.createSFOrMaterial(FontImage.MATERIAL_SEARCH, unS, tabIconMm); + Image more = FontImage.createSFOrMaterial(FontImage.MATERIAL_MORE_HORIZ, unS, tabIconMm); + tabs.addTab("Featured", star, star, new Container()); + tabs.addTab("Search", search, search, new Container()); + tabs.addTab("More", more, more, new Container()); + } tabs.setTabTextPosition(Component.BOTTOM); c = tabs; } else if ("Toolbar".equals(id)) { diff --git a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/spec/ComponentSpec.java b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/spec/ComponentSpec.java index 7f6d3f650f7..d89f28ee1f4 100644 --- a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/spec/ComponentSpec.java +++ b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/spec/ComponentSpec.java @@ -49,6 +49,7 @@ public class ComponentSpec { private List states = new ArrayList(); private List platforms = new ArrayList(); private List frames = new ArrayList(); + private List goldenSets = new ArrayList(); public String getId() { return id; @@ -266,6 +267,36 @@ public void setFrames(List frames) { this.frames = frames; } + /** + * The golden sets (OS design generations, e.g. ios-27-metal) a frames row belongs + * to; empty = every set. A motion that only one generation plays is captured and + * validated only in that generation's run. + */ + public List getGoldenSets() { + return goldenSets; + } + + public void setGoldenSets(List goldenSets) { + this.goldenSets = goldenSets; + } + + /** + * True when this row belongs to a golden set whose name starts with the given + * generation prefix (e.g. "ios-27-"), or declares no golden sets at all. + */ + public boolean appliesToGeneration(String generationPrefix) { + if (goldenSets == null || goldenSets.isEmpty() || generationPrefix == null) { + return true; + } + for (int i = 0; i < goldenSets.size(); i++) { + String g = (String) goldenSets.get(i); + if (g != null && g.startsWith(generationPrefix)) { + return true; + } + } + return false; + } + /** * True when this component should run on the given platform. A component with * no explicit platforms list runs everywhere; otherwise the platform name diff --git a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/spec/FidelitySpecParser.java b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/spec/FidelitySpecParser.java index 8281865af4a..30186463b56 100644 --- a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/spec/FidelitySpecParser.java +++ b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/spec/FidelitySpecParser.java @@ -178,6 +178,8 @@ private static void applyComponentField(ComponentSpec component, String keyValue component.setPlatforms(splitList(value)); } else if ("frames".equals(key)) { component.setFrames(splitList(value)); + } else if ("golden_sets".equals(key)) { + component.setGoldenSets(splitList(value)); } } diff --git a/scripts/fidelity-app/common/src/main/resources/fidelity-tests.yaml b/scripts/fidelity-app/common/src/main/resources/fidelity-tests.yaml index edc0e2cd145..da938534e06 100644 --- a/scripts/fidelity-app/common/src/main/resources/fidelity-tests.yaml +++ b/scripts/fidelity-app/common/src/main/resources/fidelity-tests.yaml @@ -279,11 +279,32 @@ components: cn1_uiid: Tabs backdrop: 808080 platforms: ios + golden_sets: ios-26-metal frames: 0,10,25,50,75,90,100 tile_width_mm: 60 tile_height_mm: 16 states: normal + # TabsGlassMotion: the iOS 27 selection motion, which is TIME based and measured + # from UIKit (com.codename1.ui.TabGlassMotion). Each frame freezes the tab-0 -> + # last-tab selection at value x 10 ms through Tabs.setGlassMotionTestTime: + # dense over the lift and travel (0..200 ms), then the arrival squash and the + # settle, up to 1 s. The motion is not a monotonic sweep -- the whole bar pulses + # outward and back and the lens overshoots -- so only the distinct-frame and + # did-it-move checks apply (motion_checks: distinct); the geometry itself is + # pinned frame by frame against the native capture in TabGlassMotionTest. + - id: TabsGlassMotion + material: lens + cn1_uiid: Tabs + backdrop: 808080 + platforms: ios + golden_sets: ios-27-metal + motion_checks: distinct + frames: 0,2,3,5,7,8,10,13,17,20,25,30,35,40,50,60,70,100 + tile_width_mm: 60 + tile_height_mm: 16 + states: normal + # TabOne: the MINIMAL tab bar -- one title over flat grey. UIKit reserves an icon # slot even though its item image is nil; CN1 supplies a transparent icon of the # same size so the title and bar geometry remain comparable. diff --git a/scripts/fidelity-app/goldens/README.md b/scripts/fidelity-app/goldens/README.md index a4edc0c316b..b1188656f27 100644 --- a/scripts/fidelity-app/goldens/README.md +++ b/scripts/fidelity-app/goldens/README.md @@ -67,6 +67,13 @@ Worth stating plainly because the neighbouring `-anim` directory IS native: tabs. Reading `-frames` as native too leads to preparing an `xcodegen` + XCUITest capture for something a single flag produces. +The two iOS generations do not capture the same tab frames. `ios-26-metal-frames` +holds `TabsMorph`, the progress-based iOS 26 morph. `ios-27-metal-frames` holds +`TabsGlassMotion`, the time-based iOS 27 motion measured from UIKit +(`com.codename1.ui.TabGlassMotion`), frozen at value x 10 ms. The rows say which +set they belong to with `golden_sets:`, and the device runner and +`MorphFrameValidator --golden-set` both honour it. + **Seeded goldens still have to be looked at**, because whatever CN1 rendered becomes the reference. What that check looks like, from seeding `ios-27-metal-frames`: the 24 names and sizes matched `ios-26-metal-frames` exactly, none were blank, the diff --git a/scripts/fidelity-app/goldens/ios-27-metal-anim/README.md b/scripts/fidelity-app/goldens/ios-27-metal-anim/README.md index 8256ade8f48..09e6e167c39 100644 --- a/scripts/fidelity-app/goldens/ios-27-metal-anim/README.md +++ b/scripts/fidelity-app/goldens/ios-27-metal-anim/README.md @@ -19,18 +19,19 @@ on an iPhone 16 simulator running the iOS 27.0 runtime, with Xcode 27 app finishes launching. Those seconds were cut, so each video opens in the app, as the iOS 26 references do. Each contains six tap-driven selections. -- **`cn1-tabs-*.mov`** — the Codename One morph in motion, 1088x290 at 60 fps, - the same frame the `../ios-27-metal-frames/TabsMorph_*` goldens use. The - sequence is a half-second hold on the first tab, the first-to-last-tab - selection morph, then a one-second hold on the last tab. - - Unlike the iOS 26 video, which was rendered in the JavaSE simulator, these - frames come from the real iOS renderer: the fidelity app on the same iOS 27 - simulator, with the iOS 27 theme installed. Each frame is the morph frozen - through `Tabs.setMorphTestState` at the linear timeline value a 60 fps - display would sample during the theme's 480 ms - `tabsAnimatedIndicatorDurationInt`, so it is exactly the motion the animation - plays, with no timing jitter. The morph is shown in one direction only. +- **`cn1-tabs-*.mov`** — the Codename One tab bar in motion, 1178x448 at 60 fps: + the Codename One half of `docs/videos/tabs-side-by-side-*-bar.mp4`. Both come + from `scripts/record-ios-tabs-side-by-side.sh`, which records the fidelity + app's live tab showcase (the same `Tabs`, theme and backdrop as the goldens) + and the native motion probe with the simulator's own recorder while one + XCUITest driver gives both the same real touches: six taps, a held press, a + tap on the selected tab, a slow drag and a flick. Each gesture gets a 4.2 s + slot, aligned on its touch, so the two sides line up gesture by gesture. + + To compare motion frame by frame with UIKit, use the native snapshots of + `scripts/probe-ios-tab-motion.sh` (see + `../../ios-native-ref/motion-probe/README.md`) rather than these recordings: + a screen recording is lossy and variable-rate. - **`native-switch-*.mov`** — the real `UISwitch` toggle. The switch morph is not interaction-gated, so the self-animating recording path is fine there. diff --git a/scripts/fidelity-app/goldens/ios-27-metal-anim/cn1-tabs-dark.mov b/scripts/fidelity-app/goldens/ios-27-metal-anim/cn1-tabs-dark.mov index ddd0aa16cd9..d22f03c6432 100644 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-anim/cn1-tabs-dark.mov and b/scripts/fidelity-app/goldens/ios-27-metal-anim/cn1-tabs-dark.mov differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-anim/cn1-tabs-light.mov b/scripts/fidelity-app/goldens/ios-27-metal-anim/cn1-tabs-light.mov index 247e11d0974..22191518ccf 100644 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-anim/cn1-tabs-light.mov and b/scripts/fidelity-app/goldens/ios-27-metal-anim/cn1-tabs-light.mov differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t000_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t000_dark.png new file mode 100644 index 00000000000..c17533dfb01 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t000_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t000_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t000_light.png new file mode 100644 index 00000000000..7be70dcf164 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t000_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t002_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t002_dark.png new file mode 100644 index 00000000000..72a77fd1136 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t002_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t002_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t002_light.png new file mode 100644 index 00000000000..cb7c6ebf5fa Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t002_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t003_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t003_dark.png new file mode 100644 index 00000000000..0987203eebf Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t003_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t003_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t003_light.png new file mode 100644 index 00000000000..baa2c947f62 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t003_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t005_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t005_dark.png new file mode 100644 index 00000000000..1edcae9f5eb Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t005_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t005_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t005_light.png new file mode 100644 index 00000000000..18b398289ff Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t005_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t007_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t007_dark.png new file mode 100644 index 00000000000..5bd61f8d876 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t007_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t007_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t007_light.png new file mode 100644 index 00000000000..d42808bfcc1 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t007_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t008_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t008_dark.png new file mode 100644 index 00000000000..5a68b71314e Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t008_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t008_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t008_light.png new file mode 100644 index 00000000000..1fccf881995 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t008_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t010_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t010_dark.png new file mode 100644 index 00000000000..fb037c7ab78 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t010_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t010_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t010_light.png new file mode 100644 index 00000000000..0c1ee89c163 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t010_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t013_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t013_dark.png new file mode 100644 index 00000000000..b12db19e927 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t013_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t013_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t013_light.png new file mode 100644 index 00000000000..2b5894c31da Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t013_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t017_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t017_dark.png new file mode 100644 index 00000000000..391d82667f1 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t017_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t017_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t017_light.png new file mode 100644 index 00000000000..ef8613434c1 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t017_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t020_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t020_dark.png new file mode 100644 index 00000000000..d9b6ad557b6 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t020_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t020_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t020_light.png new file mode 100644 index 00000000000..2b7ff1a982e Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t020_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t025_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t025_dark.png new file mode 100644 index 00000000000..b7d1d9ca733 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t025_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t025_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t025_light.png new file mode 100644 index 00000000000..44f6d622ef8 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t025_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t030_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t030_dark.png new file mode 100644 index 00000000000..65c122cda5c Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t030_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t030_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t030_light.png new file mode 100644 index 00000000000..55c0d375310 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t030_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t035_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t035_dark.png new file mode 100644 index 00000000000..a9c0f2865e0 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t035_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t035_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t035_light.png new file mode 100644 index 00000000000..0a2ac5d4fe7 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t035_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t040_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t040_dark.png new file mode 100644 index 00000000000..7303e3b4a40 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t040_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t040_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t040_light.png new file mode 100644 index 00000000000..0a0a4064dd8 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t040_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t050_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t050_dark.png new file mode 100644 index 00000000000..949a5540a0e Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t050_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t050_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t050_light.png new file mode 100644 index 00000000000..0214dc5d0c4 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t050_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t060_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t060_dark.png new file mode 100644 index 00000000000..e5a662ac0d1 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t060_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t060_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t060_light.png new file mode 100644 index 00000000000..6ddc6f46006 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t060_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t070_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t070_dark.png new file mode 100644 index 00000000000..d7edb12533a Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t070_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t070_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t070_light.png new file mode 100644 index 00000000000..f6d01a59a91 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t070_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t100_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t100_dark.png new file mode 100644 index 00000000000..5ebfa914a53 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t100_dark.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t100_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t100_light.png new file mode 100644 index 00000000000..5d7e92e85c0 Binary files /dev/null and b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsGlassMotion_t100_light.png differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t000_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t000_dark.png deleted file mode 100644 index e1616611f6b..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t000_dark.png and /dev/null differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t000_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t000_light.png deleted file mode 100644 index 276d580b94e..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t000_light.png and /dev/null differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t010_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t010_dark.png deleted file mode 100644 index 859b881d9c2..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t010_dark.png and /dev/null differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t010_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t010_light.png deleted file mode 100644 index 9bc4d1064e8..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t010_light.png and /dev/null differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t025_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t025_dark.png deleted file mode 100644 index 353cec243ce..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t025_dark.png and /dev/null differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t025_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t025_light.png deleted file mode 100644 index d89f58ec73a..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t025_light.png and /dev/null differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t050_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t050_dark.png deleted file mode 100644 index 0f893d2edcb..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t050_dark.png and /dev/null differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t050_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t050_light.png deleted file mode 100644 index d2b3a16fa23..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t050_light.png and /dev/null differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t075_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t075_dark.png deleted file mode 100644 index 90f7462662d..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t075_dark.png and /dev/null differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t075_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t075_light.png deleted file mode 100644 index e3660582682..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t075_light.png and /dev/null differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t090_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t090_dark.png deleted file mode 100644 index ec94f2c0f25..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t090_dark.png and /dev/null differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t090_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t090_light.png deleted file mode 100644 index e041018a1b7..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t090_light.png and /dev/null differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t100_dark.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t100_dark.png deleted file mode 100644 index 0c36e3f27aa..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t100_dark.png and /dev/null differ diff --git a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t100_light.png b/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t100_light.png deleted file mode 100644 index 9f5bef593a9..00000000000 Binary files a/scripts/fidelity-app/goldens/ios-27-metal-frames/TabsMorph_t100_light.png and /dev/null differ diff --git a/scripts/fidelity-app/ios-native-ref/motion-probe/App/App.swift b/scripts/fidelity-app/ios-native-ref/motion-probe/App/App.swift new file mode 100644 index 00000000000..f785afa852b --- /dev/null +++ b/scripts/fidelity-app/ios-native-ref/motion-probe/App/App.swift @@ -0,0 +1,522 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +// TabProbe: an instrumented native iOS tab bar -- the measuring instrument behind +// com.codename1.ui.TabGlassMotion and GlassRecipe.liquidPill27. +// +// A real UITabBarController (the only host UIKit gives the full Liquid Glass +// selection motion) over a known backdrop. For every display frame it logs the +// presentation geometry of every layer under the tab bar -- frame in window +// coordinates, bounds, position, transform, opacity, filters -- and every +// CAAnimation UIKit attaches, plus touch timestamps. UIKit drives this motion by +// writing layer values each frame rather than through CAAnimations, so the frame +// log IS the motion. Output: Documents/ (default probe.log), one record +// per line; scripts/fidelity-app/tools/tab-motion/tabmotion.py reads it. +// +// Environment: PROBE_APPEARANCE light|dark, PROBE_BACKDROP grey|stripes|photo, +// PROBE_TABS 3..5, PROBE_LOG file name. See ../motion-probe/README.md. +import UIKit + +var LOG: FileHandle? +var PROBE: Probe? +func out(_ s: String) { + if let h = LOG { h.write((s + "\n").data(using: .utf8)!) } +} + +final class ProbeWindow: UIWindow { + override func sendEvent(_ event: UIEvent) { + if let ts = event.allTouches { + for t in ts { + let p = t.location(in: self) + out(String(format: "TOUCH t=%.5f phase=%d x=%.2f y=%.2f", CACurrentMediaTime(), t.phase.rawValue, p.x, p.y)) + if t.phase == .began { + PROBE?.touchDown(CACurrentMediaTime()) + } + } + } + super.sendEvent(event) + } +} + +func describeValue(_ v: Any?) -> String { + guard let v = v else { return "nil" } + if let n = v as? NSValue { + let type = String(cString: n.objCType) + if type.contains("CGRect") { return NSCoder.string(for: n.cgRectValue) } + if type.contains("CGPoint") { return NSCoder.string(for: n.cgPointValue) } + if type.contains("CGSize") { return NSCoder.string(for: n.cgSizeValue) } + if type.contains("CATransform3D") { + let t = n.caTransform3DValue + return String(format: "T[%.4f %.4f %.4f %.4f | %.3f %.3f]", t.m11, t.m12, t.m21, t.m22, t.m41, t.m42) + } + if let num = v as? NSNumber { return num.stringValue } + return "\(n)" + } + if let a = v as? [Any] { return "[" + a.map { describeValue($0) }.joined(separator: ", ") + "]" } + let s = "\(v)" + return s.replacingOccurrences(of: "\n", with: " ") +} + +func describeAnim(_ a: CAAnimation) -> String { + var s = "class=\(type(of: a)) dur=\(a.duration) begin=\(a.beginTime) speed=\(a.speed) timeOffset=\(a.timeOffset) fill=\(a.fillMode.rawValue) additive=" + if let p = a as? CAPropertyAnimation { + s += "\(p.isAdditive) keyPath=\(p.keyPath ?? "nil")" + if let vf = p.valueFunction { s += " valueFunction=\(vf)" } + } + if let tf = a.timingFunction { + var c1: [Float] = [0, 0], c2: [Float] = [0, 0] + tf.getControlPoint(at: 1, values: &c1) + tf.getControlPoint(at: 2, values: &c2) + s += String(format: " tf=(%.3f,%.3f,%.3f,%.3f)", c1[0], c1[1], c2[0], c2[1]) + } + if let sp = a as? CASpringAnimation { + s += " mass=\(sp.mass) stiffness=\(sp.stiffness) damping=\(sp.damping) v0=\(sp.initialVelocity) settle=\(sp.settlingDuration)" + if #available(iOS 17.0, *) { + s += " perceptualDuration=\(sp.perceptualDuration) bounce=\(sp.bounce)" + } + } + if let b = a as? CABasicAnimation { + s += " from=\(describeValue(b.fromValue)) to=\(describeValue(b.toValue)) by=\(describeValue(b.byValue))" + } + if let k = a as? CAKeyframeAnimation { + s += " values=\(describeValue(k.values)) keyTimes=\(describeValue(k.keyTimes)) calc=\(k.calculationMode.rawValue)" + } + if let g = a as? CAAnimationGroup, let subs = g.animations { + s += " GROUP{" + subs.map { describeAnim($0) }.joined(separator: " ;; ") + "}" + } + return s +} + +final class Probe: NSObject { + let root: UIView + let window: UIWindow + var link: CADisplayLink? + var seen = Set() + var ids = [ObjectIdentifier: Int]() + var nextId = 0 + var lastSig = "" + init(root: UIView, window: UIWindow) { + self.root = root; self.window = window + super.init() + if snapEnabled { + // Snapshots are numbered per launch; drop an earlier launch's set. + let snaps = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + .appendingPathComponent("snap") + try? FileManager.default.removeItem(at: snaps) + } + link = CADisplayLink(target: self, selector: #selector(tick(_:))) + link!.preferredFrameRateRange = CAFrameRateRange(minimum: 60, maximum: 120, preferred: 120) + link!.add(to: .main, forMode: .common) + } + func lid(_ l: CALayer) -> Int { + let k = ObjectIdentifier(l) + if let i = ids[k] { return i } + nextId += 1; ids[k] = nextId + return nextId + } + // PROBE_SNAP=1: also snapshot the bar region on every frame for 1.4 s after + // each touch-down, as lossless PNGs named by milliseconds since the touch + // (Documents/snap/_.png), the native side of the frame-by-frame + // comparison. A screen recording is lossy and variable-rate. + var snapUntil: CFTimeInterval = 0 + var snapStart: CFTimeInterval = 0 + var snapIndex = 0 + let snapEnabled = ProcessInfo.processInfo.environment["PROBE_SNAP"] == "1" + // PROBE_FILTERS=1: log every CoreAnimation filter on the tab bar's layers with + // all of its input values (FILTER lines), whenever they change. This is how the + // vibrancy colour matrices and the glass parameters are read exactly instead of + // being fitted from pixels. + let dumpFilters = ProcessInfo.processInfo.environment["PROBE_FILTERS"] == "1" + var lastFilterSig = [Int: String]() + var lastObjSig = [Int: String]() + // PROBE_KEYS=: extra candidate input keys (one per line) to query on every + // CAFilter -- the private glass filters' keys are not discoverable at runtime. + lazy var extraFilterKeys: [String] = { + guard let p = ProcessInfo.processInfo.environment["PROBE_KEYS"], + let t = try? String(contentsOfFile: p, encoding: .utf8) else { return [] } + return t.split(separator: "\n").map(String.init).filter { !$0.isEmpty } + }() + + var dumpedClasses = Set() + + /// PROBE_FILTERS: once per class, the ObjC properties and zero-argument getters + /// of the private classes that implement the glass (CAFilter, CASDF*Layer, + /// CABackdropLayer), so their parameters can be read by name. + func dumpClass(_ cls: AnyClass) { + let n = NSStringFromClass(cls) + if dumpedClasses.contains(n) { return } + dumpedClasses.insert(n) + var count: UInt32 = 0 + var names: [String] = [] + if let props = class_copyPropertyList(cls, &count) { + for i in 0.. String { + var cls: AnyClass? = type(of: obj) + var parts: [String] = [] + while let c = cls, c != NSObject.self { + let cname = NSStringFromClass(c) + if cname == "CALayer" || cname == "UIView" || cname == "UIResponder" { break } + var count: UInt32 = 0 + if let props = class_copyPropertyList(c, &count) { + for i in 0.. String { + guard let v = v else { return "nil" } + if let n = v as? NSNumber { return n.stringValue } + if let s = v as? String { return s } + let cf = v as CFTypeRef + if CFGetTypeID(cf) == CGColor.typeID { return describeColor(cf as! CGColor) } + if let val = v as? NSValue { + let type = String(cString: val.objCType) + if type.contains("CAColorMatrix") { + var m = [Float](repeating: 0, count: 20) + m.withUnsafeMutableBytes { val.getValue($0.baseAddress!, size: 80) } + return "M[" + m.map { String(format: "%.5f", $0) }.joined(separator: ",") + "]" + } + return describeValue(val) + } + if let a = v as? [Any] { return "[" + a.map { describeFilterValue($0) }.joined(separator: ";") + "]" } + return "\(v)".replacingOccurrences(of: "\n", with: " ") +} + +func describeColor(_ c: CGColor) -> String { + let comps = (c.components ?? []).map { String(format: "%.3f", $0) }.joined(separator: ",") + return "(" + comps + ")" +} + +final class StripesView: UIView { + var mode = "stripes" + override func draw(_ rect: CGRect) { + let ctx = UIGraphicsGetCurrentContext()! + if mode == "photo", let img = UIImage(named: "glass-backdrop") { + img.draw(in: bounds); return + } + if mode == "grey" { + ctx.setFillColor(UIColor(white: 0.5, alpha: 1).cgColor); ctx.fill(bounds); return + } + // solid:RRGGBB -- a uniform backdrop, so the glass under every glyph is known + if mode.hasPrefix("solid:"), let v = UInt32(mode.dropFirst(6), radix: 16) { + ctx.setFillColor(UIColor(red: CGFloat((v >> 16) & 0xff) / 255, green: CGFloat((v >> 8) & 0xff) / 255, + blue: CGFloat(v & 0xff) / 255, alpha: 1).cgColor) + ctx.fill(bounds); return + } + let cols: [UIColor] = [.systemRed, .systemOrange, .systemYellow, .systemGreen, .systemTeal, .systemBlue, .systemIndigo, .systemPurple, .white, .black] + let h: CGFloat = 23 + var y: CGFloat = 0; var i = 0 + while y < bounds.height { + ctx.setFillColor(cols[i % cols.count].cgColor) + ctx.fill(CGRect(x: 0, y: y, width: bounds.width, height: h)) + y += h; i += 1 + } + // vertical ruler lines so horizontal refraction/scale is measurable + ctx.setFillColor(UIColor.black.cgColor) + var x: CGFloat = 0 + while x < bounds.width { ctx.fill(CGRect(x: x, y: 0, width: 1, height: bounds.height)); x += 20 } + } +} + +/// PROBE_ICONS=grid: a template icon of 1 pt vertical lines every 3 pt and a +/// horizontal line every 6 pt, so magnification and refraction are measurable. +func gridIcon() -> UIImage { + let size = CGSize(width: 30, height: 27) + let r = UIGraphicsImageRenderer(size: size) + return r.image { c in + UIColor.black.setFill() + var x: CGFloat = 0 + while x < size.width { c.fill(CGRect(x: x, y: 0, width: 1, height: size.height)); x += 3 } + var y: CGFloat = 0 + while y < size.height { c.fill(CGRect(x: 0, y: y, width: size.width, height: 1)); y += 6 } + }.withRenderingMode(.alwaysTemplate) +} + +final class Page: UIViewController { + let mode: String + init(_ title: String, _ img: String, _ tag: Int, mode: String) { + self.mode = mode + super.init(nibName: nil, bundle: nil) + let icon = ProcessInfo.processInfo.environment["PROBE_ICONS"] == "grid" ? gridIcon() : UIImage(systemName: img) + tabBarItem = UITabBarItem(title: title, image: icon, tag: tag) + } + required init?(coder: NSCoder) { fatalError() } + override func loadView() { + let v = StripesView(); v.mode = mode; v.contentMode = .redraw + view = v + } +} + +@main +final class AppDelegate: UIResponder, UIApplicationDelegate { + func application(_ application: UIApplication, configurationForConnecting s: UISceneSession, + options: UIScene.ConnectionOptions) -> UISceneConfiguration { + let c = UISceneConfiguration(name: nil, sessionRole: s.role) + c.delegateClass = SceneDelegate.self + return c + } +} + +final class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + var probe: Probe? + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options: UIScene.ConnectionOptions) { + guard let ws = scene as? UIWindowScene else { return } + let env = ProcessInfo.processInfo.environment + let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + let logURL = docs.appendingPathComponent(env["PROBE_LOG"] ?? "probe.log") + FileManager.default.createFile(atPath: logURL.path, contents: nil) + LOG = try? FileHandle(forWritingTo: logURL) + let mode = env["PROBE_BACKDROP"] ?? "grey" + let w = ProbeWindow(windowScene: ws) + let tbc = UITabBarController() + let all = [("Featured", "star.fill"), ("Search", "magnifyingglass"), ("More", "ellipsis"), + ("Library", "books.vertical.fill"), ("Settings", "gearshape.fill")] + let n = Int(env["PROBE_TABS"] ?? "3") ?? 3 + tbc.viewControllers = (0.. UIColor? { + guard let v = UInt32(env[k] ?? "", radix: 16) else { return nil } + return UIColor(red: CGFloat((v >> 16) & 0xff) / 255, green: CGFloat((v >> 8) & 0xff) / 255, + blue: CGFloat(v & 0xff) / 255, alpha: 1) + } + if let t = hex("PROBE_TINT") { tbc.tabBar.tintColor = t } + if let t = hex("PROBE_UNTINT") { tbc.tabBar.unselectedItemTintColor = t } + w.rootViewController = tbc + w.makeKeyAndVisible() + window = w + out(String(format: "START t=%.5f scale=%.1f screen=%@ appearance=%@ backdrop=%@", CACurrentMediaTime(), UIScreen.main.scale, + NSCoder.string(for: UIScreen.main.bounds), dark ? "dark" : "light", mode)) + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + // PROBE_ROOT=window walks the whole window: the bar's own glass is not + // under UITabBar. + let root: UIView = env["PROBE_ROOT"] == "window" ? w : tbc.tabBar + self.probe = Probe(root: root, window: w) + // PROBE_TINTSWEEP=: one RRGGBB per line; each is set as the tab + // bar tint and the vibrancy matrices UIKit derives for it are logged + // (TINT lines), so the tint -> matrix rule can be measured exactly. + if let p = env["PROBE_TINTSWEEP"], let text = try? String(contentsOfFile: p, encoding: .utf8) { + let tints = text.split(separator: "\n").compactMap { UInt32($0, radix: 16) } + var i = 0 + func matrices() -> [String] { + var found: [String] = [] + func walk(_ l: CALayer) { + for f in l.filters ?? [] { + let o = f as AnyObject + if (o.value(forKey: "name") as? String) == "vibrantColorMatrix", + let v = o.value(forKey: "inputColorMatrix") { + let d = String(describing: type(of: l.delegate as AnyObject? ?? l)) + ":" + describeFilterValue(v) + if !found.contains(d) { found.append(d) } + } + } + for s in l.sublayers ?? [] { walk(s) } + } + walk(tbc.tabBar.layer) + return found + } + Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { t in + if i > 0 { + out(String(format: "TINT %06X ", tints[i - 1]) + matrices().joined(separator: " ")) + } + if i >= tints.count { t.invalidate(); out("TINTDONE"); return } + let v = tints[i] + tbc.tabBar.tintColor = UIColor(red: CGFloat((v >> 16) & 0xff) / 255, green: CGFloat((v >> 8) & 0xff) / 255, + blue: CGFloat(v & 0xff) / 255, alpha: 1) + i += 1 + } + } + PROBE = self.probe + } + } +} diff --git a/scripts/fidelity-app/ios-native-ref/motion-probe/README.md b/scripts/fidelity-app/ios-native-ref/motion-probe/README.md new file mode 100644 index 00000000000..7b129f542fc --- /dev/null +++ b/scripts/fidelity-app/ios-native-ref/motion-probe/README.md @@ -0,0 +1,206 @@ +# Tab-motion probe: measuring the native iOS tab bar + +An instrumented native app that measures what a real `UITabBarController` does, +so Codename One can reproduce it from numbers instead of by eye. It is the source +of two things in the core: + +- `com.codename1.ui.TabGlassMotion` -- the Liquid Glass selection motion (lens + travel, lift, deformation, platter fade, whole-bar pulse, touch glow); +- `com.codename1.ui.TabGlassGesture` -- what depends on the gesture rather than on + a tap: holding the press, scrubbing the lens with the finger, the release; +- `com.codename1.ui.plaf.VibrancyMatrix` -- the colour matrix of vibrant content + (the tab icons and labels) as a function of the tint; +- `GlassRecipe.liquidPill27` -- the tab bar's glass material. + +`TabGlassMotionTest`, `TabGlassGestureTest` and `VibrancyMatrixTest` hold the Java +to the capture, using the fixtures in +`maven/core-unittests/src/test/resources/tab-glass-motion/` (`native-ios27.csv`, +`native-gestures.csv`, `native-vibrancy.csv`). + +## What it records + +**Motion.** UIKit plays the full selection only for genuine touches on a +controller-managed bar, so the XCUITest bundle in `UITests/` taps the app. On every +display frame the app logs the presentation geometry of every layer under the tab +bar -- window frame, bounds, position, transform, opacity, filters -- plus touch +timestamps. UIKit attaches no `CAAnimation` for this motion (only `CAMatch*` +animations that make the lens copies follow each other); it writes the layer values +itself each frame, so the frame log is the motion. + +**Material.** Lossless `simctl io screenshot`s of the resting bar over backdrops +whose every pixel is known: the shared `glass-backdrop.png`, sharp 23 pt colour +bands with 1 pt black rulers, and flat 50% grey. Do not fit the material from a +screen recording: its h264 smears exactly the detail the blur radius is read from. + +**Frames** (optional, `PROBE_SNAP=1`). Lossless snapshots of the bar region on every +frame for 1.4 s after each touch, for side-by-side visual comparison with the CN1 +frames. Snapshotting costs a frame's worth of time, and UIKit steps this motion per +frame, so a snapshot run's TIMING is distorted: compare snapshots by motion state +(travel and lift, read from the same run's log), never by clock time, and never fit +the model from a snapshot run. + +## Running it + +```bash +scripts/probe-ios-tab-motion.sh [out_dir] [simulator_udid] +``` + +It resolves an iPhone 16 simulator of the golden set's generation +(`CN1SS_FIDELITY_GOLDEN_SET`, default `ios-27-metal`), builds this project with +xcodegen, runs tap-driven captures in light and dark on a 3-tab bar and in light on +a 5-tab bar, then the hold and drag captures (`hold-light.log`, `drag-light.log`, +`dragA-light.log` .. `dragC-light.log`, `dragD-dark.log`), then takes the material +screenshots. + +Every capture is one XCUITest run driven by `PROBE_SEQ` (`UITests/TapTests.swift`), +a comma separated list of gestures played two seconds apart: + +| Token | Gesture | +|---|---| +| `N` | tap tab `N` | +| `hN:MS` | press and hold tab `N` for `MS` milliseconds | +| `dA-B:MS[:H]` | press tab `A` for 0.3 s, drag to tab `B` over `MS` ms, hold `H` ms, release | +| `dA-B.F:MS:H` | as above, but stop at fraction `F` of the way from `A` to `B` | + +For example the hold capture is `h1:1500,h1:1500,1,h2:3000`: two 1.5 s holds of +tab 1, a quick tap on it once it is selected (nothing travels, so the release +wobble is seen alone), and a 3 s hold of tab 2. + +**Vibrancy** is read, not rendered: with `PROBE_TINTSWEEP=` (one `RRGGBB` per +line) the app sets each colour as the tab bar tint, 0.25 s apart, and logs the +`vibrantColorMatrix` filters UIKit installs on the item layers as `TINT` lines, +then `TINTDONE`. The first matrix of a line is the selected item's. The app reads +the variable from its own environment, so launch it directly (as the material +screenshots do, with `SIMCTL_CHILD_PROBE_TINTSWEEP`, `SIMCTL_CHILD_PROBE_APPEARANCE` +and `SIMCTL_CHILD_PROBE_LOG`) and collect `Documents/` from the app +container once it holds `TINTDONE`. + +`tints/` holds the two lists the committed fixture was captured from, one per log +pair named in the commands below: `sweep2.txt` (1200 colours spread over the RGB +cube, `sweep2-light.log` and `sweep2-dark.log`) and `sweep.txt` (a smaller sweep +with the grey ramp, `sweep-light.log` and `sweep-dark.log`). Both appearances of +both lists are also what `vibrancy` checks the closed forms against. A simulator +app can read a host path, so pass the list's absolute path. + +## Regenerating the model + +`scripts/fidelity-app/tools/tab-motion/tabmotion.py` (needs numpy and scipy, plus +Pillow for `material`). Most of the model is closed form -- UIKit's springs and the +vibrancy matrices -- and the tool CHECKS those against a capture, failing if one +drifts past the tests' tolerance. The channels with no closed form stay 60 Hz +tables, and the commands that write Java rewrite only those, so the hand-written +code around them survives. + +```bash +T=scripts/fidelity-app/tools/tab-motion/tabmotion.py +O=artifacts/tab-motion-probe +R=maven/core-unittests/src/test/resources/tab-glass-motion +V=artifacts/tint-sweeps # PROBE_TINTSWEEP logs, see above + +# Tap-driven selection (TabGlassMotion) +python3 $T fit $O/light-grey-3tabs.log $O/dark-grey-3tabs.log $O/light-grey-5tabs.log -o $O/tpl.json +python3 $T check $O/tpl.json $O/*grey-*tabs.log # checks the springs, prints the worst per-tap errors +python3 $T java $O/tpl.json CodenameOne/src/com/codename1/ui/TabGlassMotion.java # the kept tables +python3 $T fixture $O/light-grey-3tabs.log $O/dark-grey-3tabs.log $O/light-grey-5tabs.log -o $R/native-ios27.csv +python3 $T material $O # prints the GlassRecipe numbers + +# Holds and scrubs (TabGlassGesture) +python3 $T springs --press $O/hold-light.log $O/drag-light.log $O/*grey-*tabs.log \ + --drag $O/drag-light.log # prints the spring constants +python3 $T gesture --hold $O/hold-light.log \ + --drags $O/drag-light.log $O/dragA-light.log $O/dragB-light.log $O/dragC-light.log $O/dragD-dark.log \ + --settle $O/hold-light.log $O/dragA-light.log $O/dragC-light.log $O/dragD-dark.log \ + -o CodenameOne/src/com/codename1/ui/TabGlassGesture.java +python3 $T gesture-fixture $O/hold-light.log $O/dragA-light.log $O/dragC-light.log $O/dragD-dark.log \ + -o $R/native-gestures.csv + +# Vibrancy (VibrancyMatrix), from PROBE_TINTSWEEP logs: checks the closed forms +python3 $T vibrancy --dark $V/sweep-dark.log $V/sweep2-dark.log --light $V/sweep-light.log $V/sweep2-light.log +python3 $T vibrancy-fixture --dark $V/sweep2-dark.log --light $V/sweep2-light.log \ + --grey-dark $V/sweep-dark.log --grey-light $V/sweep-light.log -o $R/native-vibrancy.csv +``` + +`fit` uses only clean taps: not the first tap of a run (it lags a frame), a quick +~66 ms press (a longer press grows the bar pulse further), no frame hitch in the +first frames, and the fast lift. + +`gesture` prints what it fits and checks along the way: + +- the **finger offset** of each drag log -- the finger's window x minus the lens + target in the lens parent's coordinates -- least-squares fitted with the follow + spring over every press of the log and rounded to 0.01 pt (59.55 to 59.71 pt on + the 3-tab bar, 21.12 pt on the 5-tab bar); +- the **release wobble**, read off the quickest press of the hold log over 45 + frames from 0.9 s after touch-up, and its difference from the wobble after each + long hold; +- the **settle** after a scrub, replayed over the `--settle` episodes: the tap's own + travel spring, started from the follow spring's position and velocity one touch + lag after lift-off -- a closed form, so this is a check, not a fit; +- the **scrub deformation kernels** -- 60 frames of delay over the lens's own speed + and acceleration, ridge 1e-2, the wobble subtracted first -- with their + leave-one-log-out error. A press whose lens moves more than 3 pt in its first + 0.25 s started on another tab and plays a tap first; it is left out. + +`vibrancy` holds every logged matrix, in both appearances and for the grey tints, +to the closed forms below, prints the worst entry error and fails if it exceeds +2e-5 (the logs carry about 5e-6). + +## What the capture showed (iOS 27.0, iPhone 16) + +Taps: + +- The lens centre follows ONE normalized curve for every jump distance: a spring of + duration 0.4 s and bounce 0.15 (at the target after ~0.33 s, a 0.6% overshoot at + ~0.38 s), which UIKit ends once less than ~0.195 pt remains past the overshoot. + Every frame is evaluated on the display's vsync grid; the logged frame times + carry the main thread's jitter, so fit on frame boundaries, not log times. +- The lift and the grey platter are critically damped springs (0.25 s and 0.4 s); + the touch glow is critically damped springs of 0.1 s and 0.5 s, its mask opacity + a fixed 0.33675 of its layer opacity. +- On touch the lens lifts: its bounds grow 16 pt both ways, the grey platter fades + out (clear glass) and the accent copy of the tabs seen through it magnifies by + 16%. The lift is released once the lens is within 3.5 pt of its target, but not + before it has snapped to full. +- Deformation is a distance-independent stretch pulse plus an arrival squash whose + depth is linear in the distance, plus a small second wobble about a second in. +- The whole bar -- glass, content and lens -- scales about its centre, adding + ~8.7 pt to its width at 0.13 s and undershooting slightly. +- The accent colour lives in a second copy of the tab content (`SelectedContentView`) + revealed only through the lens; the normal content has a lens-shaped hole. +- A second lift regime: on a 5-tab bar UIKit plays a slower lift rise for some + jumps -- the same six of the eight scripted taps in two independent captures, so + it is deterministic, but what selects it is not identified. Travel and + deformation are unchanged; the model follows the fast lift every 3-tab tap uses. + +Holds and scrubs: + +- A held press keeps the lift for as long as the finger is down. +- The whole-bar growth is a spring toward +14.58 pt of width while pressed and + back to 0 on release: omega 17.63 rad/s, zeta 0.593, 20 ms behind the touch. A + quick tap is the same spring released early. +- While dragging, the lens follows the finger on a stiffer spring: omega 32 rad/s, + zeta 0.903, 20 ms behind, within the first and last tab. +- Releasing a scrub selects the tab under the FINGER, not the one nearest the + lens, which can be far behind after a flick. +- The settle to that tab is the tap's own travel spring, started from where the + follow spring had the lens, and how fast, one touch lag after lift-off. +- Every release -- tap, hold or scrub -- ends in the same small wobble. When it + becomes visible depends on the gesture: 0.951-0.954 s after touch-up for holds + and taps, 0.917-0.919 s for drags that start on the resting tab, 0.851-0.853 s + for scrubs (which start at its peak). The model starts it at a fixed 0.9 s. +- While scrubbing, the lens deforms with its own motion: scaleX/scaleY with its + speed and acceleration, its centre offset with its velocity and acceleration. + +Vibrancy and material: + +- A vibrant glyph is a colour matrix applied to what lies behind it, chosen by the + tint. In dark the structure is exact (`M = e I + (0.5 - e) 1 t^T / |t|^2`, + `offset = t`, `e = 0.5 min(t) / max(t)`) and reproduces 1200 held-out tints to + 1e-5. In light it is `M = e I + 0.3 (0.5 - e) (1 + t) d^T / |d|^2` with + `d = 1 - t`, offset `alpha (1 + t) - 1 + e t`, and `alpha` exactly the value that + maps a white backdrop to the tint. Grey tints `g` use `M = 5/16 I / q` and + offset `(19/16 g - 1/4) / q`, `q = 1 + 0.05 g (1 - g)`, in both appearances. + Every logged matrix matches to 5e-6. +- The selection platter is a colour matrix of the bar's glass, not a fill of its + own: `1.2 I` less a share of the Rec. 709 luminance (0.33 dark, 0.07 light) plus + an offset. diff --git a/scripts/fidelity-app/ios-native-ref/motion-probe/UITests/TapTests.swift b/scripts/fidelity-app/ios-native-ref/motion-probe/UITests/TapTests.swift new file mode 100644 index 00000000000..c282ab16be5 --- /dev/null +++ b/scripts/fidelity-app/ios-native-ref/motion-probe/UITests/TapTests.swift @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +// Drives GENUINE taps on the probe's tab bar: UIKit plays the full Liquid Glass +// selection (lift, lens, bar pulse, glow) only for touch-driven selection, never +// for a programmatic selectedIndex change. PROBE_SEQ lists the gestures (taps, +// holds, drags), two seconds apart so every motion settles before the next. +import XCTest + +final class TapTests: XCTestCase { + func testTaps() { + // xcodebuild strips the TEST_RUNNER_ prefix the capture script sets. + let env = ProcessInfo.processInfo.environment + // PROBE_TARGET= drives ANOTHER app already on screen (the Codename + // One side of a side-by-side recording) at PROBE_TAB_POINTS "x:y,x:y,..." + // (points, one per tab) instead of this probe's own tab bar. + let target = env["PROBE_TARGET"] ?? "" + let app = target.isEmpty ? XCUIApplication() : XCUIApplication(bundleIdentifier: target) + var points: [CGPoint] = [] + if target.isEmpty { + app.launchEnvironment["PROBE_APPEARANCE"] = env["PROBE_APPEARANCE"] ?? "light" + app.launchEnvironment["PROBE_BACKDROP"] = env["PROBE_BACKDROP"] ?? "grey" + app.launchEnvironment["PROBE_TABS"] = env["PROBE_TABS"] ?? "3" + app.launchEnvironment["PROBE_SNAP"] = env["PROBE_SNAP"] ?? "0" + app.launchEnvironment["PROBE_LOG"] = env["PROBE_LOG"] ?? "probe.log" + app.launchEnvironment["PROBE_ICONS"] = env["PROBE_ICONS"] ?? "" + app.launchEnvironment["PROBE_FILTERS"] = env["PROBE_FILTERS"] ?? "0" + app.launchEnvironment["PROBE_TINT"] = env["PROBE_TINT"] ?? "" + app.launchEnvironment["PROBE_SELECT"] = env["PROBE_SELECT"] ?? "" + app.launch() + sleep(3) + let bar = app.tabBars.firstMatch + XCTAssertTrue(bar.waitForExistence(timeout: 10)) + for i in 0.. XCUICoordinate { origin.withOffset(CGVector(dx: p.x, dy: p.y)) } + // PROBE_SEQ, comma separated, two seconds between steps: + // N tap tab N + // hN:MS press and hold tab N for MS milliseconds + // dA-B:MS[:H] press tab A, drag to tab B over MS milliseconds, hold H ms, release + // dA-B.F:MS:H as above, but stop at fraction F of the way from A to B + // PROBE_PERIOD_MS: start step k at k * period after the first, whatever the + // app's idle waits cost, so two recordings share one gesture schedule. + let period = Double(env["PROBE_PERIOD_MS"] ?? "") ?? 0 + let start = Date() + for (k, step) in (env["PROBE_SEQ"] ?? "2,0,1,2,1,0").split(separator: ",").map(String.init).enumerated() { + if period > 0 { + let wait = start.addingTimeInterval(Double(k) * period / 1000).timeIntervalSinceNow + if wait > 0 { + Thread.sleep(forTimeInterval: wait) + } + } + if step.hasPrefix("h") { + let p = step.dropFirst().split(separator: ":") + at(points[Int(p[0])!]).press(forDuration: Double(p[1])! / 1000) + } else if step.hasPrefix("d") { + let p = step.dropFirst().split(separator: ":") + let ends = p[0].split(separator: "-") + let a = Int(ends[0])! + let target = ends[1].split(separator: ".") + let bi = Int(target[0])! + let frac = target.count > 1 ? Double("0." + target[1])! : 1.0 + let ms = Double(p[1])! + let hold = p.count > 2 ? Double(p[2])! / 1000 : 0.0 + let ea = points[a], eb = points[bi] + let tx = ea.x + (eb.x - ea.x) * frac + let velocity = XCUIGestureVelocity(abs(tx - ea.x) / CGFloat(ms / 1000)) + at(ea).press(forDuration: 0.3, thenDragTo: at(CGPoint(x: tx, y: ea.y)), withVelocity: velocity, + thenHoldForDuration: hold) + } else { + at(points[Int(step)!]).tap() + } + if period <= 0 { + Thread.sleep(forTimeInterval: 2.0) + } + } + } +} diff --git a/scripts/fidelity-app/ios-native-ref/motion-probe/project.yml b/scripts/fidelity-app/ios-native-ref/motion-probe/project.yml new file mode 100644 index 00000000000..5fc902ebabd --- /dev/null +++ b/scripts/fidelity-app/ios-native-ref/motion-probe/project.yml @@ -0,0 +1,36 @@ +# The tab-motion probe: an instrumented UITabBarController app plus the XCUITest +# bundle that taps it. Generated with xcodegen by scripts/probe-ios-tab-motion.sh; +# see README.md for what it measures and how the measurements are used. +name: TabProbe +options: + bundleIdPrefix: com.codenameone.tabprobe +targets: + TabProbe: + type: application + platform: iOS + deploymentTarget: "18.0" + sources: + - App + # The same backdrop the fidelity tiles are rendered over, so a material + # measured here is directly comparable with the committed goldens. + - path: ../../common/src/main/resources/glass-backdrop.png + buildPhase: resources + settings: + PRODUCT_BUNDLE_IDENTIFIER: com.codenameone.tabprobe + GENERATE_INFOPLIST_FILE: YES + INFOPLIST_KEY_UILaunchScreen_Generation: YES + INFOPLIST_KEY_UIApplicationSceneManifest_Generation: YES + SWIFT_VERSION: "5.0" + CODE_SIGNING_ALLOWED: NO + TabProbeUITests: + type: bundle.ui-testing + platform: iOS + deploymentTarget: "18.0" + sources: [UITests] + dependencies: + - target: TabProbe + settings: + PRODUCT_BUNDLE_IDENTIFIER: com.codenameone.tabprobe.uitests + GENERATE_INFOPLIST_FILE: YES + TEST_TARGET_NAME: TabProbe + SWIFT_VERSION: "5.0" diff --git a/scripts/fidelity-app/ios-native-ref/motion-probe/tints/sweep.txt b/scripts/fidelity-app/ios-native-ref/motion-probe/tints/sweep.txt new file mode 100644 index 00000000000..bf75a8b1a5b --- /dev/null +++ b/scripts/fidelity-app/ios-native-ref/motion-probe/tints/sweep.txt @@ -0,0 +1,182 @@ +007AFF +0A84FF +FF3B30 +34C759 +FF9500 +AF52DE +8E8E93 +FF2D55 +000000 +FFFFFF +808080 +FF0000 +00FF00 +0000FF +FFFF00 +00FFFF +FF00FF +404040 +C0C0C0 +800000 +008000 +000080 +A54DCA +182530 +BB1D6D +132CDE +D6237B +2ED91E +3F721F +CB1971 +174494 +D6493C +9D5C34 +60BE31 +201E69 +FEDAA0 +EEE8B9 +997F5C +7C2999 +FDAFE5 +93253C +D654AF +4DFAD7 +1427A0 +AEB3FE +E9232F +8AF221 +1F9EE4 +91C5B1 +0BECB5 +563BFC +1E6F93 +427ECB +C8FE29 +55E5CD +8E46DC +8ED4B7 +C2764D +2A5A4D +767706 +F85D86 +90024A +D6BDA3 +401BE9 +C8CBCC +C935F6 +CD1F61 +226AE1 +5338AE +1A3400 +4D33BA +0D246A +C04C81 +B1BAF2 +3E3BF9 +EEF5F7 +9F2B49 +34AF87 +F5520B +69B94B +0D982E +85BB55 +B672A8 +72637A +CD7466 +FCB60E +0E8FF1 +8463B0 +E4B2BA +297034 +74F064 +AC68F7 +00F5B0 +2B3DC6 +66F45B +DEAA2C +CAEDCD +2B5157 +410E4D +EE4AF2 +B34F43 +0A0734 +47DE63 +6C0E80 +6C957B +A684D6 +431FB5 +EAD742 +4D09E1 +5D024C +5848F2 +3D1FA6 +F7361D +7F618D +1532E7 +0E20E2 +A6668D +E7F47E +8467E5 +46D53E +C8E2A1 +257BDB +256C9B +3E4FBB +498146 +EF7030 +CBF953 +7252DC +CEADD7 +64B6A3 +2FBB09 +ADEAE1 +09C4A9 +972039 +75352B +878B14 +5C8A42 +D884CF +4CFDA7 +2D8E1D +5DD925 +89082D +000000 +111111 +222222 +333333 +444444 +555555 +666666 +777777 +888888 +999999 +AAAAAA +BBBBBB +CCCCCC +DDDDDD +EEEEEE +FFFFFF +000000 +200000 +400000 +600000 +800000 +A00000 +C00000 +E00000 +000000 +002000 +004000 +006000 +008000 +00A000 +00C000 +00E000 +000000 +000020 +000040 +000060 +000080 +0000A0 +0000C0 +0000E0 diff --git a/scripts/fidelity-app/ios-native-ref/motion-probe/tints/sweep2.txt b/scripts/fidelity-app/ios-native-ref/motion-probe/tints/sweep2.txt new file mode 100644 index 00000000000..e758776a47e --- /dev/null +++ b/scripts/fidelity-app/ios-native-ref/motion-probe/tints/sweep2.txt @@ -0,0 +1,1200 @@ +E7EEE7 +615EF3 +5F30E4 +9B482E +15CAE7 +500720 +1E1261 +7B0FED +A7E164 +7796FF +022BEA +8ED02A +82A175 +930F23 +37CD37 +94C522 +08006D +6B1AF0 +C0CBD6 +25658A +AC2C9F +AA07D1 +3C447E +33051E +EEF95A +60E561 +43D6C4 +3BCAD7 +6C008A +9B0A6B +5FC933 +154A6D +E28404 +A897C5 +25262E +6A7C07 +BCBEE8 +41F745 +C55D4E +9F747F +615164 +C6F728 +D71835 +371382 +7AC883 +D7FB96 +592340 +74F525 +8F6C68 +082389 +D2E47F +1E175A +90BC43 +2FB946 +E6A947 +1109F3 +B79F11 +0A26F6 +229FA3 +452526 +E7BC16 +42AEB4 +2BF227 +D50FFF +07C3C2 +062429 +2E3B83 +D5A9C6 +EAE1EC +2A0F9E +2CF60B +7539FE +F88205 +BC9A49 +6756AF +E2FF7B +A7CF80 +65DC66 +6DC470 +A26B45 +44FEB3 +14208D +5639E6 +F18C6D +D3C3FC +A1E7A4 +26108E +158FB5 +9E0945 +CFE861 +0C8879 +48183B +E437BC +276566 +F3835B +05F112 +5B738B +B151C9 +722CD2 +C642E6 +E86403 +C0AFED +A76832 +3F6D7C +C72C9E +A48608 +B22A13 +E1AFD7 +8CF90E +6F20DB +1158AB +47F04C +E1FC2C +71E094 +54839F +C36A9B +488BFE +66D23A +02C10E +16CD3E +FB2F55 +21EAD3 +CE897E +F2FC41 +ADDEF3 +A23762 +D60F85 +420B12 +634F74 +0691A4 +B57DFF +35FF3E +8065DF +0BC0D3 +51686F +6E4577 +B15CA1 +A1636F +633144 +7A432D +84C631 +DED740 +66CE09 +3166B7 +B83BAF +6024F6 +360C13 +F64B61 +5E3A68 +585090 +301F44 +EC2731 +A7C8EF +DAB5DC +6BBF06 +14665C +D0E8B8 +BDCF63 +543007 +A52BCF +61AE84 +8F3B51 +CF44A8 +BDDD5C +CF695E +24AE9A +F03305 +B61976 +8B99AC +6ECF5D +27C7FE +6D3DCA +0B3A37 +7983E3 +CD1964 +C00532 +84808D +AED433 +E32717 +C551C5 +F156FD +1DDBFD +D791CC +9FBB92 +F78A91 +970D07 +7D1550 +D1C71B +A1CB19 +A32572 +DBF480 +7C1732 +EF497D +3A19D5 +E93C68 +1AB64F +3FBAE2 +47D5E9 +86D6BA +469441 +7AF63A +9EB78C +8B618E +7A617F +64141F +048A84 +D90D13 +34718E +252C52 +78BFF7 +F5B56B +ADAFFD +44263D +E56DE3 +D984C7 +4DBC4E +A6945E +DABD31 +ECA528 +2ADDF8 +ED9904 +269E6D +299BFD +A79049 +70B7A7 +BB3CA5 +E18EE0 +9CEAA2 +71CC7F +2BB9BB +0CBACA +C663BC +C74F5A +5A2DE8 +900B71 +1D5197 +0BD820 +9C2BAB +298C35 +A02B0D +4931D8 +7D70FA +ADEBC9 +B3ACAA +45FCF9 +2517D2 +B201C1 +2DED0C +B90538 +D7D74A +7D53C0 +55A468 +C7D995 +8C1EF1 +9D3A9B +4F5B1F +EA08F6 +12A037 +655EB7 +6F78F1 +72E25E +8FCE58 +9FF585 +F8CEAF +2FF771 +CC196F +44C58C +1D7905 +F1BEE6 +7ED750 +D450A4 +4A8443 +881B4D +5B0068 +4C412A +B48A31 +F5EF2A +9B0960 +D5622E +E16A13 +D3FD50 +9FA19F +CF269C +EF22B8 +264736 +C9EC3E +E306C1 +F37996 +3AEA05 +694D94 +C098AC +C82C83 +6066C0 +38A89E +8CF0A9 +E4C32F +344830 +435B5C +62CD12 +ED2E33 +741050 +3FDED0 +25AEDA +47694B +C5F7A9 +D307CD +A27D09 +85A960 +AF9DE1 +36C168 +AA9634 +518198 +77B448 +65640D +652A45 +7DF4D5 +AAA82E +B888ED +3FE745 +7A2087 +56AB4A +3E0193 +BA7BB9 +545F27 +E810CE +5C469D +C30851 +66D2EF +1049C5 +3CCF58 +AC4386 +7CAA1B +134ACE +FE34F0 +B0D2D0 +90F8ED +D1DE3C +3AA9DD +D70CA4 +1B97DD +C0A49C +237D3F +E47CCB +1AF335 +7A9CCA +7823BC +D75471 +D73E24 +32423F +E80097 +7C990C +A65820 +E1E6CB +48B676 +179C0F +E8A09D +A6FF49 +F16967 +971E64 +A1B5F1 +085867 +786219 +369CA0 +8CD745 +ABE3D4 +285672 +9D3D64 +895C78 +978BBE +3B759A +5C738D +432EEC +27E125 +170391 +80D82C +549161 +7D5B8E +0E5F4D +11ECC4 +01FF1E +5C20B8 +A4F710 +955954 +39DF78 +E18378 +84FA71 +FF8512 +671AE5 +5CA64D +09FEDE +E2860F +253A5B +794A1B +9FD915 +A087FF +2D57C1 +827F51 +FEE81F +A1D2EF +BA6E15 +41FA44 +4AE914 +017BD1 +482EC9 +98F1D9 +271175 +97813C +7BD6EC +2C74BA +9EAC6F +1452CE +1DAF5A +ECC96D +C3C119 +6ED6B1 +7E5E53 +F568CC +6DEF9D +F1E4FA +5CF968 +C4D625 +188DB9 +8C095D +86C96F +8934B8 +1D4EB8 +467B3D +0AA4C4 +E62AC3 +A6831C +9AC97A +FA1460 +4D4110 +6A9FD2 +D9F545 +85A0E7 +C14B68 +8FE6CF +45C5B6 +733495 +BDB06E +47140C +F70598 +226EB4 +3EBF78 +842EDA +3DC8FC +1EFAC4 +A891FE +8F7151 +CE67F3 +55B37A +2FAFD8 +4DF483 +9D0BC8 +3DBA20 +CF8B1B +C1FD99 +F0A681 +250C12 +B1A5BF +98865F +F0C4AE +B90F6E +2618D2 +D4208D +22EDE6 +C3C513 +458B9E +FDB8F2 +0A4C62 +8A7484 +707800 +C9017A +FC28AA +87B135 +09A06A +CA5062 +5251AA +FB96BD +D492BB +FF797F +B9A65B +402023 +349E39 +E7F614 +53D3F6 +1BB081 +CBC881 +80BCA7 +5DAA68 +42D740 +895240 +66C095 +765E07 +F14489 +8AD681 +CBC547 +18503C +156392 +B63DCF +A80DF7 +57BF22 +9EE091 +72C8D0 +48C76E +904BE2 +593680 +F9DD3D +4DA509 +8A498C +CC8CA4 +7429F6 +66BB84 +1A4D20 +039E28 +025DAC +F74767 +D1F710 +D4B3CA +1D6E81 +8D6094 +7FE218 +5A0D56 +3C268A +B70C9E +20EE05 +6B20BA +05877D +FDCC9D +33F6CD +D1800C +044874 +A5C6BD +8DBAE6 +B59F8E +22A430 +87AB6D +5C6C18 +429B69 +5AB136 +4F49BE +B35DDC +0B04CC +1ACE30 +A4680A +59A422 +37A4DC +F270FF +D15B8B +23C7A4 +5DB605 +4BC11C +6CB0E1 +D93A3E +E8DFEF +D6C475 +C01510 +E7E491 +171BA6 +4DEFA8 +83A060 +F0CD1A +40FB38 +775F48 +3A3580 +85215E +13AF80 +B2E86A +07DA64 +FA8F7A +2536E1 +FB7B24 +FE1C21 +4DBB5B +33AA12 +B17093 +3CD55D +C60FEE +FA242D +69A164 +DCC107 +ADA18E +6A6B5C +4157EE +15428E +3B84B0 +B79D64 +042369 +B89ED1 +664A24 +88D9BF +D18EE9 +443F30 +B1D578 +FC3C91 +3F9228 +E873F4 +C665D3 +70E520 +BBD6AA +90E189 +95067D +07D2BF +4380C6 +D78EAE +343532 +E86B2D +BA08BF +0FB00B +4FF025 +B957ED +A1F51F +710ACA +DD7538 +1D22EC +CD6F6B +FEBD49 +CA312A +015581 +F23DDD +EF4F08 +BDBB0C +37B3D3 +38309A +3F2279 +422D9D +96B47D +7ED3EE +1B6FE7 +5735EB +46354F +4958D0 +3CD305 +754E38 +C05096 +0ECF38 +FC9917 +C7D599 +286D85 +39D6A8 +FA6846 +31C999 +888470 +BC2BDB +F4C4B7 +DCAAE6 +3B199F +1F53C3 +A5CC72 +32BE10 +A0B8F7 +75F904 +BFD035 +03B67E +BBBC9A +6B1CA1 +5EFE3C +B01B2F +6D818A +D9BFCD +2D9231 +2BCB73 +80A810 +A24C4F +92901D +FB18CA +2A46AE +0B6C02 +F5D6AB +26582A +E147C1 +E5DF3C +E3A324 +3BB639 +3CCB9E +3A6C31 +D4A547 +3F4F8C +F1D010 +DDC166 +FDFCDF +C6F635 +55A567 +07B153 +B3C1C6 +E7ECB7 +4BF6CA +A72EFA +7785B3 +D21783 +2082C1 +7C6DDD +215CD3 +E9C6F9 +3C765F +349C4C +11B451 +FAAAFA +4DE4B6 +9A327E +4F6F66 +B03D53 +6156BC +A6BA37 +948738 +7FB70A +588196 +928143 +86F826 +8B0E91 +8E4DC7 +51432D +53859C +1BDAB9 +77F6E7 +BDB06E +8AB276 +263824 +9A0134 +3D63BC +9EB548 +1F2248 +0FD349 +27DF5E +2CB5FF +6534D5 +47F862 +28F90F +2D4599 +83AB70 +8B4D4F +DA0AFA +A1562C +501037 +39989A +0CD410 +4C5F2A +00BE22 +6ECA76 +D0B3AB +70B1C8 +077B86 +18BDAB +B4E878 +D45966 +4C6C81 +E7F571 +AA5E79 +85820B +5E9F76 +219AA8 +68117E +2F287D +E26F16 +D446BD +B62325 +C645D6 +77AD69 +C29222 +F3071A +FF10C2 +21D951 +492269 +51FC73 +A3AF46 +4548A3 +156279 +4579E8 +BCBE3D +8B4A24 +5EB119 +3F9DA4 +5D3E8C +E2862F +297208 +279E5F +52A935 +96F7EB +B3FAEE +13614B +F00CE0 +CD1A90 +531758 +CD6C0A +0C70D9 +FF20EE +0C5DAF +07559B +15AC85 +BDF280 +A255B6 +49D5DB +EB83AA +FB7753 +EF9357 +A078CE +50AD91 +3101B1 +F539FE +C200FE +CD89FE +EC1C2E +A3FEF6 +1F490D +8FE219 +2B8F19 +78CC15 +826334 +DAB41D +25FAF6 +4D94A1 +AF646E +E49841 +7E8C98 +5A3CF3 +9B7C16 +0BAFF1 +CC2151 +388493 +C79F82 +5F8540 +1DDFD0 +C90187 +FCFB73 +C4B414 +0B52FF +75763C +4EDCA1 +EE8B5F +0D8834 +A9A81B +0D949B +ED1DAA +378F6F +F92112 +3119F2 +7F9366 +BA7BDE +A7CB23 +AD1AB5 +97D46E +D078F2 +283EDB +DDF3D4 +80D606 +2C605D +1D4C5E +8BA389 +151DE9 +1FDD36 +F36381 +1C2401 +89EDE0 +41D9FF +7DCE7A +D1172C +417D1C +F2CE08 +C425C8 +EF366A +7DF9A1 +3DAB9B +0B71D2 +CC6283 +112F89 +E56ABB +DF68E7 +DEA16C +0BD29B +2DE9F2 +E8C0CD +267497 +75E8DE +6C9873 +2BAEC8 +08D5B0 +DA842E +C271CB +9964DE +1EFF1A +6EC822 +ADBAF4 +8083D0 +500C45 +A60B8C +AFFDA7 +55FDF9 +94D74A +3218EB +ECE10B +94B4DD +1A2604 +9956BA +6EC28C +4AB70C +BFCBCF +4C9F7F +695B0D +3C2B0D +61870A +FA1D0E +95A96C +B9A4AC +6FF2A0 +0EE290 +B77272 +A34CAE +E1DC59 +5D57F2 +B5B26E +A373F5 +BA1E75 +FC1820 +0C30A5 +16B4FB +C52034 +D50A5A +8B164E +A51BEF +93710A +B67FB8 +017F95 +998507 +08BA9E +D55889 +583953 +31F061 +9FE37D +D3C7B9 +87EFE3 +72910B +979AC8 +1014E9 +9261BC +ECBEE8 +2ECB74 +632638 +7EABDA +9E1394 +B62639 +064BC7 +38E5DE +1C5823 +ED7CFF +6EE4AC +090670 +6D257C +9FAA5A +335E86 +607254 +1DB011 +47670F +53F2A4 +845EA3 +3A0316 +9BC481 +C65ED4 +FD127A +8E8A66 +2A2180 +5F79C3 +943DC4 +A3FC03 +1ED2FB +C43E81 +183B0E +B8BEA8 +C74B5C +6805FF +7AE561 +F81527 +114261 +C4EF73 +DFC053 +BA1AB3 +224B8D +53130E +7BC34A +2C1747 +AF1339 +75925C +61E639 +DF6625 +562456 +C0D14C +C131C6 +02F29D +140E63 +AB04D8 +C89492 +4352C4 +BE3714 +879198 +957D32 +5FC9AE +80393D +D4B98E +77F78A +4984CE +FE2E43 +1FDE8F +6AA5C7 +6289E5 +0F865F +BB6186 +65C2AD +003A00 +01A88C +F00D21 +DC77F6 +5BEDF2 +D789A2 +CB7BA0 +ED4D67 +405212 +D7D35D +63B71D +BD3212 +47EB83 +82E9BE +391D0D +F37533 +0F5220 +52B241 +2FB03D +BEBEDA +7B1BA7 +92E392 +D9EF6C +05E1F3 +86F650 +881E43 +B8BB31 +9425E9 +D78FC9 +F7CBF5 +B6293C +17CC28 +4DE34C +D83275 +4BE397 +9FBCC4 +6D0501 +56F748 +435345 +3A204F +6DAAE4 +5D1A90 +18AB49 +D92637 +13AAF0 +CD6917 +5BF0FD +9A9DD5 +6A25D6 +50EA5C +A41ED5 +147A47 +917F57 +A25686 +7CACD7 +72ED20 +3C3076 +567CD7 +FEF8BA +65BC27 +5A737E +412EDD +D079BF +D2BB3F +80B4EE +B4894E +93C516 +80C4A1 +33589A +C13B48 +2A82FA +733022 +B4F267 +94D98E +9CF50F +6BA6FE +62063D +D6272A +077BE8 +EFF9A3 +A311D7 +E0B584 +44B83F +40AE2E +5A312F +5BD250 +99A297 +9628E8 +12F243 +A03D50 +D3E32F +4A1852 +9E3AE0 +47EE56 +8FA935 +ABD600 +FCC12F +6C23B2 +885DD3 +A868DB +EC1583 +1EEE36 +4665FA +016DC2 +51F3E1 +5BFD6A +3DBB54 +DA762E +02F241 +B42E7B +91B2DF +E90DEF +BDE9A7 +4A4E17 +C6B710 +28D972 +B30F29 +A272BC +3A9F46 +1EC3AC +5A5E35 +AE07E3 +765F2A +6660C1 +A1F02E +9B16E7 +FAB52D +F13386 +2098AF +3057A8 +66723E +EA9AE2 +26A368 +2CD411 +199792 +95D6CC +27D424 +D27DCE +AEE074 +FF201A +9DD38A +95CC94 +BC08E9 +17ABFB +C12F8A +1A0397 +98503A +790FF6 +0A0EB8 +03E353 +0CD633 +CDFC31 +B0C58A +C74899 +EC1A19 +F343C0 +03548A +49FA39 +9DDC03 +A873C1 +8C29D1 +EC1F27 +11E2F5 +7FAD57 +35C3EC +3FE440 +8B385D +AAC4D2 +F2DE44 +9F0974 +7D65EB +F6B296 +9F0942 +2E6B15 +79337C +3C2986 +34E38E +F141B3 +8AA645 +4B836C +59733B +13D1AB +261E16 +C91FF9 +18E466 +A2DEBC +1E22C1 +CFEC69 +4CE7E2 +375001 +CB7D74 +4671D4 +440A58 diff --git a/scripts/fidelity-app/tools/tab-motion/tabmotion.py b/scripts/fidelity-app/tools/tab-motion/tabmotion.py new file mode 100755 index 00000000000..b1e62bf7a7a --- /dev/null +++ b/scripts/fidelity-app/tools/tab-motion/tabmotion.py @@ -0,0 +1,1120 @@ +#!/usr/bin/env python3 +"""Turns a native tab-motion probe capture into Codename One's model of it. + +The capture comes from scripts/probe-ios-tab-motion.sh (an instrumented +UITabBarController on the simulator, see +scripts/fidelity-app/ios-native-ref/motion-probe/README.md). Subcommands: + + fit LOG... -o templates.json + Aligns every clean tap-driven selection in the logs and averages the motion + channels at 60 Hz, the deformation as E(t) + distance * S(t). + java templates.json TabGlassMotion.java + Rewrites the tables TabGlassMotion still keeps (the deformation and the + whole-bar pulse); the other channels are closed forms (MotionForms). + fixture LOG... -o native-ios27.csv + Exports the per-frame native channels TabGlassMotionTest checks the Java + model against. + check templates.json LOG... + Replays the model -- closed forms plus the kept tables -- against every tap + on the vsync clock, prints the worst errors, and fails if a closed form is + off by more than TabGlassMotionTest allows. + material SHOT_DIR + Fits the bar's glass material (GlassRecipe) from the lossless screenshots. + +Gestures (holds and drags, see TabGlassGesture): + + gesture --hold HOLD.log --drags LOG... --settle LOG... -o TabGlassGesture.java + Fits the release wobble (from the quick tap in the hold log) and the scrub + deformation kernels (from every drag log) and rewrites the tables between + the "generated tables (tabmotion.py gesture)" markers, then replays the + settle after a scrub (the tap's travel spring, a closed form) over the + episodes of the --settle logs -- the ones gesture-fixture exports, whose + rest positions are the tabs -- and fails if it is off by more than its + tolerance. + gesture-fixture LOG... -o native-gestures.csv + Exports the per-frame native episodes TabGlassGestureTest replays. + springs --press LOG... --drag LOG + Fits the bar press spring and the scrub follow spring (the hand-written + constants at the top of TabGlassGesture). + +Vibrancy (the colour matrix of vibrant content, see VibrancyMatrix): + + vibrancy --dark LOG... --light LOG... + Holds every matrix in PROBE_TINTSWEEP logs to the closed forms VibrancyMatrix + implements, and fails if any is off by more than the log's precision. + vibrancy-fixture --dark LOG --light LOG --grey-dark LOG --grey-light LOG -o native-vibrancy.csv + Exports the measured matrices VibrancyMatrixTest checks the Java against. + +Needs numpy and scipy (and Pillow for `material`). +""" +import argparse +import json +import re +import sys + +import numpy as np + +FPS = 60.0 +N_SAMPLES = 100 +LIFT_PT = 16.0 +REST_LENS_H = 54.0 +RELEASE_PT = 3.5 +LIFT_SNAP = 0.975 + +def read_text(path, newline=None): + with open(path, newline=newline) as f: + return f.read() + + +def write_text(path, text, newline=None): + with open(path, "w", newline=newline) as f: + f.write(text) + + +def read_json(path): + with open(path) as f: + return json.load(f) + + +def write_json(obj, path): + with open(path, "w") as f: + json.dump(obj, f) + + +# --------------------------------------------------------------------------- log + +LRE = re.compile(r"L (\d+) d=(\d+) (\S+) win=\(([^)]*)\) b=\(([^)]*)\) pos=\(([^)]*)\) " + r"T=\(([^)]*)\) op=(\S+) hid=(\d) cr=(\S+) bg=(\S+) f=(.*)") + + +def load_log(path): + frames, touches, cur = [], [], None + for line in read_text(path).splitlines(True): + if line.startswith("FRAME"): + cur = {"t": float(line.split()[1][2:]), "L": {}} + frames.append(cur) + elif line.startswith("L ") and cur is not None: + m = LRE.match(line.strip()) + if not m: + continue + f = lambda s: [float(x) for x in s.split(",")] + i = int(m.group(1)) + cur["L"][i] = dict(id=i, d=int(m.group(2)), cls=m.group(3), win=f(m.group(4)), b=f(m.group(5)), + pos=f(m.group(6)), T=f(m.group(7)), op=float(m.group(8)), bg=m.group(11)) + elif line.startswith("TOUCH"): + touches.append({k: float(v) for k, v in (kv.split("=") for kv in line.split()[1:])}) + return frames, touches + + +def by_class(layers, cls): + return [l for l in sorted(layers.values(), key=lambda l: l["id"]) if l["cls"] == cls] + + +def extract_taps(path, dur=2.0): + """One record per tap: per-frame channels, aligned so t=0 is the last frame at rest.""" + frames, touches = load_log(path) + downs = [t for t in touches if t["phase"] == 0] + ups = [t for t in touches if t["phase"] == 3] + taps = [] + for k, d in enumerate(downs): + rows = [] + for f in frames: + if not (d["t"] - 0.02 <= f["t"] < d["t"] + dur): + continue + L = f["L"] + lens = (by_class(L, "_UILiquidLensView") or [None])[0] + plat = (by_class(L, "_UITabSelectionView") or [None])[0] + btns = by_class(L, "_UITabButton") + btn = max(btns, key=lambda l: abs(l["T"][0] - 1)) if btns else None + bars = by_class(L, "_UITabBarItemPlatterView") + bar = max(bars, key=lambda l: abs(l["T"][0] - 1)) if bars else None + glow = (by_class(L, "LittleGlowView") or [None])[0] + gc = [l for l in L.values() if l["cls"] == "UIView" and l["d"] == 3 and l["bg"] == "(1.000,1.000)"] + if lens is None: + continue + r = dict(t=f["t"], lx=lens["win"][0] + lens["win"][2] / 2, lw=lens["b"][0], lh=lens["b"][1], + sx=lens["T"][0], sy=lens["T"][3], tx=lens["T"][4], + platOp=plat["op"] if plat else np.nan, cs=btn["T"][0] if btn else np.nan, + bs=bar["T"][0] if bar else 1.0, bw=bar["b"][0] if bar else np.nan, + gs=glow["T"][0] if glow else np.nan, gop=glow["op"] if glow else 0.0, + gcop=gc[0]["op"] if gc else 0.0) + # Travel from the lens layer's position in its parent: the window frame + # also moves with the transform's tx, and on the first frame after a + # re-parent it is briefly reported at the window origin. + r["px"] = lens["pos"][0] + rows.append(r) + if not rows: + continue + cols = {key: np.array([r[key] for r in rows], dtype=float) for key in rows[0]} + a, b = cols["px"][0], cols["px"][-1] + if abs(b - a) < 1: + continue + p = (cols["px"] - a) / (b - a) + i0 = int(np.argmax(p > 0.001)) + t0 = cols["t"][max(i0 - 1, 0)] + up = ups[k]["t"] - d["t"] if k < len(ups) else None + taps.append(dict(src=path, k=k, D=b - a, t=cols["t"] - t0, p=p, c=cols, up=up, + dt=np.diff(cols["t"][:12]) * 1000)) + return taps + + +def is_clean(tap): + """The taps the model is fitted on: not the first of a run (it lags a frame), + a quick ~66 ms press (a longer one grows the bar pulse), no frame hitch at the + start, and the fast lift every 3-tab tap plays (see TabGlassMotion).""" + if tap["k"] == 0 or tap["up"] is None or tap["up"] > 0.075: + return False + if tap["dt"].max() > 25: + return False + return tap["c"]["lh"][:18].max() >= REST_LENS_H + LIFT_PT - 0.1 + + +# --------------------------------------------------------------------------- fit + +def grid(): + return np.arange(N_SAMPLES) / FPS + + +def rs(t, v, g): + return np.interp(g, t, v) + + +def fit(taps): + g = grid() + A = np.array([abs(tp["D"]) for tp in taps]) + X = np.vstack([np.ones_like(A), A]).T + + def lin(f): + Y = np.array([rs(tp["t"], f(tp), g) for tp in taps]) + c = np.linalg.lstsq(X, Y, rcond=None)[0] + return c[0], c[1] + + out = {"P": np.mean([rs(tp["t"], tp["p"], g) for tp in taps], 0)} + out["SXE"], out["SXS"] = lin(lambda tp: tp["c"]["sx"] - 1) + out["SYE"], out["SYS"] = lin(lambda tp: tp["c"]["sy"] - 1) + out["TXE"], out["TXS"] = lin(lambda tp: tp["c"]["tx"] * np.sign(tp["D"])) + pre, fall, gop = [], [], [] + for tp in taps: + t, L, op = tp["t"], (tp["c"]["lh"] - REST_LENS_H) / LIFT_PT, tp["c"]["platOp"] + ipk = int(np.argmax(L >= L.max() - 1e-6)) + irel = ipk + int(np.argmax(L[ipk:] < L.max() - 0.01)) - 1 + pre.append(rs(t[:irel + 1], 1 - op[:irel + 1], g[g <= t[irel]])) + s = t[irel:] - t[irel] + fall.append(rs(s, L[irel:], g)) + gop.append(rs(s, op[irel:], g)) + m = min(len(x) for x in pre) + U = np.mean([x[:m] for x in pre], 0) + while len(U) < N_SAMPLES: + r = (1 - U[-1]) / (1 - U[-2]) + U = np.append(U, 1 - (1 - U[-1]) * r) + out["U"] = U + # a tap whose first falling frame was dropped would shift the curve by a frame + keep = [i for i, f in enumerate(fall) if abs(f[1] - 0.933) < 0.02] + out["LF"] = np.mean([fall[i] for i in keep], 0) + out["GOP"] = np.mean([gop[i] for i in keep], 0) + out["BAR"] = np.mean([rs(tp["t"], (tp["c"]["bs"] - 1) * tp["c"]["bw"][-1], g) for tp in taps], 0) + def held(v): + # The glow layer is removed once it has faded; hold its last scale. + v = np.array(v, dtype=float) + last = 1.0 + for i in range(len(v)): + if np.isnan(v[i]): + v[i] = last + else: + last = v[i] + return v + + out["GLOWS"] = np.mean([rs(tp["t"], held(tp["c"]["gs"]), g) for tp in taps], 0) + out["GLOWOP"] = np.mean([rs(tp["t"], tp["c"]["gop"], g) for tp in taps], 0) + out["GLOWC"] = np.mean([rs(tp["t"], tp["c"]["gcop"], g) for tp in taps], 0) + return {k: [float(x) for x in v] for k, v in out.items()} + + +# --------------------------------------------------------------------------- closed forms + +def spring_displacement(omega, zeta, x0, v0, t): + """DampedSpring.displacement: free response of a damped spring resting at 0.""" + if t <= 0: + return x0 + decay = np.exp(-zeta * omega * t) + if zeta < 1: + wd = omega * np.sqrt(1 - zeta * zeta) + b = (v0 + zeta * omega * x0) / wd + return decay * (x0 * np.cos(wd * t) + b * np.sin(wd * t)) + if zeta == 1: + return decay * (x0 + (v0 + omega * x0) * t) + s = omega * np.sqrt(zeta * zeta - 1) + r1, r2 = -zeta * omega + s, -zeta * omega - s + c2 = (v0 - r1 * x0) / (r2 - r1) + return (x0 - c2) * np.exp(r1 * t) + c2 * np.exp(r2 * t) + + +def spring_velocity(omega, zeta, x0, v0, t, h=1e-7): + return (spring_displacement(omega, zeta, x0, v0, t + h) - spring_displacement(omega, zeta, x0, v0, t - h)) / (2 * h) + + +class MotionForms: + """The closed forms TabGlassMotion implements (same constants, same names). They + were found in these captures on the VSYNC clock: every frame renders at exactly + start + n/60 s, while the logged frame times carry the main thread's jitter.""" + START_S = 0.00226 + POSITION_OMEGA, POSITION_ZETA, SETTLE_PT = 2 * np.pi / 0.4, 0.85, 0.195 + LIFT_OMEGA, LIFT_FALL_S = 2 * np.pi / 0.25, 13.5 / FPS + PLATTER_OMEGA, PLATTER_RETURN_S = 2 * np.pi / 0.4, 28.5 / FPS + GLOW_RISE_OMEGA, GLOW_FALL_OMEGA, GLOW_PEAK, GLOW_UP_S = 2 * np.pi / 0.1, 2 * np.pi / 0.5, 0.8445, 4 / FPS + GLOW_MASK, GLOW_HIDE, GLOW_GROWTH = 0.33675, 0.005, 3.0 + + def position(self, t): + return 1 + spring_displacement(self.POSITION_OMEGA, self.POSITION_ZETA, -1, 0, t - self.START_S) + + def settled_position(self, t, d): + p = self.position(t) + peak = self.START_S + np.pi / (self.POSITION_OMEGA * np.sqrt(1 - self.POSITION_ZETA ** 2)) + return 1.0 if t >= peak and d * abs(p - 1) < self.SETTLE_PT else p + + def lift_rise(self, t): + return 1 + spring_displacement(self.LIFT_OMEGA, 1, -1, 0, t - self.START_S) + + def lift_fall(self, s): + return 0.0 if s >= self.LIFT_FALL_S else spring_displacement(self.LIFT_OMEGA, 1, 1, 0, s) + + def platter_return(self, s, release): + if s >= self.PLATTER_RETURN_S: + return 1.0 + v0 = -spring_velocity(self.LIFT_OMEGA, 1, -1, 0, release - self.START_S) + return 1 + spring_displacement(self.PLATTER_OMEGA, 1, -self.lift_rise(release), v0, s) + + def glow_layer(self, t): + if t < self.GLOW_UP_S: + return self.GLOW_PEAK + spring_displacement(self.GLOW_RISE_OMEGA, 1, -self.GLOW_PEAK, 0, t - self.START_S) + up = self.GLOW_UP_S - self.START_S + x0 = self.GLOW_PEAK + spring_displacement(self.GLOW_RISE_OMEGA, 1, -self.GLOW_PEAK, 0, up) + v0 = spring_velocity(self.GLOW_RISE_OMEGA, 1, -self.GLOW_PEAK, 0, up) + v = spring_displacement(self.GLOW_FALL_OMEGA, 1, x0, v0, t - self.GLOW_UP_S) + return 0.0 if v < self.GLOW_HIDE else v + + def glow_opacity(self, t): + layer = self.glow_layer(t) + mask = self.GLOW_MASK * layer + return 0.0 if t >= self.GLOW_UP_S and mask < self.GLOW_HIDE else mask * layer + + def glow_scale(self, t): + return 1 + self.GLOW_GROWTH * (1 + spring_displacement(self.GLOW_FALL_OMEGA, 1, -1, 0, t - self.GLOW_UP_S)) + + +FORMS = MotionForms() +# What TabGlassMotionTest allows the closed forms (pt, pt, opacity); the glow is +# checked against the raw frames here since the fixture does not carry it. +FORM_TOLERANCE = dict(pos=0.9, lift=0.15, plat=0.01, glow_scale=0.001, glow_op=0.005) + + +# --------------------------------------------------------------------------- model + +class Model: + def __init__(self, tpl): + self.T = tpl + + def samp(self, name, t): + a = self.T[name] + x = t * FPS + if x <= 0: + return a[0] + i = int(x) + if i >= len(a) - 1: + return a[-1] + f = x - i + return a[i] * (1 - f) + a[i + 1] * f + + def release(self, D): + # The first falling frame is the first one within RELEASE_PT of the target, + # but never before the frame after the lift has snapped to full. + snap = next(i for i in range(1000) if FORMS.lift_rise(i / FPS) >= LIFT_SNAP) + for i in range(1, 2 * int(FPS)): + if abs(D) * (1 - FORMS.position(i / FPS)) < RELEASE_PT: + return (max(i, snap + 1) - 1) / FPS + return 2.0 + + def frame(self, t, D): + ad, sg = abs(D), (1 if D >= 0 else -1) + tr = self.release(D) + if t < tr: + u = FORMS.lift_rise(t) + L, op = (1.0 if u >= LIFT_SNAP else u), 1 - u + else: + L, op = FORMS.lift_fall(t - tr), FORMS.platter_return(t - tr, tr) + return dict(p=FORMS.settled_position(t, ad), L=L, platOp=op, cs=1 + 0.16 * L, + sx=1 + self.samp("SXE", t) + ad * self.samp("SXS", t), + sy=1 + self.samp("SYE", t) + ad * self.samp("SYS", t), + tx=sg * (self.samp("TXE", t) + ad * self.samp("TXS", t)), + bar=self.samp("BAR", t)) + + +def check(tpl, taps): + """Every tap on the vsync clock (round(t * 60) / 60, see MotionForms). The closed + forms are held to FORM_TOLERANCE on the clean taps -- the ones with the fast + lift the model describes -- and the run fails if one is exceeded.""" + m = Model(tpl) + worst = {} + for tp in taps: + c, D, err = tp["c"], tp["D"], {} + rest_w = c["lw"][-1] + glow_scale = np.array(c["gs"], dtype=float) + last = 1.0 + for i in range(len(glow_scale)): + if np.isnan(glow_scale[i]): + glow_scale[i] = last # the glow layer is removed once it has faded + else: + last = glow_scale[i] + for i, t in enumerate(tp["t"]): + if t < 0: + continue + t = round(t * FPS) / FPS + f = m.frame(t, D) + e = dict(pos=abs(f["p"] - tp["p"][i]) * abs(D), + lift=abs(LIFT_PT * f["L"] - (c["lh"][i] - REST_LENS_H)), + w=abs((rest_w + LIFT_PT * f["L"]) * f["sx"] - c["lw"][i] * c["sx"][i]), + h=abs((REST_LENS_H + LIFT_PT * f["L"]) * f["sy"] - c["lh"][i] * c["sy"][i]), + lead=abs(f["tx"] - c["tx"][i]), plat=abs(f["platOp"] - c["platOp"][i]), + glow_scale=abs(FORMS.glow_scale(t) - glow_scale[i]), + glow_op=abs(FORMS.glow_opacity(t) - c["gop"][i] * c["gcop"][i])) + for key, v in e.items(): + err[key] = max(err.get(key, 0), v) + print("%-40s tap %d D=%7.1f clean=%s " % (tp["src"][-40:], tp["k"], D, is_clean(tp)) + + " ".join("%s=%.3f" % kv for kv in err.items())) + if is_clean(tp): + for key in FORM_TOLERANCE: + worst[key] = max(worst.get(key, 0), err[key]) + print("closed forms, worst over the clean taps: " + + " ".join("%s=%.4f (<=%g)" % (k, worst.get(k, 0), FORM_TOLERANCE[k]) for k in FORM_TOLERANCE)) + bad = [k for k in FORM_TOLERANCE if worst.get(k, 0) > FORM_TOLERANCE[k]] + if bad: + sys.exit("closed forms no longer match the capture: " + ", ".join(bad)) + + +# --------------------------------------------------------------------------- java + +# The channels TabGlassMotion still keeps as tables; the others are MotionForms. +TABLES = [("SXE", "STRETCH_X", "Lens scaleX - 1, distance-independent part."), + ("SXS", "STRETCH_X_PER_PT", "Lens scaleX - 1 per point of travel."), + ("SYE", "STRETCH_Y", "Lens scaleY - 1, distance-independent part."), + ("SYS", "STRETCH_Y_PER_PT", "Lens scaleY - 1 per point of travel."), + ("TXE", "LEAD_PT", "Lens centre lead in points along the travel direction, distance-independent part."), + ("TXS", "LEAD_PER_PT", "Lens centre lead per point of travel."), + ("BAR", "BAR_GROW_PT", "Whole-bar width growth in points; the bar scales uniformly by (w + grow) / w.")] +REST = {"SXE": 0, "SXS": 0, "SYE": 0, "SYS": 0, "TXE": 0, "TXS": 0, "BAR": 0} +PER_PT = ("SXS", "SYS", "TXS") + + +def java_tables(tpl): + out = [] + for key, name, doc in TABLES: + v, r = list(tpl[key]), REST[key] + # per-point tables are multiplied by up to ~260 pt of travel + thr = 1e-6 if key in PER_PT else 2e-4 + last = max([i for i, x in enumerate(v) if abs(x - r) > thr] + [0]) + 1 + v = v[:last + 1] + v[-1] = r + dec = 7 if key in PER_PT else 4 + vals = [("%." + str(dec) + "f") % x + "f" for x in v] + vals = [s.replace("-0." + "0" * dec + "f", "0." + "0" * dec + "f") for s in vals] + lines, cur = [], " " + for s in vals: + if len(cur) + len(s) + 2 > 100: + lines.append(cur.rstrip()) + cur = " " + cur += " " + s + "," + lines.append(cur.rstrip().rstrip(",")) + out.append(" /// %s\n private static final float[] %s = {\n%s\n };\n" % (doc, name, "\n".join(lines))) + return "\n".join(out) + "\n" + + +def write_java(tpl, path): + s = read_text(path) + a = s.index(" /// " + TABLES[0][2]) + b = s.index(" // ---- outputs ----") + write_text(path, s[:a] + java_tables(tpl) + s[b:]) + + +# --------------------------------------------------------------------------- fixture + +def fixture(taps, path): + rows = ["# Native iOS 27 UITabBarController selection motion, one row per display frame.", + "# Captured by scripts/probe-ios-tab-motion.sh and exported by", + "# scripts/fidelity-app/tools/tab-motion/tabmotion.py fixture.", + "# set,tap,travelPt,liftRegime,tSec,position,liftPt,scaleX,scaleY,leadPt,platterOpacity,contentScale,barGrowPt", + "# Every tap is a quick ~66 ms touch: a longer press grows the bar pulse further.", + "# liftRegime: fast = the lift curve every 3-tab tap uses; slow = the slower rise UIKit", + "# sometimes plays on the 5-tab bar (position/deformation still hold, lift is not asserted)."] + for tp in taps: + c = tp["c"] + regime = "fast" if c["lh"][:18].max() >= REST_LENS_H + LIFT_PT - 0.1 else "slow" + name = re.sub(r"\.log$", "", tp["src"].split("/")[-1]) + for i, t in enumerate(tp["t"]): + if t < 0 or t > 1.7: + continue + rows.append("%s,%d,%.2f,%s,%.4f,%.5f,%.3f,%.4f,%.4f,%.3f,%.4f,%.4f,%.3f" % ( + name, tp["k"], tp["D"], regime, t, tp["p"][i], c["lh"][i] - REST_LENS_H, c["sx"][i], c["sy"][i], + c["tx"][i], c["platOp"][i], c["cs"][i], (c["bs"][i] - 1) * c["bw"][-1])) + write_text(path, "\n".join(rows) + "\n") + + +# --------------------------------------------------------------------------- material + +def material(shot_dir): + from PIL import Image + from scipy.ndimage import gaussian_filter + from scipy.optimize import least_squares + import os + backdrop_png = os.path.join(os.path.dirname(__file__), "..", "..", "common", "src", "main", "resources", + "glass-backdrop.png") + + def backdrop(kind, F): + H, W, _ = F.shape + s = W / 393.0 + if kind == "photo": + with Image.open(backdrop_png) as im: + return np.array(im.convert("RGB").resize((W, H), Image.BICUBIC)).astype(float) + if kind == "grey": + return np.full_like(F, 128.0) + B = np.zeros_like(F) + for k in range(0, int(852 / 23) + 1): + y0, y1 = int(round(23 * k * s)), int(round(23 * (k + 1) * s)) + yc = min(H - 1, (y0 + y1) // 2) + B[y0:y1] = np.median(F[yc - 5:yc + 5, int(6 * s):int(18 * s)].reshape(-1, 3), 0) + for x in np.arange(0, 393, 20): + B[:, int(round(x * s)):int(round((x + 1) * s))] = F[int(705 * s), int(round(x * s)) + 1] + return B + + for appearance in ("light", "dark"): + data = [] + # The flat grey pins the transfer's absolute level exactly (the native bar + # and the committed golden agree there to the level); the bands pin the + # blur and the photo the colour response. + for kind in ("stripes", "photo", "grey"): + with Image.open(os.path.join(shot_dir, "live-%s-%s.png" % (appearance, kind))) as im: + F = np.array(im.convert("RGB")).astype(float) + s = F.shape[1] / 393.0 + regs = [(slice(int(781 * s), int(823 * s)), slice(int(x0 * s), int(x1 * s))) + for x0, x1 in ((236, 254), (298, 324))] + data.append((F, backdrop(kind, F), regs)) + best = None + for sig in (16, 24, 32, 40, 48): + M, Y = [], [] + for F, B, regs in data: + Bb = np.stack([gaussian_filter(B[..., c], sig) for c in range(3)], -1) + M += [Bb[r].reshape(-1, 3) for r in regs] + Y += [F[r].reshape(-1, 3) for r in regs] + M, Y = np.concatenate(M), np.concatenate(Y) + lum = (M @ [0.2126, 0.7152, 0.0722])[:, None] + res = least_squares(lambda p: (np.clip((lum + (M - lum) * p[0]) * p[1] + p[2], 0, 255) - Y).ravel(), + [1.8, 0.5, 60]) + rms = float(np.sqrt(np.mean(res.fun ** 2))) + if best is None or rms < best[0]: + best = (rms, sig, res.x) + # Re-anchor the offset so flat grey lands exactly on the native level: that + # one level is measured directly, the rest of the fit is a compromise. + F = data[2][0] + s = F.shape[1] / 393.0 + grey_level = F[int(795 * s):int(800 * s), int(240 * s):int(250 * s)].mean() + anchored = grey_level - 128.0 * best[2][1] + print("%s: blur %d px, sat %.3f scale %.3f offset %.1f (rms %.2f/255); grey-anchored offset %.1f" + % (appearance, best[1], best[2][0], best[2][1], best[2][2], best[0], anchored)) + + +# --------------------------------------------------------------------------- gestures + +# The springs TabGlassGesture runs (see `springs`): the lens follows the finger on +# FOLLOW, the whole bar grows on the press spring, both TOUCH_LAG behind the touch. +FOLLOW_OMEGA = 32.0 +FOLLOW_ZETA = 0.903 +TOUCH_LAG = 0.02 +# The release wobble starts WOBBLE_START after touch-up; WOBBLE_N frames are fitted. +WOBBLE_START = 0.90 +WOBBLE_N = 45 +# Scrub deformation kernels: FIR_TAPS frames of delay, ridge-regularized. +FIR_TAPS = 60 +FIR_RIDGE = 1e-2 +# The settle after a scrub is a closed form (the tap's travel spring, FORMS, started +# from the scrub's state); `gesture` replays it exactly as TabGlassGesture does over +# the fixture's scrubs. The tolerance is what it reaches on the committed captures, +# plus 10%. +SETTLE_TOLERANCE = {"rms": 0.38, "max": 3.8} +STEP = 1 / 240. +GESTURE_BEGIN = " // ---- generated tables (tabmotion.py gesture) ----\n" +TABLES_END = " // ---- end of generated tables ----\n" + + +def lens_series(path): + """The whole log as one per-frame array (every frame with a lens), and its touches. + + Columns: t, lens x (position in its parent), lift pt, lens width, scaleX, scaleY, + tx, platter opacity, bar scale, content scale.""" + frames, touches = load_log(path) + rows = [] + for f in frames: + L = f["L"] + lens = (by_class(L, "_UILiquidLensView") or [None])[0] + plat = (by_class(L, "_UITabSelectionView") or [None])[0] + bars = by_class(L, "_UITabBarItemPlatterView") + bar = max(bars, key=lambda l: abs(l["T"][0] - 1)) if bars else None + btns = by_class(L, "_UITabButton") + btn = max(btns, key=lambda l: abs(l["T"][0] - 1)) if btns else None + if not lens: + continue + rows.append((f["t"], lens["pos"][0], lens["b"][1] - REST_LENS_H, lens["b"][0], lens["T"][0], lens["T"][3], + lens["T"][4], plat["op"] if plat else np.nan, bar["T"][0] if bar else 1, + btn["T"][0] if btn else 1)) + return np.array(rows), touches + + +def presses(touches): + """(down, up) touch records of every press, in order.""" + downs = [t for t in touches if t["phase"] == 0] + ups = [t for t in touches if t["phase"] == 3] + return list(zip(downs, ups)) + + +def is_scrub(touches, d, u): + return any(t["phase"] == 1 and d["t"] < t["t"] < u["t"] for t in touches) + + +class Finger: + """The finger's x at any time: the last touch event at or before it.""" + + def __init__(self, touches): + self.t = np.array([q["t"] for q in touches]) + self.x = np.array([q["x"] for q in touches]) + + def __call__(self, t): + i = int(np.searchsorted(self.t, t, side="right")) - 1 + return None if i < 0 else self.x[i] + + +def spring(times, target, omega, zeta, x0=0.0, v0=0.0, dt=1 / 600.): + """x'' = omega^2 (target(t) - x) - 2 zeta omega x', stepped at dt, sampled at times.""" + tt = np.arange(times[0], times[-1] + dt, dt) + xs = np.empty(len(tt)) + x, v = x0, v0 + for i, t in enumerate(tt): + v += (omega * omega * (target(t) - x) - 2 * zeta * omega * v) * dt + x += v * dt + xs[i] = x + return np.interp(times, tt, xs) + + +def tab_centres(t, x): + """Lens positions held for 0.3 s (the tabs it rested on), near-duplicates merged.""" + rest = [] + for i in range(len(t)): + j = np.searchsorted(t, t[i] + 0.3) + if j < len(t) and np.ptp(x[i:j]) < 0.05: + rest.append(x[i]) + cc = [] + for c in sorted(rest): + if not cc or c - cc[-1] > 5: + cc.append(c) + return cc + + +def finger_offset(path): + """Finger (window x) minus lens target (lens-parent x), fitted with the follow + spring over every press of a drag log, and rounded to 0.01 pt. None for a log + with no drag.""" + from scipy.optimize import least_squares + R, T = lens_series(path) + t, x = R[:, 0], R[:, 1] + finger = Finger(T) + ps = [(d["t"], u["t"]) for d, u in presses(T) if is_scrub(T, d, u)] + if not ps: + return None + + def resid(p): + off, out = p[0], [] + for d, u in ps: + m = (t >= d) & (t <= u + 0.02) + if m.sum() < 5: + continue + x0 = x[m][0] + out.append(spring(t[m], lambda tt: (finger(tt - TOUCH_LAG) - off) if tt - TOUCH_LAG >= d else x0, + FOLLOW_OMEGA, FOLLOW_ZETA, x0=x0) - x[m]) + return np.concatenate(out) + + # start from the finger-to-lens gap once each press has settled on the finger + guess = np.mean([finger(u) - np.interp(u, t, x) for d, u in ps]) + r = least_squares(resid, [guess]) + off = round(float(r.x[0]), 2) + print("%s: finger offset %.2f pt (follow rms %.2f, max %.2f pt)" + % (path, off, np.sqrt(np.mean(r.fun ** 2)), np.abs(r.fun).max()), file=sys.stderr) + return off + + +def wobble(hold_log): + """The release wobble, read off the quickest press of the hold log (a tap on the + already selected tab, so nothing else moves), from WOBBLE_START after touch-up.""" + R, T = lens_series(hold_log) + t = R[:, 0] + ps = presses(T) + k = int(np.argmin([u["t"] - d["t"] for d, u in ps])) + G = WOBBLE_START + np.arange(WOBBLE_N) / FPS + + def at(u): + return {n: np.round(np.interp(u + G, t, R[:, j] - (0 if n == "tx" else 1)), 5) + for j, n in ((4, "sx"), (5, "sy"), (6, "tx"))} + + W = at(ps[k][1]["t"]) + for i, (d, u) in enumerate(ps): + if i != k: + o = at(u["t"]) + print("wobble after the %.0f ms press vs the %.0f ms tap: max diff sx %.4f sy %.4f tx %.3f pt" + % ((u["t"] - d["t"]) * 1000, (ps[k][1]["t"] - ps[k][0]["t"]) * 1000, + *(np.abs(o[n] - W[n]).max() for n in ("sx", "sy", "tx"))), file=sys.stderr) + return W + + +def fixture_episodes(lines): + """The E/T/F rows of a gesture fixture as episodes (what TabGlassGestureTest loads).""" + eps = {} + for line in lines: + p = line.split(",") + i = int(p[1]) + if p[0] == "E": + eps[i] = dict(tag=p[2], fromPt=float(p[3]), toPt=float(p[4]), minPt=float(p[5]), maxPt=float(p[6]), + upS=float(p[7]), scrubS=float(p[8]), settlePt=float(p[9]), T=[], F=[]) + else: + eps[i]["T" if p[0] == "T" else "F"].append([float(x) for x in p[2:]]) + return eps + + +def scrub_centre(e, end): + """TabGlassGesture.scrubTrack: the lens centre at 60 Hz from the scrub start -- the + follow spring (stepped like the Java) until the release reaches the lens, then + the settle spring from that state, in closed form.""" + touches = e["T"] + + def target(s): + f = None + for q in touches: + if q[0] <= s - TOUCH_LAG: + f = q[1] + else: + break + return e["fromPt"] if f is None else min(max(f, e["minPt"]), e["maxPt"]) + + w, z = FOLLOW_OMEGA, FOLLOW_ZETA + handoff = e["upS"] + TOUCH_LAG + frames = max(1, int(np.ceil((end - e["scrubS"]) * FPS)) + 2) + out = np.empty(frames) + x, v, s, settling = e["fromPt"], 0.0, e["scrubS"], False + for i in range(frames): + fs = e["scrubS"] + i / FPS + if fs >= handoff: + if not settling: + while s < handoff: + v += (w * w * (target(s) - x) - 2 * z * w * v) * STEP + x += v * STEP + s += STEP + settling = True + out[i] = e["settlePt"] + spring_displacement(FORMS.POSITION_OMEGA, FORMS.POSITION_ZETA, + x - e["settlePt"], v, fs - handoff) + continue + for k in range(int(round(1 / (FPS * STEP)))): + if s >= fs: + break + v += (w * w * (target(s) - x) - 2 * z * w * v) * STEP + x += v * STEP + s += STEP + out[i] = x + return out + + +def settle_check(lines): + """The lens centre of every scrub in the fixture rows, replayed like + TabGlassGesture.at, against the captured frames.""" + errs = [] + for e in fixture_episodes(lines).values(): + if e["scrubS"] < 0: + continue + F = np.array([f for f in e["F"] if f[0] >= 0]) + tr = scrub_centre(e, F[-1, 0] + 2 / FPS) + for f in F: + if f[0] < e["scrubS"]: + centre = e["fromPt"] + else: + fx = (f[0] - e["scrubS"]) * FPS + n = min(int(fx), len(tr) - 2) + centre = tr[n] + (tr[n + 1] - tr[n]) * (fx - n) + errs.append(centre - f[1]) + errs = np.array(errs) + rms, worst = np.sqrt(np.mean(errs ** 2)), np.abs(errs).max() + print("settle (tap spring %.3f rad/s, zeta %.3f): %d frames, rms %.3f pt, max %.2f pt (tolerance %.2f / %.1f)" + % (FORMS.POSITION_OMEGA, FORMS.POSITION_ZETA, len(errs), rms, worst, SETTLE_TOLERANCE["rms"], + SETTLE_TOLERANCE["max"]), file=sys.stderr) + return rms <= SETTLE_TOLERANCE["rms"] and worst <= SETTLE_TOLERANCE["max"] + + +def scrub_kernels(drag_logs, W): + """The lens deformation while scrubbing, as FIR kernels over its own speed and + acceleration (the release wobble W taken out first). Every press of the drag logs + is used except one that starts on another tab (it plays a tap first).""" + WG = WOBBLE_START + np.arange(WOBBLE_N) / FPS + eps = [] + for path in drag_logs: + R, T = lens_series(path) + t = R[:, 0] + for d, u in presses(T): + d, u = d["t"], u["t"] + end = u + 0.85 + g = np.arange(d - 0.03, end, 1 / 240.) + m = (t >= d - 0.2) & (t <= end + 0.3) + X = np.interp(g, t[m], R[m, 1]) + early = (g >= d) & (g <= d + 0.25) + if np.ptp(X[early]) > 3: + print("scrub: skip %s %.0f ms press (tap phase first)" % (path, (u - d) * 1000), file=sys.stderr) + continue + G = np.arange(g[0], g[-1], 1 / FPS) + ys = {} + for j, n in ((4, "sx"), (5, "sy"), (6, "tx")): + y = np.interp(G, g, np.interp(g, t[m], R[m, j])) - (0 if n == "tx" else 1) + ys[n] = y - np.interp(G - u, WG, W[n], left=0, right=0) + eps.append((path, np.interp(G, g, X), ys)) + + def design(x, names): + V = np.gradient(x, 1 / FPS) + A = np.gradient(V, 1 / FPS) + F = {"|v|": np.abs(V) / 1000, "a*s": A * np.sign(V) / 10000, "v": V / 1000, "a": A / 10000} + cols = [] + for n in names: + f = F[n] + for k in range(FIR_TAPS): + cols.append(np.r_[np.zeros(min(k, len(f))), f[:max(len(f) - k, 0)]]) + return np.column_stack(cols) + + def ridge(D, Y): + X, Y = np.vstack(D), np.concatenate(Y) + return np.linalg.solve(X.T @ X + FIR_RIDGE * np.eye(X.shape[1]), X.T @ Y) + + out = {} + for n, names in (("sx", ["|v|", "a*s"]), ("sy", ["|v|", "a*s"]), ("tx", ["v", "a"])): + Ds = [design(x, names) for p, x, ys in eps] + Ys = [ys[n] for p, x, ys in eps] + worst = [] + for log in drag_logs: + tr = [i for i, e in enumerate(eps) if e[0] != log] + te = [i for i, e in enumerate(eps) if e[0] == log] + if not te: + continue + h = ridge([Ds[i] for i in tr], [Ys[i] for i in tr]) + worst.append(max(np.abs(Ds[i] @ h - Ys[i]).max() for i in te)) + h = ridge(Ds, Ys) + print("scrub %s: %d episodes, leave-one-log-out max error per log %s (peak %.3f)" + % (n, len(eps), np.round(worst, 3), max(np.abs(y).max() for y in Ys)), file=sys.stderr) + out[n] = (h[:FIR_TAPS], h[FIR_TAPS:]) + return out + + +def java_array(name, doc, vals, dec, private=False): + """A float[] table, 7 values per line, trailing values that print as zero dropped.""" + s = [("%." + str(dec) + "f") % v for v in vals] + s = [("0." + "0" * dec) if v == "-0." + "0" * dec else v for v in s] + while len(s) > 1 and float(s[-1]) == 0: + s.pop() + s = [v + "f" for v in s] + lines = [" " + ", ".join(s[i:i + 7]) + "," for i in range(0, len(s), 7)] + lines[-1] = lines[-1].rstrip(",") + return " /// %s\n %sstatic final float[] %s = {\n%s\n };\n" % ( + doc, "private " if private else "", name, "\n".join(lines)) + + +FIR_NOTE = """ // The deformation kernels below are free 60-tap fits. Folded onto velocity + // alone each channel is one smooth kernel, scaleY's that of scaleX inverted and + // about 1.5 times larger. Two damped modes shared by all channels (omega_n 6.0 + // and 15.5 rad/s, zeta 0.38 and 0.42) match their RMS but not their worst frame. +""" + + +def gesture_tables(W, K): + w = [("WOBBLE_X", "Release wobble, lens scaleX - 1, from WOBBLE_START_S after touch-up.", W["sx"], 5), + ("WOBBLE_Y", "Release wobble, lens scaleY - 1.", W["sy"], 5), + ("WOBBLE_TX", "Release wobble, lens centre offset in points (always towards +x).", W["tx"], 4)] + k = [("FIR_SX_SPEED", "Scrub deformation kernels: scaleX - 1 per |v|/1000 pt/s, per frame of delay.", + K["sx"][0], 6), + ("FIR_SX_ACCEL", "scaleX - 1 per (a * sign(v))/10000 pt/s^2.", K["sx"][1], 6), + ("FIR_SY_SPEED", "scaleY - 1 per |v|/1000 pt/s.", K["sy"][0], 6), + ("FIR_SY_ACCEL", "scaleY - 1 per (a * sign(v))/10000 pt/s^2.", K["sy"][1], 6), + ("FIR_TX_VEL", "Centre offset (pt) per v/1000 pt/s.", K["tx"][0], 5), + ("FIR_TX_ACCEL", "Centre offset (pt) per a/10000 pt/s^2.", K["tx"][1], 5)] + return ("\n".join(java_array(*x) for x in w) + "\n" + FIR_NOTE + + "\n".join(java_array(*x) for x in k) + "\n") + + +def splice(path, begin, block): + """Replaces what lies between the begin marker line and the end marker line.""" + s = read_text(path, newline="") + if begin not in s: + sys.exit("%s has no line %r: add the markers around the generated tables first" % (path, begin.strip())) + a = s.index(begin) + len(begin) + b = s.index(TABLES_END, a) + write_text(path, s[:a] + block + s[b:], newline="") + + +def gesture_rows(logs): + """One E(pisode) row per press, its T(ouch) rows (scrubs only) and F(rame) rows.""" + out, eid = [], 0 + for log in logs: + off = finger_offset(log) + tag = re.sub(r"-(light|dark)$", "", re.sub(r"\.log$", "", log.split("/")[-1])) + fr, T = load_log(log) + rows = [] + for f in fr: + L = f["L"] + lens = (by_class(L, "_UILiquidLensView") or [None])[0] + bars = by_class(L, "_UITabBarItemPlatterView") + bar = max(bars, key=lambda l: abs(l["T"][0] - 1)) if bars else None + if lens: + rows.append((f["t"], lens["pos"][0], (lens["b"][1] - REST_LENS_H) / LIFT_PT, lens["T"][0] - 1, + lens["T"][3] - 1, lens["T"][4], (bar["T"][0] - 1) * bar["b"][0] if bar else 0)) + R = np.array(rows) + cc = tab_centres(R[:, 0], R[:, 1]) + for d, u in presses(T): + t0 = d["t"] + seg = R[(R[:, 0] >= t0 - 0.02) & (R[:, 0] <= u["t"] + 1.7)] + x0 = np.interp(t0, R[:, 0], R[:, 1]) + moves = [x for x in T if x["phase"] == 1 and t0 < x["t"] < u["t"]] + if moves: + early = (seg[:, 0] >= t0) & (seg[:, 0] <= t0 + 0.25) + if np.ptp(seg[early, 1]) > 3: + continue # started on another tab: it plays a tap first + from_pt = to_pt = x0 + fing = [x for x in T if t0 <= x["t"] <= u["t"]] + fx = u["x"] - off + settle_pt = min(cc, key=lambda c: abs(c - fx)) + scrub_start = moves[0]["t"] - t0 + else: + from_pt, to_pt, fing, scrub_start = x0, seg[-1, 1], [], -1 + settle_pt = to_pt + out.append("E,%d,%s,%.3f,%.3f,%.3f,%.3f,%.4f,%.4f,%.3f" % ( + eid, tag, from_pt, to_pt, min(cc), max(cc), u["t"] - t0, scrub_start, settle_pt)) + for q in fing: + out.append("T,%d,%.4f,%.3f" % (eid, q["t"] - t0, q["x"] - off)) + for r in seg: + out.append("F,%d,%.4f,%.3f,%.4f,%.5f,%.5f,%.4f,%.3f" % (eid, r[0] - t0, *r[1:])) + eid += 1 + return out + + +def gesture_fixture(logs, path): + out = gesture_rows(logs) + write_text(path, "# kind,episode,... see TabGlassGestureTest\n" + "\n".join(out) + "\n") + print("%d episodes, %d lines" % (sum(1 for r in out if r.startswith("E,")), len(out)), file=sys.stderr) + + +def springs(press_logs, drag_log): + from scipy.optimize import least_squares + E = [] + for path in press_logs: + fr, T = load_log(path) + rows = [] + for f in fr: + bars = by_class(f["L"], "_UITabBarItemPlatterView") + if bars: + b = max(bars, key=lambda l: abs(l["T"][0] - 1)) + rows.append((f["t"], (b["T"][0] - 1) * b["b"][0])) + R = np.array(rows) + for d, u in presses(T): + d, u = d["t"], u["t"] + m = (R[:, 0] >= d - 0.05) & (R[:, 0] < u + 0.8) + if m.sum() > 10 and (u - d) < 3.5: + E.append((R[m, 0], R[m, 1], d, u)) + + def bar_resid(p): + w, z, amp, lag = p + return np.concatenate([spring(t, lambda tt: amp if d + lag <= tt < u + lag else 0.0, w, z) - g + for t, g, d, u in E]) + + r = least_squares(bar_resid, [18, 0.6, 14, 0.02], bounds=([1, 0.05, 1, -0.1], [200, 3, 40, 0.2])) + print("bar press spring (%d presses): omega %.2f zeta %.3f press growth %.2f pt lag %.3f s, rms %.3f pt" + % (len(E), *r.x, np.sqrt(np.mean(r.fun ** 2)))) + R, T = lens_series(drag_log) + t, x = R[:, 0], R[:, 1] + finger = Finger(T) + eps = [] + for d, u in presses(T): + m = (t >= d["t"] - 0.05) & (t <= u["t"] + 0.02) + eps.append((t[m], x[m], d["t"])) + + def follow_resid(p): + w, z, lag, off = p + out = [] + for tm, xm, d in eps: + def tf(tt, x0=xm[0], d=d): + v = finger(tt - lag) + return x0 if v is None or tt - lag < d else v - off + out.append(spring(tm, tf, w, z, x0=xm[0]) - xm) + return np.concatenate(out) + + r = least_squares(follow_resid, [25, 0.8, 0.02, 59.67], bounds=([2, 0.1, -0.05, 40], [300, 3, 0.2, 80])) + print("scrub follow spring: omega %.2f zeta %.3f lag %.3f s finger offset %.2f pt, rms %.2f max %.2f pt" + % (*r.x, np.sqrt(np.mean(r.fun ** 2)), np.abs(r.fun).max())) + + +# --------------------------------------------------------------------------- vibrancy + +# The closed forms VibrancyMatrix implements; `vibrancy` holds every logged matrix to +# them. Structure and constants were found from these captures (see VibrancyMatrix). +VIB_TOLERANCE = 2e-5 + + +def dark_matrix(t): + mx, mn = t.max(), t.min() + e = 0.5 * mn / mx + return (0.5 - e) * np.outer(np.ones(3), t) / (t @ t) + e * np.eye(3), t.copy() + + +def light_matrix(t): + mx, mn = t.max(), t.min() + e = 0.5 * mn / mx + d = 1 - t + c = 0.3 * (0.5 - e) / (d @ d) + alpha = 1 - e - c * d.sum() + return c * np.outer(1 + t, d) + e * np.eye(3), alpha * (1 + t) - 1 + e * t + + +def grey_matrix(g): + q = 1 + 0.05 * g * (1 - g) + return 5.0 / 16 / q * np.eye(3), np.full(3, (19.0 / 16 * g - 0.25) / q) + + +def load_tints(path): + """(hex, tint 0..1, distinct 4x5 matrices) per TINT line of a PROBE_TINTSWEEP log. + The first matrix is the selected item's.""" + out = [] + for line in read_text(path).splitlines(True): + if not line.startswith("TINT "): + continue + parts = line.split() + h = parts[1] + t = np.array([int(h[i:i + 2], 16) for i in (0, 2, 4)]) / 255. + mats = [] + for p in parts[2:]: + m = re.match(r"(\w+):M\[([^\]]*)\]", p) + if m: + M = np.array([float(x) for x in m.group(2).split(",")]).reshape(4, 5) + if not any(np.allclose(M, U) for U in mats): + mats.append(M) + out.append((h, t, mats)) + return out + + +def vibrancy_check(dark_logs, light_logs): + """Worst matrix-entry error of the closed forms against every logged tint. A + tint's item layers carry several matrices (selected and unselected items), so + each tint is scored against the one that matches best -- the selected item's.""" + worst = 0.0 + for appearance, logs, colour in (("dark", dark_logs, dark_matrix), ("light", light_logs, light_matrix)): + n = 0 + for log in logs: + for h, t, mats in load_tints(log): + if not mats: + continue + W, off = grey_matrix(t[0]) if t.max() == t.min() else colour(t) + err = min(max(np.abs(M[:3, :3] - W).max(), np.abs(M[:3, 4] - off).max()) for M in mats) + if err > VIB_TOLERANCE: + print("%s %s %s: off by %.2e" % (log, appearance, h, err), file=sys.stderr) + worst = max(worst, err) + n += 1 + print("%s: %d tints" % (appearance, n), file=sys.stderr) + print("worst entry error %.2e (tolerance %.0e)" % (worst, VIB_TOLERANCE), file=sys.stderr) + if worst > VIB_TOLERANCE: + sys.exit("the vibrancy closed forms no longer match the capture") + + +def vibrancy_fixture(dark, light, grey_dark, grey_light, path, count=300): + rows = ["# Native iOS 27 vibrancy matrices read from UIKit (vibrantColorMatrix inputColorMatrix)", + "# for the selected tab-bar item, by tint. Captured with the motion probe (PROBE_TINTSWEEP).", + "# appearance,tint,m00,m01,m02,off0,m10,m11,m12,off1,m20,m21,m22,off2"] + + def row(app, h, M): + return "%s,%s," % (app, h) + ",".join("%.5f,%.5f,%.5f,%.5f" % (M[r, 0], M[r, 1], M[r, 2], M[r, 4]) + for r in range(3)) + for app, log in (("dark", dark), ("light", light)): + rows += [row(app, h, mats[0]) for h, t, mats in load_tints(log)[:count]] + for app, log in (("dark", grey_dark), ("light", grey_light)): + rows += [row(app, h, mats[0]) for h, t, mats in load_tints(log) if t.max() == t.min()] + write_text(path, "\n".join(rows) + "\n") + + +# --------------------------------------------------------------------------- cli + +def all_taps(logs): + taps = [] + for log in logs: + taps += extract_taps(log) + return taps + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + sub = ap.add_subparsers(dest="cmd", required=True) + a = sub.add_parser("fit") + a.add_argument("logs", nargs="+") + a.add_argument("-o", required=True) + a = sub.add_parser("java") + a.add_argument("templates") + a.add_argument("java") + a = sub.add_parser("fixture") + a.add_argument("logs", nargs="+") + a.add_argument("-o", required=True) + a = sub.add_parser("check") + a.add_argument("templates") + a.add_argument("logs", nargs="+") + a = sub.add_parser("material") + a.add_argument("shots") + a = sub.add_parser("gesture") + a.add_argument("--hold", required=True) + a.add_argument("--drags", nargs="+", required=True) + a.add_argument("--settle", nargs="+", required=True, help="the logs gesture-fixture exports") + a.add_argument("-o", required=True) + a = sub.add_parser("gesture-fixture") + a.add_argument("logs", nargs="+") + a.add_argument("-o", required=True) + a = sub.add_parser("springs") + a.add_argument("--press", nargs="+", required=True) + a.add_argument("--drag", required=True) + a = sub.add_parser("vibrancy") + a.add_argument("--dark", nargs="+", required=True) + a.add_argument("--light", nargs="+", required=True) + a = sub.add_parser("vibrancy-fixture") + for k in ("--dark", "--light", "--grey-dark", "--grey-light", "-o"): + a.add_argument(k, required=True) + args = ap.parse_args() + if args.cmd == "fit": + clean = [tp for tp in all_taps(args.logs) if is_clean(tp)] + print("fitting %d clean taps" % len(clean), file=sys.stderr) + write_json(fit(clean), args.o) + elif args.cmd == "java": + write_java(read_json(args.templates), args.java) + elif args.cmd == "fixture": + taps = [tp for tp in all_taps(args.logs) if tp["k"] != 0 and tp["up"] is not None and tp["up"] <= 0.075 + and tp["dt"].max() <= 25] + fixture(taps, args.o) + elif args.cmd == "check": + check(read_json(args.templates), all_taps(args.logs)) + elif args.cmd == "material": + material(args.shots) + elif args.cmd == "gesture": + W = wobble(args.hold) + splice(args.o, GESTURE_BEGIN, gesture_tables(W, scrub_kernels(args.drags, W))) + if not settle_check(gesture_rows(args.settle)): + sys.exit("the settle after a scrub no longer matches the capture") + elif args.cmd == "gesture-fixture": + gesture_fixture(args.logs, args.o) + elif args.cmd == "springs": + springs(args.press, args.drag) + elif args.cmd == "vibrancy": + vibrancy_check(args.dark, args.light) + elif args.cmd == "vibrancy-fixture": + vibrancy_fixture(args.dark, args.light, args.grey_dark, args.grey_light, args.o) + + +if __name__ == "__main__": + main() diff --git a/scripts/ios/screenshots-metal-27/TabsTheme_dark.png b/scripts/ios/screenshots-metal-27/TabsTheme_dark.png index 348e8badd81..fb3df3b9462 100644 Binary files a/scripts/ios/screenshots-metal-27/TabsTheme_dark.png and b/scripts/ios/screenshots-metal-27/TabsTheme_dark.png differ diff --git a/scripts/ios/screenshots-metal-27/TabsTheme_light.png b/scripts/ios/screenshots-metal-27/TabsTheme_light.png index 6c35855c918..f0b525a007e 100644 Binary files a/scripts/ios/screenshots-metal-27/TabsTheme_light.png and b/scripts/ios/screenshots-metal-27/TabsTheme_light.png differ diff --git a/scripts/probe-ios-tab-motion.sh b/scripts/probe-ios-tab-motion.sh new file mode 100755 index 00000000000..1e06e68ff64 --- /dev/null +++ b/scripts/probe-ios-tab-motion.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# Measures the native iOS tab bar: the Liquid Glass selection MOTION (per-frame +# layer geometry of genuine tap-driven selections) and the bar's MATERIAL +# (lossless screenshots over backdrops whose pixels are known). +# +# This is the instrument behind com.codename1.ui.TabGlassMotion, +# com.codename1.ui.TabGlassGesture and GlassRecipe.liquidPill27. It runs LOCALLY on +# a simulator whose OS matches the golden set, never on CI; the tables it produces +# are committed, and TabGlassMotionTest / TabGlassGestureTest hold the Java models +# to the capture. +# +# Usage: probe-ios-tab-motion.sh [out_dir] [simulator_udid] +# +# Writes into out_dir (default: artifacts/tab-motion-probe): +# -grey-tabs.log per-frame layer logs of tap-driven selections +# hold-light.log, drag*-.log +# per-frame layer logs of held presses and drags +# live--.png lossless screenshots of the resting bar +# snap-/_.png with PROBE_SNAP=1: lossless per-frame snapshots +# of each tap, for side-by-side visual comparison +# (their timing is distorted -- see the README) +# Then regenerate with scripts/fidelity-app/tools/tab-motion/tabmotion.py (see +# scripts/fidelity-app/ios-native-ref/motion-probe/README.md). +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +OUT="${1:-$ROOT/artifacts/tab-motion-probe}" +UDID="${2:-}" +GOLDEN_SET="${CN1SS_FIDELITY_GOLDEN_SET:-ios-27-metal}" +PROBE_SRC="$ROOT/scripts/fidelity-app/ios-native-ref/motion-probe" +BUNDLE_ID="com.codenameone.tabprobe" + +log() { echo "[probe-tab-motion] $*"; } + +# shellcheck source=lib/xcode.sh +source "$ROOT/scripts/lib/xcode.sh" +cn1_select_xcode log || exit 1 +log "Using Xcode $CN1_XCODE_VERSION ($DEVELOPER_DIR)" +# shellcheck source=lib/ios-sim.sh +source "$ROOT/scripts/lib/ios-sim.sh" +if [ -z "$UDID" ]; then + UDID="$(cn1_resolve_ios_sim_udid "$GOLDEN_SET" log)" || exit 1 +fi +log "Simulator $UDID ($GOLDEN_SET)" +xcrun simctl boot "$UDID" >/dev/null 2>&1 || true +xcrun simctl bootstatus "$UDID" -b >/dev/null + +if ! command -v xcodegen >/dev/null 2>&1; then + log "xcodegen not on PATH (brew install xcodegen)" >&2 + exit 3 +fi +WORK="$(mktemp -d "${TMPDIR:-/tmp}/cn1-tab-motion-probe-XXXXXX")" +# The spec's paths are relative to the spec, so generate from the source tree and +# only put the generated project and the build products in the temp dir. +xcodegen generate --spec "$PROBE_SRC/project.yml" --project "$WORK" >/dev/null +log "Building probe (xcodebuild build-for-testing)" +xcodebuild build-for-testing -project "$WORK/TabProbe.xcodeproj" -scheme TabProbe \ + -destination "id=$UDID" -derivedDataPath "$WORK/dd" >"$WORK/build.log" 2>&1 \ + || { log "FATAL: probe build failed, see $WORK/build.log"; exit 4; } +mkdir -p "$OUT" + +container() { xcrun simctl get_app_container "$UDID" "$BUNDLE_ID" data; } + +# One capture: PROBE_SEQ is a comma separated list of gestures, two seconds apart +# (UITests/TapTests.swift): N taps tab N; hN:MS holds tab N for MS ms; dA-B:MS:H +# presses tab A for 0.3 s, drags to tab B over MS ms, holds H ms and releases; +# dA-B.F:MS:H stops at fraction F of the way from A to B. +run_seq() { + local name="$1" appearance="$2" tabs="$3" seq="$4" + log "Motion capture $name ($seq)" + TEST_RUNNER_PROBE_APPEARANCE="$appearance" TEST_RUNNER_PROBE_BACKDROP=grey \ + TEST_RUNNER_PROBE_TABS="$tabs" TEST_RUNNER_PROBE_SEQ="$seq" TEST_RUNNER_PROBE_LOG="$name.log" \ + TEST_RUNNER_PROBE_SNAP="${PROBE_SNAP:-0}" \ + xcodebuild test-without-building -project "$WORK/TabProbe.xcodeproj" -scheme TabProbe \ + -destination "id=$UDID" -derivedDataPath "$WORK/dd" >"$WORK/$name.xcb.txt" 2>&1 \ + || { log "FATAL: capture failed, see $WORK/$name.xcb.txt"; exit 4; } + cp "$(container)/Documents/$name.log" "$OUT/$name.log" + if [ "${PROBE_SNAP:-0}" = "1" ]; then + rm -rf "$OUT/snap-$name" + cp -R "$(container)/Documents/snap" "$OUT/snap-$name" + fi +} +# Motion: tap-driven selections. The 3-tab sequence covers one- and two-cell +# jumps both ways; the 5-tab one adds three- and four-cell jumps. +run_taps() { run_seq "$1-grey-$2tabs" "$1" "$2" "$3"; } +run_taps light 3 "2,0,1,2,1,0" +run_taps dark 3 "2,0,1,2,1,0" +run_taps light 5 "4,0,3,1,2,4,3,0" + +# Gestures (TabGlassGesture). Holds: two 1.5 s holds and a quick tap on the tab +# that is then selected (nothing travels, so the release wobble is seen alone), +# then a 3 s hold on another tab. +run_seq hold-light light 3 "h1:1500,h1:1500,1,h2:3000" +# Drags: scrubs of every speed, length and hold, both ways, on 3- and 5-tab bars, +# ending on a tab and between tabs, and some that start on an unselected tab. +run_seq drag-light light 3 "d0-1:800:600,d1-2:400:0,d2-0:1200:500,d0-1.5:600:1500,d0-2:250:0" +run_seq dragA-light light 3 "d0-2:1500:300,d2-0:1000:300,d0-2:700:0,d2-0:450:0,d0-2:300:0,d2-0:200:0,d0-2:150:0,d2-0:120:0" +run_seq dragB-light light 3 "d0-1:600:800,d1-2:900:0,d2-1.5:700:1200,d1-0:350:500,d0-2.3:500:900,d2-1:250:0" +run_seq dragC-light light 5 "d0-4:1500:300,d4-0:600:0,d0-4:250:0,d4-1:900:400,d1-3:300:0,d3-2:700:700" +run_seq dragD-dark dark 3 "d0-2:800:300,d2-0:300:0,d0-1:500:600" + +# Material: the resting bar, screenshotted losslessly (a screen recording's h264 +# smears exactly the detail the blur radius is fitted from). +xcrun simctl install "$UDID" "$(find "$WORK/dd/Build/Products" -name TabProbe.app -maxdepth 2 | head -n1)" +for appearance in light dark; do + for backdrop in photo stripes grey; do + xcrun simctl terminate "$UDID" "$BUNDLE_ID" >/dev/null 2>&1 || true + SIMCTL_CHILD_PROBE_APPEARANCE="$appearance" SIMCTL_CHILD_PROBE_BACKDROP="$backdrop" \ + xcrun simctl launch "$UDID" "$BUNDLE_ID" >/dev/null + sleep 3 + xcrun simctl io "$UDID" screenshot "$OUT/live-$appearance-$backdrop.png" >/dev/null 2>&1 + done +done +xcrun simctl terminate "$UDID" "$BUNDLE_ID" >/dev/null 2>&1 || true +log "Wrote $OUT" diff --git a/scripts/record-ios-tabs-side-by-side.sh b/scripts/record-ios-tabs-side-by-side.sh new file mode 100755 index 00000000000..bc542931be7 --- /dev/null +++ b/scripts/record-ios-tabs-side-by-side.sh @@ -0,0 +1,270 @@ +#!/usr/bin/env bash +# Records the native iOS 27 tab bar and the Codename One one doing the SAME real +# touches -- taps, a hold, a tap on the selected tab, a slow drag and a flick -- +# with the simulator's own screen recorder, and stacks the two side by side. +# +# Native: the motion probe (scripts/fidelity-app/ios-native-ref/motion-probe), a +# real UITabBarController over the shared glass backdrop. Codename One: the +# fidelity app in its live tabs showcase (FidelityDeviceRunner.runTabsShowcase), +# the same Tabs the fidelity goldens use, over the same backdrop. Both are driven +# by the probe's XCUITest bundle, so the touches are genuine on both sides. +# +# Usage: record-ios-tabs-side-by-side.sh [light|dark|both] [udid] [out_dir] +# +# Writes /tabs-side-by-side-.mp4 (full screen, half size) and +# tabs-side-by-side--bar.mp4 (the bottom of the screen at full size), +# native on the left. Needs xcodegen, ffmpeg and a python3 with numpy and Pillow +# (PYTHON=... to choose one). Record with the iOS 27 toolchain, CN1_XCODE_MAJOR=27. +set -euo pipefail + +APP="${1:?usage: $0 [light|dark|both] [udid] [out_dir]}" +WHICH="${2:-both}" +UDID="${3:-}" +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +OUT="${4:-$ROOT/artifacts/tabs-side-by-side}" +GOLDEN_SET="${CN1SS_FIDELITY_GOLDEN_SET:-ios-27-metal}" +PROBE_SRC="$ROOT/scripts/fidelity-app/ios-native-ref/motion-probe" +PROBE_ID="com.codenameone.tabprobe" +# Taps both ways, a hold, a tap on the (then) selected tab, a slow drag, a flick. +SEQ="${TABS_SEQ:-2,0,1,2,1,0,h2:1500,2,d2-0:900:300,d0-2:350:0}" +# Tab centres of the Codename One bar in points (x:y per tab); the native side +# reads its own from the accessibility tree. +CN1_POINTS="${CN1_TAB_POINTS:-98.7:782,195.8:782,293.3:782}" +# One gesture per slot. The driver's idle waits differ between the two apps, so +# the gestures are scheduled on a fixed clock and each one is found in its slot. +PERIOD_MS="${TABS_PERIOD_MS:-4500}" + +PY="${PYTHON:-python3}" + +log() { echo "[tabs-side-by-side] $*"; } + +# shellcheck source=lib/xcode.sh +source "$ROOT/scripts/lib/xcode.sh" +cn1_select_xcode log || exit 1 +# shellcheck source=lib/ios-sim.sh +source "$ROOT/scripts/lib/ios-sim.sh" +if [ -z "$UDID" ]; then + UDID="$(cn1_resolve_ios_sim_udid "$GOLDEN_SET" log)" || exit 1 +fi +for tool in xcodegen ffmpeg "$PY"; do + command -v "$tool" >/dev/null 2>&1 || { log "$tool is required" >&2; exit 3; } +done +xcrun simctl boot "$UDID" >/dev/null 2>&1 || true +xcrun simctl bootstatus "$UDID" -b >/dev/null + +WORK="$(mktemp -d "${TMPDIR:-/tmp}/cn1-tabs-side-by-side-XXXXXX")" +xcodegen generate --spec "$PROBE_SRC/project.yml" --project "$WORK" >/dev/null +log "Building the probe and its XCUITest driver" +xcodebuild build-for-testing -project "$WORK/TabProbe.xcodeproj" -scheme TabProbe \ + -destination "id=$UDID" -derivedDataPath "$WORK/dd" >"$WORK/build.log" 2>&1 \ + || { log "FATAL: probe build failed, see $WORK/build.log"; exit 4; } +CN1_ID="$(/usr/libexec/PlistBuddy -c 'Print CFBundleIdentifier' "$APP/Info.plist")" +xcrun simctl install "$UDID" "$APP" +mkdir -p "$OUT" + +# Runs the driver against the probe (no target) or the Codename One app, while the +# simulator records the screen into $1. +record() { + local video="$1" appearance="$2" target="$3" + # Wall-clock start of the recording: with the driver log's own wall-clock + # test start it pins each gesture to its place in the video to within the + # recorder's start-up latency. + "$PY" -c 'import time; print("%.3f" % time.time())' > "$video.start" + xcrun simctl io "$UDID" recordVideo --codec h264 --force "$video" >/dev/null 2>&1 & + local rec=$! + sleep 1 + TEST_RUNNER_PROBE_APPEARANCE="$appearance" TEST_RUNNER_PROBE_BACKDROP=photo \ + TEST_RUNNER_PROBE_SEQ="$SEQ" TEST_RUNNER_PROBE_TARGET="$target" \ + TEST_RUNNER_PROBE_TAB_POINTS="$CN1_POINTS" TEST_RUNNER_PROBE_PERIOD_MS="$PERIOD_MS" \ + xcodebuild test-without-building -project "$WORK/TabProbe.xcodeproj" -scheme TabProbe \ + -destination "id=$UDID" -derivedDataPath "$WORK/dd" >"$video.xcb.txt" 2>&1 \ + || { kill -INT "$rec" 2>/dev/null || true; log "FATAL: driver failed, see $video.xcb.txt"; exit 4; } + # The last gesture's slot must be on screen in full before recording stops. + sleep 5 + kill -INT "$rec" 2>/dev/null || true + wait "$rec" 2>/dev/null || true +} + +# The start of every gesture in a 60 fps video, in seconds, one per line. The +# driver log times each synthesized touch exactly; the video fixes the offset +# between the two clocks -- the one that puts the most touches on the start of a +# burst of motion in the tab bar -- and then each gesture's own burst. +onsets() { + "$PY" - "$1" "$2" "$3" "$4" <<'PY' +import re, sys, subprocess, time, datetime, numpy as np +video, xcb, count, startFile = sys.argv[1], sys.argv[2], int(sys.argv[3]), sys.argv[4] +touches = [float(m.group(1)) for m in re.finditer(r"t =\s+([0-9.]+)s\s+Synthesize event", open(xcb).read())] +if len(touches) == count + 1: + touches = touches[1:] # the driver's warm-up tap before the schedule +if len(touches) != count: + sys.exit("expected %d touches in %s, found %d" % (count, xcb, len(touches))) +size = subprocess.run(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height", + "-of", "csv=p=0", video], capture_output=True, text=True, check=True).stdout.strip().split(",") +w = int(size[0]) // 6 +h = int(int(size[1]) * 0.14) // 6 +raw = subprocess.run(["ffmpeg", "-v", "error", "-i", video, "-vf", + "crop=iw:ih*0.14:0:ih*0.84,scale=%d:%d,format=gray" % (w, h), "-f", "rawvideo", "-"], + capture_output=True, check=True).stdout +n = len(raw) // (w * h) +fr = np.frombuffer(raw, np.uint8)[: n * w * h].reshape(n, h, w).astype(int) +d = np.abs(np.diff(fr, axis=0)).mean(axis=(1, 2)) +# Gestures run on a fixed schedule, so matching bursts alone is ambiguous by whole +# periods. The clocks settle which period: the log's wall-clock test start plus a +# touch's offset, minus the recording's wall-clock start, is where that touch is in +# the video (the recorder starts a little late, so the video runs slightly behind). +m = re.search(r"Start Test at (\d{4}-\d\d-\d\d \d\d:\d\d:\d\d\.\d+)", open(xcb).read()) +rec0 = float(open(startFile).read().strip()) +test0 = time.mktime(datetime.datetime.strptime(m.group(1)[:23], "%Y-%m-%d %H:%M:%S.%f").timetuple()) \ + + float("0." + m.group(1).split(".")[1]) +coarse = test0 - rec0 +# The clocks are good to a few frames (measured: 0.07 s), and a burst they do not +# predict -- a stray repaint -- must not win. So every gesture is placed by the +# clocks on its own, independently of the others (one bad snap must not shift the +# rest), and only nudged to the first frame that moves within -0.1 / +0.25 s of that. +out = [] +for tk in touches: + pred = coarse + tk + lo = max(0, int((pred - 0.1) * 60)) + hi = min(len(d), int((pred + 0.25) * 60) + 1) + moving = [i for i in range(lo, hi) if d[i] > 0.5] + out.append(moving[0] / 60.0 if moving else pred) +print("\n".join("%.4f" % t for t in out)) +PY +} + +label() { + "$PY" - "$1" "$2" "$3" <<'PY' +import sys +from PIL import Image, ImageDraw, ImageFont +text, width, path = sys.argv[1], int(sys.argv[2]), sys.argv[3] +img = Image.new("RGBA", (width, 64), (0, 0, 0, 170)) +d = ImageDraw.Draw(img) +try: + font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 40) +except Exception: + font = ImageFont.load_default() +tw = d.textlength(text, font=font) +d.text(((width - tw) / 2, 10), text, fill=(255, 255, 255, 255), font=font) +img.save(path) +PY +} + +# Records one side (native or cn1) and keeps it only if every gesture's first 0.6 s +# is continuous motion. The simulator's recorder drops frames -- and the wall time +# they covered -- when the machine is loaded, which silently misplaces gestures; +# a dropped take is recorded again, up to three times. +record_side() { + local side="$1" appearance="$2" count="$3" attempt + for attempt in 1 2 3; do + if [ "$side" = native ]; then + xcrun simctl terminate "$UDID" "$CN1_ID" >/dev/null 2>&1 || true + record "$WORK/native-$appearance.mov" "$appearance" "" + xcrun simctl terminate "$UDID" "$PROBE_ID" >/dev/null 2>&1 || true + else + local home + home="$(xcrun simctl get_app_container "$UDID" "$CN1_ID" data)/Documents/$CN1_ID" + mkdir -p "$home" + echo "$appearance" > "$home/tabs-showcase.txt" + # The app's console goes beside the take, for diagnosing a bad one. + xcrun simctl launch --console-pty --terminate-running-process "$UDID" "$CN1_ID" \ + > "$WORK/cn1-$appearance.console.txt" 2>&1 & + sleep 6 + record "$WORK/cn1-$appearance.mov" "$appearance" "$CN1_ID" + xcrun simctl terminate "$UDID" "$CN1_ID" >/dev/null 2>&1 || true + rm -f "$home/tabs-showcase.txt" + fi + ffmpeg -v error -y -i "$WORK/$side-$appearance.mov" -vf fps=60 -c:v libx264 -crf 12 -pix_fmt yuv420p \ + "$WORK/$side-$appearance-60.mp4" + local starts worst + starts="$(onsets "$WORK/$side-$appearance-60.mp4" "$WORK/$side-$appearance.mov.xcb.txt" "$count" \ + "$WORK/$side-$appearance.mov.start")" || starts="" + worst="$(continuity "$WORK/$side-$appearance-60.mp4" $starts)" + if [ -n "$starts" ] && [ "$worst" -ge 18 ]; then + return 0 + fi + log "$side ($appearance) take $attempt dropped frames (fewest moving frames in a gesture: ${worst:-none}); again" + done + log "FATAL: $side ($appearance) could not be recorded without dropped frames (machine too loaded?)" + exit 5 +} + +# The fewest changed frames in the first 0.6 s of any gesture (a clean take moves +# on nearly every frame there; a recorder drop shows as a jump). +continuity() { + local video="$1"; shift + [ $# -gt 0 ] || { echo 0; return; } + "$PY" - "$video" "$@" <<'PY' +import sys, subprocess, numpy as np +video = sys.argv[1] +starts = [float(x) for x in sys.argv[2:]] +size = subprocess.run(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height", + "-of", "csv=p=0", video], capture_output=True, text=True, check=True).stdout.strip().split(",") +w = int(size[0]) // 6 +h = int(int(size[1]) * 0.14) // 6 +raw = subprocess.run(["ffmpeg", "-v", "error", "-i", video, "-vf", + "crop=iw:ih*0.14:0:ih*0.84,scale=%d:%d,format=gray" % (w, h), "-f", "rawvideo", "-"], + capture_output=True, check=True).stdout +n = len(raw) // (w * h) +fr = np.frombuffer(raw, np.uint8)[: n * w * h].reshape(n, h, w).astype(int) +d = np.abs(np.diff(fr, axis=0)).mean(axis=(1, 2)) +print(min(int((d[int(t * 60):int(t * 60) + 36] > 0.05).sum()) for t in starts)) +PY +} + +run_appearance() { + local appearance="$1" + local count + count="$(echo "$SEQ" | tr ',' '\n' | grep -c .)" + if [ -n "${TABS_REUSE:-}" ]; then + # Recompose from an earlier run's raw recordings and driver logs. + WORK="$TABS_REUSE" + else + log "Native ($appearance)" + record_side native "$appearance" "$count" + log "Codename One ($appearance)" + record_side cn1 "$appearance" "$count" + fi + local on_native on_cn1 + on_native="$(onsets "$WORK/native-$appearance-60.mp4" "$WORK/native-$appearance.mov.xcb.txt" "$count" \ + "$WORK/native-$appearance.mov.start")" + on_cn1="$(onsets "$WORK/cn1-$appearance-60.mp4" "$WORK/cn1-$appearance.mov.xcb.txt" "$count" \ + "$WORK/cn1-$appearance.mov.start")" + log "gesture starts, native: $(echo $on_native)" + log "gesture starts, Codename One: $(echo $on_cn1)" + # Every gesture gets the same slot on both sides, starting a quarter second + # before its first moving frame. + local seg lead=0.25 + seg="$(awk -v p="$PERIOD_MS" 'BEGIN { printf "%.3f", p / 1000 - 0.3 }')" + trims() { + local input="$1" tag="$2" starts="$3" chain="" i=0 t + for t in $starts; do + chain="$chain[$input:v]trim=start=$(awk -v t="$t" -v l="$lead" 'BEGIN { s = t - l; if (s < 0) s = 0; printf "%.4f", s }'):duration=$seg,setpts=PTS-STARTPTS[$tag$i];" + i=$((i + 1)) + done + local joined="" j + for ((j = 0; j < i; j++)); do joined="$joined[$tag$j]"; done + echo "$chain${joined}concat=n=$i:v=1:a=0[$tag]" + } + local w=590 + label "iOS 27 native ($appearance)" "$w" "$WORK/l-native.png" + label "Codename One ($appearance)" "$w" "$WORK/l-cn1.png" + label "iOS 27 native" 1178 "$WORK/L-native.png" + label "Codename One" 1178 "$WORK/L-cn1.png" + local cut + cut="$(trims 0 n "$on_native");$(trims 1 c "$on_cn1")" + ffmpeg -v error -y -i "$WORK/native-$appearance-60.mp4" -i "$WORK/cn1-$appearance-60.mp4" \ + -i "$WORK/l-native.png" -i "$WORK/l-cn1.png" -filter_complex \ + "$cut;[n]scale=$w:-2[a0];[c]scale=$w:-2[b0];[a0][2:v]overlay=0:0[a];[b0][3:v]overlay=0:0[b];[a][b]hstack=inputs=2[v]" \ + -map "[v]" -r 60 -c:v libx264 -crf 16 -pix_fmt yuv420p "$OUT/tabs-side-by-side-$appearance.mp4" + ffmpeg -v error -y -i "$WORK/native-$appearance-60.mp4" -i "$WORK/cn1-$appearance-60.mp4" \ + -i "$WORK/L-native.png" -i "$WORK/L-cn1.png" -filter_complex \ + "$cut;[n]crop=iw:512:0:ih-512[a0];[c]crop=iw:512:0:ih-512[b0];[a0][2:v]overlay=0:0[a];[b0][3:v]overlay=0:0[b];[a][b]vstack=inputs=2[v]" \ + -map "[v]" -r 60 -c:v libx264 -crf 14 -pix_fmt yuv420p "$OUT/tabs-side-by-side-$appearance-bar.mp4" + log "Wrote $OUT/tabs-side-by-side-$appearance.mp4 and ...-bar.mp4" +} + +case "$WHICH" in + both) run_appearance light; run_appearance dark ;; + light|dark) run_appearance "$WHICH" ;; + *) log "appearance must be light, dark or both" >&2; exit 2 ;; +esac diff --git a/scripts/run-ios-fidelity-tests.sh b/scripts/run-ios-fidelity-tests.sh index 9f8eb0e01f7..aa512e32286 100755 --- a/scripts/run-ios-fidelity-tests.sh +++ b/scripts/run-ios-fidelity-tests.sh @@ -202,6 +202,7 @@ FRAME_ARGS=( --frames-dir "$FRAMES_WORK_DIR" --goldens-dir "$FRAME_GOLDENS_DIR" --spec "$CN1SS_FIDELITY_SPEC" + --golden-set "$GOLDEN_SET" --out-json "$ARTIFACTS_DIR/morph-frames.json" --strip-dir "$ARTIFACTS_DIR" ) diff --git a/scripts/test-javascript-surface-replay.mjs b/scripts/test-javascript-surface-replay.mjs index f8698077a0c..1518c69dc84 100644 --- a/scripts/test-javascript-surface-replay.mjs +++ b/scripts/test-javascript-surface-replay.mjs @@ -77,6 +77,8 @@ function extractSurfTable() { const calls = []; const ctx = { + // The self-region ops only run on a context that has a canvas. + canvas: { width: 100, height: 100 }, save() { calls.push(['save']); }, restore() { calls.push(['restore']); }, fillRect(...a) { calls.push(['fillRect', ...a]); }, @@ -101,6 +103,15 @@ const sandbox = { resolveHostRef: (m) => m, applyBlurSelfRegion() { throw new Error('unexpected blur op'); }, applyLensSelfRegion() { throw new Error('unexpected lens op'); }, + // The colour-matrix op is driven below, so these record instead of throwing. + // A mask whose `readable` is false stands for one the host cannot read yet. + colorMatrixMaskAlpha(source) { + return source && source.readable ? { alpha: 'mask:' + source.id, w: 1, h: 1 } : null; + }, + applyColorMatrixSelfRegion(c, x, y, w, h, matrix, mask, cornerRadius, amount) { + calls.push(['colorMatrix', x, y, w, h, matrix.join(' '), mask ? mask.alpha : 'none', + cornerRadius, amount]); + }, surfaceTable: {}, global: {}, }; @@ -170,6 +181,42 @@ check('a drawable source still draws, with its arguments', JSON.stringify(calls)); check('a drawable source counts no drop', sandbox.surfaceDrawImageDropped === 0); +// Graphics.colorMatrixRegion: 20 nums and ALWAYS 1 obj, whichever mask kind. +// The op behind it must get its own arguments in every case, including when the +// colour matrix itself is skipped because its mask cannot be read yet. +console.log('surface replay: the colour-matrix op keeps the cursors aligned'); +function colorMatrixNums(maskKind, maskSurface) { + return [1, 2, 30, 40, /* corner */ -1, /* amount */ 0.5, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, maskKind, maskSurface]; +} +function runColorMatrix(label, maskKind, maskSurface, maskObj, expectApplied, expectMask) { + calls.length = 0; + sandbox.surfaceTable = { 5: { canvas: { id: 'surface5', readable: true } } }; + sandbox.replaySurfaceCommands(ctx, [SURF.COLOR_MATRIX_SELF_REGION, SURF.FILL_RECT, SURF.DRAW_IMAGE_XY], 3, + colorMatrixNums(maskKind, maskSurface).concat([7, 8, 9, 10, 11, 12]), + [maskObj, { id: 'after', width: 4, height: 4 }]); + const applied = calls.filter((c) => c[0] === 'colorMatrix'); + const filled = calls.filter((c) => c[0] === 'fillRect'); + const drew = calls.filter((c) => c[0] === 'drawImage'); + if (expectApplied) { + check(label + ': applied with its own arguments', + applied.length === 1 && applied[0].slice(1).join(',') + === ['1', '2', '30', '40', '0 1 2 3 4 5 6 7 8 9 10 11', expectMask, '-1', '0.5'].join(','), + JSON.stringify(applied)); + } else { + check(label + ': skipped', applied.length === 0, JSON.stringify(applied)); + } + check(label + ': the next num op gets ITS OWN arguments', + filled.length === 1 && filled[0].slice(1).join(',') === '7,8,9,10', JSON.stringify(filled)); + check(label + ': the next obj op gets ITS OWN source', + drew.length === 1 && drew[0].join(',') === 'drawImage,after,11,12', JSON.stringify(drew)); +} +runColorMatrix('colour matrix, no mask', 0, 0, null, true, 'none'); +runColorMatrix('colour matrix, image mask', 1, 0, { id: 'img', readable: true }, true, 'mask:img'); +runColorMatrix('colour matrix, surface mask', 2, 5, null, true, 'mask:surface5'); +runColorMatrix('colour matrix, unreadable image mask', 1, 0, { id: 'img', readable: false }, false); +runColorMatrix('colour matrix, missing surface mask', 2, 99, null, false); + if (failures.length) { console.error('\nFAILED: ' + failures.length + ' assertion(s)'); process.exit(1); diff --git a/scripts/test_check_fidelity_spec.py b/scripts/test_check_fidelity_spec.py index 5b0386ea8b8..bc37d0db681 100644 --- a/scripts/test_check_fidelity_spec.py +++ b/scripts/test_check_fidelity_spec.py @@ -219,6 +219,23 @@ def test_native_table_mapping_must_match_spec(self): self.assertTrue(any("DesktopButton maps to" in e for e in validator.ERRORS)) path.write_text(original) + def test_golden_sets_and_motion_checks_are_validated(self): + original = validator.SPEC.read_text() + self.assertIn("golden_sets: ios-27-metal", original) + self.assertIn("motion_checks: distinct", original) + cases = ((" golden_sets: ios-27-metal", " golden_sets: ios-72-metal", "unknown golden set"), + (" motion_checks: distinct", " motion_checks: sometimes", "unknown motion_checks")) + for old, new, message in cases: + with self.subTest(value=new): + validator.SPEC.write_text(original.replace(old, new, 1)) + self.assertEqual(1, self.check()) + self.assertTrue(any(message in error for error in validator.ERRORS)) + # Both keys only mean something on a frames row. + validator.SPEC.write_text(original.replace(" - id: Button\n", + " - id: Button\n golden_sets: ios-27-metal\n", 1)) + self.assertEqual(1, self.check()) + self.assertTrue(any("only applies to a frames row" in error for error in validator.ERRORS)) + def test_comments_do_not_mask_a_wrong_label(self): path = validator.NATIVE_REF_SOURCES["windows"] path.write_text(path.read_text().replace('Content = "Button"', '/* Content = "Button" */ Content = "Option"', 1)) diff --git a/scripts/verify-javascript-lens-parity.mjs b/scripts/verify-javascript-lens-parity.mjs index ba68e5930bc..dc3662665dd 100644 --- a/scripts/verify-javascript-lens-parity.mjs +++ b/scripts/verify-javascript-lens-parity.mjs @@ -27,11 +27,29 @@ const end = bridge.indexOf(' // Replay one command stream', start); if (start < 0 || end < 0) { throw new Error('Unable to locate the lens implementation in browser_bridge.js'); } -const sandbox = { Math, Uint8ClampedArray }; +// `global` backs createGlassScratchCanvas, which the colour-matrix path uses +// for the canvas it draws its result back from. +class FakeScratchCanvas { + constructor(width, height) { + this.width = width; + this.height = height; + this.pixels = new Uint8ClampedArray(width * height * 4); + } + getContext() { + const canvas = this; + return { + createImageData(w, h) { return { data: new Uint8ClampedArray(w * h * 4) }; }, + putImageData(image) { canvas.pixels.set(image.data); } + }; + } +} +const sandbox = { Math, Uint8ClampedArray, global: { OffscreenCanvas: FakeScratchCanvas } }; vm.runInNewContext(bridge.substring(start, end) + '\nthis.applyLens = applyLensSelfRegion;' + '\nthis.applyMaterial = glassMaterialInPlace;' - + '\nthis.applyOptics = applyGlassOptics;', sandbox); + + '\nthis.applyOptics = applyGlassOptics;' + + '\nthis.colorMatrixBlend = colorMatrixBlendInPlace;' + + '\nthis.applyColorMatrix = applyColorMatrixSelfRegion;', sandbox); // The same foreground lens is implemented four times because each backend has // a different pixel API. Fail before the CRC probe if a tuning constant drifts. @@ -216,3 +234,138 @@ for (const probe of [ } console.log(`glass outline parity PASS ${probe.name} crc=${outlineCrc.toString(16)}`); } + +// Graphics.colorMatrixRegion. The expected CRCs come from the core reference, +// com.codename1.ui.plaf.ColorMatrixBlend.apply, over the same inputs (a probe +// that fills packed ARGB with colorMatrixPattern, applies the blend and CRCs the +// ARGB bytes). colorMatrixBlendInPlace frounds every float operation in Java's +// order, so these are exact, not approximate. The matrix has negative and >1 +// terms so both clamps are exercised; the mask is a non-divisor size so the +// nearest-sample stretch is too. +function colorMatrixPattern(width, height, originX, originY) { + const pixels = new Uint8ClampedArray(width * height * 4); + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const px = x + originX, py = y + originY, offset = (y * width + x) * 4; + pixels[offset] = (px * 17 + py * 3) & 0xff; + pixels[offset + 1] = (px * 5 + py * 19) & 0xff; + pixels[offset + 2] = (px * 11 + py * 7) & 0xff; + pixels[offset + 3] = (px * 7 + py * 13 + 31) & 0xff; + } + } + return pixels; +} + +function colorMatrixMask(width, height) { + const alpha = new Uint8ClampedArray(width * height); + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + alpha[y * width + x] = (x * 37 + y * 53) & 0xff; + } + } + return { alpha, w: width, h: height }; +} + +const vibrantMatrix = [0.4, -0.9, 1.7, 0.12, -0.35, 1.25, 0.3, -0.05, 1.1, 0.2, -0.6, 0.33] + .map(Math.fround); +const greyMatrix = [0.2126, 0.7152, 0.0722, 0, 0.2126, 0.7152, 0.0722, 0, + 0.2126, 0.7152, 0.0722, 0].map(Math.fround); +for (const probe of [ + { name: 'rect-vibrant', w: 40, h: 24, matrix: vibrantMatrix, mask: null, corner: 0, + amount: 1, expected: 0x9bce8eee }, + { name: 'capsule-grey', w: 52, h: 30, matrix: greyMatrix, mask: null, corner: -1, + amount: 0.8, expected: 0x71cccd47 }, + { name: 'rounded-masked', w: 50, h: 28, matrix: vibrantMatrix, mask: colorMatrixMask(13, 9), + corner: 7, amount: 0.65, expected: 0x6a55dd05 } +]) { + const pixels = colorMatrixPattern(probe.w, probe.h, 0, 0); + sandbox.colorMatrixBlend(pixels, probe.w, probe.h, probe.matrix, + probe.mask ? probe.mask.alpha : null, probe.mask ? probe.mask.w : 0, + probe.mask ? probe.mask.h : 0, probe.corner, probe.amount); + const actual = crc32(rgbaToArgb(pixels)); + if (actual !== probe.expected) { + throw new Error(`Colour matrix parity failed for ${probe.name}: ` + + `expected ${probe.expected.toString(16)}, got ${actual.toString(16)}`); + } + console.log(`colour matrix parity PASS ${probe.name} crc=${actual.toString(16)}`); +} + +// The replayed op end to end, on a region hanging off the top-left of the +// canvas: only the visible part is read and written, but the capsule and the +// mask stay anchored to the FULL region, which is what JavaSEPort does. The +// Java side of this CRC applies the blend to the whole 44x26 region and then +// crops the visible 38x20. +{ + const canvasWidth = 60, canvasHeight = 20; + const calls = []; + let drawn = null; + const context = { + canvas: { width: canvasWidth, height: canvasHeight }, + getTransform() { return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }; }, + getImageData(x, y, w, h) { + calls.push(['getImageData', x, y, w, h]); + return { data: colorMatrixPattern(w, h, x, y) }; + }, + putImageData() { throw new Error('putImageData ignores the clip; the op must not use it'); }, + save() { calls.push(['save']); }, + restore() { calls.push(['restore']); }, + setTransform(...args) { calls.push(['setTransform', ...args]); }, + clearRect(...args) { calls.push(['clearRect', ...args]); }, + drawImage(source, x, y) { calls.push(['drawImage', x, y]); drawn = source; } + }; + sandbox.applyColorMatrix(context, -6, -3, 44, 26, vibrantMatrix, colorMatrixMask(11, 7), -1, 0.9); + const expectedCalls = JSON.stringify([ + ['getImageData', 0, 0, 38, 20], ['save'], ['setTransform', 1, 0, 0, 1, 0, 0], + ['clearRect', 0, 0, 38, 20], ['drawImage', 0, 0], ['restore'] + ]); + if (JSON.stringify(calls) !== expectedCalls) { + throw new Error(`Colour matrix replay call sequence: ${JSON.stringify(calls)}`); + } + if (!drawn || drawn.width !== 38 || drawn.height !== 20) { + throw new Error('Colour matrix replay drew back the wrong size'); + } + const actual = crc32(rgbaToArgb(drawn.pixels)); + if (actual !== 0x48ee53ca) { + throw new Error(`Colour matrix off-canvas parity failed: expected 48ee53ca, got ${actual.toString(16)}`); + } + console.log(`colour matrix replay PASS offcanvas crc=${actual.toString(16)}`); +} + +// Graphics.colorMatrixRegion does not apply the current transform: the region is +// already in device pixels (core adds the translation; Tabs scales its own +// regions), so the same op under a scaled or a rotated canvas transform must read +// and write exactly the pixels it does under identity -- not move, and not vanish. +for (const [label, transform] of [ + ['scaled', { a: 2, b: 0, c: 0, d: 2, e: 7, f: 5 }], + ['rotated', { a: 0, b: 1, c: -1, d: 0, e: 0, f: 0 }] +]) { + const calls = []; + let drawn = null; + const context = { + canvas: { width: 60, height: 20 }, + getTransform() { return transform; }, + getImageData(x, y, w, h) { + calls.push(['getImageData', x, y, w, h]); + return { data: colorMatrixPattern(w, h, x, y) }; + }, + putImageData() { throw new Error('putImageData ignores the clip; the op must not use it'); }, + save() { calls.push(['save']); }, + restore() { calls.push(['restore']); }, + setTransform(...args) { calls.push(['setTransform', ...args]); }, + clearRect(...args) { calls.push(['clearRect', ...args]); }, + drawImage(source, x, y) { calls.push(['drawImage', x, y]); drawn = source; } + }; + sandbox.applyColorMatrix(context, -6, -3, 44, 26, vibrantMatrix, colorMatrixMask(11, 7), -1, 0.9); + const expectedCalls = JSON.stringify([ + ['getImageData', 0, 0, 38, 20], ['save'], ['setTransform', 1, 0, 0, 1, 0, 0], + ['clearRect', 0, 0, 38, 20], ['drawImage', 0, 0], ['restore'] + ]); + if (JSON.stringify(calls) !== expectedCalls) { + throw new Error(`Colour matrix under a ${label} transform: ${JSON.stringify(calls)}`); + } + const actual = crc32(rgbaToArgb(drawn.pixels)); + if (actual !== 0x48ee53ca) { + throw new Error(`Colour matrix under a ${label} transform: expected 48ee53ca, got ${actual.toString(16)}`); + } + console.log(`colour matrix replay PASS ${label} transform ignored crc=${actual.toString(16)}`); +} diff --git a/vm/ByteCodeTranslator/src/javascript/browser_bridge.js b/vm/ByteCodeTranslator/src/javascript/browser_bridge.js index 82174968812..24ec40925ff 100644 --- a/vm/ByteCodeTranslator/src/javascript/browser_bridge.js +++ b/vm/ByteCodeTranslator/src/javascript/browser_bridge.js @@ -2328,6 +2328,7 @@ DRAW_IMAGE_XY: 60, DRAW_IMAGE_XYWH: 61, DRAW_IMAGE_SRCDST: 62, BLIT_SURFACE_XY: 70, BLIT_SURFACE_XYWH: 71, BLIT_SURFACE_SRCDST: 72, BLUR_SELF_REGION: 80, LENS_SELF_REGION: 81, GLASS_SELF_REGION: 82, + COLOR_MATRIX_SELF_REGION: 83, // Text-layer DOM mutations. They ride the draw stream so the elements and the pixels of // one frame are applied in one task; see SurfaceCommandRecorder.OP_TEXT_* and TextLayerOp. TEXT_ATTACH: 90, TEXT_DETACH: 91, TEXT_CLIP_CSS: 92, TEXT_RUN_CSS: 93, @@ -2939,6 +2940,168 @@ } } + // Graphics.colorMatrixRegion. colorMatrixBlendInPlace is a line-for-line + // mirror of com.codename1.ui.plaf.ColorMatrixBlend.apply (the reference every + // port matches) over RGBA ImageData instead of packed ARGB. Every float + // operation is Math.fround-ed in Java's evaluation order so the result is + // bit-identical to the Java reference, not merely close; the parity gate in + // scripts/verify-javascript-lens-parity.mjs pins that with checksums. + // maskAlpha is one byte per mask pixel (the mask's alpha), or null. + function colorMatrixBlendInPlace(data, w, h, matrix, maskAlpha, maskW, maskH, + cornerRadius, amount) { + var f = Math.fround; + amount = f(amount); + cornerRadius = f(cornerRadius); + if (amount <= 0 || w <= 0 || h <= 0) { + return; + } + var m = []; + for (var mi = 0; mi < 12; mi++) { + m.push(f(matrix[mi])); + } + var hw = f(w / 2); + var hh = f(h / 2); + var r = 0; + if (cornerRadius !== 0) { + r = cornerRadius < 0 ? Math.min(hw, hh) : Math.min(cornerRadius, Math.min(hw, hh)); + } + var inset = f(0.5); + var hwr = f(hw - r), hhr = f(hh - r); + for (var y = 0; y < h; y++) { + var py = f(f(y + inset) - hh); + for (var x = 0; x < w; x++) { + var k = amount; + if (r > 0) { + var px = f(f(x + inset) - hw); + var dx = f(Math.abs(px) - hwr); + var dy = f(Math.abs(py) - hhr); + var ax = dx > 0 ? dx : 0; + var ay = dy > 0 ? dy : 0; + var sdf = f(f(f(Math.sqrt(f(f(ax * ax) + f(ay * ay)))) + + Math.min(Math.max(dx, dy), 0)) - r); + var cov = f(inset - sdf); + k = f(k * (cov < 0 ? 0 : (cov > 1 ? 1 : cov))); + } + if (maskAlpha) { + var mx = Math.floor(x * maskW / w); + var my = Math.floor(y * maskH / h); + k = f(k * f(maskAlpha[my * maskW + mx] / 255)); + } + if (k <= 0) { + continue; + } + var i = (y * w + x) * 4; + var pr = f(data[i] / 255); + var pg = f(data[i + 1] / 255); + var pb = f(data[i + 2] / 255); + for (var row = 0; row < 3; row++) { + var o4 = row * 4; + var v = f(f(f(f(m[o4] * pr) + f(m[o4 + 1] * pg)) + f(m[o4 + 2] * pb)) + m[o4 + 3]); + v = v < 0 ? 0 : (v > 1 ? 1 : v); + var src = row === 0 ? pr : (row === 1 ? pg : pb); + var o = f(src + f(f(v - src) * k)); + data[i + row] = Math.round(f(o * 255)); + } + // data[i + 3], the destination alpha, is kept. + } + } + } + + // The alpha channel of a colorMatrixRegion mask at its own pixel size, or + // null when the source cannot be read (not decoded yet, zero sized, or a + // cross-origin image that taints the scratch canvas). + function colorMatrixMaskAlpha(source) { + if (!drawableImageSource(source)) { + return null; + } + var mw = (typeof source.naturalWidth === 'number' ? source.naturalWidth : source.width) | 0; + var mh = (typeof source.naturalHeight === 'number' ? source.naturalHeight : source.height) | 0; + if (mw <= 0 || mh <= 0) { + return null; + } + var scratch = createGlassScratchCanvas(mw, mh); + var scratchContext = scratch && scratch.getContext('2d'); + if (!scratchContext) { + return null; + } + scratchContext.drawImage(source, 0, 0, mw, mh); + var rgba = scratchContext.getImageData(0, 0, mw, mh).data; + var alpha = new Uint8ClampedArray(mw * mh); + for (var ai = 0; ai < alpha.length; ai++) { + alpha[ai] = rgba[ai * 4 + 3]; + } + return { alpha: alpha, w: mw, h: mh }; + } + + // In-place colour matrix over this surface's own pixels. The region is in + // device pixels as given: Graphics.colorMatrixRegion does not apply the + // current transform (core has already added its translation, and callers such + // as Tabs scale the region themselves), so the context transform -- the user + // transform -- is ignored here, as on iOS and JavaSE. Pixels off + // the canvas are left out but the shape and the mask stay anchored to the + // FULL region (as JavaSEPort.colorMatrixRegion does), so a region that is + // partly scrolled off does not squeeze its rounded corners or its glyphs. + // The result is drawn back through clearRect + drawImage rather than + // putImageData so the current clip is honoured (putImageData ignores it), + // matching the Metal draw on iOS. + // mask: null, or { alpha, w, h } from colorMatrixMaskAlpha. + function applyColorMatrixSelfRegion(ctx, x, y, width, height, matrix, mask, + cornerRadius, amount) { + if (!ctx.canvas || width <= 0 || height <= 0 || amount <= 0) { + return; + } + var rect = { x: Math.round(x), y: Math.round(y), w: Math.round(width), h: Math.round(height), scale: 1 }; + if (rect.w <= 0 || rect.h <= 0) { + return; + } + var fullW = rect.w, fullH = rect.h; + var rx = rect.x, ry = rect.y, rw = fullW, rh = fullH, ox = 0, oy = 0; + var canvasWidth = ctx.canvas.width | 0, canvasHeight = ctx.canvas.height | 0; + if (rx < 0) { ox = -rx; rw += rx; rx = 0; } + if (ry < 0) { oy = -ry; rh += ry; ry = 0; } + if (rx + rw > canvasWidth) { rw = canvasWidth - rx; } + if (ry + rh > canvasHeight) { rh = canvasHeight - ry; } + if (rw <= 0 || rh <= 0) { + return; + } + var part = ctx.getImageData(rx, ry, rw, rh).data; + var full = new Uint8ClampedArray(fullW * fullH * 4); + var row; + for (row = 0; row < rh; row++) { + full.set(part.subarray(row * rw * 4, (row + 1) * rw * 4), ((row + oy) * fullW + ox) * 4); + } + var scaledCorner = cornerRadius < 0 ? cornerRadius : Math.fround(cornerRadius * rect.scale); + colorMatrixBlendInPlace(full, fullW, fullH, matrix, mask ? mask.alpha : null, + mask ? mask.w : 0, mask ? mask.h : 0, scaledCorner, amount); + var outputCanvas = createGlassScratchCanvas(rw, rh); + var outputContext = outputCanvas && outputCanvas.getContext('2d'); + if (!outputContext) { + return; + } + var result = outputContext.createImageData(rw, rh); + for (row = 0; row < rh; row++) { + var from = ((row + oy) * fullW + ox) * 4; + result.data.set(full.subarray(from, from + rw * 4), row * rw * 4); + } + outputContext.putImageData(result, 0, 0); + ctx.save(); + try { + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.globalAlpha = 1; + ctx.globalCompositeOperation = 'source-over'; + try { ctx.filter = 'none'; } catch (_ecf) {} + ctx.shadowColor = 'rgba(0,0,0,0)'; + // Clear first: the output keeps the destination alpha, and drawing a + // translucent pixel source-over would blend it with the original + // instead of replacing it. Both calls are clipped, so pixels outside the + // clip are untouched. + ctx.clearRect(rx, ry, rw, rh); + ctx.drawImage(outputCanvas, rx, ry); + } finally { + ctx.restore(); + } + } + // Replay one command stream (opcodes + nums + objs) onto ``ctx``. function replaySurfaceCommands(ctx, ops, opCount, nums, objs) { var ni = 0; // num cursor @@ -3101,6 +3264,41 @@ } break; } + case SURF.COLOR_MATRIX_SELF_REGION: { + // Graphics.colorMatrixRegion. 20 nums: x, y, w, h, cornerRadius, + // amount, the 12 matrix floats, maskKind (0 none, 1 image in the obj + // slot, 2 surface), maskSurfaceId. Always 1 obj: the image marker, or + // null. Every argument is consumed before anything can fail, so a + // skipped op never desyncs the ops behind it. + var _cx = nums[ni++], _cy = nums[ni++], _cw = nums[ni++], _ch = nums[ni++]; + var _ccr = nums[ni++], _camount = nums[ni++]; + var _cmatrix = []; + for (var _cmi = 0; _cmi < 12; _cmi++) { + _cmatrix.push(nums[ni++]); + } + var _cmaskKind = nums[ni++] | 0, _cmaskSurface = nums[ni++] | 0; + var _cmaskImage = objs[oi++]; + if (_cw > 0 && _ch > 0 && _camount > 0 && ctx.canvas) { + try { + var _cmask = null; + var _cmaskOk = true; + if (_cmaskKind === 1 || _cmaskKind === 2) { + var _cmsrc = _cmaskKind === 1 ? surfaceImageSource(_cmaskImage) + : (surfaceTable[_cmaskSurface] ? surfaceTable[_cmaskSurface].canvas : null); + _cmask = colorMatrixMaskAlpha(_cmsrc); + // A mask that cannot be read yet paints nothing: applying the + // matrix unmasked would recolour the whole region. + _cmaskOk = _cmask != null; + } + if (_cmaskOk) { + applyColorMatrixSelfRegion(ctx, _cx, _cy, _cw, _ch, _cmatrix, _cmask, + _ccr, _camount); + } + } catch (_ecm) { + } + } + break; + } // Blitting one surface onto another goes through the same guard: a // zero-sized backing canvas throws exactly like a broken image, and // taking the rest of the batch with it is what truncates a frame. diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/JavascriptTargetIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/JavascriptTargetIntegrationTest.java index ceea3a7fd49..baf6cb9ef15 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/JavascriptTargetIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/JavascriptTargetIntegrationTest.java @@ -154,6 +154,13 @@ void generatesBrowserBundleForJavascriptTarget(CompilerHelper.CompilerConfig con && browserBridge.contains("_goffset = nums[ni++]") && browserBridge.contains("ctx.drawImage(outputCanvas, rx, ry)"), "Browser bridge should preserve and render the complete native glass recipe"); + assertTrue(browserBridge.contains("COLOR_MATRIX_SELF_REGION: 83") + && browserBridge.contains("function colorMatrixBlendInPlace") + && browserBridge.contains("function applyColorMatrixSelfRegion") + && browserBridge.contains("function colorMatrixMaskAlpha") + && browserBridge.contains("_cmaskSurface = nums[ni++]") + && browserBridge.contains("_cmaskImage = objs[oi++]"), + "Browser bridge should replay Graphics.colorMatrixRegion with its mask"); assertFalse(browserBridge.contains("var _lsw = _lw / _lm"), "Glass-tab lens must not regress to the coarse uniform-zoom fallback"); assertTrue(protocolDoc.contains("Version: 1")