commit 046ad98e833ee7ef4cb8c53c0327038d886cc87e Author: Wain Date: Tue Apr 5 14:52:18 2022 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21b4487 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Project exclude paths +/out/ \ No newline at end of file diff --git a/src/com/alterdekim/rendering/AmbientLight.java b/src/com/alterdekim/rendering/AmbientLight.java new file mode 100644 index 0000000..136ab15 --- /dev/null +++ b/src/com/alterdekim/rendering/AmbientLight.java @@ -0,0 +1,13 @@ +package com.alterdekim.rendering; + +public class AmbientLight extends Light { + + public AmbientLight(Vector3 position) { + super(position); + } + + @Override + public double calculateBrightness( Vector3 rd, Vector3 ro, Vector3 normal, double d ) { + return 1.0; + } +} diff --git a/src/com/alterdekim/rendering/Camera.java b/src/com/alterdekim/rendering/Camera.java new file mode 100644 index 0000000..5ffbfba --- /dev/null +++ b/src/com/alterdekim/rendering/Camera.java @@ -0,0 +1,7 @@ +package com.alterdekim.rendering; + +public class Camera extends Object3D { + public Camera(Vector3 position) { + super(position); + } +} diff --git a/src/com/alterdekim/rendering/Canvas.java b/src/com/alterdekim/rendering/Canvas.java new file mode 100644 index 0000000..669d429 --- /dev/null +++ b/src/com/alterdekim/rendering/Canvas.java @@ -0,0 +1,29 @@ +package com.alterdekim.rendering; + +public class Canvas extends Raymarcher { + + private static final long serialVersionUID = 1L; + + private Sphere sphere = new Sphere( new Vector3(0,0,0), 1 ); + private LambertLight light = new LambertLight(new Vector3(4,2,4)); + private double d = 0; + + public Canvas() { + init(); + } + + @Override + public void onStart() { + this.setCameraPosition(new Vector3(0, 0, -3)); + sphere.getMaterial().setColor(new MaterialColor(0.3, 0.7, 1.0)); + this.add(sphere); + this.add(new Plane(new Vector3(0,0,0))); + this.addLight(light); + } + + @Override + public void onRender() { + light.setPosition(new Vector3(4.0*Math.sin(d), 2.0, 4.0 * Math.cos(d))); + d+=0.2; + } +} diff --git a/src/com/alterdekim/rendering/FOGLight.java b/src/com/alterdekim/rendering/FOGLight.java new file mode 100644 index 0000000..6912802 --- /dev/null +++ b/src/com/alterdekim/rendering/FOGLight.java @@ -0,0 +1,13 @@ +package com.alterdekim.rendering; + +public class FOGLight extends Light { + + public FOGLight(Vector3 position) { + super(position); + } + + @Override + public double calculateBrightness( Vector3 rd, Vector3 ro, Vector3 normal, double d ) { + return 1.0 - Utils.clamp( Utils.rangeConvert( d, 0, 3.6, 0, 1 ), 0.0, 1.0 ); + } +} diff --git a/src/com/alterdekim/rendering/LambertLight.java b/src/com/alterdekim/rendering/LambertLight.java new file mode 100644 index 0000000..cbf4112 --- /dev/null +++ b/src/com/alterdekim/rendering/LambertLight.java @@ -0,0 +1,19 @@ +package com.alterdekim.rendering; + +public class LambertLight extends Light { + + public LambertLight(Vector3 position) { + super(position); + } + + @Override + public double calculateBrightness( Vector3 rd, Vector3 ro, Vector3 normal, double d ) { + Vector3 lightDirection = this.calculateLightDirection(rd, ro, normal, d); + return Utils.clamp(lightDirection.dot(normal), 0.0, 1.0); + } + + @Override + public Vector3 calculateLightDirection( Vector3 rd, Vector3 ro, Vector3 normal, double d ) { + return new Vector3( this.getX(), this.getY(), this.getZ() ).subtract(rd.multiply(d).sum(ro)).normalize(); + } +} \ No newline at end of file diff --git a/src/com/alterdekim/rendering/Light.java b/src/com/alterdekim/rendering/Light.java new file mode 100644 index 0000000..a524ce5 --- /dev/null +++ b/src/com/alterdekim/rendering/Light.java @@ -0,0 +1,62 @@ +package com.alterdekim.rendering; + +public class Light { + + private double x; + private double y; + private double z; + + public Light( Vector3 position ) { + this.x = position.x; + this.y = position.y; + this.z = position.z; + } + + public void setPosition( double x, double y, double z ) { + this.x = x; + this.y = y; + this.z = z; + } + + public void setPosition( Vector3 position ) { + this.x = position.x; + this.y = position.y; + this.z = position.z; + } + + public Vector3 getPosition() { + return new Vector3( this.x, this.y, this.z ); + } + + public void setX( double x ) { + this.x = x; + } + + public void setY( double y ) { + this.y = y; + } + + public void setZ( double z ) { + this.z = z; + } + + public double getX() { + return this.x; + } + + public double getY() { + return this.y; + } + + public double getZ() { + return this.z; + } + + public double calculateBrightness( Vector3 rd, Vector3 ro, Vector3 normal, double d ) { + return 0; + } + + public Vector3 calculateLightDirection( Vector3 rd, Vector3 ro, Vector3 normal, double d ) { + return new Vector3(0,0,0); + } +} diff --git a/src/com/alterdekim/rendering/Material.java b/src/com/alterdekim/rendering/Material.java new file mode 100644 index 0000000..30fc125 --- /dev/null +++ b/src/com/alterdekim/rendering/Material.java @@ -0,0 +1,17 @@ +package com.alterdekim.rendering; + +public class Material { + private MaterialColor color; + + public Material( MaterialColor color ) { + this.color = color; + } + + public MaterialColor getColor() { + return this.color; + } + + public void setColor( MaterialColor color ) { + this.color = color; + } +} diff --git a/src/com/alterdekim/rendering/MaterialColor.java b/src/com/alterdekim/rendering/MaterialColor.java new file mode 100644 index 0000000..90e19ea --- /dev/null +++ b/src/com/alterdekim/rendering/MaterialColor.java @@ -0,0 +1,26 @@ +package com.alterdekim.rendering; + +public class MaterialColor { + + private double hue; + private double saturation; + private double brightness; + + public MaterialColor( double hue, double saturation, double brightness ) { + this.hue = hue; + this.saturation = saturation; + this.brightness = brightness; + } + + public double getHue() { + return this.hue; + } + + public double getSaturation() { + return this.saturation; + } + + public double getBrightness() { + return this.brightness; + } +} diff --git a/src/com/alterdekim/rendering/Object3D.java b/src/com/alterdekim/rendering/Object3D.java new file mode 100644 index 0000000..29e6fa6 --- /dev/null +++ b/src/com/alterdekim/rendering/Object3D.java @@ -0,0 +1,88 @@ +package com.alterdekim.rendering; + +public class Object3D { + + private double x; + private double y; + private double z; + private double rotationX = 0; + private double rotationY = 0; + private double rotationZ = 0; + private Material mat = new Material(new MaterialColor(0,0,0)); + + public Object3D( Vector3 position ) { + this.x = position.x; + this.y = position.y; + this.z = position.z; + } + + public void setMaterial( Material mat ) { + this.mat = mat; + } + + public Material getMaterial() { + return this.mat; + } + + public void setPosition( Vector3 position ) { + this.x = position.x; + this.y = position.y; + this.z = position.z; + } + + public void setRotationX( double x ) { + this.rotationX = x; + } + + public void setRotationY( double y ) { + this.rotationY = y; + } + + public void setRotationZ( double z ) { + this.rotationZ = z; + } + + public double getEulerX() { + return this.rotationX; + } + + public double getEulerY() { + return this.rotationY; + } + + public double getEulerZ() { + return this.rotationZ; + } + + public Vector3 getPosition() { + return new Vector3( this.x, this.y, this.z ); + } + + public void setX( double x ) { + this.x = x; + } + + public void setY( double y ) { + this.y = y; + } + + public void setZ( double z ) { + this.z = z; + } + + public double getX() { + return this.x; + } + + public double getY() { + return this.y; + } + + public double getZ() { + return this.z; + } + + public double calculateSDF(Vector3 position) { + return 0; + } +} diff --git a/src/com/alterdekim/rendering/Octahedron.java b/src/com/alterdekim/rendering/Octahedron.java new file mode 100644 index 0000000..d671ec6 --- /dev/null +++ b/src/com/alterdekim/rendering/Octahedron.java @@ -0,0 +1,31 @@ +package com.alterdekim.rendering; + +public class Octahedron extends Object3D { + + private double size; + + public Octahedron(Vector3 position, double size) { + super(position); + this.size = size; + } + + public double getSize() { + return this.size; + } + + @Override + public double calculateSDF( Vector3 position ) { + if( this.getEulerX() != 0 ) { + position = position.cross(Utils.rotateX(Math.toRadians(this.getEulerX()))); + } + if( this.getEulerY() != 0 ) { + position = position.cross(Utils.rotateY(Math.toRadians(this.getEulerY()))); + } + if( this.getEulerZ() != 0 ) { + position = position.cross(Utils.rotateZ(Math.toRadians(this.getEulerZ()))); + } + position = new Vector3(Math.abs(position.x), Math.abs(position.y), Math.abs(position.z)); + return (position.x+position.y+position.z-size)*0.57735027; + } + +} diff --git a/src/com/alterdekim/rendering/Plane.java b/src/com/alterdekim/rendering/Plane.java new file mode 100644 index 0000000..188dd4f --- /dev/null +++ b/src/com/alterdekim/rendering/Plane.java @@ -0,0 +1,12 @@ +package com.alterdekim.rendering; + +public class Plane extends Object3D { + public Plane(Vector3 position) { + super(position); + } + + @Override + public double calculateSDF( Vector3 position ) { + return (position.y *(-1)) + 1.0 + this.getY(); + } +} diff --git a/src/com/alterdekim/rendering/Raymarcher.java b/src/com/alterdekim/rendering/Raymarcher.java new file mode 100644 index 0000000..a8ee566 --- /dev/null +++ b/src/com/alterdekim/rendering/Raymarcher.java @@ -0,0 +1,170 @@ +package com.alterdekim.rendering; + +import java.awt.Color; +import java.awt.Graphics; +import java.util.ArrayList; + +import javax.swing.JPanel; + +public class Raymarcher extends JPanel { + + private static final long serialVersionUID = 1L; + + private final int MAX_STEPS = 80; + + private final double MAX_DIST = 100.0; + + private final double SURF_DIST = 0.001; + + private final Camera camera = new Camera(new Vector3(0,0,0)); + + private ArrayList objects = new ArrayList(); + + private ArrayList lights = new ArrayList(); + + public void init() { + Runnable task = new Runnable() { + public void run() { + while(true) { + repaint(); + } + } + }; + onStart(); + Thread thread = new Thread(task); + thread.start(); + } + + public void onStart() {} + + public void onRender() {} + + private sceneResult raymarch(Vector3 ro, Vector3 rd) { + double depth = 0; + Material mat = new Material(new MaterialColor(0,0,0)); + for( int i = 0; i < MAX_STEPS; i++ ) { + Vector3 p = rd.multiply(depth).sum(ro); + sceneResult res = sceneSDF( p ); + double dS = res.dist; + mat = res.mat; + depth += dS; + if( dS < SURF_DIST || depth > MAX_DIST ) { + break; + } + } + + return new sceneResult( depth, mat ); + } + + private sceneResult sceneSDF( Vector3 pos ) { + double min = MAX_DIST; + Material mat = new Material(new MaterialColor(0,0,0)); + for( int i = 0; i < this.objects.size(); i++ ) { + double d = this.objects.get(i).calculateSDF(pos); + if( d < min ) { + min = d; + mat = this.objects.get(i).getMaterial(); + } + } + return new sceneResult(min, mat); + } + + public void setCameraPosition( Vector3 position ) { + this.camera.setPosition(position); + } + + public void setCameraRotation( Vector3 rotation ) { + this.camera.setRotationX(rotation.x); + this.camera.setRotationY(rotation.y); + this.camera.setRotationZ(rotation.z); + } + + public Camera getCamera() { + return this.camera; + } + + public void add( Object3D object ) { + this.objects.add(object); + } + + public void remove( Object3D object ) { + this.objects.remove(object); + } + + public ArrayList children() { + return this.objects; + } + + public void addLight( Light light ) { + this.lights.add(light); + } + + public void removeLight( Light light ) { + this.lights.remove(light); + } + + public ArrayList childrenLight() { + return this.lights; + } + + private Vector3 calcNormal( Vector3 pos ) { + Vector3 xyy = new Vector3(SURF_DIST,0,0); + Vector3 yxy = new Vector3(0,SURF_DIST,0); + Vector3 yyx = new Vector3(0,0,SURF_DIST); + + Vector3 nor = new Vector3( + sceneSDF(pos.sum(xyy)).dist - sceneSDF(pos.subtract(xyy)).dist, + sceneSDF(pos.sum(yxy)).dist - sceneSDF(pos.subtract(yxy)).dist, + sceneSDF(pos.sum(yyx)).dist - sceneSDF(pos.subtract(yyx)).dist); + return nor.normalize(); + } + + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + onRender(); + Vector3 ro = new Vector3( camera.getX(), camera.getY(), camera.getZ() ); + double minres = Math.min(getWidth(), getHeight()); + for( int x = 0; x < getWidth(); x++ ) { + for( int y = 0; y < getHeight(); y++ ) { + Vector3 rd = new Vector3((((double)x) - 0.5* ((double) getWidth())) / minres, (((double)y) - 0.5* ((double) getHeight())) / minres, 1); + if( camera.getEulerX() != 0 ) { + rd = rd.cross(Utils.rotateX(Math.toRadians(camera.getEulerX()))); + } + if( camera.getEulerY() != 0 ) { + rd = rd.cross(Utils.rotateY(Math.toRadians(camera.getEulerY()))); + } + if( camera.getEulerZ() != 0 ) { + rd = rd.cross(Utils.rotateZ(Math.toRadians(camera.getEulerZ()))); + } + rd = rd.normalize(); + sceneResult res = raymarch( ro, rd ); + double d = res.dist; + if( d < MAX_DIST ) { + double dif = 0; + Vector3 p = rd.multiply(d).sum(ro); + Vector3 normal = calcNormal(p); + for( int i = 0; i < this.lights.size(); i++ ) { + dif += this.lights.get(i).calculateBrightness(rd, ro, normal, d); + dif = Utils.clamp( dif, 0.0, 1.0 ); + } + g.setColor(Color.getHSBColor( (float)res.mat.getColor().getHue(), (float)res.mat.getColor().getSaturation(), (float)dif )); + } else { + g.setColor(Color.BLACK); + } + + g.fillRect(x, y, 1, 1); + } + } + } + + private class sceneResult { + public double dist; + public Material mat; + + public sceneResult( double dist, Material mat ) { + this.dist = dist; + this.mat = mat; + } + } +} diff --git a/src/com/alterdekim/rendering/RenderingWindow.java b/src/com/alterdekim/rendering/RenderingWindow.java new file mode 100644 index 0000000..b42409a --- /dev/null +++ b/src/com/alterdekim/rendering/RenderingWindow.java @@ -0,0 +1,40 @@ +package com.alterdekim.rendering; + +import java.awt.EventQueue; + +import javax.swing.JFrame; +import java.awt.BorderLayout; + +public class RenderingWindow { + + private JFrame frmRendering; + + public static void main(String[] args) { + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + RenderingWindow window = new RenderingWindow(); + window.frmRendering.setVisible(true); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + public RenderingWindow() { + initialize(); + } + + private void initialize() { + frmRendering = new JFrame(); + frmRendering.setTitle("Rendering"); + frmRendering.setResizable(false); + frmRendering.setBounds(100, 100, 400, 400); + frmRendering.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + Canvas panel = new Canvas(); + frmRendering.getContentPane().add(panel, BorderLayout.CENTER); + } + +} diff --git a/src/com/alterdekim/rendering/Sphere.java b/src/com/alterdekim/rendering/Sphere.java new file mode 100644 index 0000000..7065d72 --- /dev/null +++ b/src/com/alterdekim/rendering/Sphere.java @@ -0,0 +1,29 @@ +package com.alterdekim.rendering; + +public class Sphere extends Object3D { + + private double radius; + + public Sphere( Vector3 position, double radius ) { + super( position ); + this.radius = radius; + } + + public double getRadius() { + return this.radius; + } + + @Override + public double calculateSDF( Vector3 position ) { + if( this.getEulerX() != 0 ) { + position = position.cross(Utils.rotateX(Math.toRadians(this.getEulerX()))); + } + if( this.getEulerY() != 0 ) { + position = position.cross(Utils.rotateY(Math.toRadians(this.getEulerY()))); + } + if( this.getEulerZ() != 0 ) { + position = position.cross(Utils.rotateZ(Math.toRadians(this.getEulerZ()))); + } + return position.subtract(this.getPosition()).length() - this.getRadius(); + } +} diff --git a/src/com/alterdekim/rendering/Triangle.java b/src/com/alterdekim/rendering/Triangle.java new file mode 100644 index 0000000..377ade3 --- /dev/null +++ b/src/com/alterdekim/rendering/Triangle.java @@ -0,0 +1,48 @@ +package com.alterdekim.rendering; + +public class Triangle extends Object3D { + + private Vector3 a; + private Vector3 b; + private Vector3 c; + + public Triangle(Vector3 position, Vector3 a, Vector3 b, Vector3 c) { + super(position); + this.a = a; + this.b = b; + this.c = c; + } + + @Override + public double calculateSDF(Vector3 p) { + if( this.getEulerX() != 0 ) { + p = p.cross(Utils.rotateX(Math.toRadians(this.getEulerX()))); + } + if( this.getEulerY() != 0 ) { + p = p.cross(Utils.rotateY(Math.toRadians(this.getEulerY()))); + } + if( this.getEulerZ() != 0 ) { + p = p.cross(Utils.rotateZ(Math.toRadians(this.getEulerZ()))); + } + Vector3 ba = b.subtract(a); Vector3 pa = p.subtract(a); + Vector3 cb = c.subtract(b); Vector3 pb = p.subtract(b); + Vector3 ac = a.subtract(c); Vector3 pc = p.subtract(c); + Vector3 nor = ba.cross(ac); + + return Math.sqrt( + (Math.signum(ba.cross(nor).dot(pa)) + + Math.signum(cb.cross(nor).dot(pb)) + + Math.signum(ac.cross(nor).dot(pc))<2.0) + ? + Math.min( Math.min( + dot2(ba.multiply(Utils.clamp(ba.dot(pa)/dot2(ba),0.0,1.0)).subtract(pa)), + dot2(cb.multiply(Utils.clamp(cb.dot(pb)/dot2(cb),0.0,1.0)).subtract(pb)) ), + dot2(ac.multiply(Utils.clamp(ac.dot(pc)/dot2(ac),0.0,1.0)).subtract(pc)) ) + : + nor.dot(pa)*nor.dot(pa)/dot2(nor) ); + } + + private double dot2( Vector3 v ) { + return v.dot(v); + } +} diff --git a/src/com/alterdekim/rendering/Utils.java b/src/com/alterdekim/rendering/Utils.java new file mode 100644 index 0000000..38df412 --- /dev/null +++ b/src/com/alterdekim/rendering/Utils.java @@ -0,0 +1,47 @@ +package com.alterdekim.rendering; + +import java.util.ArrayList; + +public class Utils { + + public static double rangeConvert( double value, double leftMin, double leftMax, double rightMin, double rightMax ) { + double leftSpan = leftMax - leftMin; + double rightSpan = rightMax - rightMin; + double valueScaled = (value - leftMin) / (leftSpan); + return rightMin + (valueScaled * rightSpan); + } + + public static double clamp( double value, double MIN_VALUE, double MAX_VALUE ) { + return (value > MAX_VALUE ? MAX_VALUE : value < MIN_VALUE ? MIN_VALUE : value); + } + + public static ArrayList rotateX(double theta) { + double c = Math.cos(theta); + double s = Math.sin(theta); + ArrayList v = new ArrayList(); + v.add(new Vector3(1, 0, 0)); + v.add(new Vector3(0, c, -s)); + v.add(new Vector3(0, s, c)); + return v; + } + + public static ArrayList rotateY(double theta) { + double c = Math.cos(theta); + double s = Math.sin(theta); + ArrayList v = new ArrayList(); + v.add(new Vector3(c, 0, s)); + v.add(new Vector3(0, 1, 0)); + v.add(new Vector3(-s, 0, c)); + return v; + } + + public static ArrayList rotateZ(double theta) { + double c = Math.cos(theta); + double s = Math.sin(theta); + ArrayList v = new ArrayList(); + v.add(new Vector3(c, -s, 0)); + v.add(new Vector3(s, c, 0)); + v.add(new Vector3(0, 0, 1)); + return v; + } +} diff --git a/src/com/alterdekim/rendering/Vector3.java b/src/com/alterdekim/rendering/Vector3.java new file mode 100644 index 0000000..54553cf --- /dev/null +++ b/src/com/alterdekim/rendering/Vector3.java @@ -0,0 +1,71 @@ +package com.alterdekim.rendering; + +import java.util.ArrayList; + +public class Vector3 { + + public double x; + public double y; + public double z; + + public Vector3( double x, double y, double z ) { + this.x = x; + this.y = y; + this.z = z; + } + + public Vector3 subtract( Vector3 s ) { + return new Vector3( this.x - s.x, this.y - s.y, this.z - s.z ); + } + + public Vector3 multiply( double num ) { + return new Vector3( this.x * num, this.y * num, this.z * num ); + } + + public Vector3 divide( double num ) { + return new Vector3( this.x / num, this.y / num, this.z / num ); + } + + public Vector3 sum( Vector3 s ) { + return new Vector3( this.x + s.x, this.y + s.y, this.z + s.z ); + } + + public double dot( Vector3 s ) { + return (this.x * s.x) + (this.y * s.y) + (this.z * s.z); + } + + public Vector3 normalize() { + Vector3 v3 = new Vector3(0,0,0); + + double length = Math.sqrt( this.x*this.x + this.y*this.y + this.z*this.z ); + if (length != 0) { + v3.x = this.x/length; + v3.y = this.y/length; + v3.z = this.z/length; + } + + return v3; + } + + public double length() { + return Math.sqrt( this.x*this.x + this.y*this.y + this.z*this.z ); + } + + public Vector3 cross( Vector3 c ) { + return new Vector3( (this.y * c.z) - (this.z * c.y), + (this.z * c.x) - (this.x * c.z), + (this.x * c.y) - (this.y * c.x)); + } + + public Vector3 cross( ArrayList matrix ) { + Vector3 result = new Vector3( this.x, this.y, this.z ); + result.x = (matrix.get(0).x * this.x) + (matrix.get(0).y * this.y) + (matrix.get(0).z * this.z); + result.y = (matrix.get(1).x * this.x) + (matrix.get(1).y * this.y) + (matrix.get(1).z * this.z); + result.z = (matrix.get(2).x * this.x) + (matrix.get(2).y * this.y) + (matrix.get(2).z * this.z); + return result; + } + + public Vector3 reflect( Vector3 normal, Vector3 v ) { + return v.subtract(normal.multiply(2).cross(v).cross(normal)); + } +} \ No newline at end of file