Skip to content

Latest commit

 

History

History
144 lines (113 loc) · 5.87 KB

File metadata and controls

144 lines (113 loc) · 5.87 KB

Tear Effect: explicación paso a paso

1. Qué hace TearEffect.metal

El shader tearDistortion recibe, por píxel, la posición actual y una serie de parámetros que llegan desde SwiftUI:

  • tearStart: punto de inicio del desgarro (en el borde real de la vista).
  • tearEnd: punto actual del dedo (proyectado sobre la línea fija del desgarro).
  • tearProgress: progreso general del gesto (0...1).
  • maxGap: apertura máxima entre las dos mitades.
  • edgeRoughness: irregularidad del borde rasgado.
  • viewSize: tamaño de la vista para recortes seguros.

Flujo interno del shader:

  1. Si tearProgress es casi 0, no altera nada (layer.sample(position)).
  2. Construye la línea del desgarro con tearDir = tearEnd - tearStart.
  3. Obtiene dos ejes:
  • tearNorm: eje a lo largo del corte.
  • tearPerp: eje perpendicular al corte.
  1. Para cada píxel, calcula:
  • alongTear: cuánto avanza ese píxel sobre la línea de corte.
  • acrossTear: distancia perpendicular al corte.
  1. Define una apertura en forma de cuña:
  • máxima en tearStart.
  • cero en tearEnd.
  1. Aplica ruido sinusoidal sobre el borde para simular fibras rasgadas (edgeRoughness).
  2. Si el píxel cae dentro del hueco (absDist < halfGap), lo vuelve transparente.
  3. Si no cae en el hueco, desplaza el muestreo hacia su lado para “separar” visualmente las dos mitades.
  4. Si el muestreo queda fuera de viewSize, devuelve transparente para evitar artefactos.

Resultado: se ve un desgarro limpio con separación de mitades y borde irregular, sin deformación tipo “curl”.

2. Cómo se conecta en la estructura del package

La integración está separada en:

  • TearState.swift: estado + cálculos geométricos.
  • TearModifier.swift: orquestación del gesto + uniforms del shader.
  • Extensions/View+TearKitExtensions.swift: API pública tearable(...).

Estado (TearState)

Variables clave:

  • edgePoint: ancla real del desgarro en el borde.
  • fingerPoint: extremo actual del desgarro (dedo proyectado).
  • fixedDirection: dirección unitaria bloqueada al inicio del drag.
  • directionLocked: indica si ya se bloqueó la dirección.
  • gapWidth: apertura enviada como maxGap.
  • tearProgress: progreso enviado al shader.
  • lockProjection: baseline del momento de bloqueo para evitar saltos.
  • maxTrajectoryDistance: longitud máxima de la línea de desgarro que queda dentro de la vista.
  • hasFullyTorn: evita reactivar el gesto cuando ya se rompió completamente.

Render

En body(content:):

  1. geometryReader guarda viewSize.
  2. layerEffect crea un Shader(function:arguments:) para tearDistortion y le pasa los valores del estado.
  3. isEnabled activa el shader solo cuando:
  • hay gesto activo,
  • la dirección ya está bloqueada,
  • y hay apertura real (gapWidth > 0.1).
  1. El modifier no aplica caída/opacidad; esas reacciones se delegan fuera del componente mediante callbacks.

Gesto (DragGesture)

  1. Al primer onChanged, si el startLocation está cerca de un borde:
  • activa el estado,
  • inicializa edgePoint y fingerPoint con ese inicio.
  1. Mientras no hay bloqueo (directionLocked == false):
  • espera a superar ~20pt de drag,
  • cuando supera, llama lockDirection(from:to:cutDirection:).
  1. lockDirection:
  • calcula dirección unitaria,
  • hace raycast hacia atrás para encontrar el borde real (edgePoint),
  • guarda lockProjection con la proyección actual del dedo para usarlo como baseline.
  1. Hasta terminar esa fase inicial, se fuerza:
  • tearProgress = 0,
  • gapWidth = 0,
  • sin efecto visible.
  1. Ya bloqueada la dirección:
  • proyecta el dedo con projectedEnd(fingerLocation:),
  • limita la proyección al tramo válido dentro de la vista,
  • calcula distancia, progreso y apertura.
  1. En onEnded:
  • si tearProgress >= breakThreshold (por defecto 0.9): marca rotura total y notifica por callback.
  • si no: recuperación animada.

3. Regla de negocio: 90% rompe / menos de 90% recupera

La decisión se toma al finalizar el gesto:

  1. Se compara tearProgress contra breakThreshold.
    • tearProgress se calcula como:
    • distancia recorrida sobre la línea fija
    • dividida por la distancia máxima de esa misma línea dentro de la vista.
  2. Si supera el umbral:
  • se fuerza tearProgress = 1 y gap máximo,
  • se dispara onCompletion(true).
    • La animación/acción final (caída, fade, navegación, etc.) la decide la vista que usa el modifier.
  1. Si no supera el umbral:
  • primero se anima el cierre del corte por frames (interpolación explícita de finger/progress/gap a 0),
  • y al terminar se hace reset() interno,
  • se dispara onCompletion(false).

4. API expuesta para integración

La extensión tearable(...) ahora expone:

  • edgeRoughness: intensidad de irregularidad del borde rasgado.
  • maxGapWidth: apertura máxima cerca del inicio del corte.
  • breakThreshold: umbral de rotura total (0...1).
  • cutDirection: modo de dirección (.vertical, .horizontal, .anyDirection).
  • isBroken: Binding<Bool> opcional para observar/controlar estado roto.
  • onProgressChanged: callback continuo del progreso (0...1).
  • onCompletion: callback final con resultado booleano.

Ejemplos rápidos de cutDirection:

  • .vertical: restringe el corte a vertical.
  • .horizontal: restringe el corte a horizontal.
  • .anyDirection: sigue la dirección inicial del drag.

Semántica de onCompletion:

  • true: la vista se rompió del todo y cae.
  • false: no llegó al umbral y se recuperó.

Semántica de isBroken:

  • poner false dispara recuperación/reset.
  • poner true fuerza el estado roto externamente.

5. Relación mental rápida (Gesture -> State -> Shader -> Resultado)

  1. Usuario arrastra desde el borde.
  2. SwiftUI actualiza TearState.
  3. TearModifier manda uniforms al shader.
  4. tearDistortion calcula hueco + separación por píxel.
  5. Al soltar, se evalúa umbral (>=90% o no).
  6. Se informa el resultado con onCompletion(Bool).