This commit is contained in:
2026-02-21 22:17:44 -08:00
parent 423807c62f
commit 0adcafd697
14 changed files with 981 additions and 59 deletions

View File

@ -53,6 +53,21 @@ class App {
static function update() {
if (Scene.active == null || !Scene.active.ready) return;
// VR is handling it so we prevent double updates
// TODO: avoid js.Syntax
#if (kha_webgl && lnx_vr)
var vrActive = false;
js.Syntax.code("
if (typeof kha !== 'undefined' && kha.vr && kha.vr.VrInterface) {
const vr = kha.vr.VrInterface.instance;
if (vr && vr.IsPresenting && vr.IsPresenting()) {
{0} = true;
}
}
", vrActive);
if (vrActive) return;
#end
iron.system.Time.update();
if (lastw == -1) {
@ -138,6 +153,21 @@ class App {
traitInits.splice(0, traitInits.length);
}
// skip for XR callback to handle rendering
// TODO: avoid js Syntax
#if (kha_webgl && lnx_vr)
var vrActive = false;
js.Syntax.code("
if (typeof kha !== 'undefined' && kha.vr && kha.vr.VrInterface) {
const vr = kha.vr.VrInterface.instance;
if (vr && vr.IsPresenting && vr.IsPresenting()) {
{0} = true;
}
}
", vrActive);
if (!vrActive) {
#end
Scene.active.renderFrame(frame.g4);
for (f in traitRenders) {
@ -146,6 +176,10 @@ class App {
render2D(frame);
#if (kha_webgl && lnx_vr)
}
#end
#if lnx_debug
renderPathTime = kha.Scheduler.realTime() - startTime;
#end

View File

@ -18,10 +18,44 @@ import iron.object.LightObject;
import iron.object.MeshObject;
import iron.object.Uniforms;
import iron.object.Clipmap;
#if lnx_vr
import iron.math.Vec4;
import iron.math.Mat4;
import iron.math.Quat;
#end
class RenderPath {
public static var active: RenderPath;
#if lnx_vr
static var vrSimulateMode: Bool = false;
static var vrCameraOffsetSet:Bool = false;
static var vrCameraOffset:Vec4 = new Vec4();
static var wasVRPresenting:Bool = false;
public static var vrCalibrationPosition:Vec4 = null;
public static var vrCalibrationRotation:iron.math.Quat = null;
public static var vrCalibrationSaved:Bool = false;
public static var vrCenterCameraWorld:Mat4 = null;
static var vrOriginalSuperSample:Float = -1.0;
public static inline function isVRPresenting(): Bool {
#if (kha_webgl && lnx_vr)
return kha.vr.VrInterface.instance != null && kha.vr.VrInterface.instance.IsPresenting();
#else
return false;
#end
}
public static inline function isVRSimulateMode(): Bool {
return vrSimulateMode;
}
// TODO: done remove safely
public static inline function debugLog(msg: String, once: Bool = true): Void {
return;
}
#end
public var frameScissor = false;
public var frameScissorX = 0;
@ -43,9 +77,15 @@ class RenderPath {
public var isProbe = false;
public var currentG: Graphics = null;
public var frameG: Graphics;
#if lnx_vr
var beginCalled = false;
var scissorSet = false;
var viewportScaled = false;
var renderToXRFramebuffer = false;
#end
public var drawOrder = DrawOrder.Distance;
public var paused = false;
public var ready(get, null): Bool;
public var ready(get, never): Bool;
function get_ready(): Bool { return loading == 0; }
public var commands: Void->Void = null;
public var setupDepthTexture: Void->Void = null;
@ -123,9 +163,93 @@ class RenderPath {
public function renderFrame(g: Graphics) {
if (!ready || paused || iron.App.w() == 0 || iron.App.h() == 0) return;
if (lastW > 0 && (lastW != iron.App.w() || lastH != iron.App.h())) resize();
lastW = iron.App.w();
lastH = iron.App.h();
var appW = iron.App.w();
var appH = iron.App.h();
// use native XR framebuffer dimensions
#if (kha_webgl && lnx_vr)
if (kha.vr.VrInterface.instance != null) {
var vr = kha.vr.VrInterface.instance;
var isPresenting = vr != null && vr.IsPresenting();
// save/restore camera position between modes
if (!wasVRPresenting && isPresenting) {
if (Scene.active != null && Scene.active.camera != null) {
if (vrCalibrationPosition == null) vrCalibrationPosition = new Vec4();
if (vrCalibrationRotation == null) vrCalibrationRotation = new Quat();
vrCalibrationPosition.setFrom(Scene.active.camera.transform.loc);
vrCalibrationRotation.setFrom(Scene.active.camera.transform.rot);
vrCalibrationSaved = true;
}
// save original super sampling for later
vrOriginalSuperSample = leenkx.renderpath.Inc.superSample;
// compositeToXR function handles blitting to VR framebuffer
var xrVr: kha.js.vr.VrInterface = cast vr;
if (xrVr.xrGLLayer != null) {
var vrWidth = untyped xrVr.xrGLLayer.framebufferWidth;
var vrHeight = untyped xrVr.xrGLLayer.framebufferHeight;
}
}
else if (wasVRPresenting && !isPresenting) {
// reset VR frame time before anything else
#if (kha_webgl && lnx_vr)
iron.system.Time.vrFrameTime = -1.0;
#end
if (vrCalibrationSaved && Scene.active != null && Scene.active.camera != null) {
Scene.active.camera.transform.loc.setFrom(vrCalibrationPosition);
Scene.active.camera.transform.rot.setFrom(vrCalibrationRotation);
Scene.active.camera.buildMatrix();
Scene.active.camera.buildProjection();
}
// restore original super sampling from simulate mode
if (vrOriginalSuperSample >= 0.0) {
leenkx.renderpath.Inc.superSample = vrOriginalSuperSample;
for (rt in renderTargets) {
if (rt.raw.width == 0 && rt.raw.scale != null) {
rt.raw.scale = vrOriginalSuperSample;
}
}
resize();
vrOriginalSuperSample = -1.0;
}
// reset offset for next session
vrCameraOffsetSet = false;
vrCameraOffset = null;
}
wasVRPresenting = isPresenting;
if (isPresenting) {
// TODO: re-investigate using super sampling to avoid pixelation in simulate mode while giving max quality in headset
if (vrOriginalSuperSample >= 0.0 && leenkx.renderpath.Inc.superSample != 4.0) {
leenkx.renderpath.Inc.superSample = 4.0;
for (rt in renderTargets) {
if (rt.raw.width == 0 && rt.raw.scale != null) {
rt.raw.scale = 4.0;
}
}
resize();
}
var xrVr: kha.js.vr.VrInterface = cast vr;
if (xrVr.xrGLLayer != null) {
appW = xrVr.xrGLLayer.framebufferWidth;
appH = xrVr.xrGLLayer.framebufferHeight;
}
}
}
#end
if (lastW > 0 && (lastW != appW || lastH != appH)) resize();
lastW = appW;
lastH = appH;
frameTime = Time.time() - lastFrameTime;
lastFrameTime = Time.time();
@ -191,7 +315,9 @@ class RenderPath {
}
light = Scene.active.lights[0];
commands();
if (commands != null) {
commands();
}
if (!isProbe) frame++;
}
@ -207,13 +333,13 @@ class RenderPath {
begin(frameG, Scene.active.camera.currentFace);
}
else { // Screen, planar probe
currentW = iron.App.w();
currentH = iron.App.h();
currentW = kha.System.windowWidth();
currentH = kha.System.windowHeight();
if (frameScissor) setFrameScissor();
begin(frameG);
if (!isProbe) {
setCurrentViewport(iron.App.w(), iron.App.h());
setCurrentScissor(iron.App.w(), iron.App.h());
setCurrentViewport(kha.System.windowWidth(), kha.System.windowHeight());
setCurrentScissor(kha.System.windowWidth(), kha.System.windowHeight());
}
}
}
@ -258,16 +384,42 @@ class RenderPath {
if (currentG != null) end();
currentG = g;
additionalTargets = additionalRenderTargets;
face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets);
// we still bind but skip begin() when explicitly rendering to XR framebuffer (renderToXRFramebuffer flag)
#if lnx_vr
if (!renderToXRFramebuffer) {
face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets);
beginCalled = true;
} else {
// XR framebuffer is already bound by VrInterface so we dont rebind
beginCalled = false;
}
#else
face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets);
#end
}
inline function end() {
if (currentG == null) return;
if (scissorSet) {
currentG.disableScissor();
scissorSet = false;
}
#if lnx_vr
if (beginCalled) {
currentG.end();
beginCalled = false;
}
// persist for rendering both eyes
if (!isVRPresenting()) {
currentG = null;
additionalTargets = null;
}
#else
currentG.end();
currentG = null;
#end
bindParams = null;
}
@ -341,8 +493,8 @@ class RenderPath {
if (a.data.sortingIndex != b.data.sortingIndex) {
return a.data.sortingIndex > b.data.sortingIndex ? 1 : -1;
}
return a.data.name >= b.data.name ? 1 : -1; });
return a.data.name >= b.data.name ? 1 : -1;
});
}
public function drawMeshes(context: String) {
@ -521,44 +673,208 @@ class RenderPath {
return Reflect.field(kha.Shaders, handle + "_comp");
}
#if lnx_vr
// blits to each eyes viewport in the XR framebuffer.
public function compositeToXR(sourceTarget: String) {
#if (kha_webgl && lnx_vr)
var vr: kha.js.vr.VrInterface = cast kha.vr.VrInterface.instance;
if (vr == null || vr._glContext == null || vr.xrGLLayer == null) {
return;
}
var gl: js.html.webgl.WebGL2RenderingContext = cast vr._glContext;
var source = renderTargets.get(sourceTarget);
if (source == null) {
return;
}
var sourceFB: js.html.webgl.Framebuffer = untyped source.image.g4.renderTargetFrameBuffer;
if (sourceFB == null) {
return;
}
// trace('Framebuffer OK');
renderToXRFramebuffer = true;
gl.bindFramebuffer(js.html.webgl.WebGL2RenderingContext.DRAW_FRAMEBUFFER, vr.xrGLLayer.framebuffer);
gl.bindFramebuffer(js.html.webgl.WebGL2RenderingContext.READ_FRAMEBUFFER, sourceFB);
var readStatus = gl.checkFramebufferStatus(js.html.webgl.WebGL2RenderingContext.READ_FRAMEBUFFER);
var drawStatus = gl.checkFramebufferStatus(js.html.webgl.WebGL2RenderingContext.DRAW_FRAMEBUFFER);
if (readStatus != js.html.webgl.WebGL2RenderingContext.FRAMEBUFFER_COMPLETE ||
drawStatus != js.html.webgl.WebGL2RenderingContext.FRAMEBUFFER_COMPLETE) {
return;
}
var halfWidth = Std.int(source.image.width / 2);
var fullHeight = source.image.height;
if (vr._leftViewport != null) {
var vp = vr._leftViewport;
gl.blitFramebuffer(
0, 0, halfWidth, fullHeight,
vp.x, vp.y, vp.x + vp.width, vp.y + vp.height,
js.html.webgl.WebGL2RenderingContext.COLOR_BUFFER_BIT,
js.html.webgl.WebGL2RenderingContext.LINEAR
);
}
if (vr._rightViewport != null) {
var vp = vr._rightViewport;
gl.blitFramebuffer(
halfWidth, 0, source.image.width, fullHeight,
vp.x, vp.y, vp.x + vp.width, vp.y + vp.height,
js.html.webgl.WebGL2RenderingContext.COLOR_BUFFER_BIT,
js.html.webgl.WebGL2RenderingContext.LINEAR
);
}
gl.bindFramebuffer(js.html.webgl.WebGL2RenderingContext.FRAMEBUFFER, null);
renderToXRFramebuffer = false;
#end
}
#end
#if lnx_vr
public function drawStereo(drawMeshes: Void->Void) {
var vr = kha.vr.VrInterface.instance;
vrSimulateMode = false;
if (currentG == null && frameG != null) {
currentG = frameG;
}
var appw = iron.App.w();
var apph = iron.App.h();
var halfw = Std.int(appw / 2);
var g = currentG;
// get render target dimensions not App.w/h gbuffer is scaled in simulate mode with supersampling
if (vr != null && vr.IsPresenting()) {
// Left eye
Scene.active.camera.V.setFrom(Scene.active.camera.leftV);
var gbuffer0 = renderTargets.get("gbuffer0");
var actualWidth = (gbuffer0 != null && gbuffer0.image != null) ? gbuffer0.image.width : appw;
var actualHeight = (gbuffer0 != null && gbuffer0.image != null) ? gbuffer0.image.height : apph;
var actualHalfWidth = Std.int(actualWidth / 2);
var vrFBWidth = actualWidth;
var vrFBHeight = actualHeight;
var vrHalfWidth = actualHalfWidth;
var isVRPresenting = false;
vrSimulateMode = false;
var vr:Dynamic = null;
var vrExists = false;
#if (kha_webgl && lnx_vr)
if (kha.vr.VrInterface.instance != null) {
vr = kha.vr.VrInterface.instance;
vrExists = true;
}
#end
if (vrExists && vr != null && vr.IsPresenting()) {
vrSimulateMode = false;
isVRPresenting = true;
// get framebuffer dimensions from XR layer
#if (kha_webgl && lnx_vr)
var xrVr: kha.js.vr.VrInterface = cast vr;
if (xrVr.xrGLLayer != null) {
vrFBWidth = untyped xrVr.xrGLLayer.framebufferWidth;
vrFBHeight = untyped xrVr.xrGLLayer.framebufferHeight;
vrHalfWidth = Std.int(vrFBWidth / 2);
}
#end
if (Scene.active == null || Scene.active.camera == null) {
return;
}
#if (kha_webgl && lnx_vr)
if (vrCenterCameraWorld == null) vrCenterCameraWorld = Mat4.identity();
vrCenterCameraWorld.setFrom(Scene.active.camera.transform.world);
#end
// LEFT EYE
// HMD center for room scale position tracking
#if (kha_webgl && lnx_vr)
var xrVr: kha.js.vr.VrInterface = cast vr;
if (xrVr.currentViewerPose != null) {
var viewerTransform = untyped xrVr.currentViewerPose.transform;
if (viewerTransform != null && viewerTransform.position != null) {
// VR present calibration is used to position objects in world space not the camera
var pos = viewerTransform.position;
// camera follows headset directly in local floor space
Scene.active.camera.transform.loc.set(pos.x, pos.y, pos.z);
if (viewerTransform.orientation != null) {
Scene.active.camera.transform.rot.set(
viewerTransform.orientation.x,
viewerTransform.orientation.y,
viewerTransform.orientation.z,
viewerTransform.orientation.w
);
}
Scene.active.camera.transform.buildMatrix();
}
}
iron.system.VRController.updatePoses();
#end
Scene.active.camera.V.self = vr.GetViewMatrix(0);
Scene.active.camera.P.self = vr.GetProjectionMatrix(0);
g.viewport(0, 0, halfw, apph);
Scene.active.camera.VP.setFrom(Scene.active.camera.P);
Scene.active.camera.VP.multmat(Scene.active.camera.V);
Scene.active.camera.buildMatrix(); // update frustum for culling
var renderWidth = actualWidth;
var renderHeight = actualHeight;
var renderHalfWidth = actualHalfWidth;
// left half of render target
g.viewport(0, 0, renderHalfWidth, renderHeight);
g.scissor(0, 0, renderHalfWidth, renderHeight);
drawMeshes();
// Right eye
begin(g, additionalTargets);
Scene.active.camera.V.setFrom(Scene.active.camera.rightV);
// RIGHT EYE
Scene.active.camera.V.self = vr.GetViewMatrix(1);
Scene.active.camera.P.self = vr.GetProjectionMatrix(1);
g.viewport(halfw, 0, halfw, apph);
Scene.active.camera.VP.setFrom(Scene.active.camera.P);
Scene.active.camera.VP.multmat(Scene.active.camera.V);
Scene.active.camera.buildMatrix();
// right half of render target
g.viewport(renderHalfWidth, 0, renderHalfWidth, renderHeight);
g.scissor(renderHalfWidth, 0, renderHalfWidth, renderHeight);
drawMeshes();
// restore for post-processing
g.disableScissor();
g.viewport(0, 0, renderWidth, renderHeight);
}
else { // Simulate
Scene.active.camera.buildProjection(halfw / apph);
vrSimulateMode = true;
var ipd_offset = 0.032 * 35.0;
// Left eye
g.viewport(0, 0, halfw, apph);
#if (kha_webgl && lnx_vr)
if (vrCenterCameraWorld == null) vrCenterCameraWorld = Mat4.identity();
vrCenterCameraWorld.setFrom(Scene.active.camera.transform.world);
#end
Scene.active.camera.buildProjection(actualHalfWidth / actualHeight);
Scene.active.camera.transform.move(Scene.active.camera.right(), -ipd_offset);
Scene.active.camera.buildMatrix();
g.viewport(0, 0, actualHalfWidth, actualHeight);
g.scissor(0, 0, actualHalfWidth, actualHeight);
drawMeshes();
// Right eye
begin(g, additionalTargets);
Scene.active.camera.transform.move(Scene.active.camera.right(), 0.032);
Scene.active.camera.transform.move(Scene.active.camera.right(), ipd_offset * 2.0);
Scene.active.camera.buildMatrix();
g.viewport(halfw, 0, halfw, apph);
g.viewport(actualHalfWidth, 0, actualHalfWidth, actualHeight);
g.scissor(actualHalfWidth, 0, actualHalfWidth, actualHeight);
drawMeshes();
Scene.active.camera.transform.move(Scene.active.camera.right(), -0.032);
Scene.active.camera.transform.move(Scene.active.camera.right(), -ipd_offset);
Scene.active.camera.buildMatrix();
g.disableScissor();
g.viewport(0, 0, actualWidth, actualHeight);
}
}
#end

View File

@ -954,7 +954,12 @@ class Scene {
static function createTraitClassInstance(traitName: String, args: Array<Dynamic>): Dynamic {
var cname = Type.resolveClass(traitName);
if (cname == null) return null;
return Type.createInstance(cname, args);
try {
return Type.createInstance(cname, args);
} catch() {
trace("Error creating trait: " + traitName);
return null;
}
}
function loadEmbeddedData(datas: Array<String>, done: Void->Void) {

View File

@ -37,7 +37,9 @@ class Armature {
}
public function getAction(name: String): TAction {
for (a in actions) if (a.name == name) return a;
for (a in actions) {
if (a.name == name) return a;
}
return null;
}

View File

@ -141,6 +141,7 @@ class Animation {
sampler.cacheSet = false;
sampler.trackEnd = false;
if (anim == null || anim.tracks == null || anim.tracks.length == 0) return;
var track = anim.tracks[0];
if (frameIndex == -1) {
@ -442,7 +443,12 @@ class ActionSampler {
*/
public inline function setObjectAction(actionData: TObj) {
this.actionData = [actionData];
this.totalFrames = actionData.anim.tracks[0].frames.length;
if (actionData != null && actionData.anim != null && actionData.anim.tracks != null && actionData.anim.tracks.length > 0) {
this.totalFrames = actionData.anim.tracks[0].frames.length;
}
else {
this.totalFrames = 0;
}
actionDataInit = true;
}

View File

@ -108,9 +108,11 @@ class BoneAnimation extends Animation {
object.transform.rot.set(0, 0, 0, 1);
object.transform.buildMatrix();
var refs = mo.parent.raw.bone_actions;
if (refs != null && refs.length > 0) {
Data.getSceneRaw(refs[0], function(action: TSceneFormat) { play(action.name); });
if (mo.parent != null && mo.parent.raw != null && mo.parent.raw.bone_actions != null) {
var refs = mo.parent.raw.bone_actions;
if (refs.length > 0) {
Data.getSceneRaw(refs[0], function(action: TSceneFormat) { play(action.name); });
}
}
}
if (armatureObject.raw.relative_bone_constraints) relativeBoneConstraints = true;
@ -183,8 +185,10 @@ class BoneAnimation extends Animation {
}
function setAction(action: String) {
if (armature == null) return;
armature.initMats();
var a = armature.getAction(action);
if (a == null) return;
skeletonBones = a.bones;
skeletonMats = a.mats;
if(! rootMotionCacheInit) skeletonMats.push(Mat4.identity());
@ -193,8 +197,11 @@ class BoneAnimation extends Animation {
}
function getAction(action: String): Array<TObj> {
if (armature == null) return null;
armature.initMats();
return armature.getAction(action).bones;
var a = armature.getAction(action);
if (a == null) return null;
return a.bones;
}
function multParent(i: Int, fasts: Array<Mat4>, bones: Array<TObj>, mats: Array<Mat4>) {
@ -225,9 +232,9 @@ class BoneAnimation extends Animation {
}
override public function play(action = "", onComplete: Void->Void = null, blendTime = 0.2, speed = 1.0, loop = true) {
super.play(action, onComplete, blendTime, speed, loop);
if (action != "") {
setAction(action);
super.play(action, onComplete, blendTime, speed, loop);
var tempAnimParam = new ActionSampler(action);
registerAction("tempAction", tempAnimParam);
updateAnimation = function(mats){
@ -239,6 +246,10 @@ class BoneAnimation extends Animation {
override public function update(delta: FastFloat) {
this.delta = delta;
if (!isSkinned && skeletonBones == null) setAction(armature.actions[0].name);
// TODO: double check skip culling for skinned meshes if they need animation updates for bounds
// if (object != null && !object.visible) return;
if (object != null && (!object.visible || object.culled)) return;
if (skeletonBones == null || skeletonBones.length == 0) return;
@ -248,7 +259,6 @@ class BoneAnimation extends Animation {
super.update(delta);
if(updateAnimation != null) {
updateAnimation(skeletonMats);
}
@ -401,6 +411,7 @@ class BoneAnimation extends Animation {
}
var bones = sampler.getBoneAction();
if (bones == null) return;
for(b in bones){
if (b.anim != null) {
updateTrack(b.anim, sampler);
@ -410,13 +421,14 @@ class BoneAnimation extends Animation {
}
public function sampleAction(sampler: ActionSampler, actionMats: Array<Mat4>) {
if(! sampler.actionDataInit) {
var bones = getAction(sampler.action);
sampler.setBoneAction(bones);
}
var bones = sampler.getBoneAction();
if (bones == null) return;
actionMats[skeletonBones.length].setIdentity();
var rootMotionEnabled = sampler.rootMotionPos || sampler.rootMotionRot;
for (i in 0...bones.length) {
@ -427,7 +439,6 @@ class BoneAnimation extends Animation {
updateAnimSampled(bones[i].anim, actionMats[i], sampler);
}
}
}
function updateAnimSampled(anim: TAnimation, mm: Mat4, sampler: ActionSampler) {
@ -588,6 +599,9 @@ class BoneAnimation extends Animation {
public override function getTotalFrames(sampler: ActionSampler): Int {
var bones = getAction(sampler.action);
if (bones == null){
return 0;
}
var track = bones[0].anim.tracks[0];
return Std.int(track.frames[track.frames.length - 1] - track.frames[0]);
}
@ -1048,9 +1062,9 @@ class BoneAnimation extends Animation {
var rootLen = root.bone_length * rootMat.getScale().x;
// Get distance form root to goal
var goalLen = Math.abs(Vec4.distance(rootMat.getLoc(), goal));
var goalLen: FastFloat = Math.abs(Vec4.distance(rootMat.getLoc(), goal));
var totalLength = effectorLen + rootLen;
var totalLength: FastFloat = effectorLen + rootLen;
// Get tip location of effector bone
var effectorTipPos = new Vec4().setFrom(effectorMat.look()).normalize();
@ -1070,7 +1084,7 @@ class BoneAnimation extends Animation {
// Get unit vector of effector bone
var vectorEffector = new Vec4().setFrom(effectorMat.look()).normalize();
// Get dot product of vectors
var dot = new Vec4().setFrom(vectorRootEffector).dot(vectorRoot);
// Calmp between -1 and 1

View File

@ -42,9 +42,10 @@ class CameraObject extends Object {
this.data = data;
#if lnx_vr
iron.system.VR.initButton();
#end
// dont just auto initialize VR button - headset trait controls VR
// #if lnx_vr
// iron.system.VR.initButton();
// #end
buildProjection();
@ -85,7 +86,14 @@ class CameraObject extends Object {
projectionJitter();
#end
// matrices are set by VR system so avoid rebuilding transforms unless its in preview/not presenting
#if (kha_webgl && lnx_vr)
if (@:privateAccess !RenderPath.isVRPresenting()) {
buildMatrix();
}
#else
buildMatrix();
#end
RenderPath.active.renderFrame(g);

View File

@ -59,6 +59,9 @@ class LightObject extends Object {
public static var clustersData: kha.Image = null;
static var lpos = new Vec4();
public static var LWVPMatrixArray: Float32Array = null;
#if lnx_vr
static var originalLightPositions: Float32Array = null;
#end
#end // lnx_clusters
public var V: Mat4 = Mat4.identity();
@ -519,7 +522,7 @@ class LightObject extends Object {
updateLightsArray(); // TODO: only update on light change
}
static function updateLightsArray() {
public static function updateLightsArray() {
if (lightsArray == null) { // vec4x3 - 1: pos, a, color, b, 2: dir, c
lightsArray = new Float32Array(maxLights * 4 * 3);
#if lnx_spot
@ -578,6 +581,49 @@ class LightObject extends Object {
}
}
// VR deferred stereo we save original light positions before adjusting for per-eye rendering
#if lnx_vr
public static function saveOriginalLightPositions() {
if (lightsArray == null) return;
if (originalLightPositions == null) {
originalLightPositions = new Float32Array(lightsArray.length);
}
for (i in 0...lightsArray.length) {
originalLightPositions[i] = lightsArray[i];
}
}
// negative for left eye, positive for right eye
public static function adjustLightPositionsForVREye(offsetX: Float, rightVec: Vec4) {
if (lightsArray == null) return;
var lights = Scene.active.lights;
var n = lights.length > maxLights ? maxLights : lights.length;
var i = 0;
for (l in lights) {
if (discardLightCulled(l)) continue;
if (i >= n) break;
lightsArray[i * 12 ] = originalLightPositions[i * 12 ] + rightVec.x * offsetX;
lightsArray[i * 12 + 1] = originalLightPositions[i * 12 + 1] + rightVec.y * offsetX;
lightsArray[i * 12 + 2] = originalLightPositions[i * 12 + 2] + rightVec.z * offsetX;
i++;
}
}
public static function restoreOriginalLightPositions() {
if (lightsArray == null || originalLightPositions == null) return;
for (i in 0...lightsArray.length) {
lightsArray[i] = originalLightPositions[i];
}
}
#end
public static function updateLWVPMatrixArray(object: Object, type: String) {
if (LWVPMatrixArray == null) {
LWVPMatrixArray = new Float32Array(maxLightsCluster * 16);
@ -629,8 +675,8 @@ class LightObject extends Object {
LWVPMatrixArray[i * 16 + 13] = m._31;
LWVPMatrixArray[i * 16 + 14] = m._32;
LWVPMatrixArray[i * 16 + 15] = m._33;
i++; // only increment in light type
}
i++;
}
return LWVPMatrixArray;
}

View File

@ -20,6 +20,13 @@ class MorphTarget {
public var morphDataPos: Image;
public var morphDataNor: Image;
public var morphMap: Map<String, Int> = null;
public var isDirty: Bool = true;
var previousWeights: Float32Array;
var changeThreshold: FastFloat = 0.001; // skip smaller
var pendingUpdates: Map<Int, Float> = null;
var batchUpdateEnabled: Bool = true;
var lastFlushFrame: Int = 0;
public function new(data: TMorphTarget) {
initWeights(data.morph_target_defaults);
@ -42,6 +49,14 @@ class MorphTarget {
morphMap.set(name, i);
i++;
}
previousWeights = new Float32Array(morphWeights.length);
for (i in 0...morphWeights.length) {
previousWeights.set(i, morphWeights.get(i));
}
// batch system
pendingUpdates = new Map<Int, Float>();
}
inline function initWeights(defaults: Float32Array) {
@ -54,9 +69,96 @@ class MorphTarget {
public function setMorphValue(name: String, value: Float) {
var i = morphMap.get(name);
if (i != null) {
morphWeights.set(i, value);
if (batchUpdateEnabled) {
pendingUpdates.set(i, value);
} else {
setMorphValueDirect(i, value);
}
}
}
// faster indexed access
public inline function setMorphValueDirect(index: Int, value: Float) {
var current = morphWeights.get(index);
// allow explicit zero values to reset
if (value == 0.0 && current != 0.0) {
morphWeights.set(index, value);
isDirty = true;
return;
}
var delta = value - current;
if (delta < -changeThreshold || delta > changeThreshold) {
morphWeights.set(index, value);
isDirty = true;
}
}
// flush pending batch
public function flushBatchedUpdates() {
if (pendingUpdates.keys().hasNext()) {
var anyChanged = false;
var hasZeros = false;
for (index in pendingUpdates.keys()) {
var value = pendingUpdates.get(index);
if (value == null) continue;
if (value == 0.0) hasZeros = true;
var current = morphWeights.get(index);
var delta = value - current;
if (value == 0.0 && current != 0.0) {
try{
morphWeights.set(index, cast value);
}catch(e){
trace("ERROR: " + e);
}
anyChanged = true;
}
else if (delta < -changeThreshold || delta > changeThreshold) {
morphWeights.set(index, cast value);
anyChanged = true;
}
}
pendingUpdates.clear();
if (anyChanged || hasZeros) {
isDirty = true;
}
}
}
public inline function markClean() {
isDirty = false;
for (i in 0...morphWeights.length) {
previousWeights.set(i, morphWeights.get(i));
}
}
public inline function markDirty() {
isDirty = true;
}
// toggle batch mode
public inline function setBatchMode(enabled: Bool) {
if (!enabled && batchUpdateEnabled) {
flushBatchedUpdates();
}
batchUpdateEnabled = enabled;
}
public function resetAllWeights() {
for (i in 0...morphWeights.length) {
morphWeights.set(i, 0.0);
}
pendingUpdates.clear();
isDirty = true;
}
}
#end

View File

@ -210,8 +210,12 @@ class Object {
}
#if lnx_skin
public function getBoneAnimation(armatureUid): BoneAnimation {
for (a in Scene.active.animations) if (a.armature != null && a.armature.uid == armatureUid) return cast a;
public function getBoneAnimation(armatureUid: Int): BoneAnimation {
for (a in Scene.active.animations) {
if (a.armature != null && a.armature.uid == armatureUid) {
return cast a;
}
}
return null;
}
#else

View File

@ -97,7 +97,9 @@ class ObjectAnimation extends Animation {
}
public override function getTotalFrames(sampler: ActionSampler): Int {
var track = getAction(sampler.action).anim.tracks[0];
var action = getAction(sampler.action);
if (action == null || action.anim == null || action.anim.tracks == null || action.anim.tracks.length == 0) return 0;
var track = action.anim.tracks[0];
return Std.int(track.frames[track.frames.length - 1] - track.frames[0]);
}

View File

@ -7,16 +7,20 @@ import kha.graphics4.TextureFilter;
import kha.graphics4.MipMapFilter;
import kha.arrays.Float32Array;
import iron.math.Vec4;
import iron.math.Mat4;
import iron.math.Quat;
import iron.math.Mat3;
import iron.math.Mat4;
import iron.data.WorldData;
import iron.data.MaterialData;
import iron.data.ShaderData;
import iron.data.SceneFormat;
import iron.data.WorldData;
import iron.data.SceneFormat.TShaderConstant;
import iron.data.SceneFormat.TBindConstant;
import iron.object.Transform;
import iron.object.LightObject;
import iron.Scene;
import iron.RenderPath;
import iron.system.Input;
import iron.system.Time;
import iron.RenderPath;
using StringTools;
// Structure for setting shader uniforms
@ -38,6 +42,7 @@ class Uniforms {
public static var helpMat = Mat4.identity();
public static var helpMat2 = Mat4.identity();
public static var helpMat3 = Mat3.identity();
public static var helpMat4 = Mat4.identity();
public static var helpVec = new Vec4();
public static var helpVec2 = new Vec4();
public static var helpQuat = new Quat(); // Keep at identity
@ -47,6 +52,10 @@ class Uniforms {
public static var externalVec4Links: Array<Object->MaterialData->String->Vec4> = null;
public static var externalVec3Links: Array<Object->MaterialData->String->Vec4> = null;
public static var externalVec2Links: Array<Object->MaterialData->String->Vec4> = null;
public static var eyeLeftCallCount = 0;
public static var lastFrameChecked = -1;
public static var externalFloatLinks: Array<Object->MaterialData->String->Null<kha.FastFloat>> = null;
public static var externalFloatsLinks: Array<Object->MaterialData->String->Float32Array> = null;
public static var externalIntLinks: Array<Object->MaterialData->String->Null<Int>> = null;
@ -59,6 +68,10 @@ class Uniforms {
public static var defaultFilter = TextureFilter.LinearFilter;
#end
#if lnx_morph_target
public static var forceUploadMorphWeights: Bool = false;
#end
public static function setContextConstants(g: Graphics, context: ShaderContext, bindParams: Array<String>) {
if (context.raw.constants != null) {
for (i in 0...context.raw.constants.length) {
@ -181,11 +194,15 @@ class Uniforms {
// Multiple voxel volumes, always set params
g.setImageTexture(context.textureUnits[j], rt.image); // image2D/3D
if (rt.raw.name.startsWith("voxels_")) {
g.setTextureParameters(context.textureUnits[j], TextureAddressing.Clamp, TextureAddressing.Clamp, TextureFilter.LinearFilter, TextureFilter.LinearFilter, MipMapFilter.NoMipFilter);
g.setTextureParameters(context.textureUnits[j], TextureAddressing.Clamp, TextureAddressing.Clamp, TextureFilter.LinearFilter, TextureFilter.LinearFilter, MipMapFilter.LinearMipFilter);
}
else if (rt.raw.name.startsWith("voxelsSDF"))
{
g.setTexture3DParameters(context.textureUnits[j], TextureAddressing.Clamp, TextureAddressing.Clamp, TextureAddressing.Clamp, TextureFilter.PointFilter, TextureFilter.PointFilter, MipMapFilter.NoMipFilter);
}
else if (rt.raw.name.startsWith("voxels"))
{
g.setTexture3DParameters(context.textureUnits[j], TextureAddressing.Clamp, TextureAddressing.Clamp, TextureAddressing.Clamp, TextureFilter.LinearFilter, TextureFilter.LinearFilter, MipMapFilter.NoMipFilter);
g.setTexture3DParameters(context.textureUnits[j], TextureAddressing.Clamp, TextureAddressing.Clamp, TextureAddressing.Clamp, TextureFilter.LinearFilter, TextureFilter.LinearFilter, MipMapFilter.PointMipFilter);
}
else
{
@ -286,6 +303,89 @@ class Uniforms {
helpMat.getInverse(helpMat);
m = helpMat;
}
#if lnx_vr
case "_inverseViewProjectionMatrixLeft": {
var vr = kha.vr.VrInterface.instance;
if (vr != null && vr.IsPresenting()) {
var leftView = vr.GetViewMatrix(0);
var leftProj = vr.GetProjectionMatrix(0);
helpMat._00 = leftView._00; helpMat._01 = leftView._01; helpMat._02 = leftView._02; helpMat._03 = leftView._03;
helpMat._10 = leftView._10; helpMat._11 = leftView._11; helpMat._12 = leftView._12; helpMat._13 = leftView._13;
helpMat._20 = leftView._20; helpMat._21 = leftView._21; helpMat._22 = leftView._22; helpMat._23 = leftView._23;
helpMat._30 = leftView._30; helpMat._31 = leftView._31; helpMat._32 = leftView._32; helpMat._33 = leftView._33;
helpMat2._00 = leftProj._00; helpMat2._01 = leftProj._01; helpMat2._02 = leftProj._02; helpMat2._03 = leftProj._03;
helpMat2._10 = leftProj._10; helpMat2._11 = leftProj._11; helpMat2._12 = leftProj._12; helpMat2._13 = leftProj._13;
helpMat2._20 = leftProj._20; helpMat2._21 = leftProj._21; helpMat2._22 = leftProj._22; helpMat2._23 = leftProj._23;
helpMat2._30 = leftProj._30; helpMat2._31 = leftProj._31; helpMat2._32 = leftProj._32; helpMat2._33 = leftProj._33;
helpMat.multmat(helpMat2);
helpMat.getInverse(helpMat);
} else if (iron.RenderPath.isVRSimulateMode()) {
var ipd_offset = 0.032 * 35.0; // Match eye offset
var rightVec = camera.rightWorld();
var eyeLeftX = camera.transform.worldx() - rightVec.x * ipd_offset;
var eyeLeftY = camera.transform.worldy() - rightVec.y * ipd_offset;
var eyeLeftZ = camera.transform.worldz() - rightVec.z * ipd_offset;
helpMat.setFrom(camera.transform.world);
helpMat._30 = eyeLeftX;
helpMat._31 = eyeLeftY;
helpMat._32 = eyeLeftZ;
helpMat.getInverse(helpMat); // Now it's a view matrix
helpMat.multmat(camera.P);
helpMat.getInverse(helpMat);
} else {
helpMat.setFrom(camera.V);
helpMat.multmat(camera.P);
helpMat.getInverse(helpMat);
}
m = helpMat;
}
case "_inverseViewProjectionMatrixRight": {
var vr = kha.vr.VrInterface.instance;
if (vr != null && vr.IsPresenting()) {
var rightView = vr.GetViewMatrix(1);
var rightProj = vr.GetProjectionMatrix(1);
// kha.math.FastMatrix4 to iron.math.Mat4
helpMat2._00 = rightView._00; helpMat2._01 = rightView._01; helpMat2._02 = rightView._02; helpMat2._03 = rightView._03;
helpMat2._10 = rightView._10; helpMat2._11 = rightView._11; helpMat2._12 = rightView._12; helpMat2._13 = rightView._13;
helpMat2._20 = rightView._20; helpMat2._21 = rightView._21; helpMat2._22 = rightView._22; helpMat2._23 = rightView._23;
helpMat2._30 = rightView._30; helpMat2._31 = rightView._31; helpMat2._32 = rightView._32; helpMat2._33 = rightView._33;
helpMat4._00 = rightProj._00; helpMat4._01 = rightProj._01; helpMat4._02 = rightProj._02; helpMat4._03 = rightProj._03;
helpMat4._10 = rightProj._10; helpMat4._11 = rightProj._11; helpMat4._12 = rightProj._12; helpMat4._13 = rightProj._13;
helpMat4._20 = rightProj._20; helpMat4._21 = rightProj._21; helpMat4._22 = rightProj._22; helpMat4._23 = rightProj._23;
helpMat4._30 = rightProj._30; helpMat4._31 = rightProj._31; helpMat4._32 = rightProj._32; helpMat4._33 = rightProj._33;
helpMat2.multmat(helpMat4);
helpMat2.getInverse(helpMat2);
m = helpMat2;
} else if (iron.RenderPath.isVRSimulateMode()) {
var ipd_offset = 0.032 * 35.0;
var rightVec = camera.rightWorld();
// calculate right eye position in world space
var eyeRightX = camera.transform.worldx() + rightVec.x * ipd_offset;
var eyeRightY = camera.transform.worldy() + rightVec.y * ipd_offset;
var eyeRightZ = camera.transform.worldz() + rightVec.z * ipd_offset;
helpMat2.setFrom(camera.transform.world);
helpMat2._30 = eyeRightX;
helpMat2._31 = eyeRightY;
helpMat2._32 = eyeRightZ;
helpMat2.getInverse(helpMat2);
helpMat2.multmat(camera.P);
helpMat2.getInverse(helpMat2);
m = helpMat2;
} else {
// fallback to center camera
helpMat2.setFrom(camera.V);
helpMat2.multmat(camera.P);
helpMat2.getInverse(helpMat2);
m = helpMat2;
}
}
#end
case "_viewProjectionMatrix": {
#if lnx_centerworld
m = vmat(camera.V);
@ -398,6 +498,28 @@ class Uniforms {
v = helpVec;
}
}
#if lnx_vr
case "_pointPositionLeft": {
var point = RenderPath.active.point;
if (point != null) {
var lightWorldX = point.transform.worldx();
var lightWorldY = point.transform.worldy();
var lightWorldZ = point.transform.worldz();
helpVec.set(lightWorldX, lightWorldY, lightWorldZ);
v = helpVec;
}
}
case "_pointPositionRight": {
var point = RenderPath.active.point;
if (point != null) {
var lightWorldX = point.transform.worldx();
var lightWorldY = point.transform.worldy();
var lightWorldZ = point.transform.worldz();
helpVec.set(lightWorldX, lightWorldY, lightWorldZ);
v = helpVec;
}
}
#end
#if lnx_spot
case "_spotDirection": {
var point = RenderPath.active.point;
@ -484,6 +606,84 @@ class Uniforms {
helpVec = camera.rightWorld().normalize();
v = helpVec;
}
#if lnx_vr
case "_eyeLeft": {
var currentFrame = iron.RenderPath.active.frame;
if (currentFrame != lastFrameChecked) {
eyeLeftCallCount = 0;
lastFrameChecked = currentFrame;
}
eyeLeftCallCount++;
var vr = kha.vr.VrInterface.instance;
if (vr != null && vr.IsPresenting()) {
var leftViewMatrix = vr.GetViewMatrix(0);
var invLeft = leftViewMatrix.inverse();
helpVec.set(invLeft._30, invLeft._31, invLeft._32);
// trace("eyeLeft: " + helpVec.x + ", " + helpVec.y + ", " + helpVec.z);
} else if (iron.RenderPath.isVRSimulateMode()) {
var ipd_offset = 0.032 * 35.0;
var rightVec = camera.rightWorld();
var centerX = camera.transform.worldx();
var centerY = camera.transform.worldy();
var centerZ = camera.transform.worldz();
helpVec.set(
centerX - rightVec.x * ipd_offset,
centerY - rightVec.y * ipd_offset,
centerZ - rightVec.z * ipd_offset
);
} else {
helpVec.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
}
v = helpVec;
}
case "_eyeRight": {
var vr = kha.vr.VrInterface.instance;
if (vr != null && vr.IsPresenting()) {
var rightViewMatrix = vr.GetViewMatrix(1);
var invRight = rightViewMatrix.inverse();
helpVec.set(invRight._30, invRight._31, invRight._32);
} else if (iron.RenderPath.isVRSimulateMode()) {
var ipd_offset = 0.032 * 35.0;
var rightVec = camera.rightWorld();
var centerX = camera.transform.worldx();
var centerY = camera.transform.worldy();
var centerZ = camera.transform.worldz();
helpVec.set(
centerX + rightVec.x * ipd_offset,
centerY + rightVec.y * ipd_offset,
centerZ + rightVec.z * ipd_offset
);
} else {
helpVec.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
}
v = helpVec;
}
case "_eyeLookLeft": {
var vr = kha.vr.VrInterface.instance;
if (vr != null && vr.IsPresenting()) {
var leftViewMatrix = vr.GetViewMatrix(0);
var invLeft = leftViewMatrix.inverse();
helpVec.set(-invLeft._20, -invLeft._21, -invLeft._22);
helpVec.normalize();
} else {
helpVec = camera.lookWorld().normalize();
}
v = helpVec;
}
case "_eyeLookRight": {
var vr = kha.vr.VrInterface.instance;
if (vr != null && vr.IsPresenting()) {
var rightViewMatrix = vr.GetViewMatrix(1);
var invRight = rightViewMatrix.inverse();
helpVec.set(-invRight._20, -invRight._21, -invRight._22);
helpVec.normalize();
} else {
helpVec = camera.lookWorld().normalize();
}
v = helpVec;
}
#end
case "_backgroundCol": {
if (camera.data.raw.clear_color != null) helpVec.set(camera.data.raw.clear_color[0], camera.data.raw.clear_color[1], camera.data.raw.clear_color[2]);
v = helpVec;
@ -1161,7 +1361,19 @@ class Uniforms {
#end // lnx_clusters
#if lnx_morph_target
case "_morphWeights": {
fa = cast(object, MeshObject).morphTarget.morphWeights;
var morphTarget = cast(object, MeshObject).morphTarget;
morphTarget.flushBatchedUpdates();
if (forceUploadMorphWeights) {
fa = morphTarget.morphWeights;
}
else {
if (morphTarget.isDirty) {
fa = morphTarget.morphWeights;
}
else {
return;
}
}
}
#end
}
@ -1175,6 +1387,12 @@ class Uniforms {
if (fa == null) return;
g.setFloats(location, fa);
#if lnx_morph_target
if (c.link == "_morphWeights") {
cast(object, MeshObject).morphTarget.markClean();
}
#end
}
else if (c.type == "int") {
var i: Null<Int> = null;
@ -1203,6 +1421,7 @@ class Uniforms {
if (materialContext.raw.bind_constants != null) {
for (i in 0...materialContext.raw.bind_constants.length) {
var matc = materialContext.raw.bind_constants[i];
if (matc == null) continue;
var pos = -1;
for (i in 0...context.raw.constants.length) {
if (context.raw.constants[i].name == matc.name) {

View File

@ -3,6 +3,14 @@ package iron.system;
class Time {
public static var scale = 1.0;
// TODO: VR Frame Time Override - used to sync physics with VR headset refresh rate
#if lnx_vr
public static var vrFrameTime: Float = -1.0; // VR frame time in seconds (-1 = not in VR)
static var lastVRFrameTime: Float = 0.0;
static var vrFrameCount: Int = 0;
static var normalModeLogged: Bool = false;
#end
static var frequency: Null<Int> = null;
static function initFrequency() {
frequency = kha.Display.primary != null ? kha.Display.primary.frequency : 60;
@ -47,6 +55,24 @@ class Time {
}
public static function update() {
#if lnx_vr
// TODO: use VR frame time when in VR present mode to sync physics with headset refresh
if (vrFrameTime >= 0.0) {
if (lastVRFrameTime > 0.0) {
_delta = vrFrameTime - lastVRFrameTime;
} else {
_delta = 1.0 / 90.0; // Default to 90Hz for first VR frame
}
lastVRFrameTime = vrFrameTime;
return;
} else {
if (!normalModeLogged) {
normalModeLogged = true;
}
}
#end
_delta = realTime() - lastTime;
lastTime = realTime();
}

View File

@ -0,0 +1,138 @@
package iron.system;
#if lnx_vr
import iron.math.Vec4;
import iron.math.Quat;
class VRController {
public static var leftHandPosition: Vec4 = new Vec4();
public static var leftHandRotation: Quat = new Quat();
public static var rightHandPosition: Vec4 = new Vec4();
public static var rightHandRotation: Quat = new Quat();
public static var leftHandActive: Bool = false;
public static var rightHandActive: Bool = false;
public static var leftThumbstickX: Float = 0.0;
public static var leftThumbstickY: Float = 0.0;
public static var rightThumbstickX: Float = 0.0;
public static var rightThumbstickY: Float = 0.0;
public static var leftTrigger: Float = 0.0;
public static var rightTrigger: Float = 0.0;
public static var leftGrip: Float = 0.0;
public static var rightGrip: Float = 0.0;
public static var leftButtonX: Bool = false;
public static var leftButtonY: Bool = false;
public static var rightButtonA: Bool = false;
public static var rightButtonB: Bool = false;
public static var debugLog:Bool = false;
public static function enableDebug() {
debugLog = true;
}
public static function disableDebug() {
debugLog = false;
}
public static function updatePoses() {
var vr: kha.js.vr.VrInterface = cast kha.vr.VrInterface.instance;
if (vr == null || !vr.IsPresenting()) {
if (debugLog) trace("[VRController] Not presenting or VR null");
leftHandActive = false;
rightHandActive = false;
return;
}
untyped window._vrControllerFrame = (untyped window._vrControllerFrame || 0) + 1;
leftHandActive = false;
rightHandActive = false;
leftButtonX = false;
leftButtonY = false;
rightButtonA = false;
rightButtonB = false;
var refSpace = untyped vr.xrRefSpace;
if (vr.currentInputSources == null || vr.currentFrame == null || refSpace == null) {
return;
}
var inputSources = vr.currentInputSources;
var frame = vr.currentFrame;
var sourceCount:Int = untyped inputSources.length;
for (i in 0...sourceCount) {
var inputSource = untyped inputSources[i];
if (inputSource == null) continue;
var handedness = untyped inputSource.handedness; // "left", "right", or "none"
var gripSpace = untyped inputSource.gripSpace;
var targetRaySpace = untyped inputSource.targetRaySpace;
// use targetRaySpace first laser/pointer and fall back to gripSpace
var space = (targetRaySpace != null) ? targetRaySpace : gripSpace;
if (space == null) {
continue;
}
var pose = untyped frame.getPose(space, refSpace);
if (pose == null || pose.transform == null) {
continue;
}
var transform = pose.transform;
var pos = transform.position;
var orient = transform.orientation;
if (handedness == "left") {
leftHandPosition.set(pos.x, pos.y, pos.z);
leftHandRotation.set(orient.x, orient.y, orient.z, orient.w);
leftHandActive = true;
var gamepad = untyped inputSource.gamepad;
if (gamepad != null) {
// [0]=thumbstickX [1]=thumbstickY [2]=touchpadX [3]=touchpadY
if (gamepad.axes != null && gamepad.axes.length >= 2) {
leftThumbstickX = gamepad.axes[0];
leftThumbstickY = gamepad.axes[1];
}
// [0]=trigger [1]=grip [4]=X [5]=Y
if (gamepad.buttons != null) {
if (gamepad.buttons.length > 0) leftTrigger = gamepad.buttons[0].value;
if (gamepad.buttons.length > 1) leftGrip = gamepad.buttons[1].value;
if (gamepad.buttons.length > 4) leftButtonX = gamepad.buttons[4].pressed;
if (gamepad.buttons.length > 5) leftButtonY = gamepad.buttons[5].pressed;
}
}
}
else if (handedness == "right") {
rightHandPosition.set(pos.x, pos.y, pos.z);
rightHandRotation.set(orient.x, orient.y, orient.z, orient.w);
rightHandActive = true;
var gamepad = untyped inputSource.gamepad;
if (gamepad != null) {
if (gamepad.axes != null && gamepad.axes.length >= 2) {
rightThumbstickX = gamepad.axes[0];
rightThumbstickY = gamepad.axes[1];
}
if (gamepad.buttons != null) {
if (gamepad.buttons.length > 0) rightTrigger = gamepad.buttons[0].value;
if (gamepad.buttons.length > 1) rightGrip = gamepad.buttons[1].value;
if (gamepad.buttons.length > 4) rightButtonA = gamepad.buttons[4].pressed;
if (gamepad.buttons.length > 5) rightButtonB = gamepad.buttons[5].pressed;
}
}
}
}
}
}
#end