void FffPolygonGenerator::processInsets(SliceDataStorage& storage, unsigned int layer_nr) 
{
    for(SliceMeshStorage& mesh : storage.meshes)
    {
        int inset_count = mesh.settings->getSettingAsCount("wall_line_count");
        if (mesh.settings->getSettingBoolean("magic_spiralize") && static_cast<int>(layer_nr) < mesh.settings->getSettingAsCount("bottom_layers") && layer_nr % 2 == 1)//Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight.
            inset_count += 5;
        SliceLayer* layer = &mesh.layers[layer_nr];
        int line_width_x = mesh.settings->getSettingInMicrons("wall_line_width_x");
        int line_width_0 = mesh.settings->getSettingInMicrons("wall_line_width_0");
        if (mesh.settings->getSettingBoolean("alternate_extra_perimeter"))
            inset_count += layer_nr % 2; 
        generateInsets(layer, line_width_0, line_width_x, inset_count, mesh.settings->getSettingBoolean("remove_overlapping_walls_0_enabled"), mesh.settings->getSettingBoolean("remove_overlapping_walls_x_enabled"));

        for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
        {
            if (layer->parts[partNr].insets.size() > 0)
            {
                sendPolygons(Inset0Type, layer_nr, layer->parts[partNr].insets[0], line_width_0);
                for(unsigned int inset=1; inset<layer->parts[partNr].insets.size(); inset++)
                    sendPolygons(InsetXType, layer_nr, layer->parts[partNr].insets[inset], line_width_x);
            }
        }
    }
}
void FffPolygonGenerator::processSkins(SliceDataStorage& storage, unsigned int layer_nr) 
{
    for(SliceMeshStorage& mesh : storage.meshes)
    {
        int extrusionWidth = mesh.settings->getSettingInMicrons("wall_line_width_x");
        generateSkins(layer_nr, mesh, extrusionWidth, mesh.settings->getSettingAsCount("bottom_layers"), mesh.settings->getSettingAsCount("top_layers"), mesh.settings->getSettingAsCount("skin_outline_count"), mesh.settings->getSettingBoolean("remove_overlapping_walls_0_enabled"), mesh.settings->getSettingBoolean("remove_overlapping_walls_x_enabled"));
        if (mesh.settings->getSettingInMicrons("infill_line_distance") > 0)
        {
            int infill_skin_overlap = 0;
            if (mesh.settings->getSettingInMicrons("infill_line_distance") > mesh.settings->getSettingInMicrons("infill_line_width") + 10)
            {
                infill_skin_overlap = extrusionWidth / 2;
            }
            generateSparse(layer_nr, mesh, extrusionWidth, infill_skin_overlap);
            if (mesh.settings->getSettingString("fill_perimeter_gaps") == "Skin")
            {
                generatePerimeterGaps(layer_nr, mesh, extrusionWidth, mesh.settings->getSettingAsCount("bottom_layers"), mesh.settings->getSettingAsCount("top_layers"));
            }
            else if (mesh.settings->getSettingString("fill_perimeter_gaps") == "Everywhere")
            {
                generatePerimeterGaps(layer_nr, mesh, extrusionWidth, 0, 0);
            }
        }

        SliceLayer& layer = mesh.layers[layer_nr];
        for(SliceLayerPart& part : layer.parts)
        {
            for (SkinPart& skin_part : part.skin_parts)
            {
                sendPolygons(SkinType, layer_nr, skin_part.outline, extrusionWidth);
            }
        }
    }
}
void FffPolygonGenerator::processFuzzyWalls(SliceMeshStorage& mesh)
{
    int64_t fuzziness = mesh.getSettingInMicrons("magic_fuzzy_skin_thickness");
    int64_t avg_dist_between_points = mesh.getSettingInMicrons("magic_fuzzy_skin_point_dist");
    int64_t min_dist_between_points = avg_dist_between_points * 3 / 4; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
    int64_t range_random_point_dist = avg_dist_between_points / 2;
    for (unsigned int layer_nr = 0; layer_nr < mesh.layers.size(); layer_nr++)
    {
        SliceLayer& layer = mesh.layers[layer_nr];
        for (SliceLayerPart& part : layer.parts)
        {
            Polygons results;
            Polygons& skin = (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)? part.outline : part.insets[0];
            for (PolygonRef poly : skin)
            {
                // generate points in between p0 and p1
                PolygonRef result = results.newPoly();
                
                int64_t dist_left_over = rand() % (min_dist_between_points / 2); // the distance to be traversed on the line before making the first new point
                Point* p0 = &poly.back();
                for (Point& p1 : poly)
                { // 'a' is the (next) new point between p0 and p1
                    Point p0p1 = p1 - *p0;
                    int64_t p0p1_size = vSize(p0p1);    
                    int64_t dist_last_point = dist_left_over + p0p1_size * 2; // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size
                    for (int64_t p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + rand() % range_random_point_dist)
                    {
                        int r = rand() % (fuzziness * 2) - fuzziness;
                        Point perp_to_p0p1 = crossZ(p0p1);
                        Point fuzz = normal(perp_to_p0p1, r);
                        Point pa = *p0 + normal(p0p1, p0pa_dist) + fuzz;
                        result.add(pa);
                        dist_last_point = p0pa_dist;
                    }
                    dist_left_over = p0p1_size - dist_last_point;
                    
                    p0 = &p1;
                }
                while (result.size() < 3 )
                {
                    unsigned int point_idx = poly.size() - 2;
                    result.add(poly[point_idx]);
                    if (point_idx == 0) { break; }
                    point_idx--;
                }
                if (result.size() < 3)
                {
                    result.clear();
                    for (Point& p : poly)
                        result.add(p);
                }
            }
            skin = results;
            sendPolygons(Inset0Type, layer_nr, skin, mesh.getSettingInMicrons("wall_line_width_0"));
        }
    }
}
void FffPolygonGenerator::processInsets(SliceDataStorage& storage, unsigned int layer_nr) 
{
    for(SliceMeshStorage& mesh : storage.meshes)
    {
        SliceLayer* layer = &mesh.layers[layer_nr];
        if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::SURFACE)
        {
            int inset_count = mesh.getSettingAsCount("wall_line_count");
            if (mesh.getSettingBoolean("magic_spiralize") && static_cast<int>(layer_nr) < mesh.getSettingAsCount("bottom_layers") && layer_nr % 2 == 1)//Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight.
                inset_count += 5;
            int line_width_x = mesh.getSettingInMicrons("wall_line_width_x");
            int line_width_0 = mesh.getSettingInMicrons("wall_line_width_0");
            if (mesh.getSettingBoolean("alternate_extra_perimeter"))
                inset_count += layer_nr % 2; 
            generateInsets(layer, mesh.getSettingInMicrons("machine_nozzle_size"), line_width_0, line_width_x, inset_count, mesh.getSettingBoolean("remove_overlapping_walls_0_enabled"), mesh.getSettingBoolean("remove_overlapping_walls_x_enabled"));

            for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
            {
                if (layer->parts[partNr].insets.size() > 0)
                {
//                     sendPolygons(Inset0Type, layer_nr, layer->parts[partNr].insets[0], line_width_0); // done after processing fuzzy skin
                    for(unsigned int inset=1; inset<layer->parts[partNr].insets.size(); inset++)
                        sendPolygons(InsetXType, layer_nr, layer->parts[partNr].insets[inset], line_width_x);
                }
            }
        }
        if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
        {
            for (PolygonRef polyline : layer->openPolyLines)
            {
                Polygons segments;
                for (unsigned int point_idx = 1; point_idx < polyline.size(); point_idx++)
                {
                    PolygonRef segment = segments.newPoly();
                    segment.add(polyline[point_idx-1]);
                    segment.add(polyline[point_idx]);
                }
                sendPolygons(Inset0Type, layer_nr, segments, mesh.getSettingInMicrons("wall_line_width_0"));
            }
        }
    }
}
void FffPolygonGenerator::slices2polygons_magicPolygonMode(SliceDataStorage& storage, TimeKeeper& timeKeeper)
{
    // const 
    unsigned int totalLayers = storage.meshes[0].layers.size();
    
    for(unsigned int layer_nr=0; layer_nr<totalLayers; layer_nr++)
    {
        for(SliceMeshStorage& mesh : storage.meshes)
        {
            SliceLayer* layer = &mesh.layers[layer_nr];
            for(SliceLayerPart& part : layer->parts)
            {
                sendPolygons(Inset0Type, layer_nr, part.outline, mesh.settings->getSettingInMicrons("wall_line_width_0"));
            }
        }
    }
}
void FffPolygonGenerator::processSkins(SliceDataStorage& storage, unsigned int layer_nr) 
{
    for(SliceMeshStorage& mesh : storage.meshes)
    {
        if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE) { continue; }
        
        int skin_extrusion_width = mesh.getSettingInMicrons("skin_line_width");
        int innermost_wall_extrusion_width = mesh.getSettingInMicrons("wall_line_width_x");
        int extrusionWidth_infill = mesh.getSettingInMicrons("infill_line_width");
        generateSkins(layer_nr, mesh, skin_extrusion_width, mesh.getSettingAsCount("bottom_layers"), mesh.getSettingAsCount("top_layers"), innermost_wall_extrusion_width, mesh.getSettingAsCount("skin_outline_count"), mesh.getSettingBoolean("skin_no_small_gaps_heuristic"), mesh.getSettingBoolean("remove_overlapping_walls_0_enabled"), mesh.getSettingBoolean("remove_overlapping_walls_x_enabled"));
        if (mesh.getSettingInMicrons("infill_line_distance") > 0)
        {
            int infill_skin_overlap = 0;
            if (mesh.getSettingInMicrons("infill_line_distance") > mesh.getSettingInMicrons("infill_line_width") + 10)
            {
                infill_skin_overlap = skin_extrusion_width / 2;
            }
            generateInfill(layer_nr, mesh, extrusionWidth_infill, infill_skin_overlap);
            if (mesh.getSettingAsFillPerimeterGapMode("fill_perimeter_gaps") == FillPerimeterGapMode::SKIN)
            {
                generatePerimeterGaps(layer_nr, mesh, skin_extrusion_width, mesh.getSettingAsCount("bottom_layers"), mesh.getSettingAsCount("top_layers"));
            }
            else if (mesh.getSettingAsFillPerimeterGapMode("fill_perimeter_gaps") == FillPerimeterGapMode::EVERYWHERE)
            {
                generatePerimeterGaps(layer_nr, mesh, skin_extrusion_width, 0, 0);
            }
        }

        bool frontend_can_show_polygon_as_filled_polygon = false;
        if (frontend_can_show_polygon_as_filled_polygon)
        {
            SliceLayer& layer = mesh.layers[layer_nr];
            for(SliceLayerPart& part : layer.parts)
            {
//                  sendPolygons(InfillType, layer_nr, part.infill_area[0], extrusionWidth_infill); // sends the outline, not the actual infill
                for (SkinPart& skin_part : part.skin_parts)
                {
                    sendPolygons(SkinType, layer_nr, skin_part.outline, innermost_wall_extrusion_width);
                }
            }
        }
    }
}
void FffPolygonGenerator::processPlatformAdhesion(SliceDataStorage& storage)
{
    switch(getSettingAsPlatformAdhesion("adhesion_type"))
    {
    case Adhesion_Skirt:
        if (getSettingInMicrons("draft_shield_height") == 0)
        { // draft screen replaces skirt
            generateSkirt(storage, getSettingInMicrons("skirt_gap"), getSettingInMicrons("skirt_line_width"), getSettingAsCount("skirt_line_count"), getSettingInMicrons("skirt_minimal_length"));
        }
        break;
    case Adhesion_Brim:
        generateSkirt(storage, 0, getSettingInMicrons("skirt_line_width"), getSettingAsCount("brim_line_count"), getSettingInMicrons("skirt_minimal_length"));
        break;
    case Adhesion_Raft:
        generateRaft(storage, getSettingInMicrons("raft_margin"));
        break;
    }
    
    sendPolygons(SkirtType, 0, storage.skirt, getSettingInMicrons("skirt_line_width"));
}
void FffPolygonGenerator::processPlatformAdhesion(SliceDataStorage& storage)
{
    switch(getSettingAsPlatformAdhesion("adhesion_type"))
    {
    case EPlatformAdhesion::SKIRT:
        if (getSettingInMicrons("draft_shield_height") == 0)
        { // draft screen replaces skirt
            generateSkirt(storage, getSettingInMicrons("skirt_gap"), getSettingAsCount("skirt_line_count"), getSettingInMicrons("skirt_minimal_length"));
        }
        break;
    case EPlatformAdhesion::BRIM:
        generateSkirt(storage, 0, getSettingAsCount("brim_line_count"), getSettingInMicrons("skirt_minimal_length"));
        break;
    case EPlatformAdhesion::RAFT:
        generateRaft(storage, getSettingInMicrons("raft_margin"));
        break;
    }
    
    Polygons skirt_sent = storage.skirt[0];
    for (int extruder = 1; extruder < storage.meshgroup->getExtruderCount(); extruder++)
        skirt_sent.add(storage.skirt[extruder]);
    sendPolygons(SkirtType, 0, skirt_sent, getSettingInMicrons("skirt_line_width"));
}
void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& time_keeper)
{
    if (commandSocket)
        commandSocket->beginSendSlicedObject();
    
    // const 
    unsigned int total_layers = storage.meshes.at(0).layers.size();
    //layerparts2HTML(storage, "output/output.html");
    for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
    {
        processInsets(storage, layer_number);
        
        Progress::messageProgress(Progress::Stage::INSET, layer_number+1, total_layers, commandSocket);
    }
    
    removeEmptyFirstLayers(storage, getSettingInMicrons("layer_height"), total_layers);
    
    if (total_layers < 1)
    {
        log("Stopping process because there are no layers.\n");
        return;
    }
        
    processOozeShield(storage, total_layers);
    
    Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper, commandSocket);  
            
    for(SliceMeshStorage& mesh : storage.meshes)
    {
        generateSupportAreas(storage, &mesh, total_layers, commandSocket);
        for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++)
        {
            Polygons& support = storage.support.supportLayers[layer_idx].supportAreas;
            sendPolygons(SupportType, layer_idx, support, getSettingInMicrons("support_line_width"));
        }
    }
    if (getSettingBoolean("support_roof_enable"))
    {
        generateSupportRoofs(storage, commandSocket, getSettingInMicrons("layer_height"), getSettingInMicrons("support_roof_height"));
    }
    
    Progress::messageProgressStage(Progress::Stage::SKIN, &time_keeper, commandSocket);
    for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
    {
        if (!getSettingBoolean("magic_spiralize") || static_cast<int>(layer_number) < getSettingAsCount("bottom_layers"))    //Only generate up/downskin and infill for the first X layers when spiralize is choosen.
        {
            processSkins(storage, layer_number);
        }
        Progress::messageProgress(Progress::Stage::SKIN, layer_number+1, total_layers, commandSocket);
    }
    
    for(unsigned int layer_number = total_layers-1; layer_number > 0; layer_number--)
    {
        for(SliceMeshStorage& mesh : storage.meshes)
            combineSparseLayers(layer_number, mesh, mesh.settings->getSettingAsCount("fill_sparse_combine"));
    }

    processWipeTower(storage, total_layers);
    
    processDraftShield(storage, total_layers);
    
    processPlatformAdhesion(storage);

}
void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& time_keeper)
{
    size_t total_layers = 0;
    for (SliceMeshStorage& mesh : storage.meshes)
    {
        total_layers = std::max<unsigned int>(total_layers, mesh.layers.size());
    }
    
    //layerparts2HTML(storage, "output/output.html");
    for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
    {
        processInsets(storage, layer_number);
        
        Progress::messageProgress(Progress::Stage::INSET, layer_number+1, total_layers, commandSocket);
    }
    
    removeEmptyFirstLayers(storage, getSettingInMicrons("layer_height"), total_layers);
    
    if (total_layers < 1)
    {
        log("Stopping process because there are no layers.\n");
        return;
    }
    
    Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper, commandSocket);  
            
    AreaSupport::generateSupportAreas(storage, total_layers, commandSocket);
    /*
    if (storage.support.generated)
    {
        for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++)
        {
            Polygons& support = storage.support.supportLayers[layer_idx].supportAreas;
            sendPolygons(SupportType, layer_idx, support, getSettingInMicrons("support_line_width"));
        }
    }
    */
    
    Progress::messageProgressStage(Progress::Stage::SKIN, &time_keeper, commandSocket);
    int mesh_max_bottom_layer_count = 0;
    if (getSettingBoolean("magic_spiralize"))
    {
        for(SliceMeshStorage& mesh : storage.meshes)
        {
            mesh_max_bottom_layer_count = std::max(mesh_max_bottom_layer_count, mesh.getSettingAsCount("bottom_layers"));
        }
    }
    for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
    {
        if (!getSettingBoolean("magic_spiralize") || static_cast<int>(layer_number) < mesh_max_bottom_layer_count)    //Only generate up/downskin and infill for the first X layers when spiralize is choosen.
        {
            processSkins(storage, layer_number);
        }
        Progress::messageProgress(Progress::Stage::SKIN, layer_number+1, total_layers, commandSocket);
    }
    
    for(unsigned int layer_number = total_layers-1; layer_number > 0; layer_number--)
    {
        for(SliceMeshStorage& mesh : storage.meshes)
            combineInfillLayers(layer_number, mesh, mesh.getSettingAsCount("infill_sparse_combine"));
    }

    storage.primeTower.computePrimeTowerMax(storage);
    storage.primeTower.generatePaths(storage, total_layers);
    
    processOozeShield(storage, total_layers);
        
    processDraftShield(storage, total_layers);
    
    processPlatformAdhesion(storage);

    
    for(SliceMeshStorage& mesh : storage.meshes)
    {
        if (mesh.getSettingBoolean("magic_fuzzy_skin_enabled"))
        {
            processFuzzyWalls(mesh);
        }
        else 
        { // only send polygon data
            for (unsigned int layer_nr = 0; layer_nr < total_layers; layer_nr++)
            {
                SliceLayer* layer = &mesh.layers[layer_nr];
                for(SliceLayerPart& part : layer->parts)
                {
                    sendPolygons(Inset0Type, layer_nr, (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)? part.outline : part.insets[0], mesh.getSettingInMicrons("wall_line_width_0"));
                }
            }
        }
    }
}