// QUARTZ_BASIC_PORT.pde // Port of the N88-BASIC program "QUARTZ.BAS" to Processing (Java Mode) // ***** Import required libraries ***** import java.util.Collections; import java.util.Comparator; import java.util.ArrayList; // ***** Global variables and constants ***** // Screen center coordinates (BASIC: X00, Y00) final int X00 = 400; final int Y00 = 300; // Constants (BASIC: PI, RAD, SQ2, SQ6) final float PI = 3.14159f; final float RAD = PI / 180.0f; final float SQ2 = (float)Math.sqrt(2.0); final float SQ6 = (float)Math.sqrt(6.0); // Colors (BASIC: CL1, CL2, CL3) final int CL1 = color(255, 0, 0); // Red (Si) final int CL2 = color(40, 90, 255); // Violet (O) final int CL3 = color(128); // Gray (bond lines) final int CL_WHITE = color(255); final int CL_BG = color(0); // Background color // Animation control (BASIC: THETA, PHAI, PH, C, D, K) float THETA = 0; // View angle (horizontal) float PHAI = 15; // View angle (vertical) float PH = 0; // Axis rotation angle (unused) // Drawing size and repetition (BASIC: N, M, R1, R2, SX, SY, SZ) final int N_CELL = 0; // Number of crystal cell repetitions (0 means a single cell) final int M = 14; // Coordinate scaling factor final float R1 = 12; // Radius of Si atoms (adjusted: 8 -> 16) final float R2 = 18; // Radius of O atoms (adjusted: 10 -> 20) float SX, SY, SZ; // Cell spacing // Coordinate arrays (BASIC: X(I,J), Y(I,J), Z(I,J)) float[][] X_coord = new float[5][7]; float[][] Y_coord = new float[5][7]; float[][] Z_coord = new float[5][7]; // Constants for 3D transformation (BASIC: XYZINIT) float THE, PHI; float SINTH, COSTH, SINPH, COSPH; float RR = 500; // Viewpoint distance (Z offset) float LL = 500; // Projection distance (focal length) // BASIC control variables int K = 0; int C = 0; // List for hidden-surface processing ArrayList drawingList = new ArrayList(); // Axis rotation constants float CT2, ST2, CP2, SP2; float STCP2, CTCP2, STSP2, CTSP2; // ***** Definition of drawing element classes (for hidden-surface processing) ***** // Atom information (Si or O) class Atom { float x, y, cz; // Projected coordinates and depth (CZ) float radius; // Radius (R1 or R2) int atomColor; // Color (CL1 or CL2) // Larger CZ means farther away float depth() { return cz; } } // Bond information class Bond { float x1, y1, cz1; // Start point (Si atom) float x2, y2, cz2; // End point (O atom) // Depth is defined as the average CZ of start and end points float depth() { return (cz1 + cz2) / 2.0f; } } // ***** SETUP ***** void setup() { // Processing initialization (BASIC: SCREEN 3, CONSOLE ,,,1, CLS 3) // Match BASIC screen size (640x400) size(800, 600); background(CL_BG); strokeWeight(1.5f); // Make bond lines easier to see // Initialize variables SX = (10 + 2 * SQ6 + 4 * SQ2) * M; SY = (10 + 2 * SQ6 + 4 * SQ2) * M; SZ = (10 + 2 * SQ6 + 4 * SQ2) * M; // Initialize crystal coordinates quartzInit(); // ★★★ Add frameRate(25) here ★★★ frameRate(25); } // ***** DRAW (main loop) ***** void draw() { // Replacement for BASIC: 1160 SCREEN 3,0,C,D:CLS 2 background(CL_BG); // Compute view transformation constants (BASIC: 1170 GOSUB *XYZINIT) xyzInit(); // Display information displayInfo(); // Crystal cell drawing loop (store drawing elements in the list) // 1240-1300 for (int JJ = 0; JJ <= N_CELL; JJ++) { for (int II = 0; II <= N_CELL; II++) { for (int KK = 0; KK <= N_CELL; KK++) { quartzCel(II, JJ, KK); } } } // --- Core of hidden-surface processing --- // 1. Sort based on depth (CZ) // Draw from back to front (larger CZ to smaller CZ) Collections.sort(drawingList, new Comparator() { @Override public int compare(Object a, Object b) { // Determine whether Atom or Bond and get depth() float depthA = (a instanceof Atom) ? ((Atom)a).depth() : ((Bond)a).depth(); float depthB = (b instanceof Atom) ? ((Atom)b).depth() : ((Bond)b).depth(); // Descending order (draw distant elements first) if (depthA < depthB) return 1; if (depthA > depthB) return -1; return 0; } }); // 2. Draw in the sorted order for (Object obj : drawingList) { if (obj instanceof Bond) { Bond b = (Bond)obj; stroke(CL3); line(b.x1, b.y1, b.x2, b.y2); } else if (obj instanceof Atom) { Atom a = (Atom)obj; drawAtom(a.x, a.y, a.radius, a.cz, a.atomColor); } } // --- End of hidden-surface processing --- // Animation update (BASIC: 1310-1330) K = K + 1; C = K % 2; THETA = THETA + 1; // Adjusted rotation speed (4 -> 1) // ★★★ Add image saving code here ★★★ // Using the ".tif" extension saves high-quality lossless (uncompressed) sequential images. // Images are saved in the "frames" folder inside the sketch directory. saveFrame("frames/quartz-####.jpg"); } // Exit with ESC key (BASIC: return to menu) void keyPressed() { if (key == ESC) { exit(); } } // ***** Subroutine (function) definitions ***** // Initialize crystal coordinates (BASIC: *QUARTZ.INIT 1360-1530) void quartzInit() { // Initial coordinates of 5 atoms (1370-1410) X_coord[0][1] = 5 + SQ6 + SQ2 * 0.5f; Y_coord[0][1] = 0; Z_coord[0][1] = 1 + SQ6 + SQ2 * 0.5f; X_coord[1][1] = 6 + SQ6; Y_coord[1][1] = SQ6; Z_coord[1][1] = SQ6; X_coord[2][1] = 6 + SQ6; Y_coord[2][1] = -SQ6; Z_coord[2][1] = SQ6; X_coord[3][1] = 3 + SQ6 + 0.4f; Y_coord[3][1] = 0; Z_coord[3][1] = 3 + SQ6 + 0.4f; X_coord[4][1] = X_coord[0][1] + SQ2 * 1.5f; Y_coord[4][1] = 0; Z_coord[4][1] = Z_coord[0][1] + SQ2 * 1.5f - 0.5f; // Scaling (1420-1440) for (int I = 0; I <= 4; I++) { X_coord[I][1] *= M; Y_coord[I][1] *= M; Z_coord[I][1] *= M; } // Coordinate generation by symmetry operations (1460-1520) for (int I = 0; I <= 4; I++) { X_coord[I][2] = Z_coord[I][1]; Y_coord[I][2] = Y_coord[I][1]; Z_coord[I][2] = X_coord[I][1]; X_coord[I][3] = Z_coord[I][1]; Y_coord[I][3] = Y_coord[I][1]; Z_coord[I][3] = -X_coord[I][1]; X_coord[I][4] = X_coord[I][1]; Y_coord[I][4] = Y_coord[I][1]; Z_coord[I][4] = -Z_coord[I][1]; X_coord[I][5] = X_coord[I][1]; Y_coord[I][5] = Z_coord[I][1]; Z_coord[I][5] = -Y_coord[I][1]; X_coord[I][6] = Z_coord[I][1]; Y_coord[I][6] = X_coord[I][1]; Z_coord[I][6] = -Y_coord[I][1]; } } // Draw a crystal cell (BASIC: *QUARTZ.CEL 1550-1760) // This function has been modified to add elements to the drawing list instead of drawing directly void quartzCel(int II, int JJ, int KK) { // Initialize drawing element list (initialized once per draw()) if (II == 0 && JJ == 0 && KK == 0) { drawingList.clear(); } // Loop for axis rotation subroutine (BASIC: 1560-1750) for (int TH = 0; TH <= 270; TH += 90) { tsfInit(TH, PH); // Six symmetry operations J=1 to 6 for (int J = 1; J <= 6; J++) { // Processing of Si atom (I=0) float X = X_coord[0][J]; float Y = Y_coord[0][J]; float Z = Z_coord[0][J]; PVector transformed_si = tsf(X, Y, Z, II, JJ, KK); ScreenPoint sp_si = xyz(transformed_si.x, transformed_si.y, transformed_si.z); float PX = sp_si.x; float PY = sp_si.y; float CZ_SI = sp_si.cz; // Depth of Si atom // Bonds to O atoms (I=1 to 4) and processing of O atoms for (int I = 1; I <= 4; I++) { float X_o = X_coord[I][J]; float Y_o = Y_coord[I][J]; float Z_o = Z_coord[I][J]; PVector transformed_o = tsf(X_o, Y_o, Z_o, II, JJ, KK); ScreenPoint sp_o = xyz(transformed_o.x, transformed_o.y, transformed_o.z); float QX = sp_o.x; float QY = sp_o.y; float CZ_O = sp_o.cz; // Depth of O atom // Add bond to drawing list Bond bond = new Bond(); bond.x1 = PX; bond.y1 = PY; bond.cz1 = CZ_SI; bond.x2 = QX; bond.y2 = QY; bond.cz2 = CZ_O; drawingList.add(bond); // Add O atom to drawing list Atom atom_o = new Atom(); atom_o.x = QX; atom_o.y = QY; atom_o.cz = CZ_O; atom_o.radius = R2; atom_o.atomColor = CL2; drawingList.add(atom_o); } // Add Si atom to drawing list Atom atom_si = new Atom(); atom_si.x = PX; atom_si.y = PY; atom_si.cz = CZ_SI; atom_si.radius = R1; atom_si.atomColor = CL1; drawingList.add(atom_si); } } } // Atom drawing helper function (emulates BASIC CIRCLE) void drawAtom(float cx, float cy, float radius, float cz, int atomColor) { // Adjust radius according to depth (BASIC: R*-.012*CZ) float adjustedRadius = radius - 0.012f * cz; // Draw filled circle with a spherical appearance noStroke(); fill(atomColor); ellipse(cx, cy, adjustedRadius * 2, adjustedRadius * 2); // Highlight/shadow (simple highlight) fill(CL_WHITE, 150); ellipse(cx - adjustedRadius * 0.2f, cy - adjustedRadius * 0.2f, adjustedRadius * 0.5f, adjustedRadius * 0.5f); } // Compute view transformation constants (BASIC: *XYZINIT 2310-2400) void xyzInit() { THE = THETA * RAD; PHI = (90 - PHAI) * RAD; // BASIC: 90-PHAI SINTH = sin(THE); SINPH = sin(PHI); COSTH = cos(THE); COSPH = cos(PHI); } // xyz coordinates → screen coordinates transformation (BASIC: *XYZ 2420-2480) class ScreenPoint { float x, y, cz; // x: projected x, y: projected y, cz: depth (BASIC CZ) ScreenPoint(float x, float y, float cz) { this.x = x; this.y = y; this.cz = cz; } } ScreenPoint xyz(float XX, float YY, float ZZ) { // Transformation to view coordinates (affine transform) float CX = -YY * SINTH + XX * COSTH; float CY = -YY * COSTH * COSPH - XX * SINTH * COSPH + ZZ * SINPH; float CZ = -YY * SINPH * COSTH - XX * SINPH * SINTH - ZZ * COSPH + RR; // Perspective projection float XXX = LL * (CX / CZ) + X00; float ZZZ = -LL * (CY / CZ) + Y00; // In BASIC, Y coordinate is stored in ZZZ return new ScreenPoint(XXX, ZZZ, CZ); // Projected X, projected Y, depth } // Compute axis rotation constants (BASIC: *TSFINIT 1920-1960) void tsfInit(float TH, float PH) { float TH2 = TH * RAD; float PH2 = PH * RAD; CT2 = cos(TH2); ST2 = sin(TH2); CP2 = cos(PH2); SP2 = sin(PH2); STCP2 = ST2 * CP2; CTCP2 = CT2 * CP2; STSP2 = ST2 * SP2; CTSP2 = CT2 * SP2; } // xyz coordinates → axis rotation (BASIC: *TSF 1980-2020) PVector tsf(float X, float Y, float Z, int II, int JJ, int KK) { // Axis rotation and translation for cell repetition float XX = CT2 * X + ST2 * Y + II * SX; float YY = -STCP2 * X + CTCP2 * Y + SP2 * Z + JJ * SY; float ZZ = STSP2 * X + CTSP2 * Y - CP2 * Z + KK * SZ; return new PVector(XX, YY, ZZ); } // Information display (BASIC: 1200-1206 LOCATE/PRINT) void displayInfo() { fill(CL_WHITE); textSize(14); // Display text at top-left of the screen textAlign(LEFT, TOP); text("*石英の結晶構造*", 10, 10); text("SiO4四面体の網状構造", 10, 30); text("Si:赤 (R=" + R1 + ")", 10, 50); text("O:紫 (R=" + R2 + ")", 10, 70); // ESC key 안내 textSize(12); text("ESCキーで終了", 10, height - 20); // Debug information (view angles) text("φ=" + nf(PHAI, 0, 1) + "°", width - 80, 10); text("θ=" + nf(THETA % 360, 0, 1) + "°", width - 80, 30); } // (END OF CODE)