반응형
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 |