package kha.vr; import kha.arrays.Float32Array; import kha.graphics4.FragmentShader; import kha.graphics4.Graphics; import kha.Framebuffer; import kha.graphics4.ConstantLocation; import kha.graphics4.IndexBuffer; import kha.graphics4.PipelineState; import kha.graphics4.TextureUnit; import kha.graphics4.Usage; import kha.graphics4.VertexBuffer; import kha.graphics4.VertexShader; import kha.graphics4.VertexStructure; import kha.graphics4.VertexData; import kha.input.Keyboard; import kha.input.KeyCode; import kha.math.FastMatrix4; import kha.math.Matrix4; import kha.math.Quaternion; import kha.math.Vector4; import kha.math.Vector3; import kha.math.Vector2; import kha.Shaders; import kha.vr.Pose; import kha.vr.PoseState; import kha.vr.SensorState; import kha.vr.TimeWarpParms; import kha.input.Gamepad; import kha.input.Mouse; class VrInterfaceEmulated extends kha.vr.VrInterface { public var framebuffer: Framebuffer; var orientation: Quaternion; // private var f: Float = 0.0; var pitchDegrees: Float = 0.0; var yawDegrees: Float = 0.0; var pitchDelta: Float = 0.0; var yawDelta: Float = 0.0; static inline var keyboardSpeed: Float = 2.0; static inline var mouseSpeed: Float = 0.1; static inline var minPitchDegrees: Float = -80; static inline var maxPitchDegrees: Float = 80; function degreesToRadians(degrees: Float): Float { return degrees * Math.PI / 180.0; } function updateOrientation(): Void { // Update from keyboard input yawDegrees += yawDelta; pitchDegrees += pitchDelta; if (pitchDegrees < minPitchDegrees) pitchDegrees = minPitchDegrees; if (pitchDegrees > maxPitchDegrees) pitchDegrees = maxPitchDegrees; // Compute from pitch and yaw var pitchQuat = Quaternion.fromAxisAngle(new Vector3(1, 0, 0), degreesToRadians(pitchDegrees)); var yawQuat = Quaternion.fromAxisAngle(new Vector3(0, 1, 0), degreesToRadians(yawDegrees)); orientation = yawQuat.mult(pitchQuat); } function buttonEvent(button: Int, value: Float): Void {} function axisEvent(axis: Int, value: Float): Void {} function keyDownEvent(code: KeyCode): Void { switch (code) { case KeyCode.Left: yawDelta = keyboardSpeed; case KeyCode.Right: yawDelta = -keyboardSpeed; case KeyCode.Up: pitchDelta = keyboardSpeed; case KeyCode.Down: pitchDelta = -keyboardSpeed; default: } } function keyUpEvent(code: KeyCode): Void { switch (code) { case KeyCode.Left: yawDelta = 0.0; case KeyCode.Right: yawDelta = 0.0; case KeyCode.Up: pitchDelta = 0.0; case KeyCode.Down: pitchDelta = 0.0; default: } } var oldMouseX: Int = 0; var oldMouseY: Int = 0; function mouseMoveEvent(x: Int, y: Int, movementX: Int, movementY: Int) { if (!mouseButtonDown) return; var mouseDeltaX: Int = x - oldMouseX; var mouseDeltaY: Int = y - oldMouseY; oldMouseX = x; oldMouseY = y; yawDegrees += mouseDeltaX * mouseSpeed; pitchDegrees += mouseDeltaY * mouseSpeed; if (pitchDegrees < minPitchDegrees) pitchDegrees = minPitchDegrees; if (pitchDegrees > maxPitchDegrees) pitchDegrees = maxPitchDegrees; } var mouseButtonDown: Bool = false; function mouseButtonDownEvent(button: Int, x: Int, y: Int) { if (button == 0) { mouseButtonDown = true; oldMouseX = x; oldMouseY = y; } } function mouseButtonUpEvent(button: Int, x: Int, y: Int) { if (button == 0) { mouseButtonDown = false; } } // Returns the current sensor state // Returns the predicted sensor state at the specified time public override function GetSensorState(): SensorState { return GetPredictedSensorState(0.0); } // Returns the predicted sensor state at the specified time public override function GetPredictedSensorState(time: Float): SensorState { // TODO: Would be better if the interface was called independently each frame - we don't know how often this function is called. updateOrientation(); var result: SensorState = new SensorState(); // TODO: Check values result.Status = 0; result.Temperature = 75; result.Predicted = new PoseState(); result.Recorded = result.Predicted; result.Predicted.AngularAcceleration = new Vector3(); result.Predicted.AngularVelocity = new Vector3(); result.Predicted.LinearAcceleration = new Vector3(); result.Predicted.LinearVelocity = new Vector3(); result.Predicted.TimeInSeconds = time; result.Predicted.Pose = new Pose(); result.Predicted.Pose.Orientation = orientation; result.Predicted.Pose.Position = new Vector3(); // TODO: Simulate the head movement using the mouse return result; } // Sends a black image to the warp swap thread public override function WarpSwapBlack(): Void { // TODO: Implement } // Sends the Oculus loading symbol to the warp swap thread public override function WarpSwapLoadingIcon(): Void { // TODO: Implement } // Sends the set of images to the warp swap thread public override function WarpSwap(parms: TimeWarpParms): Void { var g: Graphics = framebuffer.g4; g.begin(); g.setPipeline(pipeline); g.setVertexBuffer(vb); g.setIndexBuffer(ib); var matrixLocation: ConstantLocation = pipeline.getConstantLocation("projectionMatrix"); var p: FastMatrix4 = FastMatrix4.identity(); g.setMatrix(matrixLocation, p); var texture: TextureUnit = pipeline.getTextureUnit("tex"); g.setTexture(texture, parms.RightImage.Image); g.drawIndexedVertices(); // Check for an overlay image /* if (parms.LeftImage.Image != null) { g.setTexture(texture, parms.RightOverlay.Image); g.drawIndexedVertices(); } */ g.end(); } public override function GetTimeInSeconds(): Float { // TODO: Is it in seconds? return System.time; } var vb: VertexBuffer; var ib: IndexBuffer; var pipeline: PipelineState; function setVertex(a: Float32Array, index: Int, pos: Vector3, uv: Vector2, color: Vector4) { var base: Int = index * 9; a.set(base + 0, pos.x); a.set(base + 1, pos.y); a.set(base + 2, pos.z); base += 3; a.set(base + 0, uv.x); a.set(base + 1, uv.y); base += 2; a.set(base + 0, color.x); a.set(base + 1, color.y); a.set(base + 2, color.z); a.set(base + 3, color.w); } public function new() { super(); Gamepad.get(0).notify(axisEvent, buttonEvent); Keyboard.get(0).notify(keyDownEvent, keyUpEvent); Mouse.get(0).notify(mouseButtonDownEvent, mouseButtonUpEvent, mouseMoveEvent, null); var structure: VertexStructure = new VertexStructure(); orientation = new Quaternion(); updateOrientation(); structure.add("vertexPosition", VertexData.Float32_3X); structure.add("vertexUV", VertexData.Float32_2X); structure.add("vertexColor", VertexData.Float32_4X); vb = new VertexBuffer(4, structure, Usage.StaticUsage); var verts = vb.lock(); setVertex(verts, 0, new Vector3(-1, -1, 0), new Vector2(0, 0), new Vector4(1, 1, 1, 1)); setVertex(verts, 1, new Vector3(-1, 1, 0), new Vector2(0, 1), new Vector4(1, 1, 1, 1)); setVertex(verts, 2, new Vector3(1, -1, 0), new Vector2(1, 0), new Vector4(1, 1, 1, 1)); setVertex(verts, 3, new Vector3(1, 1, 0), new Vector2(1, 1), new Vector4(1, 1, 1, 1)); vb.unlock(); ib = new IndexBuffer(6, Usage.StaticUsage); var indices = ib.lock(); indices[0] = 0; indices[1] = 1; indices[2] = 2; indices[3] = 1; indices[4] = 3; indices[5] = 2; ib.unlock(); pipeline = new PipelineState(); pipeline.vertexShader = Shaders.painter_image_vert; pipeline.fragmentShader = Shaders.painter_image_frag; pipeline.inputLayout = [structure]; pipeline.compile(); } }