<commit_before>//
//  CurveView.m
//  Binspect
//
//  Created by <NAME> on 26/10/2014.
//  Copyright (c) 2014 <NAME>. All rights reserved.
//

#import "CurveView.h"
#include <OpenGL/gl.h>

@implementation CurveView

// The following three Hilbert curve algorithms are based on those shown on Wikipedia.
// http://en.wikipedia.org/wiki/Hilbert_curve#Applications_and_mapping_algorithms
+ (unsigned long) getHilbertCurveIndex:(unsigned long)size forCoords:(CGPoint)point {
    CGPoint rotation;
    unsigned long result = 0, temporarySize;
    for (temporarySize = size / 2; temporarySize > 0; temporarySize /= 2) {
        rotation.x = ((int)point.x & temporarySize) > 0;
        rotation.y = ((int)point.y & temporarySize) > 0;
        result += temporarySize * temporarySize * ((3 * (int)rotation.x) ^ (int)rotation.y);
        [CurveView rotateHilbertCurveQuadrant:temporarySize by:rotation forPoint:&point];
    }
    
    return result;
}

+ (CGPoint) getHilbertCurveCoordinates:(unsigned long)size forIndex:(unsigned long)index {
    CGPoint rotation, result = CGPointMake(0, 0);
    unsigned long temporarySize, temporaryIndex = index;
    for (temporarySize = 1; temporarySize < size; temporarySize *= 2) {
        rotation.x = 1 & (temporaryIndex / 2);
        rotation.y = 1 & (temporaryIndex ^ (int)rotation.x);
        [CurveView rotateHilbertCurveQuadrant:temporarySize by:rotation forPoint:&result];
        result.x += temporarySize * (int)rotation.x;
        result.y += temporarySize * (int)rotation.y;
        temporaryIndex /= 4;
    }
    
    return result;
}

+ (void) rotateHilbertCurveQuadrant:(unsigned long)size by:(CGPoint)rotation forPoint:(CGPoint *)point {
    if (rotation.y == 0) {
        if (rotation.x == 1) {
            point->x = (size - 1) - point->x;
            point->y = (size - 1) - point->y;
        }
        
        // Swap coordinates, (x, y) -> (y, x)
        CGFloat temporary  = point->x;
        point->x = point->y;
        point->y = temporary;
    }
}

+ (unsigned int) getZigzagCurveIndex:(unsigned int)width forCoords:(CGPoint)point {
    unsigned int result, rowNumber = point.y;
    bool oddRow = (rowNumber % 2 == 1);
    result = (point.y * width) + (oddRow ? (width - 1) - point.x : point.x);
    
    return result;
}

+ (CGPoint) getZigzagCurveCoordinates:(unsigned int)width forIndex:(unsigned int)index {
    CGPoint result = CGPointMake(0, 0);
    unsigned int rowNumber = index / width;
    bool oddRow = (rowNumber % 2 == 1);
    result.x = index % width;
    result.y = rowNumber;
    if (oddRow) result.x = (width - 1) - result.x;
    
    return result;
}

// Note: _vertexArray and _colourArray should be indexed the same as _data (for sequential drawing, indexed access, etc.)
- (void) setCurveType:(CurveViewType)type {
    _type = type;
    switch(_type) {
        case CurveViewTypeHilbert:
            {
                unsigned int nearestPowerOfTwo = (unsigned int)(sqrt([_data length]) + 0.5f);
                
                // Bit Twiddling Hacks: "Round up to the next highest power of 2"
                // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
                nearestPowerOfTwo--;
                nearestPowerOfTwo |= nearestPowerOfTwo >> 1;
                nearestPowerOfTwo |= nearestPowerOfTwo >> 2;
                nearestPowerOfTwo |= nearestPowerOfTwo >> 4;
                nearestPowerOfTwo |= nearestPowerOfTwo >> 8;
                nearestPowerOfTwo |= nearestPowerOfTwo >> 16;
                nearestPowerOfTwo++;
                
                // Spit the curve into chunks to be stacked on top of each other for rectangular viewing.
                //
                // A _pointSize of 2^(2n) works particularly well for this, as it produces a desirable 'chunkWidth'
                // which ensures that the Hilbert chunk will finish in the bottom left, and so tiles excellently
                // with, and has proper visual locality with, the next chunk.
                //
                // In fact, if _pointSize is not of this type then chunking makes the visualisation somewhat ugly.
                // I wouldn't recommend using this chunking method (and instead enabling horizontal scrolling
                // of a square curve) for _pointSize values which are not of this type.
                unsigned long chunkWidth = nearestPowerOfTwo,
                              chunks    = 1,
                              maxWidth  = _viewBounds.width / _pointSize;
                if (nearestPowerOfTwo > maxWidth) {
                    chunkWidth = maxWidth;
                    chunks = (unsigned long)(((float)[_data length] / (float)(maxWidth * maxWidth)) + 1.0f);
                }
                
                // Set the vertex array values for each chunk
                for(int chunk = 0; chunk < chunks; chunk++) {
                    unsigned long currentChunkArea = chunkWidth * chunkWidth,
                                  lastPointCovered = chunkWidth * chunkWidth * chunk;
                    if (chunk + 1 == chunks) currentChunkArea = [_data length] - lastPointCovered;
                    for(unsigned long i = 0; i < currentChunkArea; i++) {
                        unsigned long index = lastPointCovered + i;
                        CGPoint point = [CurveView getHilbertCurveCoordinates:(chunkWidth * chunkWidth) forIndex:i];
                        
                        point.x = (point.x * _pointSize) + (_pointSize / 2.0f);
                        point.y = (point.y * _pointSize) + (_pointSize / 2.0f);
                        
                        // Assign the (x, y, z) co-ordinates for this point in the vertex array
                        _vertexArray[(3 * index)]     = point.x;
                        _vertexArray[(3 * index) + 1] = point.y + (chunk * chunkWidth * _pointSize);
                        _vertexArray[(3 * index) + 2] = 0.0f;
                    }
                }
                break;
            }
        case CurveViewTypeZigzag:
            {
                for(int i = 0; i < [_data length]; i++) {
                    CGPoint point = [CurveView getZigzagCurveCoordinates:(_viewBounds.width / _pointSize) forIndex:i];
                    point.x = (point.x * _pointSize) + (_pointSize / 2.0f);
                    point.y = (point.y * _pointSize) + (_pointSize / 2.0f);
                    
                    // Assign the (x, y, z) co-ordinates for this point in the vertex array
                    _vertexArray[(3 * i)]     = point.x;
                    _vertexArray[(3 * i) + 1] = point.y;
                    _vertexArray[(3 * i) + 2] = 0.0f;
                }
                break;
            }
    }
}
- (void) setCurveColourMode:(CurveViewColourMode)mode {
    if (mode == _colourMode) return; // Don't re-set the colour mode if it's not necessary.
    
    _colourMode = mode;
    switch (_colourMode) {
        case CurveViewColourModeSimilarity:
            for(int i = 0; i < (3 * [_data length]); i++)
                _colourArray[i] = rand() / (float)RAND_MAX;
            break;
        case CurveViewColourModeEntropy:
            break;
        case CurveViewColourModeStructural:
            // Note: Being able to change the repeat cycle size in preferences might be a nice touch for the future.
            // To consider: Having the cycle never repeat is useful sometimes (mainly for seeing raw files offsets)
            // [ Alt (square-size region cycling): (_viewBounds.width / _pointSize) * (_viewBounds.width / _pointSize) * 2 ]
            const unsigned int colourRepeatCycleSize = 128 * 128 * 2; // TODO: Reduce? Maybe? idek :/
            bool repeatingPalette = ([_data length] > colourRepeatCycleSize);
            NSMutableArray *palette = [[NSMutableArray alloc] init];
            NSColorSpace *rgbSpace = [NSColorSpace sRGBColorSpace];
            for(int i = 0; i < [_data length]; i++) {<commit_msg>change structural colour cycling to be chunk-relative<commit_after>//
//  CurveView.m
//  Binspect
//
//  Created by <NAME> on 26/10/2014.
//  Copyright (c) 2014 <NAME>. All rights reserved.
//

#import "CurveView.h"
#include <OpenGL/gl.h>

@implementation CurveView

// The following three Hilbert curve algorithms are based on those shown on Wikipedia.
// http://en.wikipedia.org/wiki/Hilbert_curve#Applications_and_mapping_algorithms
+ (unsigned long) getHilbertCurveIndex:(unsigned long)size forCoords:(CGPoint)point {
    CGPoint rotation;
    unsigned long result = 0, temporarySize;
    for (temporarySize = size / 2; temporarySize > 0; temporarySize /= 2) {
        rotation.x = ((int)point.x & temporarySize) > 0;
        rotation.y = ((int)point.y & temporarySize) > 0;
        result += temporarySize * temporarySize * ((3 * (int)rotation.x) ^ (int)rotation.y);
        [CurveView rotateHilbertCurveQuadrant:temporarySize by:rotation forPoint:&point];
    }
    
    return result;
}

+ (CGPoint) getHilbertCurveCoordinates:(unsigned long)size forIndex:(unsigned long)index {
    CGPoint rotation, result = CGPointMake(0, 0);
    unsigned long temporarySize, temporaryIndex = index;
    for (temporarySize = 1; temporarySize < size; temporarySize *= 2) {
        rotation.x = 1 & (temporaryIndex / 2);
        rotation.y = 1 & (temporaryIndex ^ (int)rotation.x);
        [CurveView rotateHilbertCurveQuadrant:temporarySize by:rotation forPoint:&result];
        result.x += temporarySize * (int)rotation.x;
        result.y += temporarySize * (int)rotation.y;
        temporaryIndex /= 4;
    }
    
    return result;
}

+ (void) rotateHilbertCurveQuadrant:(unsigned long)size by:(CGPoint)rotation forPoint:(CGPoint *)point {
    if (rotation.y == 0) {
        if (rotation.x == 1) {
            point->x = (size - 1) - point->x;
            point->y = (size - 1) - point->y;
        }
        
        // Swap coordinates, (x, y) -> (y, x)
        CGFloat temporary  = point->x;
        point->x = point->y;
        point->y = temporary;
    }
}

+ (unsigned int) getZigzagCurveIndex:(unsigned int)width forCoords:(CGPoint)point {
    unsigned int result, rowNumber = point.y;
    bool oddRow = (rowNumber % 2 == 1);
    result = (point.y * width) + (oddRow ? (width - 1) - point.x : point.x);
    
    return result;
}

+ (CGPoint) getZigzagCurveCoordinates:(unsigned int)width forIndex:(unsigned int)index {
    CGPoint result = CGPointMake(0, 0);
    unsigned int rowNumber = index / width;
    bool oddRow = (rowNumber % 2 == 1);
    result.x = index % width;
    result.y = rowNumber;
    if (oddRow) result.x = (width - 1) - result.x;
    
    return result;
}

// Note: _vertexArray and _colourArray should be indexed the same as _data (for sequential drawing, indexed access, etc.)
- (void) setCurveType:(CurveViewType)type {
    _type = type;
    switch(_type) {
        case CurveViewTypeHilbert:
            {
                unsigned int nearestPowerOfTwo = (unsigned int)(sqrt([_data length]) + 0.5f);
                
                // Bit Twiddling Hacks: "Round up to the next highest power of 2"
                // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
                nearestPowerOfTwo--;
                nearestPowerOfTwo |= nearestPowerOfTwo >> 1;
                nearestPowerOfTwo |= nearestPowerOfTwo >> 2;
                nearestPowerOfTwo |= nearestPowerOfTwo >> 4;
                nearestPowerOfTwo |= nearestPowerOfTwo >> 8;
                nearestPowerOfTwo |= nearestPowerOfTwo >> 16;
                nearestPowerOfTwo++;
                
                // Spit the curve into chunks to be stacked on top of each other for rectangular viewing.
                //
                // A _pointSize of 2^(2n) works particularly well for this, as it produces a desirable 'chunkWidth'
                // which ensures that the Hilbert chunk will finish in the bottom left, and so tiles excellently
                // with, and has proper visual locality with, the next chunk.
                //
                // In fact, if _pointSize is not of this type then chunking makes the visualisation somewhat ugly.
                // I wouldn't recommend using this chunking method (and instead enabling horizontal scrolling
                // of a square curve) for _pointSize values which are not of this type.
                unsigned long chunkWidth = nearestPowerOfTwo,
                              chunks    = 1,
                              maxWidth  = _viewBounds.width / _pointSize;
                if (nearestPowerOfTwo > maxWidth) {
                    chunkWidth = maxWidth;
                    chunks = (unsigned long)(((float)[_data length] / (float)(maxWidth * maxWidth)) + 1.0f);
                }
                
                // Set the vertex array values for each chunk
                for(int chunk = 0; chunk < chunks; chunk++) {
                    unsigned long currentChunkArea = chunkWidth * chunkWidth,
                                  lastPointCovered = chunkWidth * chunkWidth * chunk;
                    if (chunk + 1 == chunks) currentChunkArea = [_data length] - lastPointCovered;
                    for(unsigned long i = 0; i < currentChunkArea; i++) {
                        unsigned long index = lastPointCovered + i;
                        CGPoint point = [CurveView getHilbertCurveCoordinates:(chunkWidth * chunkWidth) forIndex:i];
                        
                        point.x = (point.x * _pointSize) + (_pointSize / 2.0f);
                        point.y = (point.y * _pointSize) + (_pointSize / 2.0f);
                        
                        // Assign the (x, y, z) co-ordinates for this point in the vertex array
                        _vertexArray[(3 * index)]     = point.x;
                        _vertexArray[(3 * index) + 1] = point.y + (chunk * chunkWidth * _pointSize);
                        _vertexArray[(3 * index) + 2] = 0.0f;
                    }
                }
                break;
            }
        case CurveViewTypeZigzag:
            {
                for(int i = 0; i < [_data length]; i++) {
                    CGPoint point = [CurveView getZigzagCurveCoordinates:(_viewBounds.width / _pointSize) forIndex:i];
                    point.x = (point.x * _pointSize) + (_pointSize / 2.0f);
                    point.y = (point.y * _pointSize) + (_pointSize / 2.0f);
                    
                    // Assign the (x, y, z) co-ordinates for this point in the vertex array
                    _vertexArray[(3 * i)]     = point.x;
                    _vertexArray[(3 * i) + 1] = point.y;
                    _vertexArray[(3 * i) + 2] = 0.0f;
                }
                break;
            }
    }
}
- (void) setCurveColourMode:(CurveViewColourMode)mode {
    if (mode == _colourMode) return; // Don't re-set the colour mode if it's not necessary.
    
    _colourMode = mode;
    switch (_colourMode) {
        case CurveViewColourModeSimilarity:
            for(int i = 0; i < (3 * [_data length]); i++)
                _colourArray[i] = rand() / (float)RAND_MAX;
            break;
        case CurveViewColourModeEntropy:
            break;
        case CurveViewColourModeStructural:
            // Note: Being able to change the repeat cycle size in preferences might be a nice touch for the future.
            // To consider: Having the cycle never repeat is useful sometimes (mainly for seeing raw files offsets)
            // [ Alt. absolute cycle size: 128 * 128 * 4. Can be difficult to see distinctions at different zoom sizes. ]
            const unsigned int colourRepeatCycleSize = (_viewBounds.width / _pointSize) * (_viewBounds.width / _pointSize) * 2;
            bool repeatingPalette = ([_data length] > colourRepeatCycleSize);
            NSMutableArray *palette = [[NSMutableArray alloc] init];
            NSColorSpace *rgbSpace = [NSColorSpace sRGBColorSpace];
            for(int i = 0; i < [_data length]; i++) {
{'fraction_non_alphanumeric': 0.10298816012027814, 'fraction_numerical': 0.01140136565808432, 'mean_word_length': 1.8915051621083137, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 57, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '472143', 'n_tokens_mistral': 4709, 'n_tokens_neox': 4380, 'n_words': 1292}