User prompt
smoothingFactor To 1
User prompt
smoothingFactor To 0.4
User prompt
Switch bypass to false
Code edit (1 edits merged)
Please save this source code
Code edit (6 edits merged)
Please save this source code
User prompt
switch bypassTracking to true
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (17 edits merged)
Please save this source code
User prompt
switch bypassTracking to false
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
switch bypassTracking to true
Code edit (11 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'head')' in or related to this line: 'self.scales = {' Line Number: 238
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'head')' in or related to this line: 'self.scales = {' Line Number: 224
Code edit (1 edits merged)
Please save this source code
Code edit (8 edits merged)
Please save this source code
User prompt
switch bypassTracking to false
User prompt
switch bypassTracking to true
Code edit (1 edits merged)
Please save this source code
Code edit (18 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'parse')' in or related to this line: 'previousFacekit = JSON.parse(JSON.stringify(currentKit));' Line Number: 609
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'parse')' in or related to this line: 'previousFacekit = JSON.parse(JSON.stringify(currentKit));' Line Number: 600
Code edit (1 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); var facekit = LK.import("@upit/facekit.v1"); /**** * Classes ****/ var DebugPoints = Container.expand(function () { var self = Container.call(this); // Create points for face tracking debugging self.points = { leftEye: self.attachAsset('debugFacePoints', { anchorX: 0.5, anchorY: 0.5 }), rightEye: self.attachAsset('debugFacePoints', { anchorX: 0.5, anchorY: 0.5 }), noseTip: self.attachAsset('debugFacePoints', { anchorX: 0.5, anchorY: 0.5 }), mouthCenter: self.attachAsset('debugFacePoints', { anchorX: 0.5, anchorY: 0.5 }), upperLip: self.attachAsset('debugFacePoints', { anchorX: 0.5, anchorY: 0.5 }), lowerLip: self.attachAsset('debugFacePoints', { anchorX: 0.5, anchorY: 0.5 }), chin: self.attachAsset('debugFacePoints', { anchorX: 0.5, anchorY: 0.5 }) }; // Update debug points to match face points self.update = function () { if (!facekit) { return; } if (facekit.leftEye) { self.points.leftEye.x = facekit.leftEye.x; self.points.leftEye.y = facekit.leftEye.y; } if (facekit.rightEye) { self.points.rightEye.x = facekit.rightEye.x; self.points.rightEye.y = facekit.rightEye.y; } if (facekit.noseTip) { self.points.noseTip.x = facekit.noseTip.x; self.points.noseTip.y = facekit.noseTip.y; } if (facekit.mouthCenter) { self.points.mouthCenter.x = facekit.mouthCenter.x; self.points.mouthCenter.y = facekit.mouthCenter.y; } if (facekit.upperLip) { self.points.upperLip.x = facekit.upperLip.x; self.points.upperLip.y = facekit.upperLip.y; } if (facekit.lowerLip) { self.points.lowerLip.x = facekit.lowerLip.x; self.points.lowerLip.y = facekit.lowerLip.y; } if (facekit.chin) { self.points.chin.x = facekit.chin.x; self.points.chin.y = facekit.chin.y; } }; return self; }); var TrollFace = Container.expand(function () { var self = Container.call(this); self.scales = { head: { x: 1, y: 1 }, eye: { x: 1, y: 1 }, lip: { x: 1, y: 1 } }; // Properties self.currentStyle = 1; self.currentOffsets = trollFaceOffsets[self.currentStyle - 1]; self.elements = {}; // Initialize cached position objects to reuse self.positionCache = { head: { x: 0, y: 0, scaleX: 1, scaleY: 1 }, leftEye: { x: 0, y: 0, scaleX: 1, scaleY: 1 }, rightEye: { x: 0, y: 0, scaleX: 1, scaleY: 1 }, upperLip: { x: 0, y: 0, scaleX: 1, scaleY: 1 }, lowerLip: { x: 0, y: 0, scaleX: 1, scaleY: 1 } }; // Initialize the troll face elements self.initialize = function (style) { // Clear previous elements self.removeAllElements(); // Create new elements for the selected style self.createFaceElements(style || self.currentStyle); }; // Create face elements for a specific style self.createFaceElements = function (style) { // Create head self.elements.head = self.attachAsset('trollHead' + style, { anchorX: 0.5, anchorY: 0.5, scale: 1, alpha: 1 // DEBUG }); // Create eyes self.elements.leftEye = self.attachAsset('trollLeftEye' + style, { anchorX: 0.5, anchorY: 0.5, scale: 1 }); self.elements.rightEye = self.attachAsset('trollRightEye' + style, { anchorX: 0.5, anchorY: 0.5, scale: 1 }); // Create upper lip self.elements.upperLip = self.attachAsset('trollUpperLip' + style, { anchorX: 0.5, anchorY: 0.5, scale: 1 }); // Create lower lip self.elements.lowerLip = self.attachAsset('trollLowerLip' + style, { anchorX: 0.5, anchorY: 0.5, scale: 1 }); }; // Remove all face elements self.removeAllElements = function () { if (self.elements.head) { self.removeChild(self.elements.head); } if (self.elements.leftEye) { self.removeChild(self.elements.leftEye); } if (self.elements.rightEye) { self.removeChild(self.elements.rightEye); } if (self.elements.upperLip) { self.removeChild(self.elements.upperLip); } if (self.elements.lowerLip) { self.removeChild(self.elements.lowerLip); } }; // Change to the next troll face style self.nextStyle = function (forcedStyle) { self.currentStyle = forcedStyle || self.currentStyle % 3 + 1; self.initialize(); self.currentOffsets = trollFaceOffsets[self.currentStyle - 1]; // Calculate face scale once and reuse var baseScale = self.calculateFaceScale(facekit); // Calculate all scales upfront self.scales = { head: { x: baseScale * self.currentOffsets.head.sx, y: baseScale * self.currentOffsets.head.sy }, eye: { x: baseScale * self.currentOffsets.leftEye.sx, y: baseScale * self.currentOffsets.leftEye.sy }, lip: { x: baseScale * self.currentOffsets.upperLip.sx, y: baseScale * self.currentOffsets.upperLip.sy } }; return self.currentStyle; }; // Helper function to clamp a value between min and max self.clampPosition = function (value, min, max) { return Math.min(Math.max(value, min), max); }; // Helper function to ensure scale is an object self.ensureScaleIsObject = function (element) { if (_typeof(element.scale) !== 'object') { element.scale = { x: 1, y: 1 }; } }; // Helper function to update a face element self.updateFaceElement = function (elementName, x, y, scaleX, scaleY, makeVisible) { var element = self.elements[elementName]; if (!element) { return; } // Ensure scale is an object self.ensureScaleIsObject(element); // Apply position with clamping using scaled boundaries var elementOffset = self.currentOffsets[elementName]; element.x = self.clampPosition(x, elementOffset.minX * currentEyeDistance, elementOffset.maxX * currentEyeDistance); element.y = self.clampPosition(y, elementOffset.minY * currentEyeDistance, elementOffset.maxY * currentEyeDistance); // Apply scale element.scale.x = scaleX; element.scale.y = scaleY; // Set visibility if needed if (makeVisible) { element.visible = true; } }; // Helper function to update all face elements self.updateAllFaceElements = function (kit, makeVisible, useDefaultScales) { var elementNames = ['head', 'leftEye', 'rightEye', 'upperLip', 'lowerLip']; var positions = self.positionCache; // Calculate positions for all elements positions.head.x = 0; positions.head.y = 0; positions.head.scaleX = useDefaultScales ? 1 : self.scales.head.x; positions.head.scaleY = useDefaultScales ? 1 : self.scales.head.y; // Get the rotation angle for constraint calculations var rotationAngle = self.rotation; var cosAngle = Math.cos(-rotationAngle); // Negative to counter-rotate var sinAngle = Math.sin(-rotationAngle); // For other elements, calculate based on kit positions for (var i = 1; i < elementNames.length; i++) { var name = elementNames[i]; var kitElement = kit[name]; if (kitElement) { var scaleX, scaleY; // Determine which scale to use based on element type and useDefaultScales flag if (useDefaultScales) { scaleX = 1 * self.currentOffsets[name].sx; scaleY = 1 * self.currentOffsets[name].sy; } else { if (name === 'leftEye' || name === 'rightEye') { scaleX = self.scales.eye.x; scaleY = self.scales.eye.y; } else { scaleX = self.scales.lip.x; scaleY = self.scales.lip.y; } } // Calculate position using relative offsets scaled by eye distance var rawX = kitElement.x - self.x + self.currentOffsets[name].x * currentEyeDistance; var rawY = kitElement.y - self.y + self.currentOffsets[name].y * currentEyeDistance; // Apply rotation constraints to maintain relative positions // This counter-rotates the positions to keep elements in proper alignment positions[name].x = rawX * cosAngle - rawY * sinAngle; positions[name].y = rawX * sinAngle + rawY * cosAngle; positions[name].scaleX = scaleX; positions[name].scaleY = scaleY; } } // Update each element with calculated positions for (var j = 0; j < elementNames.length; j++) { var elemName = elementNames[j]; if (self.elements[elemName] && positions[elemName]) { var pos = positions[elemName]; self.updateFaceElement(elemName, pos.x, pos.y, pos.scaleX, pos.scaleY, makeVisible); } } // Handle mouth open adjustment if (kit.mouthOpen && self.elements.lowerLip) { //self.elements.lowerLip.scale.y = self.scales.lip.y * 1.5; } }; // Update face elements to match real face self.updateFacePosition = function () { if (!facekit) { return; } // Get kit based on tracking state var kit = bypassTracking ? fakeCamera : facekitMgr.isTracking ? facekitMgr.currentFacekit : facekit; // If re-centering, use fakeCamera data for positioning without changing bypassTracking if (self.isRecentering) { kit = fakeCamera; } // Update global eye distance once per frame currentEyeDistance = self.getEyeDistance(kit); var baseScale = self.calculateFaceScale(kit); // Calculate face rotation - use FacekitManager if tracking, otherwise use local calculation var rotation = facekitMgr.isTracking ? facekitMgr.currentRotation + Math.PI : self.calculateFaceRotation(kit); // If re-centering, reset rotation to 0 if (self.isRecentering) { rotation = 0; } // Update scales self.scales = { head: { x: baseScale * self.currentOffsets.head.sx, y: baseScale * self.currentOffsets.head.sy }, eye: { x: baseScale * self.currentOffsets.leftEye.sx, y: baseScale * self.currentOffsets.leftEye.sy }, lip: { x: baseScale * self.currentOffsets.upperLip.sx, y: baseScale * self.currentOffsets.upperLip.sy } }; if (bypassTracking) { self.x = 2048 / 2; self.y = 2732 / 2; self.rotation = 0; // Reset rotation in bypass mode // Use the global fakeCamera with dynamic scales self.updateAllFaceElements(fakeCamera, true, false); return; } // Apply rotation to the entire troll face container self.rotation = rotation; // Update all elements with kit self.updateAllFaceElements(kit, true, false); }; // Calculate scale based on eye distance self.calculateFaceScale = function (kit) { return currentEyeDistance * 0.005; }; // Calculate face rotation angle based on eye positions self.calculateFaceRotation = function (kit) { if (kit && kit.leftEye && kit.rightEye) { // Get eye positions var leftEye = kit.leftEye; var rightEye = kit.rightEye; // Calculate angle - simpler approach // This gives us the angle of the line connecting the eyes var angle = Math.atan2(rightEye.y - leftEye.y, rightEye.x - leftEye.x); // More efficient angle wrapping using modulo var angleDiff = angle - currentRotation; // Normalize the difference to [-PI, PI] range angleDiff = (angleDiff + Math.PI) % (2 * Math.PI) - Math.PI; // Apply smoothing to the rotation currentRotation = currentRotation + angleDiff * 0.2; return currentRotation + Math.PI; } return 0; // Default rotation (no rotation) }; // Get eye distance from kit self.getEyeDistance = function (kit) { if (kit && kit.leftEye && kit.rightEye) { var dx = kit.leftEye.x - kit.rightEye.x; var dy = kit.leftEye.y - kit.rightEye.y; return Math.sqrt(dx * dx + dy * dy); } return 200; // Default eye distance }; // Add a property to track if we're in re-centering mode self.isRecentering = false; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xFFFFFF }); /**** * Game Code ****/ /**** * Global Variables ****/ var debugMode = false; // DEBUG MODE DEBUG MODE DEBUG MODE var bypassTracking = false; // Global variable to bypass face tracking var currentEyeDistance = 200; // Global variable to store current eye distance var currentRotation = 0; // Current smoothed rotation value // FacekitManager: handles face tracking, smoothing, and fallback var FacekitManager = function FacekitManager() { var self = new Container(); // Public properties self.currentFacekit = null; // Current smoothed face data self.isTracking = false; // Current tracking state self.currentRotation = 0; // Current smoothed rotation (accessible directly for performance) self.smoothingFactor = 0.2; // Default smoothing factor self.lastNosePosition = null; // Last nose position for tracking detection self.trackingStoppedCounter = 100; // Counter for tracking detection self.trackingStoppedDelay = 100; // Delay threshold for tracking detection // Default positions - defined inline var defaultFacekit = { leftEye: { x: 1380, y: 958 }, rightEye: { x: 673, y: 970 }, upperLip: { x: 1027, y: 1610 }, lowerLip: { x: 1030, y: 1613 }, noseTip: { x: 1024, y: 1366 }, mouthOpen: false }; // Initialize manager self.initialize = function (fakeFacekit) { // Use provided fallback or default self.fallbackFacekit = fakeFacekit || defaultFacekit; // Initialize current data as reference to fallback self.currentFacekit = self.fallbackFacekit; // Create a reusable object for smoothing calculations self.smoothFacekit = { leftEye: { x: 0, y: 0 }, rightEye: { x: 0, y: 0 }, upperLip: { x: 0, y: 0 }, lowerLip: { x: 0, y: 0 }, noseTip: { x: 0, y: 0 }, mouthOpen: false }; return self; }; // Process new tracking data and update tracking status self.updateTrackingStatus = function (rawFacekit) { // Check if there's valid face data var hasFaceData = !!(rawFacekit && rawFacekit.noseTip); if (!hasFaceData) { self.isTracking = false; return false; } // Check if nose position has changed using existing tracking mechanism if (self.lastNosePosition === rawFacekit.noseTip.x) { self.trackingStoppedCounter--; if (self.trackingStoppedCounter <= 0) { self.isTracking = false; self.trackingStoppedCounter = self.trackingStoppedDelay; // Reset delay } } else { self.isTracking = true; self.lastNosePosition = rawFacekit.noseTip.x; self.trackingStoppedCounter = self.trackingStoppedDelay; // Reset delay } // If tracking, process the face data if (self.isTracking) { // If tracking just started, initialize smooth values if (self.currentFacekit === self.fallbackFacekit) { self._initSmoothValues(rawFacekit); } // Apply smoothing to each facial feature self._smoothValues(rawFacekit); // Set currentFacekit to the smoothed values self.currentFacekit = self.smoothFacekit; } else { // Use fallback when not tracking self.currentFacekit = self.fallbackFacekit; } // Return tracking state return self.isTracking; }; // Initialize smooth values with raw data self._initSmoothValues = function (rawFacekit) { // Directly set initial values from raw data for (var key in rawFacekit) { if (rawFacekit[key] && self.smoothFacekit[key] && typeof rawFacekit[key].x !== 'undefined') { self.smoothFacekit[key].x = rawFacekit[key].x; self.smoothFacekit[key].y = rawFacekit[key].y; } } // Initialize rotation directly if (rawFacekit.leftEye && rawFacekit.rightEye) { self.currentRotation = Math.atan2(rawFacekit.rightEye.y - rawFacekit.leftEye.y, rawFacekit.rightEye.x - rawFacekit.leftEye.x); } }; // Apply smoothing to all values self._smoothValues = function (rawFacekit) { // Smooth positions (reuse existing objects) for (var key in rawFacekit) { if (rawFacekit[key] && self.smoothFacekit[key] && typeof rawFacekit[key].x !== 'undefined') { // Apply position smoothing directly self.smoothFacekit[key].x += (rawFacekit[key].x - self.smoothFacekit[key].x) * self.smoothingFactor; self.smoothFacekit[key].y += (rawFacekit[key].y - self.smoothFacekit[key].y) * self.smoothingFactor; } } // Calculate and smooth rotation if (rawFacekit.leftEye && rawFacekit.rightEye) { var newAngle = Math.atan2(rawFacekit.rightEye.y - rawFacekit.leftEye.y, rawFacekit.rightEye.x - rawFacekit.leftEye.x); // Improved angle normalization to prevent full rotations - no while loops var angleDiff = newAngle - self.currentRotation; // Efficiently handle -PI/PI boundary crossing (replaces the while loops) if (angleDiff > Math.PI) { angleDiff -= 2 * Math.PI; } else if (angleDiff < -Math.PI) { angleDiff += 2 * Math.PI; } // Apply safeguard against large changes var maxRotationPerFrame = Math.PI / 10; // Limit rotation change // Clamp the rotation change to prevent sudden large rotations if (angleDiff > maxRotationPerFrame) { angleDiff = maxRotationPerFrame; } if (angleDiff < -maxRotationPerFrame) { angleDiff = -maxRotationPerFrame; } // Apply smoothing self.currentRotation += angleDiff * self.smoothingFactor; // Normalize the result angle to ensure it stays in -PI to PI range // Using efficient modulo approach rather than while loops self.currentRotation = (self.currentRotation + 3 * Math.PI) % (2 * Math.PI) - Math.PI; } // Copy mouth state self.smoothFacekit.mouthOpen = rawFacekit.mouthOpen; }; return self; }; var trollFaceOffsets = [{ head: { x: 0, y: 0, sx: 1, sy: 1 }, leftEye: { x: 0.75, // 150/200 = 0.75 y: 1.0, // 200/200 = 1.0 sx: 0.6, sy: 0.6, minX: -1.0, // -200/200 = -1.0 maxX: 1.0, // 200/200 = 1.0 minY: -1.0, maxY: 1.0 }, rightEye: { x: 0, // 0/200 = 0 y: 1.0, // 200/200 = 1.0 sx: 0.6, sy: 0.6, minX: -1.0, maxX: 1.0, minY: -1.0, maxY: 1.0 }, upperLip: { x: 0, // 0/200 = 0 y: 0.15, // 30/200 = 0.15 sx: 1, sy: 1, minX: -1.0, maxX: 1.0, minY: -1.0, maxY: 1.0 }, lowerLip: { x: 0, // 0/200 = 0 y: 0.65, // 130/200 = 0.65 sx: 1, sy: 1, minX: -1.0, maxX: 1.0, minY: -1.0, maxY: 1.0 } }, { head: { x: 0, y: 0, sx: 1, sy: 1 }, leftEye: { x: 0, y: 0.5, sx: 0.6, sy: 0.6, minX: 0.2, maxX: 0.7, minY: 0, maxY: 0.6 }, rightEye: { x: 0.1, y: 0.3, sx: 0.6, sy: 0.6, minX: -0.7, maxX: 0.1, minY: 0, maxY: 0.4 }, upperLip: { x: -0.2, y: 0.1, sx: 0.5, sy: 0.5, minX: -0.3, maxX: 0, minY: -1, maxY: 1 }, lowerLip: { x: -0.10, y: 0.12, sx: 0.2, sy: 0.2, minX: -0.2, maxX: 0, minY: 0, maxY: 0.8 } }, { head: { x: 0, y: 0, sx: 1, sy: 1 }, leftEye: { x: -0.275, // -55/200 = -0.275 y: -1.0, // -200/200 = -1.0 sx: 1, sy: 1, minX: -1.0, maxX: 1.0, minY: -1.0, maxY: 1.0 }, rightEye: { x: -0.775, // -155/200 = -0.775 y: -1.0, // -200/200 = -1.0 sx: 1, sy: 1, minX: -1.0, maxX: 1.0, minY: -1.0, maxY: 1.0 }, upperLip: { x: 0, // 0/200 = 0 y: 0.16, // 32/200 = 0.16 sx: 1, sy: 1.5, minX: -1.0, maxX: 1.0, minY: -1.0, maxY: 1.0 }, lowerLip: { x: 0, // 0/200 = 0 y: 0.26, // 52/200 = 0.26 sx: 1, sy: 1.5, minX: -1.0, maxX: 1.0, minY: -1.0, maxY: 1.0 } }]; // Define fakeCamera globally with fixed positions for testing var fakeCamera = { leftEye: { x: 1380, y: 958 }, rightEye: { x: 673, y: 970 }, upperLip: { x: 1027, y: 1610 }, lowerLip: { x: 1030, y: 1613 }, mouthOpen: false }; // Global object for bypass tracking mode // Initialize FacekitManager var facekitMgr = new FacekitManager(); facekitMgr.initialize(fakeCamera); var background; var instructionText; var styleText; var trollFace; var debugPoints; var backgroundContainer; var middlegroundContainer; var foregroundContainer; var isTrackingFace = false; // Global flag to track face detection state var targetPosition; var lastNosePosition = null; // Global variable to store last facekit.noseTip.x var trackingStoppedDelay = 100; var trackingStoppedCounter = trackingStoppedDelay; function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } /**** * Game Functions ****/ // Handle tap anywhere on the screen to change face game.down = function (x, y, obj) { // Switch to the next troll face style var newStyle = trollFace.nextStyle(); // Save the current style to storage storage.lastTrollStyle = newStyle; // Update the style text styleText.setText('Style: ' + newStyle + '/3'); // Play switch sound LK.getSound('switchTroll').play(); }; // Update function called every frame game.update = function () { if (bypassTracking) { // When bypassing tracking, position the troll face at the center and update its elements trollFace.updateFacePosition(); return; } if (!facekit || !facekit.noseTip) { return; } // Update tracking status and get smoothed face data var isCurrentlyTracking = facekitMgr.updateTrackingStatus(facekit); // Update tracking state UI if changed if (isCurrentlyTracking !== isTrackingFace) { isTrackingFace = isCurrentlyTracking; instructionText.setText(isCurrentlyTracking ? "Tracking..." : "No Face found"); } // Update troll face position to match real face if (isTrackingFace) { // Reset isRecentering flag when face tracking is detected trollFace.isRecentering = false; // Use the smoothed nose tip position trollFace.x = facekitMgr.currentFacekit.noseTip.x; trollFace.y = facekitMgr.currentFacekit.noseTip.y; // Use the original updateFacePosition with smoothed data trollFace.updateFacePosition(); trollFace.isCentered = false; trollFace.isCentering = false; } else { // If face is not detected, return the face to the center if (!trollFace.isCentered) { if (trollFace.isCentering) { // Don't exit the update function, just skip starting a new tween // This allows other updates to continue // Continue updating face elements during centering trollFace.updateFacePosition(); } else { trollFace.isCentering = true; // Set isRecentering flag to use fakeCamera data for positioning trollFace.isRecentering = true; LK.effects.flashScreen(0xFFFFFF, 1000); // Flash screen tween(trollFace, { x: 2048 / 2, y: 2732 / 2 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { trollFace.isCentered = true; trollFace.isCentering = false; // Keep isRecentering true to maintain centered elements trollFace.updateFacePosition(); } }); } } // Update face tracking state if (isTrackingFace) { isTrackingFace = false; instructionText.setText("No Face found"); } } // If in debug mode, display the face elements positions if (debugMode) { // Clear existing debug points if (debugPoints) { for (var i = 0; i < debugPoints.children.length; i++) { debugPoints.children[i].alpha = 0; } } // Draw debug points for each face element if (facekit) { var pointIndex = 0; // Draw a point for each facial feature for (var key in facekit) { if (facekit[key] && typeof facekit[key].x !== 'undefined') { var point = debugPoints.children[pointIndex++]; if (point) { point.x = facekit[key].x; point.y = facekit[key].y; point.alpha = 1; } } } // Display rotation in degrees instructionText.setText("Rotation: " + Math.round(trollFace.rotation * 180 / Math.PI) + "°"); } } }; function initializeGame() { // Initialize game // Create containers for layering backgroundContainer = new Container(); middlegroundContainer = new Container(); foregroundContainer = new Container(); // Add containers to game game.addChild(backgroundContainer); game.addChild(middlegroundContainer); game.addChild(foregroundContainer); // Global target position for the troll face targetPosition = { x: 2048 / 2, y: 2732 / 2 }; // Setup background background = LK.getAsset('whiteBackground', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, visible: false }); backgroundContainer.addChild(background); // Setup UI text instructionText = new Text2('Tap anywhere to change troll face', { size: 50, fill: 0xFF0000 }); instructionText.anchor.set(0.5, 0); LK.gui.top.addChild(instructionText); instructionText.y = 1700; // Load the last used style from storage var lastStyle = storage.lastTrollStyle || 1; // Create the troll face trollFace = new TrollFace(); trollFace.currentStyle = lastStyle; trollFace.initialize(); trollFace.nextStyle(lastStyle); middlegroundContainer.addChild(trollFace); // Set the troll face position to the center of the screen trollFace.x = 2048 / 2; trollFace.y = 2732 / 2; // Initialize tracking state isTrackingFace = false; // Add style text styleText = new Text2('Style: ' + lastStyle + '/3', { size: 50, fill: 0xFF0000 }); styleText.anchor.set(0.5, 0); LK.gui.top.addChild(styleText); styleText.y = 120; // Debug mode (turn on for development, off for production) debugPoints = null; if (debugMode) { debugPoints = new DebugPoints(); foregroundContainer.addChild(debugPoints); // Log facekit to console every second LK.setInterval(function () { console.log(facekit); if (facekit.lowerLip) { var elementOffset = trollFace.currentOffsets.lowerLip; var eyeDistance = trollFace.getEyeDistance(facekit); console.log("lowerLip y:", facekit.lowerLip.y, "minY:", elementOffset.minY * eyeDistance, "maxY:", elementOffset.maxY * eyeDistance); } // Display rotation angle in degrees for debugging var rotationDegrees = Math.round(trollFace.rotation * (180 / Math.PI)); instructionText.setText("le:".concat(Math.round(facekit.leftEye.x), ",").concat(Math.round(facekit.leftEye.y), " / ") + "re:".concat(Math.round(facekit.rightEye.x), ",").concat(Math.round(facekit.rightEye.y), " / ") + "ul:".concat(Math.round(facekit.upperLip.x), ",").concat(Math.round(facekit.upperLip.y), " / ") + "ll:".concat(Math.round(facekit.lowerLip.x), ",").concat(Math.round(facekit.lowerLip.y), " / ") + "rot:".concat(rotationDegrees, "°")); }, 1000); } } // Initialize the game initializeGame();
===================================================================
--- original.js
+++ change.js
@@ -305,17 +305,23 @@
self.updateFacePosition = function () {
if (!facekit) {
return;
}
- // Recalculate scales based on the current kit
- var kit = bypassTracking ? fakeCamera : facekit;
+ // Get kit based on tracking state
+ var kit = bypassTracking ? fakeCamera : facekitMgr.isTracking ? facekitMgr.currentFacekit : facekit;
+ // If re-centering, use fakeCamera data for positioning without changing bypassTracking
+ if (self.isRecentering) {
+ kit = fakeCamera;
+ }
// Update global eye distance once per frame
currentEyeDistance = self.getEyeDistance(kit);
var baseScale = self.calculateFaceScale(kit);
- // Calculate face rotation
- var rotation = self.calculateFaceRotation(kit);
- // Add PI (180 degrees) to flip the face right-side up, remove negative sign to fix direction
- rotation = rotation + Math.PI;
+ // Calculate face rotation - use FacekitManager if tracking, otherwise use local calculation
+ var rotation = facekitMgr.isTracking ? facekitMgr.currentRotation + Math.PI : self.calculateFaceRotation(kit);
+ // If re-centering, reset rotation to 0
+ if (self.isRecentering) {
+ rotation = 0;
+ }
// Update scales
self.scales = {
head: {
x: baseScale * self.currentOffsets.head.sx,
@@ -339,10 +345,10 @@
return;
}
// Apply rotation to the entire troll face container
self.rotation = rotation;
- // Update all elements with facekit
- self.updateAllFaceElements(facekit, true, false);
+ // Update all elements with kit
+ self.updateAllFaceElements(kit, true, false);
};
// Calculate scale based on eye distance
self.calculateFaceScale = function (kit) {
return currentEyeDistance * 0.005;
@@ -355,18 +361,15 @@
var rightEye = kit.rightEye;
// Calculate angle - simpler approach
// This gives us the angle of the line connecting the eyes
var angle = Math.atan2(rightEye.y - leftEye.y, rightEye.x - leftEye.x);
- // Handle angle wrapping to prevent 360° jumps
- while (angle - currentRotation > Math.PI) {
- angle -= 2 * Math.PI;
- }
- while (angle - currentRotation < -Math.PI) {
- angle += 2 * Math.PI;
- }
+ // More efficient angle wrapping using modulo
+ var angleDiff = angle - currentRotation;
+ // Normalize the difference to [-PI, PI] range
+ angleDiff = (angleDiff + Math.PI) % (2 * Math.PI) - Math.PI;
// Apply smoothing to the rotation
- currentRotation = currentRotation + (angle - currentRotation) * 0.2;
- return currentRotation;
+ currentRotation = currentRotation + angleDiff * 0.2;
+ return currentRotation + Math.PI;
}
return 0; // Default rotation (no rotation)
};
// Get eye distance from kit
@@ -377,8 +380,10 @@
return Math.sqrt(dx * dx + dy * dy);
}
return 200; // Default eye distance
};
+ // Add a property to track if we're in re-centering mode
+ self.isRecentering = false;
return self;
});
/****
@@ -397,8 +402,167 @@
var debugMode = false; // DEBUG MODE DEBUG MODE DEBUG MODE
var bypassTracking = false; // Global variable to bypass face tracking
var currentEyeDistance = 200; // Global variable to store current eye distance
var currentRotation = 0; // Current smoothed rotation value
+// FacekitManager: handles face tracking, smoothing, and fallback
+var FacekitManager = function FacekitManager() {
+ var self = new Container();
+ // Public properties
+ self.currentFacekit = null; // Current smoothed face data
+ self.isTracking = false; // Current tracking state
+ self.currentRotation = 0; // Current smoothed rotation (accessible directly for performance)
+ self.smoothingFactor = 0.2; // Default smoothing factor
+ self.lastNosePosition = null; // Last nose position for tracking detection
+ self.trackingStoppedCounter = 100; // Counter for tracking detection
+ self.trackingStoppedDelay = 100; // Delay threshold for tracking detection
+ // Default positions - defined inline
+ var defaultFacekit = {
+ leftEye: {
+ x: 1380,
+ y: 958
+ },
+ rightEye: {
+ x: 673,
+ y: 970
+ },
+ upperLip: {
+ x: 1027,
+ y: 1610
+ },
+ lowerLip: {
+ x: 1030,
+ y: 1613
+ },
+ noseTip: {
+ x: 1024,
+ y: 1366
+ },
+ mouthOpen: false
+ };
+ // Initialize manager
+ self.initialize = function (fakeFacekit) {
+ // Use provided fallback or default
+ self.fallbackFacekit = fakeFacekit || defaultFacekit;
+ // Initialize current data as reference to fallback
+ self.currentFacekit = self.fallbackFacekit;
+ // Create a reusable object for smoothing calculations
+ self.smoothFacekit = {
+ leftEye: {
+ x: 0,
+ y: 0
+ },
+ rightEye: {
+ x: 0,
+ y: 0
+ },
+ upperLip: {
+ x: 0,
+ y: 0
+ },
+ lowerLip: {
+ x: 0,
+ y: 0
+ },
+ noseTip: {
+ x: 0,
+ y: 0
+ },
+ mouthOpen: false
+ };
+ return self;
+ };
+ // Process new tracking data and update tracking status
+ self.updateTrackingStatus = function (rawFacekit) {
+ // Check if there's valid face data
+ var hasFaceData = !!(rawFacekit && rawFacekit.noseTip);
+ if (!hasFaceData) {
+ self.isTracking = false;
+ return false;
+ }
+ // Check if nose position has changed using existing tracking mechanism
+ if (self.lastNosePosition === rawFacekit.noseTip.x) {
+ self.trackingStoppedCounter--;
+ if (self.trackingStoppedCounter <= 0) {
+ self.isTracking = false;
+ self.trackingStoppedCounter = self.trackingStoppedDelay; // Reset delay
+ }
+ } else {
+ self.isTracking = true;
+ self.lastNosePosition = rawFacekit.noseTip.x;
+ self.trackingStoppedCounter = self.trackingStoppedDelay; // Reset delay
+ }
+ // If tracking, process the face data
+ if (self.isTracking) {
+ // If tracking just started, initialize smooth values
+ if (self.currentFacekit === self.fallbackFacekit) {
+ self._initSmoothValues(rawFacekit);
+ }
+ // Apply smoothing to each facial feature
+ self._smoothValues(rawFacekit);
+ // Set currentFacekit to the smoothed values
+ self.currentFacekit = self.smoothFacekit;
+ } else {
+ // Use fallback when not tracking
+ self.currentFacekit = self.fallbackFacekit;
+ }
+ // Return tracking state
+ return self.isTracking;
+ };
+ // Initialize smooth values with raw data
+ self._initSmoothValues = function (rawFacekit) {
+ // Directly set initial values from raw data
+ for (var key in rawFacekit) {
+ if (rawFacekit[key] && self.smoothFacekit[key] && typeof rawFacekit[key].x !== 'undefined') {
+ self.smoothFacekit[key].x = rawFacekit[key].x;
+ self.smoothFacekit[key].y = rawFacekit[key].y;
+ }
+ }
+ // Initialize rotation directly
+ if (rawFacekit.leftEye && rawFacekit.rightEye) {
+ self.currentRotation = Math.atan2(rawFacekit.rightEye.y - rawFacekit.leftEye.y, rawFacekit.rightEye.x - rawFacekit.leftEye.x);
+ }
+ };
+ // Apply smoothing to all values
+ self._smoothValues = function (rawFacekit) {
+ // Smooth positions (reuse existing objects)
+ for (var key in rawFacekit) {
+ if (rawFacekit[key] && self.smoothFacekit[key] && typeof rawFacekit[key].x !== 'undefined') {
+ // Apply position smoothing directly
+ self.smoothFacekit[key].x += (rawFacekit[key].x - self.smoothFacekit[key].x) * self.smoothingFactor;
+ self.smoothFacekit[key].y += (rawFacekit[key].y - self.smoothFacekit[key].y) * self.smoothingFactor;
+ }
+ }
+ // Calculate and smooth rotation
+ if (rawFacekit.leftEye && rawFacekit.rightEye) {
+ var newAngle = Math.atan2(rawFacekit.rightEye.y - rawFacekit.leftEye.y, rawFacekit.rightEye.x - rawFacekit.leftEye.x);
+ // Improved angle normalization to prevent full rotations - no while loops
+ var angleDiff = newAngle - self.currentRotation;
+ // Efficiently handle -PI/PI boundary crossing (replaces the while loops)
+ if (angleDiff > Math.PI) {
+ angleDiff -= 2 * Math.PI;
+ } else if (angleDiff < -Math.PI) {
+ angleDiff += 2 * Math.PI;
+ }
+ // Apply safeguard against large changes
+ var maxRotationPerFrame = Math.PI / 10; // Limit rotation change
+ // Clamp the rotation change to prevent sudden large rotations
+ if (angleDiff > maxRotationPerFrame) {
+ angleDiff = maxRotationPerFrame;
+ }
+ if (angleDiff < -maxRotationPerFrame) {
+ angleDiff = -maxRotationPerFrame;
+ }
+ // Apply smoothing
+ self.currentRotation += angleDiff * self.smoothingFactor;
+ // Normalize the result angle to ensure it stays in -PI to PI range
+ // Using efficient modulo approach rather than while loops
+ self.currentRotation = (self.currentRotation + 3 * Math.PI) % (2 * Math.PI) - Math.PI;
+ }
+ // Copy mouth state
+ self.smoothFacekit.mouthOpen = rawFacekit.mouthOpen;
+ };
+ return self;
+};
var trollFaceOffsets = [{
head: {
x: 0,
y: 0,
@@ -577,8 +741,11 @@
y: 1613
},
mouthOpen: false
}; // Global object for bypass tracking mode
+// Initialize FacekitManager
+var facekitMgr = new FacekitManager();
+facekitMgr.initialize(fakeCamera);
var background;
var instructionText;
var styleText;
var trollFace;
@@ -622,55 +789,26 @@
}
if (!facekit || !facekit.noseTip) {
return;
}
- // Check if lastNosePosition is the same after trackingStoppedDelay ticks
- if (lastNosePosition === facekit.noseTip.x) {
- trackingStoppedCounter--;
- if (trackingStoppedCounter <= 0) {
- isTrackingFace = false;
- instructionText.setText("Stopped tracking");
- trackingStoppedCounter = trackingStoppedDelay; // Reset delay
- }
- } else {
- isTrackingFace = true;
- lastNosePosition = facekit.noseTip.x;
- instructionText.setText("Tracking...");
- trackingStoppedCounter = trackingStoppedDelay; // Reset delay
+ // Update tracking status and get smoothed face data
+ var isCurrentlyTracking = facekitMgr.updateTrackingStatus(facekit);
+ // Update tracking state UI if changed
+ if (isCurrentlyTracking !== isTrackingFace) {
+ isTrackingFace = isCurrentlyTracking;
+ instructionText.setText(isCurrentlyTracking ? "Tracking..." : "No Face found");
}
// Update troll face position to match real face
if (isTrackingFace) {
- // Use the nose tip position for the face with easing
- targetPosition.x = facekit.noseTip.x;
- targetPosition.y = facekit.noseTip.y;
- trollFace.x = targetPosition.x;
- trollFace.y = targetPosition.y;
- trollFace.isTweening = true; // TEMP DEBUG
- // Apply tweening for smooth movement and wait for it to finish before starting the next
- // if (!trollFace.isTweening) {
- // trollFace.isTweening = true;
- // tween(trollFace, {
- // x: targetPosition.x,
- // y: targetPosition.y
- // }, {
- // duration: 300,
- // easing: tween.easeInOut,
- // onFinish: function onFinish() {
- // trollFace.isTweening = false;
- // }
- // });
- // }
- // Update face elements to match real face features
+ // Reset isRecentering flag when face tracking is detected
+ trollFace.isRecentering = false;
+ // Use the smoothed nose tip position
+ trollFace.x = facekitMgr.currentFacekit.noseTip.x;
+ trollFace.y = facekitMgr.currentFacekit.noseTip.y;
+ // Use the original updateFacePosition with smoothed data
trollFace.updateFacePosition();
trollFace.isCentered = false;
trollFace.isCentering = false;
- // Update face tracking state
- if (!isTrackingFace) {
- isTrackingFace = true;
- instructionText.setText("Tracking..." + new Date());
- console.log("facekit", facekit);
- }
- //console.log("noseTip x=", facekit.noseTip.x);
} else {
// If face is not detected, return the face to the center
if (!trollFace.isCentered) {
if (trollFace.isCentering) {
@@ -679,8 +817,10 @@
// Continue updating face elements during centering
trollFace.updateFacePosition();
} else {
trollFace.isCentering = true;
+ // Set isRecentering flag to use fakeCamera data for positioning
+ trollFace.isRecentering = true;
LK.effects.flashScreen(0xFFFFFF, 1000); // Flash screen
tween(trollFace, {
x: 2048 / 2,
y: 2732 / 2
@@ -689,8 +829,9 @@
easing: tween.easeInOut,
onFinish: function onFinish() {
trollFace.isCentered = true;
trollFace.isCentering = false;
+ // Keep isRecentering true to maintain centered elements
trollFace.updateFacePosition();
}
});
}
@@ -700,8 +841,34 @@
isTrackingFace = false;
instructionText.setText("No Face found");
}
}
+ // If in debug mode, display the face elements positions
+ if (debugMode) {
+ // Clear existing debug points
+ if (debugPoints) {
+ for (var i = 0; i < debugPoints.children.length; i++) {
+ debugPoints.children[i].alpha = 0;
+ }
+ }
+ // Draw debug points for each face element
+ if (facekit) {
+ var pointIndex = 0;
+ // Draw a point for each facial feature
+ for (var key in facekit) {
+ if (facekit[key] && typeof facekit[key].x !== 'undefined') {
+ var point = debugPoints.children[pointIndex++];
+ if (point) {
+ point.x = facekit[key].x;
+ point.y = facekit[key].y;
+ point.alpha = 1;
+ }
+ }
+ }
+ // Display rotation in degrees
+ instructionText.setText("Rotation: " + Math.round(trollFace.rotation * 180 / Math.PI) + "°");
+ }
+ }
};
function initializeGame() {
// Initialize game
// Create containers for layering