internal PlayerEffectSpaceSceneGraph(PlayerHandSceneGraph parent, PlayerEffectSpaceModel model)
            : base()
        {
            m_parent = parent;
            m_model = model;

            RootNode = new GroupNode(parent.RootNode, Transform.Identity, "EffectSpace");
            // TODO: this should really use the starting drag location as its translation, and the rest of this too
            RootNode.LocalTransform = Transform.Identity;

            // We preallocate our big circle.
            m_boundingCircleNode = new SpriteNode(RootNode, "bounding circle", parent.Content.HollowCircle);
            m_boundingCircleNode.LocalTransform = new Transform(
                Vector2.Zero, new Vector2(MagicNumbers.EffectSpaceBoundingCircleMultiple * (1 / MagicNumbers.EffectSpaceBoundingCircleSize)));
            m_boundingCircleNode.Color = new Color(0, 0, 0, 0);
            m_boundingCircleNode.Origin = new Vector2(0.5f);
            m_boundingCircleNode.SetSecondaryViewOption(SecondaryViewOption.PositionMirrored);

            m_effectKnobLineNode = new LineNode(RootNode, "effect line");
            m_effectKnobLineNode.Color = new Color(0, 0, 0, 0);
            m_effectKnobLineNode.SetSecondaryViewOption(SecondaryViewOption.PositionMirrored);

            m_effectKnobNode = new SpriteNode(RootNode, "effect knob", parent.Content.FilledCircle);
            m_effectKnobNode.Origin = new Vector2(0.5f);
            m_effectKnobNode.LocalTransform = new Transform(Vector2.Zero, new Vector2(MagicNumbers.EffectSpaceKnobMultiple));
            m_effectKnobNode.Color = new Color(0, 0, 0, 0);
            m_effectKnobNode.SetSecondaryViewOption(SecondaryViewOption.PositionMirrored);
        }
        internal PlayerSceneGraph(HolofunkSceneGraph parent, int playerIndex, int channel)
            : base()
        {
            m_parent = parent;

            m_playerIndex = playerIndex;
            m_channel = channel;

            m_averageLevelRatio = new FloatAverager(15); // don't flicker face changes too fast
            m_averageLevelRatio.Update(0); // make sure it's initially zero

            // Center the textures.
            Vector2 origin = new Vector2(0.5f);

            RootNode = new GroupNode(parent.RootNode, Transform.Identity, "Player #" + playerIndex);

            m_headGroup = new GroupNode(RootNode, Transform.Identity, "Head group");

            m_headNode = new SpriteNode(
                m_headGroup,
                "Head",
                PlayerIndex == 0 ? parent.Content.HollowOneOval : parent.Content.HollowTwoOval);

            m_headNode.Origin = new Vector2(0.5f, 0.5f);
            // semi-transparent heads, hopefully this will make them seem "less interactive"
            m_headNode.Color = new Color(0.7f, 0.7f, 0.7f, 0.7f);
            m_headNode.SetSecondaryViewOption(SecondaryViewOption.PositionMirrored | SecondaryViewOption.SecondTexture);
            m_headNode.SecondaryTexture = parent.Content.Dot;

            // Make a faded mike signal that sticks to the head.
            m_headMikeSignal = new TrackNode(
                m_headGroup,
                new Transform(Vector2.Zero, new Vector2(MagicNumbers.LoopieScale)),
                "MikeSignal",
                parent.Content,
                -1,
                null,
                true,
                () => Audio.LevelRatio(Channel),
                () => new Color(63, 0, 0, 63),
                () => Color.White,
                () => 0,
                () => 0,
                () => new Color(127, 0, 0, 127),
                () => 1f);

            m_leftHandSceneGraph = new PlayerHandSceneGraph(this, false);
            m_rightHandSceneGraph = new PlayerHandSceneGraph(this, true);
        }