-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDemo.java
More file actions
198 lines (160 loc) · 7.26 KB
/
Copy pathDemo.java
File metadata and controls
198 lines (160 loc) · 7.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package fastascii.raymarch;
import fastansi.FastANSI;
import fastascii.FastGlyphDensity;
import java.nio.charset.StandardCharsets;
public class Demo {
// Vector Math
static class Vec3 {
final double x, y, z;
Vec3(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
Vec3 add(Vec3 v) {
return new Vec3(x + v.x, y + v.y, z + v.z);
}
Vec3 sub(Vec3 v) {
return new Vec3(x - v.x, y - v.y, z - v.z);
}
Vec3 mul(double s) {
return new Vec3(x * s, y * s, z * s);
}
double dot(Vec3 v) {
return x * v.x + y * v.y + z * v.z;
}
double length() {
return Math.sqrt(x * x + y * y + z * z);
}
Vec3 normalize() {
double l = length();
return l == 0 ? new Vec3(0, 0, 0) : new Vec3(x / l, y / l, z / l);
}
}
// Geometry Map
static double map(Vec3 p) {
// Sphere definition
double dSphere = p.sub(new Vec3(0, 0, 5)).length() - 2.625;
// Wall definition
double dWall = 10.0 - p.z;
return Math.min(dSphere, dWall);
}
// Surface Normal Calculation
static Vec3 calcNormal(Vec3 p) {
double e = 0.001;
return new Vec3(
map(new Vec3(p.x + e, p.y, p.z)) - map(new Vec3(p.x - e, p.y, p.z)),
map(new Vec3(p.x, p.y + e, p.z)) - map(new Vec3(p.x, p.y - e, p.z)),
map(new Vec3(p.x, p.y, p.z + e)) - map(new Vec3(p.x, p.y, p.z - e))
).normalize();
}
// Shoot Ray
static double rayMarch(Vec3 ro, Vec3 rd, double maxDist) {
double t = 0.0;
for (int i = 0; i < 80; i++) {
Vec3 p = ro.add(rd.mul(t));
double d = map(p);
if (d < 0.001) return t; // Hit
t += d;
if (t > maxDist) break; // Miss
}
return -1;
}
public static void main(String[] args) throws Exception {
System.out.println("FastASCII 3D Raymarcher Demo");
System.out.println("Initializing Scene...");
int width = 120;
int height = 30;
System.out.write("\033[?25l".getBytes(StandardCharsets.UTF_8)); // Hide Cursor
long startTime = System.currentTimeMillis();
try {
while (true) {
double time = (System.currentTimeMillis() - startTime) / 1000.0;
// Light position
Vec3 lightPos = new Vec3(
Math.sin(time) * 5.0,
4.0 + Math.cos(time * 0.7) * 2.0,
0.0
);
StringBuilder sb = new StringBuilder();
sb.append("\033[?2026h"); // Start frame update
sb.append("\033[H"); // Reset cursor to 0,0
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// Normalize Coordinates from -1.0 to 1.0
double nx = (double) x / width * 2.0 - 1.0;
double ny = (double) y / height * 2.0 - 1.0;
// Adjust for terminal font aspect ratio
nx *= (double) width / height * 0.5;
ny = -ny; // Flip Y axis
Vec3 ro = new Vec3(0, 0, 0); // Camera at origin
Vec3 rd = new Vec3(nx, ny, 1.0).normalize(); // Ray direction
double dist = rayMarch(ro, rd, 50.0);
if (dist > 0) {
Vec3 hitPos = ro.add(rd.mul(dist));
Vec3 normal = calcNormal(hitPos);
// Determine hit object
double distToSphere = hitPos.sub(new Vec3(0, 0, 5)).length() - 2.625;
boolean hitWall = distToSphere > 0.1;
// Diffuse Lighting (Dot product)
Vec3 lightDir = lightPos.sub(hitPos).normalize();
double diffuse = Math.max(0.0, normal.dot(lightDir));
// Distance attenuation
double lightDist = lightPos.sub(hitPos).length();
double attenuation = 1.0 / (1.0 + 0.02 * lightDist * lightDist);
diffuse *= attenuation * 2.0; // Scale light intensity
// Specular highlight
double specular = 0.0;
if (!hitWall) {
Vec3 viewDir = ro.sub(hitPos).normalize();
Vec3 reflectDir = lightDir.mul(-1).add(normal.mul(2.0 * normal.dot(lightDir))).normalize();
double specAngle = Math.max(0.0, viewDir.dot(reflectDir));
specular = Math.pow(specAngle, 16.0) * 0.8; // Specular exponent
}
// Shadow computation
double shadowDist = rayMarch(hitPos.add(normal.mul(0.02)), lightDir, lightDist);
if (shadowDist > 0 && shadowDist < lightDist) {
diffuse *= 0.05; // Apply shadow
specular = 0.0;
}
// Combine lighting
double finalLighting = diffuse + specular;
if (finalLighting > 1.0) finalLighting = 1.0;
if (finalLighting < 0.0) finalLighting = 0.0;
// Determine output glyph
char glyph;
if (finalLighting <= 0.02) glyph = ' '; // Force empty space for pure shadow
else glyph = FastGlyphDensity.getGlyphForOpacity((float) finalLighting);
// Assign base colors
int r, g, b;
if (hitWall) {
r = 128;
g = 128;
b = 128; // Gray wall
} else {
r = 255;
g = 255;
b = 255; // White sphere
}
sb.append(FastANSI.fg(r, g, b)).append(glyph);
} else {
sb.append(FastANSI.RESET).append(' '); // Empty space
}
}
if (y < height - 1) sb.append("\n");
}
sb.append("\033[?2026l"); // Commit frame
byte[] bytes = sb.toString().getBytes(StandardCharsets.UTF_8);
System.out.write(bytes);
System.out.flush();
// Sleep to cap at 60 FPS
Thread.sleep(16);
}
} finally {
// Restore VT and cursor if interrupted
System.out.write("\033[?2026l".getBytes(StandardCharsets.UTF_8));
System.out.write("\033[?25h".getBytes(StandardCharsets.UTF_8)); // Show Cursor
System.out.println(FastANSI.RESET);
}
}
}