<commit_before>#import "Scene.h"

@implementation Scene
{
	
    SCNNode *headRotationNode, *headPositionNode;
    float hrx, hry, hrz;  // head rotation angles in radians
	
	NSMutableDictionary *keyDownHandlers, *keyUpHandlers, *mouseDownHandlers, *mouseUpHandlers;
    BOOL isMovingForward, isMovingBackward, isMovingLeft, isMovingRight;
}

@synthesize eye;
@synthesize roomSize;
@synthesize avatarHeight;
@synthesize avatarSpeed;
@synthesize turnSpeed;
@synthesize headPosition;

#pragma mark - Singleton

static Scene *currentLeftScene = nil;
+ (id)currentLeftScene
{
	@synchronized(self)
	{
        if (currentLeftScene == nil)
            currentLeftScene = [[self alloc] init];
    }
	return currentLeftScene;
}
static Scene *currentRightScene = nil;
+ (id)currentRightScene
{
	@synchronized(self)
	{
        if (currentRightScene == nil)
            currentRightScene = [[self alloc] init];
    }
	return currentRightScene;
}
+ (void)setCurrentSceneLeft:(Scene*)leftScene
					  right:(Scene*)rightScene
{
	@synchronized(self)
	{
		currentLeftScene = leftScene;
		currentRightScene = rightScene;
		[leftScene resetEventHandlers];
		[rightScene resetEventHandlers];
		[leftScene addEventHandlers];
		[rightScene addEventHandlers];
    }
}

#pragma mark - Left or Right

- (BOOL)isLeft  { return [eye isEqual: @"left"]; }
- (BOOL)isRight { return [eye isEqual: @"right"]; }

- (NSString*)eye
{
	return eye;
}
- (void)setEye:(NSString*)theEye
{
	eye = theEye;
}

#pragma mark - Initialization

- (id)init
{
    if (!(self = [super init])) return nil;
    
    // create nodes for eye cameras and head sensors
    headPositionNode = [SCNNode node];
    headPositionNode.position = SCNVector3Make(0, 0, 0);
    headRotationNode = [SCNNode node];
    [headPositionNode addChildNode:headRotationNode];
    [self.rootNode    addChildNode:headPositionNode];
	
	// default sizes
    roomSize = 1000;
	avatarHeight = 100;
	avatarSpeed = 1;
	turnSpeed = 0.025;
	
	[self resetEventHandlers];  // initialize handler storage
	[self stopMoving];  // set all isMovings to NO
	
    return self;
}

// Register event handlers with the main window.
// Defaults are StepWASD and LeftMouseDownMoveForward.
- (void) addEventHandlers
{
	// default event handlers
	[self addEventHandlersForStepWASD];
	[self addEventHandlersForStepArrows];
	[self addEventHandlersForLeftMouseDownMoveForward];
}


- (SCNNode*) loadNode:(NSString*)nodename
			  fromDae:(NSString*)filename
{
	// TODO: error handling
	NSURL *fileURL = [[NSBundle mainBundle] URLForResource:filename withExtension:@"dae"];
	if (!fileURL)
	{
		NSLog(@"no DAE file named %@", filename);
		return nil;
	}
	
	SCNScene *scene = [SCNScene sceneWithURL:fileURL options:nil error:nil];
	if (!scene)
	{
		NSLog(@"no scene in file %@.dae at %@", filename, fileURL);
		return nil;
	}
	
	SCNNode *node = [scene.rootNode childNodeWithName:nodename recursively:YES];
	if (!node)
		NSLog(@"no node named %@ in file %@.dae\n%@", nodename, filename, scene);
	return node;
}

#pragma mark - Avatar head position and rotation

- (Vector3f)vector3fFromSCNVector3:(SCNVector3)v { return Vector3f(v.x, v.y, v.z); }
- (SCNVector3)scnvector3FromVector3f:(Vector3f)v { return SCNVector3Make(v.x, v.y, v.z); }

- (Vector4f)vector4fFromSCNVector4:(SCNVector4)v { return Vector4f(v.x, v.y, v.z, v.w); }
- (SCNVector4)scnvector4FromVector4f:(Vector4f)v { return SCNVector4Make(v.x, v.y, v.z, v.w); }

// position is public, node is private
- (SCNVector3) headPosition { return headPositionNode.position; }
- (Vector3f) headPositionVector3f { return [self vector3fFromSCNVector3:headPositionNode.position]; }

- (void)setHeadPosition:(SCNVector3)position { headPositionNode.position = position; }
- (void)setHeadPositionVector3f:(Vector3f)position { headPositionNode.position = [self scnvector3FromVector3f:position]; }

- (Vector4f) headRotationVector4f { return [self vector4fFromSCNVector4:headRotationNode.rotation]; }

- (void)setHeadRotationX:(float)x Y:(float)y Z:(float)z
{
	//NSLog(@"set hr %.2fx %.2fy %.2fz", x, y, z);
	
    hrx = x;
    hry = y;
    hrz = z;
    
    CATransform3D transform    =      CATransform3DMakeRotation(x, 0, 1, 0);
    transform                  = CATransform3DRotate(transform, y, 1, 0, 0);
    headRotationNode.transform = CATransform3DRotate(transform, z, 0, 0, 1);
}
- (void)addHeadRotationX:(float)x Y:(float)y Z:(float)z
{
	//NSLog(@"add hr %.2fx %.2fy %.2fz", x, y, z);
	
    hrx = x;
    hry = y;
    hrz = z;
    
    CATransform3D transform    = headRotationNode.transform;
	transform				   = CATransform3DRotate(transform, x, 0, 1, 0);
    transform                  = CATransform3DRotate(transform, y, 1, 0, 0);
    headRotationNode.transform = CATransform3DRotate(transform, z, 0, 0, 1);
}
- (void)turnDirection:(Vector3f)direction distance:(float)distance
{
    Vector3f t = direction * distance;
	//NSLog(@"turn %.2f %.2f %.2f", t.x, t.y, t.z);
	[self addHeadRotationX:t.x Y:t.y Z:t.z];
}

- (void)turnUp	  { [self turnDirection:Vector3f(0,-1,0) distance:turnSpeed]; }
- (void)turnDown  { [self turnDirection:Vector3f(0, 1,0) distance:turnSpeed]; }
- (void)turnLeft  { [self turnDirection:Vector3f( 1,0,0) distance:turnSpeed]; }
- (void)turnRight { [self turnDirection:Vector3f(-1,0,0) distance:turnSpeed]; }

- (void)linkNodeToHeadPosition:(SCNNode*)node { [headPositionNode addChildNode:node]; }
- (void)linkNodeToHeadRotation:(SCNNode*)node { [headRotationNode addChildNode:node]; }


#pragma mark - Avatar movement
// TODO: add diagonal 2D movement (add and normalize vectors)
// MAYBE: add XY WASD movement (locked to world, not direction facing)

- (BOOL)isMoving { return isMovingForward || isMovingBackward || isMovingLeft || isMovingRight; }
- (void)stopMoving
{<commit_msg>Fixed movement direction in debug mode.<commit_after>#import "Scene.h"

@implementation Scene
{
	
    SCNNode *headRotationNode, *headPositionNode;
    float hrx, hry, hrz;  // head rotation angles in radians
	
	NSMutableDictionary *keyDownHandlers, *keyUpHandlers, *mouseDownHandlers, *mouseUpHandlers;
    BOOL isMovingForward, isMovingBackward, isMovingLeft, isMovingRight;
}

@synthesize eye;
@synthesize roomSize;
@synthesize avatarHeight;
@synthesize avatarSpeed;
@synthesize turnSpeed;
@synthesize headPosition;

#pragma mark - Singleton

static Scene *currentLeftScene = nil;
+ (id)currentLeftScene
{
	@synchronized(self)
	{
        if (currentLeftScene == nil)
            currentLeftScene = [[self alloc] init];
    }
	return currentLeftScene;
}
static Scene *currentRightScene = nil;
+ (id)currentRightScene
{
	@synchronized(self)
	{
        if (currentRightScene == nil)
            currentRightScene = [[self alloc] init];
    }
	return currentRightScene;
}
+ (void)setCurrentSceneLeft:(Scene*)leftScene
					  right:(Scene*)rightScene
{
	@synchronized(self)
	{
		currentLeftScene = leftScene;
		currentRightScene = rightScene;
		[leftScene resetEventHandlers];
		[rightScene resetEventHandlers];
		[leftScene addEventHandlers];
		[rightScene addEventHandlers];
    }
}

#pragma mark - Left or Right

- (BOOL)isLeft  { return [eye isEqual: @"left"]; }
- (BOOL)isRight { return [eye isEqual: @"right"]; }

- (NSString*)eye
{
	return eye;
}
- (void)setEye:(NSString*)theEye
{
	eye = theEye;
}

#pragma mark - Initialization

- (id)init
{
    if (!(self = [super init])) return nil;
    
    // create nodes for eye cameras and head sensors
    headPositionNode = [SCNNode node];
    headPositionNode.position = SCNVector3Make(0, 0, 0);
    headRotationNode = [SCNNode node];
    [headPositionNode addChildNode:headRotationNode];
    [self.rootNode    addChildNode:headPositionNode];
	
	// default sizes
    roomSize = 1000;
	avatarHeight = 100;
	avatarSpeed = 1;
	turnSpeed = 0.025;
	
	[self resetEventHandlers];  // initialize handler storage
	[self stopMoving];  // set all isMovings to NO
	
    return self;
}

// Register event handlers with the main window.
// Defaults are StepWASD and LeftMouseDownMoveForward.
- (void) addEventHandlers
{
	// default event handlers
	[self addEventHandlersForStepWASD];
	[self addEventHandlersForStepArrows];
	[self addEventHandlersForLeftMouseDownMoveForward];
}


- (SCNNode*) loadNode:(NSString*)nodename
			  fromDae:(NSString*)filename
{
	// TODO: error handling
	NSURL *fileURL = [[NSBundle mainBundle] URLForResource:filename withExtension:@"dae"];
	if (!fileURL)
	{
		NSLog(@"no DAE file named %@", filename);
		return nil;
	}
	
	SCNScene *scene = [SCNScene sceneWithURL:fileURL options:nil error:nil];
	if (!scene)
	{
		NSLog(@"no scene in file %@.dae at %@", filename, fileURL);
		return nil;
	}
	
	SCNNode *node = [scene.rootNode childNodeWithName:nodename recursively:YES];
	if (!node)
		NSLog(@"no node named %@ in file %@.dae\n%@", nodename, filename, scene);
	return node;
}

#pragma mark - Avatar head position and rotation

- (Vector3f)vector3fFromSCNVector3:(SCNVector3)v { return Vector3f(v.x, v.y, v.z); }
- (SCNVector3)scnvector3FromVector3f:(Vector3f)v { return SCNVector3Make(v.x, v.y, v.z); }

- (Vector4f)vector4fFromSCNVector4:(SCNVector4)v { return Vector4f(v.x, v.y, v.z, v.w); }
- (SCNVector4)scnvector4FromVector4f:(Vector4f)v { return SCNVector4Make(v.x, v.y, v.z, v.w); }

// position is public, node is private
- (SCNVector3) headPosition { return headPositionNode.position; }
- (Vector3f) headPositionVector3f { return [self vector3fFromSCNVector3:headPositionNode.position]; }

- (void)setHeadPosition:(SCNVector3)position { headPositionNode.position = position; }
- (void)setHeadPositionVector3f:(Vector3f)position { headPositionNode.position = [self scnvector3FromVector3f:position]; }

- (Vector4f) headRotationVector4f { return [self vector4fFromSCNVector4:headRotationNode.rotation]; }

- (void)setHeadRotationX:(float)x Y:(float)y Z:(float)z
{
    hrx = x;
    hry = y;
    hrz = z;
	//NSLog(@"set hr %.2fx %.2fy %.2fz", x, y, z);
    
    CATransform3D transform    =      CATransform3DMakeRotation(x, 0, 1, 0);
    transform                  = CATransform3DRotate(transform, y, 1, 0, 0);
    headRotationNode.transform = CATransform3DRotate(transform, z, 0, 0, 1);
}
- (void)addHeadRotationX:(float)x Y:(float)y Z:(float)z
{
    hrx += x;
    hry += y;
    hrz += z;
	//NSLog(@"add hr %.2fx %.2fy %.2fz = %.2fx %.2fy %.2fz", x, y, z, hrx, hry, hrz);
    
    CATransform3D transform    = headRotationNode.transform;
	transform				   = CATransform3DRotate(transform, x, 0, 1, 0);
    transform                  = CATransform3DRotate(transform, y, 1, 0, 0);
    headRotationNode.transform = CATransform3DRotate(transform, z, 0, 0, 1);
}
- (void)turnDirection:(Vector3f)direction distance:(float)distance
{
    Vector3f t = direction * distance;
	//NSLog(@"turn %.2f %.2f %.2f", t.x, t.y, t.z);
	[self addHeadRotationX:t.x Y:t.y Z:t.z];
}

- (void)turnUp	  { [self turnDirection:Vector3f(0,-1,0) distance:turnSpeed]; }
- (void)turnDown  { [self turnDirection:Vector3f(0, 1,0) distance:turnSpeed]; }
- (void)turnLeft  { [self turnDirection:Vector3f( 1,0,0) distance:turnSpeed]; }
- (void)turnRight { [self turnDirection:Vector3f(-1,0,0) distance:turnSpeed]; }

- (void)linkNodeToHeadPosition:(SCNNode*)node { [headPositionNode addChildNode:node]; }
- (void)linkNodeToHeadRotation:(SCNNode*)node { [headRotationNode addChildNode:node]; }


#pragma mark - Avatar movement
// TODO: add diagonal 2D movement (add and normalize vectors)
// MAYBE: add XY WASD movement (locked to world, not direction facing)

- (BOOL)isMoving { return isMovingForward || isMovingBackward || isMovingLeft || isMovingRight; }
- (void)stopMoving
{
{'fraction_non_alphanumeric': 0.13334486336907644, 'fraction_numerical': 0.018246281563472848, 'mean_word_length': 4.432127759511507, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2433686', 'n_tokens_mistral': 4342, 'n_tokens_neox': 3995, 'n_words': 899}