Roi Atalla avatar Roi Atalla committed 8f048ff

Java code for Exeample 12.1 ported.

Comments (0)

Files changed (5)

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter12/LightManager.java

 package com.ra4king.opengl.arcsynthesis.gl33.chapter12;
 
+import java.nio.FloatBuffer;
 import java.util.ArrayList;
 import java.util.HashMap;
 
+import org.lwjgl.BufferUtils;
+
 import com.ra4king.opengl.util.Timer;
 import com.ra4king.opengl.util.Timer.Type;
 import com.ra4king.opengl.util.interpolators.ConstVelLinearInterpolatorVector;
 		public Vector4 cameraSpaceLightPos;
 		public Vector4 lightIntensity;
 		
+		public static final int SIZE = 2 * 4 * 4;
+		
 		public PerLight(Vector4 cameraSpaceLightPos, Vector4 lightIntensity) {
 			this.cameraSpaceLightPos = cameraSpaceLightPos;
 			this.lightIntensity = lightIntensity;
 		}
+		
+		private static final FloatBuffer buffer = BufferUtils.createFloatBuffer(SIZE / 4);
+		
+		public FloatBuffer toBuffer() {
+			buffer.clear();
+			buffer.put(cameraSpaceLightPos.toBuffer());
+			buffer.put(lightIntensity.toBuffer());
+			buffer.flip();
+			return buffer;
+		}
 	}
 	
 	public static class LightBlock {
 		public float lightAttenuation;
 		public PerLight[] lights = new PerLight[NUMBER_OF_LIGHTS];
 		
+		public static final int SIZE = 2 * 4 * 4 + NUMBER_OF_LIGHTS * PerLight.SIZE;
+		
 		public LightBlock(Vector4 ambientIntensity, float lightAttenuation) {
 			this.ambientIntensity = ambientIntensity;
 			this.lightAttenuation = lightAttenuation;
 		}
+		
+		private static final FloatBuffer buffer = BufferUtils.createFloatBuffer(SIZE / 4);
+		
+		public FloatBuffer toBuffer() {
+			buffer.clear();
+			buffer.put(ambientIntensity.toBuffer());
+			buffer.put(lightAttenuation);
+			buffer.put(0).put(0).put(0);
+			
+			for(PerLight light : lights)
+				buffer.put(light.toBuffer());
+			
+			buffer.flip();
+			return buffer;
+		}
 	}
 	
 	public static class LightBlockHDR {
 		public float maxIntensity;
 		public PerLight[] lights = new PerLight[NUMBER_OF_LIGHTS];
 		
+		public static final int SIZE = 2 * 4 * 4 + NUMBER_OF_LIGHTS * PerLight.SIZE;
+		
 		public LightBlockHDR(Vector4 ambientIntensity, float lightAttenuation, float maxIntensity) {
 			this.ambientIntensity = ambientIntensity;
 			this.lightAttenuation = lightAttenuation;
 			this.maxIntensity = maxIntensity;
 		}
+		
+		private static final FloatBuffer buffer = BufferUtils.createFloatBuffer(SIZE / 4);
+		
+		public FloatBuffer toBuffer() {
+			buffer.clear();
+			buffer.put(ambientIntensity.toBuffer());
+			buffer.put(lightAttenuation);
+			buffer.put(maxIntensity);
+			buffer.put(0).put(0);
+			
+			for(PerLight light : lights)
+				buffer.put(light.toBuffer());
+			
+			buffer.flip();
+			return buffer;
+		}
 	}
 	
 	public static class LightBlockGamma {
 		public float gamma;
 		public PerLight[] lights = new PerLight[NUMBER_OF_LIGHTS];
 		
+		public static final int SIZE = 2 * 4 * 4 + NUMBER_OF_LIGHTS * PerLight.SIZE;
+		
 		public LightBlockGamma(Vector4 ambientIntensity, float lightAttenuation, float maxIntensity, float gamma) {
 			this.ambientIntensity = ambientIntensity;
 			this.lightAttenuation = lightAttenuation;
 			this.maxIntensity = maxIntensity;
 			this.gamma = gamma;
 		}
+		
+		private static final FloatBuffer buffer = BufferUtils.createFloatBuffer(SIZE / 4);
+		
+		public FloatBuffer toBuffer() {
+			buffer.clear();
+			buffer.put(ambientIntensity.toBuffer());
+			buffer.put(lightAttenuation);
+			buffer.put(maxIntensity);
+			buffer.put(gamma);
+			buffer.put(0);
+			
+			for(PerLight light : lights)
+				buffer.put(light.toBuffer());
+			
+			buffer.flip();
+			return buffer;
+		}
 	}
 	
 	public static class SunlightValue {

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter12/Scene.java

 import com.ra4king.opengl.util.math.Vector4;
 
 public class Scene {
-	private Mesh terrainMesh;
-	private Mesh cubeMesh;
-	private Mesh tetraMesh;
-	private Mesh cylMesh;
-	private Mesh sphereMesh;
+	public final Mesh terrainMesh;
+	public final Mesh cubeMesh;
+	public final Mesh tetraMesh;
+	public final Mesh cylMesh;
+	public final Mesh sphereMesh;
 	
 	private final int sizeMaterialBlock;
 	private int materialUniformBuffer;
 	}
 	
 	public static class ProgramData {
-		private ShaderProgram program;
+		public ShaderProgram program;
 		
-		private int modelToCameraMatrixUniform;
-		private int normalModelTocameraMatrixUniform;
+		public int modelToCameraMatrixUniform;
+		public int normalModelTocameraMatrixUniform;
 		
 		public ProgramData(ShaderProgram program) {
 			this.program = program;

src/main/java/com/ra4king/opengl/arcsynthesis/gl33/chapter12/example1/Example12_1.java

 package com.ra4king.opengl.arcsynthesis.gl33.chapter12.example1;
 
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.opengl.GL15.*;
+import static org.lwjgl.opengl.GL20.*;
+import static org.lwjgl.opengl.GL30.*;
+import static org.lwjgl.opengl.GL31.*;
+import static org.lwjgl.opengl.GL32.*;
+
 import com.ra4king.opengl.GLProgram;
+import com.ra4king.opengl.arcsynthesis.gl33.chapter12.LightManager;
+import com.ra4king.opengl.arcsynthesis.gl33.chapter12.LightManager.LightBlock;
+import com.ra4king.opengl.arcsynthesis.gl33.chapter12.LightManager.SunlightValue;
+import com.ra4king.opengl.arcsynthesis.gl33.chapter12.Scene;
+import com.ra4king.opengl.arcsynthesis.gl33.chapter12.Scene.LightingProgramTypes;
+import com.ra4king.opengl.arcsynthesis.gl33.chapter12.Scene.ProgramData;
+import com.ra4king.opengl.arcsynthesis.gl33.chapter12.Scene.ProgramStore;
+import com.ra4king.opengl.util.MousePoles.MouseButton;
+import com.ra4king.opengl.util.ShaderProgram;
+import com.ra4king.opengl.util.Utils;
+import com.ra4king.opengl.util.MousePoles.ViewData;
+import com.ra4king.opengl.util.MousePoles.ViewPole;
+import com.ra4king.opengl.util.MousePoles.ViewScale;
+import com.ra4king.opengl.util.Timer.Type;
+import com.ra4king.opengl.util.math.Matrix4;
+import com.ra4king.opengl.util.math.MatrixStack;
+import com.ra4king.opengl.util.math.Quaternion;
+import com.ra4king.opengl.util.math.Vector3;
+import com.ra4king.opengl.util.math.Vector4;
 
 public class Example12_1 extends GLProgram {
 	public static void main(String[] args) {
 		new Example12_1().run(true);
 	}
 	
+	private ProgramData[] programs = new ProgramData[Scene.LightingProgramTypes.values().length];
+	private UnlitProgramData unlit;
+	
+	private Scene scene;
+	private LightManager lights;
+	
+	private ViewPole viewPole;
+	
+	private Vector4 skyDaylightColor = new Vector4(0.65f, 0.65f, 1, 1);
+	
+	private final int materialBlockIndex = 0;
+	private final int lightBlockIndex = 1;
+	private final int projectionBlockIndex = 2;
+	
+	private int lightUniformBuffer;
+	private int projectionUniformBuffer;
+	
+	private boolean drawCameraPos, drawLights = true;
+	
 	public Example12_1() {
 		super("Example 12.1", 500, 500, true);
 	}
 	
 	@Override
 	public void init() {
+		ViewData viewData = new ViewData(new Vector3(-59.5f, 44, 95), new Quaternion(0.3826834f, 0.0f, 0.0f, 0.92387953f), 50, 0);
+		ViewScale viewScale = new ViewScale(3, 80, 4, 1, 5, 1, 90/250f);
 		
+		viewPole = new ViewPole(viewData, viewScale, MouseButton.LEFT_BUTTON, false);
+		
+		String[] vertexShaders = { "PCN.vert", "PCN.vert", "PN.vert", "PN.vert" };
+		String[] fragmentShaders = { "DiffuseSpecular.frag", "DiffuseOnly.frag", "DiffuseSpecularMtl.frag", "DiffuseOnlyMtl.frag" };
+		
+		for(int a = 0; a < vertexShaders.length && a < fragmentShaders.length; a++)
+			programs[a] = loadLitProgram("example12.1." + vertexShaders[a], "example12.1." + fragmentShaders[a]);
+		
+		unlit = loadUnlitProgram("example12.1.PosTransform.vert", "example12.1.UniformColor.frag");
+		
+		scene = new Scene(new ProgramStore() {
+			@Override
+			public ProgramData getProgram(LightingProgramTypes type) {
+				return programs[type.ordinal()];
+			}
+		});
+		
+		lights = new LightManager();
+		lights.createTimer("tetra", Type.LOOP, 2.5f);
+		
+		setupDaytimeLighting();
+		
+		glEnable(GL_CULL_FACE);
+		glCullFace(GL_BACK);
+		glFrontFace(GL_CW);
+		
+		glEnable(GL_DEPTH_TEST);
+		glDepthMask(true);
+		glDepthFunc(GL_LEQUAL);
+		glDepthRange(0, 1);
+		glEnable(GL_DEPTH_CLAMP);
+		
+		lightUniformBuffer = glGenBuffers();
+		glBindBuffer(GL_UNIFORM_BUFFER, lightUniformBuffer);
+		glBufferData(GL_UNIFORM_BUFFER, LightBlock.SIZE, GL_DYNAMIC_DRAW);
+		
+		projectionUniformBuffer = glGenBuffers();
+		glBindBuffer(GL_UNIFORM_BUFFER, projectionUniformBuffer);
+		glBufferData(GL_UNIFORM_BUFFER, 16 * 4, GL_DYNAMIC_DRAW);
+		glBindBuffer(GL_UNIFORM_BUFFER, 0);
+		
+		glBindBufferRange(GL_UNIFORM_BUFFER, lightBlockIndex, lightUniformBuffer, 0, LightBlock.SIZE);
+		glBindBufferRange(GL_UNIFORM_BUFFER, projectionBlockIndex, projectionUniformBuffer, 0, 16 * 4);
+	}
+	
+	private void setupDaytimeLighting() {
+		SunlightValue[] values = {
+									new SunlightValue(0.0f / 24.0f, new Vector4(0.2f, 0.2f, 0.2f, 1.0f), new Vector4(0.6f, 0.6f, 0.6f, 1.0f), skyDaylightColor),
+									new SunlightValue(4.5f / 24.0f, new Vector4(0.2f, 0.2f, 0.2f, 1.0f), new Vector4(0.6f, 0.6f, 0.6f, 1.0f), skyDaylightColor),
+									new SunlightValue(6.5f / 24.0f, new Vector4(0.15f, 0.05f, 0.05f, 1.0f), new Vector4(0.3f, 0.1f, 0.10f, 1.0f), new Vector4(0.5f, 0.1f, 0.1f, 1.0f)),
+									new SunlightValue(8.0f / 24.0f, new Vector4(0.0f, 0.0f, 0.0f, 1.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f)),
+									new SunlightValue(18.0f / 24.0f, new Vector4(0.0f, 0.0f, 0.0f, 1.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f)),
+									new SunlightValue(19.5f / 24.0f, new Vector4(0.15f, 0.05f, 0.05f, 1.0f), new Vector4(0.3f, 0.1f, 0.1f, 1.0f), new Vector4(0.5f, 0.1f, 0.1f, 1.0f)),
+									new SunlightValue(20.5f / 24.0f, new Vector4(0.2f, 0.2f, 0.2f, 1.0f), new Vector4(0.6f, 0.6f, 0.6f, 1.0f), skyDaylightColor)
+		};
+		
+		lights.setSunlightValues(values);
+		
+		lights.setPointLightIntensity(0, new Vector4(0.2f, 0.2f, 0.2f, 1));
+		lights.setPointLightIntensity(1, new Vector4(0, 0, 0.3f, 1));
+		lights.setPointLightIntensity(2, new Vector4(0.3f, 0, 0, 1));
+	}
+	
+	private void setupNighttimeLighting() {
+		SunlightValue[] values = {
+									new SunlightValue(0.0f / 24.0f, new Vector4(0.2f, 0.2f, 0.2f, 1.0f), new Vector4(0.6f, 0.6f, 0.6f, 1.0f), skyDaylightColor),
+									new SunlightValue(4.5f / 24.0f, new Vector4(0.2f, 0.2f, 0.2f, 1.0f), new Vector4(0.6f, 0.6f, 0.6f, 1.0f), skyDaylightColor),
+									new SunlightValue(6.5f / 24.0f, new Vector4(0.15f, 0.05f, 0.05f, 1.0f), new Vector4(0.3f, 0.1f, 0.10f, 1.0f), new Vector4(0.5f, 0.1f, 0.1f, 1.0f)),
+									new SunlightValue(8.0f / 24.0f, new Vector4(0.0f, 0.0f, 0.0f, 1.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f)),
+									new SunlightValue(18.0f / 24.0f, new Vector4(0.0f, 0.0f, 0.0f, 1.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f), new Vector4(0.0f, 0.0f, 0.0f, 1.0f)),
+									new SunlightValue(19.5f / 24.0f, new Vector4(0.15f, 0.05f, 0.05f, 1.0f), new Vector4(0.3f, 0.1f, 0.1f, 1.0f), new Vector4(0.5f, 0.1f, 0.1f, 1.0f)),
+									new SunlightValue(20.5f / 24.0f, new Vector4(0.2f, 0.2f, 0.2f, 1.0f), new Vector4(0.6f, 0.6f, 0.6f, 1.0f), skyDaylightColor)
+		};
+		
+		lights.setSunlightValues(values);
+		
+		lights.setPointLightIntensity(0, new Vector4(0.6f, 0.6f, 0.6f, 1));
+		lights.setPointLightIntensity(1, new Vector4(0, 0, 0.7f, 1));
+		lights.setPointLightIntensity(2, new Vector4(0.7f, 0, 0, 1));
+	}
+	
+	private ProgramData loadLitProgram(String vertexShader, String fragmentShader) {
+		ProgramData data = new ProgramData(new ShaderProgram(readFromFile(vertexShader), readFromFile(fragmentShader)));
+		data.modelToCameraMatrixUniform = glGetUniformLocation(data.program.getProgram(), "modelToCameraMatrix");
+		data.normalModelTocameraMatrixUniform = glGetUniformLocation(data.program.getProgram(), "normalModelToCameraMatrix");
+		
+		int materialBlock = glGetUniformBlockIndex(data.program.getProgram(), "Material");
+		int lightBlock = glGetUniformBlockIndex(data.program.getProgram(), "Light");
+		int projectionBlock = glGetUniformBlockIndex(data.program.getProgram(), "Projection");
+		
+		if(materialBlock != GL_INVALID_INDEX)
+			glUniformBlockBinding(data.program.getProgram(), materialBlock, materialBlockIndex);
+		glUniformBlockBinding(data.program.getProgram(), lightBlock, lightBlockIndex);
+		glUniformBlockBinding(data.program.getProgram(), projectionBlock, projectionBlockIndex);
+		
+		return data;
+	}
+	
+	private UnlitProgramData loadUnlitProgram(String vertexShader, String fragmentShader) {
+		UnlitProgramData data = new UnlitProgramData(new ShaderProgram(readFromFile(vertexShader), readFromFile(fragmentShader)));
+		data.modelToCameraMatrixUniform = glGetUniformLocation(data.program.getProgram(), "modelTocameraMatrix");
+		data.objectColorUniform = glGetUniformLocation(data.program.getProgram(), "objectColor");
+		
+		int projectionBlock = glGetUniformBlockIndex(data.program.getProgram(), "Projection");
+		glUniformBlockBinding(data.program.getProgram(), projectionBlock, projectionBlockIndex);
+		
+		return data;
+	}
+	
+	@Override
+	public void resized() {
+		super.resized();
+		
+		glBindBuffer(GL_UNIFORM_BUFFER, projectionUniformBuffer);
+		glBufferSubData(GL_UNIFORM_BUFFER, 0, new Matrix4().clearToPerspectiveDeg(45, getWidth(), getHeight(), 1, 1000).toBuffer());
+		glBindBuffer(GL_UNIFORM_BUFFER, 0);
+	}
+	
+	@Override
+	public void update(long deltaTime) {
+		lights.updateTime(deltaTime);
+		
+		Utils.updateMousePoles(viewPole, null);
 	}
 	
 	@Override
 	public void render() {
+		Vector4 bkg = lights.getBackgroundColor();
 		
+		glClearColor(bkg.x(), bkg.y(), bkg.z(), bkg.w());
+		glClearDepth(1);
+		
+		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		
+		MatrixStack modelMatrix = new MatrixStack();
+		modelMatrix.setTop(viewPole.calcMatrix());
+		
+		Matrix4 worldToCameraMatrix = modelMatrix.getTop();
+		LightBlock lightData = lights.getLightInformation(worldToCameraMatrix);
+		
+		glBindBuffer(GL_UNIFORM_BUFFER, lightUniformBuffer);
+		glBufferSubData(GL_UNIFORM_BUFFER, 0, lightData.toBuffer());
+		glBindBuffer(GL_UNIFORM_BUFFER, 0);
+		
+		{
+			modelMatrix.pushMatrix();
+			scene.draw(modelMatrix, materialBlockIndex, lights.getTimerValue("tetra"));
+			modelMatrix.popMatrix();
+		}
+		
+		{
+			modelMatrix.pushMatrix();
+			
+			{
+				modelMatrix.pushMatrix();
+				
+				modelMatrix.getTop().translate(new Vector3(lights.getSunlightDirection()).mult(500));
+				modelMatrix.getTop().scale(30, 30, 30);
+				
+				unlit.program.begin();
+				glUniformMatrix4(unlit.modelToCameraMatrixUniform, false, modelMatrix.getTop().toBuffer());
+				glUniform4(unlit.objectColorUniform, lights.getSunlightIntensity().toBuffer());
+				scene.sphereMesh.render("flat");
+				unlit.program.end();
+				
+				modelMatrix.popMatrix();
+			}
+			
+			if(drawLights) {
+				for(int light = 0; light < lights.getNumberOfPointLights(); light++) {
+					modelMatrix.pushMatrix();
+					
+					modelMatrix.getTop().translate(lights.getWorldLightPosition(light));
+					
+					unlit.program.begin();
+					glUniformMatrix4(unlit.modelToCameraMatrixUniform, false, modelMatrix.getTop().toBuffer());
+					glUniform4(unlit.objectColorUniform, lights.getPointLightIntensity(light).toBuffer());
+					scene.cubeMesh.render("flat");
+					unlit.program.end();
+					
+					modelMatrix.popMatrix();
+				}
+			}
+			
+			if(drawCameraPos) {
+				modelMatrix.pushMatrix();
+				
+				modelMatrix.getTop().clearToIdentity();
+				modelMatrix.getTop().translate(0, 0, -viewPole.getView().radius);
+				
+				unlit.program.begin();
+				
+				glDisable(GL_DEPTH_TEST);
+				glDepthMask(false);
+				glUniformMatrix4(unlit.modelToCameraMatrixUniform, false, modelMatrix.getTop().toBuffer());
+				glUniform4f(unlit.objectColorUniform, 0.25f, 0.25f, 0.25f, 1);
+				scene.cubeMesh.render("flat");
+				
+				glDepthMask(true);
+				glEnable(GL_DEPTH_TEST);
+				glUniform4f(unlit.objectColorUniform, 1, 1, 1, 1);
+				scene.cubeMesh.render("flat");
+				
+				unlit.program.end();
+			}
+			
+			modelMatrix.popMatrix();
+		}
+	}
+	
+	private static class UnlitProgramData {
+		private ShaderProgram program;
+		
+		private int objectColorUniform;
+		private int modelToCameraMatrixUniform;
+		
+		public UnlitProgramData(ShaderProgram program) {
+			this.program = program;
+		}
 	}
 }

src/main/java/com/ra4king/opengl/util/MousePoles.java

 	}
 	
 	public static class ObjectData {
-		private Vector3 position;
-		private Quaternion orientation;
+		public Vector3 position;
+		public Quaternion orientation;
 		
 		public ObjectData(ObjectData data) {
 			position = new Vector3(data.position);
 	}
 	
 	public static class ViewData {
-		private Vector3 targetPos;
-		private Quaternion orient;
-		private float radius;
-		private float degSpinRotation;
+		public Vector3 targetPos;
+		public Quaternion orient;
+		public float radius;
+		public float degSpinRotation;
 		
 		public ViewData(ViewData data) {
 			targetPos = new Vector3(data.targetPos);
 	}
 	
 	public static class ViewScale {
-		private float minRadius;
-		private float maxRadius;
-		private float largeRadiusDelta;
-		private float smallRadiusDelta;
-		private float largePosOffset;
-		private float smallPosOffset;
-		private float rotationScale;
+		public float minRadius;
+		public float maxRadius;
+		public float largeRadiusDelta;
+		public float smallRadiusDelta;
+		public float largePosOffset;
+		public float smallPosOffset;
+		public float rotationScale;
 		
 		public ViewScale(float min, float max, float large, float small, float largePos, float smallPos, float rot) {
 			minRadius = min;

src/main/java/com/ra4king/opengl/util/Utils.java

 			MouseButton button = MouseButton.getButton(Mouse.getEventButton());
 			if(button != null) {
 				boolean pressed = Mouse.getEventButtonState();
-				viewPole.mouseClick(button, pressed, getModifier(), Mouse.getX(), Mouse.getY());
-				objectPole.mouseClick(button, pressed, getModifier(), Mouse.getX(), Mouse.getY());
+				if(viewPole != null)
+					viewPole.mouseClick(button, pressed, getModifier(), Mouse.getX(), Mouse.getY());
+				if(objectPole != null)
+					objectPole.mouseClick(button, pressed, getModifier(), Mouse.getX(), Mouse.getY());
 			}
 			else {
 				int dwheel = Mouse.getDWheel();
 				
 				if(dwheel != 0) {
-					viewPole.mouseWheel(dwheel, getModifier(), Mouse.getX(), Mouse.getY());
-					objectPole.mouseWheel(dwheel, getModifier(), Mouse.getX(), Mouse.getY());
+					if(viewPole != null)
+						viewPole.mouseWheel(dwheel, getModifier(), Mouse.getX(), Mouse.getY());
+					if(objectPole != null)
+						objectPole.mouseWheel(dwheel, getModifier(), Mouse.getX(), Mouse.getY());
 				}
 				else {
-					viewPole.mouseMove(Mouse.getX(), Mouse.getY());
-					objectPole.mouseMove(Mouse.getX(), Mouse.getY());
+					if(viewPole != null)
+						viewPole.mouseMove(Mouse.getX(), Mouse.getY());
+					if(objectPole != null)
+						objectPole.mouseMove(Mouse.getX(), Mouse.getY());
 				}
 			}
 		}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.