#if SPECIAL #include "main.h" #endif static real32 Bezier_SolveYForX(v2 Point_P0, v2 Point_P1, v2 Point_P2, v2 Point_P3, real32 TargetX) { real32 Y = 0; v2 m1 = (Point_P2 - Point_P0) / (2 * Tau); v2 m2 = (Point_P3 - Point_P1) / (2 * Tau); real32 Precision = 0.000001; real32 t = (TargetX - Point_P0.x) / (Point_P3.x - Point_P0.x); int Iterations = 0; for (;;) { real32 t2 = t * t; real32 t3 = t2 * t; real32 mt = 1-t; real32 mt2 = mt * mt; real32 mt3 = mt2 * mt; v2 Point = (Point_P0 * mt3) + (3 * Point_P1 * mt2 * t) + (3 * Point_P2 * mt * t2) + (Point_P3 * t3); bool32 Cond1 = (Point.x <= (TargetX - Precision)); bool32 Cond2 = (Point.x >= (TargetX + Precision)); if ((Cond1 || Cond2) && Iterations < 10) { t = t * TargetX / Point.x; Iterations++; } else { Y = Point.y; break; } } return Y; } // All the extra inputs on the second line are for when we need to take // interactive mode into account. // TODO(fox): Incorporate sorting for non-continuous shapes. static uint32 Bezier_Shape_Sort(memory *Memory, shape_layer *Shape, bezier_point *PointData, project_state *State, layer_transforms T, int Width, int Height, int CompWidth, int CompHeight, real32 Radius, bool32 Interact) { bezier_point *PointStart = PointData; for (int i = 0; i < Shape->Point_Count; i++) { bezier_point Point = *Bezier_LookupAddress(Memory, Shape->Block_Bezier_Index, i, 1); if (Width != 0 && Height != 0) { for (int a = 0; a < 3; a++) { Point.Pos[a] = Point.Pos[a] * V2(Width, Height); } } if (State->Interact_Active == interact_type_keyframe_move && Interact && Point.IsSelected) { v2 Pos = Point.Pos[0]; Pos = TransformPoint(T, Width, Height, Pos); Pos.x += State->Interact_Offset[0]; Pos.y += State->Interact_Offset[1]; Pos = T_CompPosToLayerPos(T, CompWidth, CompHeight, Width, Height, Pos.x, Pos.y); Point.Pos[0] = Pos; } // Corner rounder if ((Radius > 0 && Point.Type == interpolation_type_linear) && (Shape->IsClosed || (i != 0 && i != (Shape->Point_Count - 1)) )) { v2 Pos = Point.Pos[0]; int Index_Prev = (i != 0) ? i-1 : Shape->Point_Count-1; int Index_Next = (i != Shape->Point_Count-1) ? i+1 : 0; bezier_point *Point_Prev = Bezier_LookupAddress(Memory, Shape->Block_Bezier_Index, Index_Prev, 1); bezier_point *Point_Next = Bezier_LookupAddress(Memory, Shape->Block_Bezier_Index, Index_Next, 1); v2 Pos_Prev = Point_Prev->Pos[0]; v2 Pos_Next = Point_Next->Pos[0]; if (Width != 0 && Height != 0) { Pos_Prev = Pos_Prev * V2(Width, Height); Pos_Next = Pos_Next * V2(Width, Height); } // TODO(fox): debloat if (State->Interact_Active == interact_type_keyframe_move && Interact && Point_Prev->IsSelected) { v2 Pos = Pos_Prev; Pos = TransformPoint(T, Width, Height, Pos); Pos.x += State->Interact_Offset[0]; Pos.y += State->Interact_Offset[1]; Pos = T_CompPosToLayerPos(T, CompWidth, CompHeight, Width, Height, Pos.x, Pos.y); Pos_Prev = Pos; } if (State->Interact_Active == interact_type_keyframe_move && Interact && Point_Next->IsSelected) { v2 Pos = Pos_Next; Pos = TransformPoint(T, Width, Height, Pos); Pos.x += State->Interact_Offset[0]; Pos.y += State->Interact_Offset[1]; Pos = T_CompPosToLayerPos(T, CompWidth, CompHeight, Width, Height, Pos.x, Pos.y); Pos_Next = Pos; } v2 Vector_Prev = Pos - Pos_Prev; v2 Vector_Next = Pos - Pos_Next; real32 Length_Prev = sqrtf(LengthSq(Vector_Prev)); real32 Length_Next = sqrtf(LengthSq(Vector_Next)); // real32 RadAngle = acos(Inner(Vector_Prev, VectorR) / (LengthL * LengthR)) * PI / 180; // real32 AngleKappa = (4.f/3) * tan(RadAngle * 1/4); real32 Ratio_Prev = Min(Radius / Length_Prev, 0.5); real32 Ratio_Prev_Inv = 1.0f - Ratio_Prev; real32 Ratio_Next = Min(Radius / Length_Next, 0.5); real32 Ratio_Next_Inv = 1.0f - Ratio_Next; v2 Point_1 = Pos_Prev + V2(Vector_Prev.x * Ratio_Prev_Inv, (Vector_Prev.y * Ratio_Prev_Inv)); v2 Point_2 = Vector_Prev * Ratio_Prev * (1-KAPPA); v2 Point_3 = Pos_Next + V2(Vector_Next.x * Ratio_Next_Inv, Vector_Next.y * Ratio_Next_Inv); v2 Point_4 = Vector_Next * Ratio_Next * (1-KAPPA); *PointData++ = { 1, { Point_1, Point_2, V2(0, 0) }, interpolation_type_bezier, Point.IsSelected }; *PointData++ = { 1, { Point_3, V2(0, 0), Point_4 }, interpolation_type_bezier, Point.IsSelected }; } else { *PointData++ = Point; } } return PointData - PointStart; } static bezier_point * Bezier_LookupAddress(memory *Memory, uint16 *Block_Bezier_Index, uint16 Index, bool32 AssertExists) { Assert(Index < (MAX_KEYFRAMES_PER_BLOCK * MAX_KEYFRAME_BLOCKS)); int SeekBlock = Index / MAX_KEYFRAMES_PER_BLOCK; int SeekIndex = Index - (SeekBlock * MAX_KEYFRAMES_PER_BLOCK); block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, Block_Bezier_Index[SeekBlock], 0); Assert(Bezier->Occupied); if (AssertExists) Assert(Bezier->Point[SeekIndex].Occupied); return &Bezier->Point[SeekIndex]; } static void Bezier_Interact_Evaluate(project_state *State, bezier_point *PointAddress, v2 *Pos, real32 GraphZoomHeight, real32 Y_Increment) { Pos[0] = PointAddress->Pos[0]; Pos[1] = PointAddress->Pos[1]; Pos[2] = PointAddress->Pos[2]; if (PointAddress->IsSelected) { if (State->Interact_Active == interact_type_keyframe_move) { if (State->Interact_Modifier != 2) Pos[PointAddress->IsSelected - 1].x += (int32)State->Interact_Offset[0]; if (State->Interact_Modifier != 1) Pos[PointAddress->IsSelected - 1].y -= (State->Interact_Offset[1] / GraphZoomHeight / Y_Increment); } else if (State->Interact_Active == interact_type_keyframe_scale) { Pos[1].x += State->Interact_Offset[0]; Pos[2].x -= State->Interact_Offset[0]; } else if (State->Interact_Active == interact_type_keyframe_rotate) { // how do I do this?? Assert(0); } } } static void Bezier_Add(memory *Memory, memory_table_list TableName, uint16 *Block_Bezier_Index, uint16 *Block_Bezier_Count, uint16 *PointCount, bezier_point PointData) { int k = 0; for (;;) { int SeekBlock = k / MAX_KEYFRAMES_PER_BLOCK; if ((SeekBlock + 1) > *Block_Bezier_Count) { Block_Bezier_Index[SeekBlock] = Memory_Block_AllocateNew(Memory, F_Bezier); block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, Block_Bezier_Index[SeekBlock], 0); Bezier->Occupied = true; History_Action_Swap(Memory, TableName, sizeof(*Block_Bezier_Count), Block_Bezier_Count); *Block_Bezier_Count += 1; } bezier_point *Point = Bezier_LookupAddress(Memory, Block_Bezier_Index, k, 0); if (!Point->Occupied) { History_Action_Swap(Memory, F_Bezier, sizeof(*Point), Point); *Point = PointData; History_Action_Swap(Memory, TableName, sizeof(*PointCount), PointCount); *PointCount += 1; return; } k++; } } static void Bezier_Delete(memory *Memory, memory_table_list TableName, uint16 *Block_Bezier_Index, uint16 *PointCount, uint16 *ArrayLocation) { for (int p = 0; p < *PointCount; p++) { int k = ArrayLocation[p]; bezier_point *Point = Bezier_LookupAddress(Memory, Block_Bezier_Index, k); if (Point->IsSelected) { History_Action_Swap(Memory, F_Bezier, sizeof(Point->Occupied), &Point->Occupied); Point->Occupied = 0; History_Action_Swap(Memory, TableName, sizeof(*PointCount), PointCount); *PointCount -= 1; } } } static int32 Bezier_CheckSameX(memory *Memory, memory_table_list TableName, uint16 *Block_Bezier_Index, uint16 KeyframeCount, uint16 *ArrayLocation, real32 ValX) { for (int p = 0; p < KeyframeCount; p++) { int k = ArrayLocation[p]; bezier_point *Point = Bezier_LookupAddress(Memory, Block_Bezier_Index, k); if (Point->Pos[0].x == ValX) { return k; } } return -1; } // return all points static void Bezier_CubicCalcPointsCasteljauStep(void *Data, uint32 Size, uint32 *Increment, real32 x1, real32 y1, real32 x2, real32 y2, real32 x3, real32 y3, real32 x4, real32 y4, real32 tess_tol, int level) { real32 dx = x4 - x1; real32 dy = y4 - y1; real32 d2 = ((x2 - x4) * dy - (y2 - y4) * dx); real32 d3 = ((x3 - x4) * dy - (y3 - y4) * dx); d2 = (d2 >= 0) ? d2 : -d2; d3 = (d3 >= 0) ? d3 : -d3; if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) { real32 *Address = (real32 *)((uint8 *)Data + *Increment*Size); *Address = x4; *(Address + 1) = y4; *(Address + 2) = 0; *Increment += 1; } else if (level < 10) { real32 x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f; real32 x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f; real32 x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f; real32 x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f; real32 x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f; real32 x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f; Bezier_CubicCalcPointsCasteljauStep(Data, Size, Increment, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); Bezier_CubicCalcPointsCasteljauStep(Data, Size, Increment, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); } } void * Bezier_CubicCalcPoints(v2 p1, v2 p2, v2 p3, v2 p4, void *Data, uint32 Size) { uint32 Increment = 0; real32 tess_tol = TESS_TOL; void *Pointer = Data; Bezier_CubicCalcPointsCasteljauStep(Pointer, Size, &Increment, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0); return ((uint8 *)Data + (Increment * Size)); } v2 Bezier_LineDist(v2 a, v2 b, v2 p) { v2 ap = p - a; v2 ab_dir = b - a; real32 dot = ap.x * ab_dir.x + ap.y * ab_dir.y; if (dot < 0.0f) { return a; } real32 ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y; if (dot > ab_len_sqr) { return b; } return p - (a + ab_dir * dot / ab_len_sqr); } v2 Bezier_LineClosestPoint(v2 a, v2 b, v2 p) { v2 ap = p - a; v2 ab_dir = b - a; real32 dot = ap.x * ab_dir.x + ap.y * ab_dir.y; if (dot < 0.0f) { return a; } real32 ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y; if (dot > ab_len_sqr) { return b; } return a + ab_dir * dot / ab_len_sqr; } // The ratio here is just the dot product divided by the squared length. v2 Bezier_LineClosestPointR(v2 a, v2 b, v2 p, real32 *ratio) { v2 ap = p - a; v2 ab_dir = b - a; real32 dot = ap.x * ab_dir.x + ap.y * ab_dir.y; if (dot < 0.0f) { *ratio = 0.0f; return a; } real32 ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y; if (dot > ab_len_sqr) { *ratio = 1.0f; return b; } *ratio = dot / ab_len_sqr; return a + ab_dir * dot / ab_len_sqr; } static void Bezier_CubicBoxCasteljauStep(v2 Min, v2 Max, v2 *p_closest, v2 *p_last, real32 *p_closest_dist2, real32 x1, real32 y1, real32 x2, real32 y2, real32 x3, real32 y3, real32 x4, real32 y4, real32 tess_tol, int level) { real32 dx = x4 - x1; real32 dy = y4 - y1; real32 d2 = ((x2 - x4) * dy - (y2 - y4) * dx); real32 d3 = ((x3 - x4) * dy - (y3 - y4) * dx); d2 = (d2 >= 0) ? d2 : -d2; d3 = (d3 >= 0) ? d3 : -d3; if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) { v2 p_current = V2(x4, y4); v2 p[4] = { Min, Max, V2(Min.x, Max.y), V2(Max.y, Min.x) }; for (int i = 0; i < 4; i++) { v2 p_line = Bezier_LineClosestPoint(*p_last, p_current, p[i]); real32 dist2 = LengthSq(p[i] - p_line); if (dist2 < p_closest_dist2[i]) { p_closest[i] = p_line; p_closest_dist2[i] = dist2; } } *p_last = p_current; } else if (level < 10) { real32 x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f; real32 x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f; real32 x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f; real32 x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f; real32 x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f; real32 x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f; Bezier_CubicBoxCasteljauStep(Min, Max, p_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); Bezier_CubicBoxCasteljauStep(Min, Max, p_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); } } static void Bezier_BoxTest(v2 p1, v2 p2, v2 p3, v2 p4, v2 Min, v2 Max, v2 *p_closest) { real32 tess_tol = TESS_TOL; v2 p_last = p1; real32 p_closest_dist2[4] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; Bezier_CubicBoxCasteljauStep(Min, Max, p_closest, &p_last, p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0); } #if 0 // Following the algorithm, we take the ratio from the _leftmost_ point in each // subdivision of the cubic spline until we're within tess_tol, and then we // interpolate it with the subdivision's rightmost point in the ClosestPoint call. // The pow(0.5, level) represents the ratio of the next subdivision's leftmost // point (AKA the rightmost point of the current subdivision). // finds the point closest to p and also fills out its ratio along the curve static void Bezier_CubicClosestPointCasteljauStep(v2 p, v2 *p_closest, real32 ratio, real32 *r_closest, v2 *p_last, real32 *p_closest_dist2, real32 x1, real32 y1, real32 x2, real32 y2, real32 x3, real32 y3, real32 x4, real32 y4, real32 tess_tol, int level) { real32 dx = x4 - x1; real32 dy = y4 - y1; real32 d2 = ((x2 - x4) * dy - (y2 - y4) * dx); real32 d3 = ((x3 - x4) * dy - (y3 - y4) * dx); d2 = (d2 >= 0) ? d2 : -d2; d3 = (d3 >= 0) ? d3 : -d3; if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) { v2 p_current = V2(x4, y4); real32 added_ratio; v2 p_line = Bezier_LineClosestPoint(*p_last, p_current, p, &added_ratio); real32 dist2 = LengthSq(p - p_line); if (dist2 < *p_closest_dist2) { *p_closest = p_line; *p_closest_dist2 = dist2; *r_closest = ratio + pow(0.5, level)*added_ratio; } *p_last = p_current; } else if (level < 10) { real32 x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f; real32 x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f; real32 x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f; real32 x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f; real32 x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f; real32 x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f; Bezier_CubicClosestPointCasteljauStep(p, p_closest, ratio, r_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); Bezier_CubicClosestPointCasteljauStep(p, p_closest, ratio + pow(0.5, level+1), r_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); } } real32 Bezier_CubicRatioOfPoint(v2 p1, v2 p2, v2 p3, v2 p4, v2 p) { real32 tess_tol = TESS_TOL; v2 p_last = p1; v2 p_closest; real32 p_closest_dist2 = FLT_MAX; real32 ratio = 0; Bezier_CubicClosestPointCasteljauStep(p, &p_closest, 0, &ratio, &p_last, &p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0); return ratio; } #endif #if 0 // A modified version of the bezier code in ImGui with extra features for bitmap and path interaction. // Function to convert a ratio back into a point for the bezier handles. v2 Line_RatioToPoint(v2 a, v2 b, real32 ratio) { v2 ab_dir = b - a; real32 ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y; real32 dot = ratio*ab_len_sqr; return a + ab_dir * dot / V2(ab_len_sqr); } // finds the min/max bounds of the curve static void Bezier_CubicMinMaxCasteljauStep(v2 *p_min, v2 *p_max, real32 x1, real32 y1, real32 x2, real32 y2, real32 x3, real32 y3, real32 x4, real32 y4, real32 tess_tol, int level) { real32 dx = x4 - x1; real32 dy = y4 - y1; real32 d2 = ((x2 - x4) * dy - (y2 - y4) * dx); real32 d3 = ((x3 - x4) * dy - (y3 - y4) * dx); d2 = (d2 >= 0) ? d2 : -d2; d3 = (d3 >= 0) ? d3 : -d3; if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) { v2 p_current = V2(x4, y4); if (p_current.x < p_min->x) { p_min->x = p_current.x; } if (p_current.y < p_min->y) { p_min->y = p_current.y; } if (p_current.x > p_max->x) { p_max->x = p_current.x; } if (p_current.y > p_max->y) { p_max->y = p_current.y; } } else if (level < 10) { real32 x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f; real32 x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f; real32 x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f; real32 x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f; real32 x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f; real32 x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f; Bezier_CubicMinMaxCasteljauStep(p_min, p_max, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); Bezier_CubicMinMaxCasteljauStep(p_min, p_max, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); } } real32 Bezier_CubicRatioOfPoint(v2 p1, v2 p2, v2 p3, v2 p4, v2 p) { real32 tess_tol = TESS_TOL; v2 p_last = p1; v2 p_closest; real32 p_closest_dist2 = FLT_MAX; real32 ratio = 0; Bezier_CubicClosestPointCasteljauStep(p, &p_closest, 0, &ratio, &p_last, &p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0); return ratio; } // These functions will become more generalized as shapes are added. static void Mask_AddPointToLine(mask *Mask, uint16 Index, v2 Pos) { for (int i = Mask->NumberOfPoints - 1; i > Index; i--) { Mask->Point[i+1] = Mask->Point[i]; } mask_point *PointToAdd = &Mask->Point[Index+1]; PointToAdd->Pos = Pos; Mask->NumberOfPoints++; } static void Mask_PushPoint(mask *Mask, v2 Pos) { mask_point *PointToAdd = &Mask->Point[Mask->NumberOfPoints]; PointToAdd->Pos = Pos; Mask->NumberOfPoints++; } static void Mask_ShiftPointers(mask *Mask, int16 Increment, int16 StopAt) { if (Increment > 0) { int16 i = Mask->NumberOfPoints - 1; while (i >= StopAt) { mask_point *CurrentPoint = &Mask->Point[i]; mask_point *NextPoint = &Mask->Point[i + Increment]; *NextPoint = *CurrentPoint; i--; } } else { int16 i = StopAt; while (i <= Mask->NumberOfPoints - 1) { mask_point *CurrentPoint = &Mask->Point[i]; mask_point *NextPoint = &Mask->Point[i - Increment]; *CurrentPoint = *NextPoint; i++; } } } static void Mask_DeletePoint(memory *Memory, mask *Mask, uint16 Index) { History_Entry_Commit(Memory, action_entry_default, "Delete keyframe"); mask_point *MaskPointIndex = &Mask->Point[Index]; History_Action_StoreData(Memory, MaskPointIndex, sizeof(mask_point)); History_Action_Change_Decrement(Memory, &Mask->NumberOfPoints, action_type_change_u16); // History_Action_Shift(Memory, action_type_shift_bezier, Mask, -1, Index); void *StartingAddress = &Mask->Point[0]; History_Action_Shift_2(Memory, StartingAddress, sizeof(mask_point), Mask->NumberOfPoints, -1, Index); // Mask_ShiftPointers(Mask, -1, Index); History_Entry_End(Memory); } // It's more useful to input the ratio here instead of the cursor position // since we have to use it to calculate the four new handle lengths (two on the // new point and one on each edge). static void Mask_AddPointToCurve(memory *Memory, mask *Mask, uint16 Index, real32 ratio) { mask_point *Point0 = &Mask->Point[Index]; mask_point *Point1 = &Mask->Point[Index+1]; if (Index + 1 == Mask->NumberOfPoints) Point1 = &Mask->Point[0]; v2 Point0_Pos_Right = Point0->Pos + Point0->TangentRight; v2 Point1_Pos_Left = Point1->Pos + Point1->TangentLeft; v2 Handle0_Half = Line_RatioToPoint(Point0->Pos, Point0_Pos_Right, ratio); v2 Handle1_Half = Line_RatioToPoint(Point1_Pos_Left, Point1->Pos, ratio); v2 Top_Half = Line_RatioToPoint(Point0_Pos_Right, Point1_Pos_Left, ratio); v2 NewHandleLeft = Line_RatioToPoint(Handle0_Half, Top_Half, ratio); v2 NewHandleRight = Line_RatioToPoint(Top_Half, Handle1_Half, ratio); v2 NewPos = Line_RatioToPoint(NewHandleLeft, NewHandleRight, ratio); History_Entry_Commit(Memory, action_entry_default, "Add point to curve"); v2 NewPoint0Pos = -(Point0->Pos - Handle0_Half); v2 NewPoint1Pos = -(Point1->Pos - Handle1_Half); History_Action_Change_V2(Memory, &Point0->TangentRight, &Point0->TangentRight, &NewPoint0Pos); History_Action_Change_V2(Memory, &Point1->TangentLeft, &Point1->TangentLeft, &NewPoint1Pos); void *StartingAddress = &Mask->Point[0]; History_Action_Shift_2(Memory, StartingAddress, sizeof(mask_point), Mask->NumberOfPoints, 1, Index); mask_point *PointToAdd = &Mask->Point[Index+1]; // NOTE(fox): The above shift duplicates the keyframe at Index into where // we're writing, Index+1. I'm using the Change action (which is normally // for changing values that already exist) on this intermediate keyframe // slot to save having to write a bunch of special actions for shifting and // adding new data. History_Action_Change_V2(Memory, &PointToAdd->Pos, &PointToAdd->Pos, &NewPos); v2 NewLeftPos = -(NewPos - NewHandleLeft); v2 NewRightPos = -(NewPos - NewHandleRight); History_Action_Change_V2(Memory, &PointToAdd->TangentLeft, &PointToAdd->TangentLeft, &NewLeftPos); History_Action_Change_V2(Memory, &PointToAdd->TangentRight, &PointToAdd->TangentRight, &NewRightPos); History_Action_Change_Increment(Memory, &Mask->NumberOfPoints, action_type_change_u16); History_Entry_End(Memory); } static void Mask_RasterizePoints(mask *Mask) { Mask->NumberOfVerts = 0; for (int i = 0; i < Mask->NumberOfPoints; i++) { mask_point Point0 = Mask->Point[i]; mask_point Point1 = Mask->Point[i+1]; if (i+1 == Mask->NumberOfPoints) Point1 = Mask->Point[0]; if (Point0.HandleBezier && Point1.HandleBezier) { Bezier_CubicCalcPoints(Point0.Pos, Point0.Pos + Point0.TangentRight, Point1.Pos + Point1.TangentLeft, Point1.Pos, Mask->TriangulatedPointCache, &Mask->NumberOfVerts); } else if (Point0.HandleBezier) { Bezier_CubicCalcPoints(Point0.Pos, Point0.Pos + Point0.TangentRight, Point1.Pos, Point1.Pos, Mask->TriangulatedPointCache, &Mask->NumberOfVerts); } else if (Point1.HandleBezier) { Bezier_CubicCalcPoints(Point0.Pos, Point0.Pos, Point1.Pos + Point1.TangentLeft, Point1.Pos, Mask->TriangulatedPointCache, &Mask->NumberOfVerts); } else { real32 *Data = (real32 *)Mask->TriangulatedPointCache + Mask->NumberOfVerts*3; *(Data++) = Point0.Pos.x; *(Data++) = Point0.Pos.y; *(Data++) = 0; // NOTE(fox): CubicCalcPoints sometimes misses generating the start // point of the next path in the above two cases, so I'm making // straight lines always add both points as a hotfix. This leads // to cases of duplicate verts, but it doesn't seem like it harms // the rendering in any way. *(Data++) = Point1.Pos.x; *(Data++) = Point1.Pos.y; *(Data++) = 0; Mask->NumberOfVerts += 2; } } } void Mask_TriangulateAndRasterize(memory *Memory, project_layer *Layer, mask *Mask) { if (!Mask->TriangulatedPointCache) { Mask->TriangulatedPointCache = AllocateMemory(Memory, 50*1024, P_VectorPoints); } Mask_RasterizePoints(Mask); GL_RasterizeShape(Layer, Mask); } #endif