본문 바로가기
이것저것

3차원 나선형 매트릭스로 보는 소수의 분포 시각화

by 꼬맹님 2026. 5. 27.
반응형

3차원 공간(Voxel Grid) 속에 숫자를 배치하고 그 안에서 수학적 질서(특히 소수의 분포)를 찾아내는 '3D Spiral Matrix' 프로젝트입니다.

1. 프로젝트의 핵심 컨셉: '핵(Nucleus)'에서 피어나는 질서

이 프로젝트의 시작점은 정중앙에 위치한 숫자 '1'입니다. 마치 수정이 결정화되듯, 중앙의 핵에서부터 숫자들이 사방으로 뻗어 나갑니다.

단순히 순서대로 쌓는 것이 아니라, 다음과 같은 수학적 거리를 기준으로 배열했습니다.

  • 1단계(Shell): 체비쇼프 거리(Chebyshev Distance)를 이용해 겹겹이 양파 껍질처럼 층을 쌓습니다.
  • 2단계(Manhattan): 동일한 층 안에서는 맨해튼 거리(Manhattan Distance)를 우선시하여, 중심과 '면'이 닿아 있는 칸부터 숫자를 채워나갑니다.

이렇게 탄생한 매트릭스는 중심에서부터 가장 논리적이고 조화로운 방식으로 공간을 점유하게 됩니다.

2. 시각적 하이라이트: 소수(Prime Number)의 별자리

이 매트릭스 안에서 소수(Prime Number)는 특별한 대우를 받습니다.

  • Red Glowing: 소수에 해당하는 칸은 강렬한 빨간색 테두리와 굵은 선으로 강조됩니다.
  • Transparency: 일반 숫자는 투명도를 낮추어 배경처럼 흐르게 만들고, 소수들만이 공간 속에 '별자리'처럼 떠 있는 듯한 효과를 주었습니다.
  • X-ray View: hint(DISABLE_DEPTH_TEST)를 사용하여 겉면에 가려진 내부의 소수 분포까지 한눈에 투시할 수 있습니다.

3. 자유로운 탐색: 6자유도 내비게이션

단순히 보기만 하는 것이 아니라, 마우스를 이용해 이 가상 공간을 직접 탐험할 수 있습니다.

  • 좌클릭 드래그: 매트릭스를 360도 회전시킵니다.
  • 휠 스크롤: 중심부로 깊숙이 파고들거나 멀리서 조망합니다.
  • 휠 클릭 드래그(Pan): 화면의 중심 자체를 이동시켜 구석구석을 살펴봅니다. (줌 배율에 따른 이동 속도 보정 적용!)

4. Processing 코드 (Java)

이 시뮬레이션은 오픈소스 그래픽 라이브러리인 Processing(P3D) 환경에서 제작되었습니다. 아래는 전체 소스코드입니다.

/* 
 * 3D Spiral Matrix Visualization 
 * Features: Shell/Manhattan Sorting, Prime Highlighting, Pan/Zoom/Rotate Controls
 */

import java.util.*;

float angleX = 0.5;
float angleY = 0.5;
float zoom = 1.0; 
float cubeSize = 700; 

// 시점 이동(Pan)을 위한 오프셋 변수
float offsetX = 0;
float offsetY = 0;

// 격자 크기 (21x21x21) (홀수로 수정 가능)
int cols = 21; 
int rows = cols; 
int depths = cols; 

int[][][] gridSpace;
PShape totalGrid; 

void setup() {
  size(1000, 1000, P3D);
  gridSpace = new int[cols][rows][depths];
  
  assignUserFormulaNumbers();
  buildGridShape();
  
  PFont font = createFont("SansSerif.bold", 30);
  textFont(font);
}

void draw() {
  background(0); 
  hint(DISABLE_DEPTH_TEST); 
  
  // 시점 이동 적용
  translate(width/2 + offsetX, height/2 + offsetY, 0);
  scale(zoom);
  
  // 컨트롤 로직
  if (mousePressed) {
    if (mouseButton == LEFT) {
      angleY += (mouseX - pmouseX) * 0.01;
      angleX += (mouseY - pmouseY) * 0.01;
    } else if (mouseButton == CENTER) {
      offsetX += (mouseX - pmouseX) * (1.0 / zoom);
      offsetY += (mouseY - pmouseY) * (1.0 / zoom);
    }
  }
  
  rotateX(angleX);
  rotateY(angleY);

  shape(totalGrid); 
  drawNumbers(); 

  hint(ENABLE_DEPTH_TEST);
}

// [Algorithm] Shell -> Manhattan 정렬 기반 숫자 배치
void assignUserFormulaNumbers() {
  int cx = cols / 2;
  int cy = rows / 2;
  int cz = depths / 2;
  
  List<VoxelPos> posList = new ArrayList<VoxelPos>();
  
  for (int i = 0; i < cols; i++) {
    for (int j = 0; j < rows; j++) {
      for (int k = 0; k < depths; k++) {
        posList.add(new VoxelPos(i, j, k, cx, cy, cz));
      }
    }
  }
  
  Collections.sort(posList, new Comparator<VoxelPos>() {
    public int compare(VoxelPos p1, VoxelPos p2) {
      if (p1.shell != p2.shell) return p1.shell - p2.shell; 
      if (p1.manhattan != p2.manhattan) return p1.manhattan - p2.manhattan; 
      if (p1.z != p2.z) return p1.z - p2.z;
      if (p1.y != p2.y) return p1.y - p2.y;
      return p1.x - p2.x;
    }
  });
  
  for (int i = 0; i < posList.size(); i++) {
    VoxelPos p = posList.get(i);
    gridSpace[p.x][p.y][p.z] = i + 1;
  }
}

class VoxelPos {
  int x, y, z;
  int shell, manhattan;
  
  VoxelPos(int x, int y, int z, int cx, int cy, int cz) {
    this.x = x; this.y = y; this.z = z;
    int dx = abs(x - cx);
    int dy = abs(y - cy);
    int dz = abs(z - cz);
    this.shell = max(dx, max(dy, dz));
    this.manhattan = dx + dy + dz;
  }
}

boolean isPrime(int n) {
  if (n < 2) return false;
  if (n == 2) return true;
  if (n % 2 == 0) return false;
  for (int i = 3; i <= sqrt(n); i += 2) {
    if (n % i == 0) return false;
  }
  return true;
}

void buildGridShape() {
  totalGrid = createShape(GROUP);
  float w = cubeSize / cols;
  float start = -cubeSize / 2;

  for (int i = 0; i < cols; i++) {
    for (int j = 0; j < rows; j++) {
      for (int k = 0; k < depths; k++) {
        float x = start + (i * w) + w/2;
        float y = start + (j * w) + w/2; 
        float z = start + (k * w) + w/2;
        
        PShape boxShape = createShape(BOX, w, w, w);
        boxShape.translate(x, y, z);
        boxShape.noFill(); 
        
        int val = gridSpace[i][j][k];
        if (isPrime(val)) {
          boxShape.setStroke(color(255, 0, 0, 220)); 
          boxShape.setStrokeWeight(2.0); 
        } else {
          boxShape.setStroke(color(0, 150, 255, 8)); 
          boxShape.setStrokeWeight(0.5);
        }
        totalGrid.addChild(boxShape);
      }
    }
  }
}

void drawNumbers() {
  float w = cubeSize / cols;
  float start = -cubeSize / 2;
  textAlign(CENTER, CENTER);
  
  for (int i = 0; i < cols; i++) {
    for (int j = 0; j < rows; j++) {
      for (int k = 0; k < depths; k++) {
        int val = gridSpace[i][j][k];
        float x = start + (i * w) + w/2;
        float y = start + (j * w) + w/2;
        float z = start + (k * w) + w/2;
        
        pushMatrix();
        translate(x, y, z);
        rotateY(-angleY); rotateX(-angleX); 
        
        if (val == 1) {
          fill(255, 255, 255); textSize(w * 0.7);
          text("1", 0, 0);
        } else if (isPrime(val)) {
          fill(255, 50, 50); textSize(w * 0.4);
          text(str(val), 0, 0);
        } else {
          fill(100, 100, 100, 80); textSize(w * 0.25);
          text(str(val), 0, 0);
        }
        popMatrix();
      }
    }
  }
}

void mouseWheel(MouseEvent event) {
  float e = event.getCount();
  if (e < 0) zoom *= 1.1; else zoom *= 0.9;
}

 

21*21*21 매트릭스

 

 

11*11*11 매트릭스

 

숫자 배열 알고리즘을 바꿔서 해보면 더 좋을 것 같습니다.

반응형

'이것저것' 카테고리의 다른 글

Transistor vs MOSFET, 모터 제어에 어떤 차이가 있을까?  (0) 2025.04.25
PC PORT 포트 열기  (0) 2024.02.21