Java tutorial
/** * Copyright 2013 Mark Browning, StellaArtois * Licensed under the LGPL 3.0 or later (See LICENSE.md for details) */ package com.mtbs3d.minecrift.provider; import com.mtbs3d.minecrift.api.*; import com.mtbs3d.minecrift.settings.VRSettings; import de.fruitfly.ovr.*; import de.fruitfly.ovr.enums.*; import de.fruitfly.ovr.structs.*; import net.minecraft.client.Minecraft; import net.minecraft.util.Vec3; import org.lwjgl.opengl.*; import org.lwjgl.util.vector.Quaternion; public class MCOculus extends OculusRift //OculusRift does most of the heavy lifting implements IEyePositionProvider, IOrientationProvider, IBasePlugin, IHMDInfo, IStereoProvider, IEventNotifier, IEventListener { public static final int NOT_CALIBRATING = 0; public static final int CALIBRATE_AWAITING_FIRST_ORIGIN = 1; public static final int CALIBRATE_AT_FIRST_ORIGIN = 2; public static final int CALIBRATE_COOLDOWN = 7; public static final long COOLDOWNTIME_MS = 1500L; private boolean isCalibrated = false; private long coolDownStart = 0L; private int calibrationStep = NOT_CALIBRATING; private int MagCalSampleCount = 0; private boolean forceMagCalibration = false; // Don't force mag cal initially private FrameTiming frameTiming = new FrameTiming(); private float yawOffsetRad = 0f; private float pitchOffsetRad = 0f; private float rollHeadRad = 0f; private float pitchHeadRad = 0f; private float yawHeadRad = 0f; private boolean polledThisFrame = false; private TrackerState ts = new TrackerState(); private Posef[] eyePose = new Posef[2]; private EulerOrient[] eulerOrient = new EulerOrient[2]; public MCOculus() { super(); eyePose[0] = new Posef(); eyePose[1] = new Posef(); eulerOrient[0] = new EulerOrient(); eulerOrient[1] = new EulerOrient(); } @Override public EyeType eyeRenderOrder(int index) { HmdDesc desc = getHMDInfo(); if (index < 0 || index >= desc.EyeRenderOrder.length) return EyeType.ovrEye_Left; return desc.EyeRenderOrder[index]; } @Override public String getVersion() { return OculusRift.getVersionString(); } @Override public boolean usesDistortion() { return true; } @Override public boolean isStereo() { return true; } @Override public boolean isGuiOrtho() { return false; } public FrameTiming getFrameTiming() { return frameTiming; }; public static UserProfileData theProfileData = null; @Override public void beginFrame() { beginFrame(0); } @Override public void beginFrame(int frameIndex) { polledThisFrame = false; frameTiming = super.beginFrameGetTiming(frameIndex); } @Override public Posef getEyePose(EyeType eye) { if (eye == EyeType.ovrEye_Center) eye = EyeType.ovrEye_Left; this.eyePose[eye.value()] = super.getEyePose(eye); this.eulerOrient[eye.value()] = OculusRift.getEulerAnglesDeg(this.eyePose[eye.value()].Orientation, 1.0f, Axis.Axis_Y, Axis.Axis_X, Axis.Axis_Z, HandedSystem.Handed_L, RotateDirection.Rotate_CCW); return this.eyePose[eye.value()]; } @Override public FullPoseState getEyePoses(int frameIndex) { EyeType eye; // Adjust viewAdjust to account for world scale float worldScale = Minecraft.getMinecraft().vrSettings.worldScale; Vector3f leftViewAdjust = erp.Eyes[EyeType.ovrEye_Left.value()].ViewAdjust; leftViewAdjust.x *= worldScale; leftViewAdjust.y *= worldScale; leftViewAdjust.z *= worldScale; Vector3f rightViewAdjust = erp.Eyes[EyeType.ovrEye_Right.value()].ViewAdjust; rightViewAdjust.x *= worldScale; rightViewAdjust.y *= worldScale; rightViewAdjust.z *= worldScale; // Get our eye pose and tracker state in one hit FullPoseState fullPoseState = super.getEyePoses(frameIndex, leftViewAdjust, rightViewAdjust); // Account for the need for negated y position values fullPoseState.leftEyePose.Position.y *= -1f; fullPoseState.rightEyePose.Position.y *= -1f; fullPoseState.trackerState.HeadPose.ThePose.Position.y *= -1f; // Set left eye pose eye = EyeType.ovrEye_Left; this.eyePose[eye.value()] = fullPoseState.leftEyePose; this.eulerOrient[eye.value()] = OculusRift.getEulerAnglesDeg(this.eyePose[eye.value()].Orientation, 1.0f, Axis.Axis_Y, Axis.Axis_X, Axis.Axis_Z, HandedSystem.Handed_L, RotateDirection.Rotate_CCW); // Set right eye pose eye = EyeType.ovrEye_Right; this.eyePose[eye.value()] = fullPoseState.rightEyePose; this.eulerOrient[eye.value()] = OculusRift.getEulerAnglesDeg(this.eyePose[eye.value()].Orientation, 1.0f, Axis.Axis_Y, Axis.Axis_X, Axis.Axis_Z, HandedSystem.Handed_L, RotateDirection.Rotate_CCW); // Set tracker info ts = fullPoseState.trackerState; return fullPoseState; } public Matrix4f getMatrix4fProjection(FovPort fov, float nearClip, float farClip) { return super.getMatrix4fProjection(fov, nearClip, farClip); } public void endFrame() { GL11.glDisable(GL11.GL_CULL_FACE); // Oculus wants CW orientations, avoid the problem by turning off culling... GL11.glDisable(GL11.GL_DEPTH_TEST); // Nothing is drawn with depth test on... //GL30.glBindVertexArray(0); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); // Unbind GL_ARRAY_BUFFER for my own vertex arrays to work... GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0); // End the frame super.endFrame(); GL11.glFrontFace(GL11.GL_CCW); // Needed for OVR SDK 0.4.0 GL11.glEnable(GL11.GL_CULL_FACE); // Turn back on... GL11.glEnable(GL11.GL_DEPTH_TEST); // Turn back on... GL11.glClearDepth(1); // Oculus set this to 0 (the near plane), return to normal... ARBShaderObjects.glUseProgramObjectARB(0); // Oculus shader is still active, turn it off... Display.processMessages(); } @Override public HmdDesc getHMDInfo() { HmdDesc hmdDesc = new HmdDesc(); if (isInitialized()) hmdDesc = getHmdDesc(); return hmdDesc; } @Override public String getName() { return "Oculus Rift"; } @Override public String getID() { return "oculus"; } @Override public void update(float ipd, float yawHeadDegrees, float pitchHeadDegrees, float rollHeadDegrees, float worldYawOffsetDegrees, float worldPitchOffsetDegrees, float worldRollOffsetDegrees) { if (!polledThisFrame) { //ts = poll(frameTiming.ScanoutMidpointSeconds); polledThisFrame = true; } rollHeadRad = (float) Math.toRadians(rollHeadDegrees); pitchHeadRad = (float) Math.toRadians(pitchHeadDegrees); yawHeadRad = (float) Math.toRadians(yawHeadDegrees); yawOffsetRad = (float) Math.toRadians(worldYawOffsetDegrees); pitchOffsetRad = (float) Math.toRadians(worldPitchOffsetDegrees); } @Override public Vec3 getCenterEyePosition() { VRSettings vr = Minecraft.getMinecraft().vrSettings; Vec3 eyePosition = new Vec3(0, 0, 0); if (Minecraft.getMinecraft().vrSettings.usePositionTracking) { eyePosition = new Vec3(ts.HeadPose.ThePose.Position.x * vr.posTrackDistanceScale, ts.HeadPose.ThePose.Position.y * vr.posTrackDistanceScale, ts.HeadPose.ThePose.Position.z * vr.posTrackDistanceScale); } return eyePosition; } @Override public Vec3 getEyePosition(EyeType eye) { if (eye == EyeType.ovrEye_Center) return getCenterEyePosition(); VRSettings vr = Minecraft.getMinecraft().vrSettings; Vec3 eyePosition = new Vec3(0, 0, 0); if (Minecraft.getMinecraft().vrSettings.usePositionTracking) { Vector3f eyePos = super.getEyePos(eye); eyePosition = new Vec3(eyePos.x * vr.posTrackDistanceScale, eyePos.y * vr.posTrackDistanceScale, eyePos.z * vr.posTrackDistanceScale); } return eyePosition; } @Override public void resetOrigin() { super.resetTracking(); } @Override public void resetOriginRotation() { // TODO: } @Override public void setPrediction(float delta, boolean enable) { // Now ignored } @Override public void beginCalibration(PluginType type) { if (isInitialized()) processCalibration(); } @Override public void updateCalibration(PluginType type) { if (isInitialized()) processCalibration(); } @Override public boolean isCalibrated(PluginType type) { if (!isInitialized()) return true; // Return true if not initialised if (!getHMDInfo().IsReal) return true; // Return true if debug (fake) Rift... if (type != PluginType.PLUGIN_POSITION) // Only position provider needs calibrating return true; return isCalibrated; } @Override public String getCalibrationStep(PluginType type) { String step = ""; String newline = "\n"; switch (calibrationStep) { case CALIBRATE_AWAITING_FIRST_ORIGIN: { StringBuilder sb = new StringBuilder(); sb.append("HEALTH AND SAFETY WARNING").append(newline).append(newline) .append("Read and follow all warnings and instructions").append(newline) .append("included with the Headset before use. Headset").append(newline) .append("should be calibrated for each user. Not for use by").append(newline) .append("children under 13. Stop use if you experience any").append(newline) .append("discomfort or health reactions.").append(newline).append(newline) .append("More: www.oculus.com/warnings").append(newline).append(newline) .append("Look ahead and press SPACEBAR to acknowledge").append(newline) .append("and reset origin."); step = sb.toString(); break; } case CALIBRATE_AT_FIRST_ORIGIN: case CALIBRATE_COOLDOWN: { step = "Done!"; break; } } return step; } @Override public void eventNotification(int eventId) { switch (eventId) { case IBasePlugin.EVENT_CALIBRATION_SET_ORIGIN: { if (calibrationStep == CALIBRATE_AWAITING_FIRST_ORIGIN) { calibrationStep = CALIBRATE_AT_FIRST_ORIGIN; processCalibration(); } break; } case IBasePlugin.EVENT_SET_ORIGIN: { resetOrigin(); } } } @Override public synchronized void registerListener(IEventListener listener) { listeners.add(listener); } @Override public synchronized void notifyListeners(int eventId) { for (IEventListener listener : listeners) { if (listener != null) listener.eventNotification(eventId); } } private void processCalibration() { switch (calibrationStep) { case NOT_CALIBRATING: { calibrationStep = CALIBRATE_AWAITING_FIRST_ORIGIN; isCalibrated = false; break; } case CALIBRATE_AT_FIRST_ORIGIN: { //_reset(); // Calibration of Mag cal is now handled solely by the Oculus config utility. MagCalSampleCount = 0; coolDownStart = System.currentTimeMillis(); calibrationStep = CALIBRATE_COOLDOWN; resetOrigin(); notifyListeners(IBasePlugin.EVENT_SET_ORIGIN); break; } case CALIBRATE_COOLDOWN: { if ((System.currentTimeMillis() - coolDownStart) > COOLDOWNTIME_MS) { coolDownStart = 0; calibrationStep = NOT_CALIBRATING; isCalibrated = true; } break; } } } @Override public void poll(/*EyeType eyeHint, */float delta) { // Do nothing } @Override public float getHeadYawDegrees(EyeType eye) { if (eye == EyeType.ovrEye_Center) eye = EyeType.ovrEye_Left; return this.eulerOrient[eye.value()].yaw; } @Override public float getHeadPitchDegrees(EyeType eye) { if (eye == EyeType.ovrEye_Center) eye = EyeType.ovrEye_Left; return this.eulerOrient[eye.value()].pitch; } @Override public float getHeadRollDegrees(EyeType eye) { if (eye == EyeType.ovrEye_Center) eye = EyeType.ovrEye_Left; return this.eulerOrient[eye.value()].roll; } @Override public Quaternion getOrientationQuaternion(EyeType eye) { if (eye == EyeType.ovrEye_Center) eye = EyeType.ovrEye_Left; Quatf orient = this.eyePose[eye.value()].Orientation; return new Quaternion(orient.x, orient.y, orient.z, orient.w); } @Override public UserProfileData getProfileData() { UserProfileData userProfile = null; if (isInitialized()) { userProfile = _getUserProfileData(); } else { userProfile = new UserProfileData(); } return userProfile; } @Override public double getCurrentTimeSecs() { return getCurrentTimeSeconds(); } }