<commit_before>package de.fabmax.kool.scene

import de.fabmax.kool.InputManager
import de.fabmax.kool.platform.Math
import de.fabmax.kool.platform.RenderContext
import de.fabmax.kool.util.*

/**
 * A special kind of transform group which translates mouse input into a spherical transform. This is mainly useful
 * for camera manipulation.
 *
 * @author fabmax
 */

fun sphericalInputTransform(name: String? = null, block: SphericalInputTransform.() -> Unit): SphericalInputTransform {
    val sit = SphericalInputTransform(name)
    sit.block()
    return sit
}

open class SphericalInputTransform(name: String? = null) : TransformGroup(name), InputManager.DragHandler {

    var leftDragMethod = DragMethod.ROTATE
    var middleDragMethod = DragMethod.NONE
    var rightDragMethod = DragMethod.PAN

    var verticalAxis = Vec3f.Y_AXIS
    var horizontalAxis = Vec3f.X_AXIS

    val translation = MutableVec3f()
    var verticalRotation = 0f
    var horizontalRotation = 0f
    var zoom = 10f
        set(value) { field = Math.clamp(value, minZoom, maxZoom) }

    var minZoom = 1f
    var maxZoom = 100f
    var translationBounds: BoundingBox? = null

    var panMethod: PanBase = yPlanePan()

    private var stiffness = 0f
    private var damping = 0f

    private val animRotV = AnimatedVal(0f)
    private val animRotH = AnimatedVal(0f)
    private val animZoom = AnimatedVal(zoom)

    private var prevButtonMask = 0
    private var dragMethod = DragMethod.NONE
    private var dragStart = false
    private val deltaPos = MutableVec2f()
    private var deltaScroll = 0f

    private val ptrPos = MutableVec2f()
    private val panPlane = Plane()
    private val pointerHitStart = MutableVec3f()
    private val pointerHit = MutableVec3f()
    private val tmpVec1 = MutableVec3f()
    private val tmpVec2 = MutableVec3f()

    private val mouseTransform = Mat4f()
    private val mouseTransformInv = Mat4f()

    var smoothness: Float = 0f
        set(value) {
            field = value
            if (!Math.isZero(value)) {
                stiffness = 50.0f / value
                damping = 2f * Math.sqrt(stiffness.toDouble()).toFloat()
            }
        }

    override fun onSceneChanged(oldScene: Scene?, newScene: Scene?) {
        super.onSceneChanged(oldScene, newScene)
        oldScene?.removeDragHandler(this)
        newScene?.registerDragHandler(this)
    }

    init {
        smoothness = 0.5f
        panPlane.p.set(Vec3f.ZERO)
        panPlane.n.set(Vec3f.Y_AXIS)
    }

    fun setMouseRotation(vertical: Float, horizontal: Float) {
        animRotV.set(vertical)
        animRotH.set(horizontal)
        verticalRotation = vertical
        horizontalRotation = horizontal
    }

    fun setMouseTranslation(x: Float, y: Float, z: Float) {
        translation.set(x, y, z)
    }

    override fun render(ctx: RenderContext) {
        val scene = this.scene ?: return

        if (panMethod.computePanPoint(pointerHit, scene, ptrPos, ctx)) {
            if (dragStart) {
                dragStart = false
                pointerHitStart.set(pointerHit)

                // stop any ongoing smooth motion, as we start a new one
                stopSmoothMotion()

            } else if (dragMethod == DragMethod.PAN) {
                val s = Math.clamp(1 - smoothness, 0.1f, 1f)
                tmpVec1.set(pointerHitStart).subtract(pointerHit).scale(s)

                // limit panning speed
                val tLen = tmpVec1.length()
                if (tLen > scene.camera.globalRange * 0.5f) {
                    tmpVec1.scale(scene.camera.globalRange * 0.5f / tLen)
                }

                translation.add(tmpVec1)
            }
        } else {
            pointerHit.set(scene.camera.globalLookAt)
        }

        if (!Math.isZero(deltaScroll)) {
            zoom *= 1f + deltaScroll / 10f
            deltaScroll = 0f
        }

        if (dragMethod == DragMethod.ROTATE) {
            verticalRotation -= deltaPos.x / 3
            horizontalRotation -= deltaPos.y / 3
            horizontalRotation = Math.clamp(horizontalRotation, -90f, 90f)
            deltaPos.set(Vec2f.ZERO)
        }

        animRotV.desired = verticalRotation
        animRotH.desired = horizontalRotation
        animZoom.desired = zoom

        val oldZ = animZoom.actual
        val z = animZoom.animate(ctx.deltaT)
        if (!Math.isEqual(oldZ, z)) {
            // tmpVec1 = zoomed pos on pointer ray
            scene.camera.globalPos.subtract(tmpVec1, pointerHit).scale(z / oldZ).add(pointerHit)
            // tmpVec2 = zoomed pos on view center ray
            scene.camera.globalPos.subtract(tmpVec2, scene.camera.globalLookAt).scale(z / oldZ)
                    .add(scene.camera.globalLookAt)
            translation.add(tmpVec1).subtract(tmpVec2)
        }

        translationBounds?.clampToBounds(translation)

        mouseTransform.invert(mouseTransformInv)
        mul(mouseTransformInv)

        mouseTransform.setIdentity()
        mouseTransform.translate(translation.x, translation.y, translation.z)
        mouseTransform.scale(z, z, z)
        mouseTransform.rotate(animRotV.animate(ctx.deltaT), verticalAxis)
        mouseTransform.rotate(animRotH.animate(ctx.deltaT), horizontalAxis)
        mul(mouseTransform)

        super.render(ctx)
    }

    private fun stopSmoothMotion() {
        animRotV.set(animRotV.actual)
        animRotH.set(animRotH.actual)
        animZoom.set(animZoom.actual)

        verticalRotation = animRotV.actual
        horizontalRotation = animRotH.actual
        zoom = animZoom.actual
    }

    override fun handleDrag(dragPtrs: List<InputManager.Pointer>, ctx: RenderContext): Int {
        if (dragPtrs.size == 1 && dragPtrs[0].isInViewport(ctx)) {
            if (dragPtrs[0].buttonEventMask != 0 || dragPtrs[0].buttonMask != prevButtonMask) {
                if (dragPtrs[0].isLeftButtonDown) {
                    dragMethod = leftDragMethod
                } else if (dragPtrs[0].isRightButtonDown) {
                    dragMethod = rightDragMethod
                } else if (dragPtrs[0].isMiddleButtonDown) {
                    dragMethod = middleDragMethod
                } else {
                    dragMethod = DragMethod.NONE<commit_msg>some refactoring for camera transform<commit_after>package de.fabmax.kool.scene

import de.fabmax.kool.InputManager
import de.fabmax.kool.platform.Math
import de.fabmax.kool.platform.RenderContext
import de.fabmax.kool.util.*

/**
 * A special kind of transform group which translates mouse input into a spherical transform. This is mainly useful
 * for camera manipulation.
 *
 * @author fabmax
 */

fun sphericalInputTransform(name: String? = null, block: SphericalInputTransform.() -> Unit): SphericalInputTransform {
    val sit = SphericalInputTransform(name)
    sit.block()
    return sit
}

open class SphericalInputTransform(name: String? = null) : TransformGroup(name), InputManager.DragHandler {

    var leftDragMethod = DragMethod.ROTATE
    var middleDragMethod = DragMethod.NONE
    var rightDragMethod = DragMethod.PAN

    var verticalAxis = Vec3f.Y_AXIS
    var horizontalAxis = Vec3f.X_AXIS

    val translation = MutableVec3f()
    var verticalRotation = 0f
    var horizontalRotation = 0f
    var zoom = 10f
        set(value) { field = Math.clamp(value, minZoom, maxZoom) }

    var minZoom = 1f
    var maxZoom = 100f
    var translationBounds: BoundingBox? = null

    var panMethod: PanBase = yPlanePan()

    private var stiffness = 0f
    private var damping = 0f

    private val animRotV = AnimatedVal(0f)
    private val animRotH = AnimatedVal(0f)
    private val animZoom = AnimatedVal(zoom)

    private var prevButtonMask = 0
    private var dragMethod = DragMethod.NONE
    private var dragStart = false
    private val deltaPos = MutableVec2f()
    private var deltaScroll = 0f

    private val ptrPos = MutableVec2f()
    private val panPlane = Plane()
    private val pointerHitStart = MutableVec3f()
    private val pointerHit = MutableVec3f()
    private val tmpVec1 = MutableVec3f()
    private val tmpVec2 = MutableVec3f()

    private val mouseTransform = Mat4f()
    private val mouseTransformInv = Mat4f()

    var smoothness: Float = 0f
        set(value) {
            field = value
            if (!Math.isZero(value)) {
                stiffness = 50.0f / value
                damping = 2f * Math.sqrt(stiffness.toDouble()).toFloat()
            }
        }

    override fun onSceneChanged(oldScene: Scene?, newScene: Scene?) {
        super.onSceneChanged(oldScene, newScene)
        oldScene?.removeDragHandler(this)
        newScene?.registerDragHandler(this)
    }

    init {
        smoothness = 0.5f
        panPlane.p.set(Vec3f.ZERO)
        panPlane.n.set(Vec3f.Y_AXIS)
    }

    fun setMouseRotation(vertical: Float, horizontal: Float) {
        animRotV.set(vertical)
        animRotH.set(horizontal)
        verticalRotation = vertical
        horizontalRotation = horizontal
    }

    fun setMouseTranslation(x: Float, y: Float, z: Float) {
        translation.set(x, y, z)
    }

    override fun render(ctx: RenderContext) {
        val scene = this.scene ?: return

        if (panMethod.computePanPoint(pointerHit, scene, ptrPos, ctx)) {
            if (dragStart) {
                dragStart = false
                pointerHitStart.set(pointerHit)

                // stop any ongoing smooth motion, as we start a new one
                stopSmoothMotion()

            } else if (dragMethod == DragMethod.PAN) {
                val s = Math.clamp(1 - smoothness, 0.1f, 1f)
                tmpVec1.set(pointerHitStart).subtract(pointerHit).scale(s)

                // limit panning speed
                val tLen = tmpVec1.length()
                if (tLen > scene.camera.globalRange * 0.5f) {
                    tmpVec1.scale(scene.camera.globalRange * 0.5f / tLen)
                }

                translation.add(tmpVec1)
            }
        } else {
            pointerHit.set(scene.camera.globalLookAt)
        }

        if (!Math.isZero(deltaScroll)) {
            zoom *= 1f + deltaScroll / 10f
            deltaScroll = 0f
        }

        if (dragMethod == DragMethod.ROTATE) {
            verticalRotation -= deltaPos.x / 3
            horizontalRotation -= deltaPos.y / 3
            horizontalRotation = Math.clamp(horizontalRotation, -90f, 90f)
            deltaPos.set(Vec2f.ZERO)
        }

        animRotV.desired = verticalRotation
        animRotH.desired = horizontalRotation
        animZoom.desired = zoom

        val oldZ = animZoom.actual
        val z = animZoom.animate(ctx.deltaT)
        if (!Math.isEqual(oldZ, z)) {
            computeZoomTranslationPerspective(scene, oldZ, z)
        }

        translationBounds?.clampToBounds(translation)

        mouseTransform.invert(mouseTransformInv)
        mul(mouseTransformInv)

        mouseTransform.setIdentity()
        mouseTransform.translate(translation.x, translation.y, translation.z)
        mouseTransform.scale(z, z, z)
        mouseTransform.rotate(animRotV.animate(ctx.deltaT), verticalAxis)
        mouseTransform.rotate(animRotH.animate(ctx.deltaT), horizontalAxis)
        mul(mouseTransform)

        super.render(ctx)
    }

    /**
     * Computes the required camera translation so that the camera zooms to the point under the pointer (only works
     * with perspective cameras)
     */
    protected open fun computeZoomTranslationPerspective(scene: Scene, oldZoom: Float, newZoom: Float) {
        // tmpVec1 = zoomed pos on pointer ray
        scene.camera.globalPos.subtract(tmpVec1, pointerHit).scale(newZoom / oldZoom).add(pointerHit)
        // tmpVec2 = zoomed pos on view center ray
        scene.camera.globalPos.subtract(tmpVec2, scene.camera.globalLookAt).scale(newZoom / oldZoom)
                .add(scene.camera.globalLookAt)
        translation.add(tmpVec1).subtract(tmpVec2)
    }

    private fun stopSmoothMotion() {
        animRotV.set(animRotV.actual)
        animRotH.set(animRotH.actual)
        animZoom.set(animZoom.actual)

        verticalRotation = animRotV.actual
        horizontalRotation = animRotH.actual
        zoom = animZoom.actual
    }

    override fun handleDrag(dragPtrs: List<InputManager.Pointer>, ctx: RenderContext): Int {
        if (dragPtrs.size == 1 && dragPtrs[0].isInViewport(ctx)) {
            if (dragPtrs[0].buttonEventMask != 0 || dragPtrs[0].buttonMask != prevButtonMask) {
                if (dragPtrs[0].isLeftButtonDown) {
                    dragMethod = leftDragMethod
                } else if (dragPtrs[0].isRightButtonDown) {
                    dragMethod = rightDragMethod
                } else if (dragPtrs[0].isMiddleButtonDown) {
                    dragMethod = middleDragMethod
                } else {
                    dragMethod = DragMethod.NONE
{'fraction_non_alphanumeric': 0.08917593961559411, 'fraction_numerical': 0.011360983581044277, 'mean_word_length': 2.2970754232939967, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 9, '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': '5529421', 'n_tokens_mistral': 3856, 'n_tokens_neox': 3645, 'n_words': 834}