{"id":10107,"date":"2025-09-19T14:25:48","date_gmt":"2025-09-19T12:25:48","guid":{"rendered":"https:\/\/spgoo.org\/?page_id=10107"},"modified":"2025-09-19T14:25:49","modified_gmt":"2025-09-19T12:25:49","slug":"tambour-3d","status":"publish","type":"page","link":"https:\/\/spgoo.org\/?page_id=10107","title":{"rendered":"Tambour 3D"},"content":{"rendered":" <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/three.js\/r128\/three.min.js\"><\/script>\r\n    <style>\r\n        \r\n        #containerTambour {\r\n            position: relative;\r\n            width: 1000px; \/* 80vw; *\/\r\n            height: 700px;\/* 80vh; *\/\r\n        }\r\n        \r\n        #renderer {\r\n            display: block; \r\n\t\t\t\/*position:absolute;\r\n\t\t\ttop:100px;\r\n\t\t\tleft:50%;*\/\r\n        }\r\n        \r\n        .ui-overlay {\r\n            position: absolute;\r\n            top: 0;\r\n            left: 0;\r\n            width: 100%;\r\n            height: 100%;\r\n            pointer-events: none;\r\n            \/* z-index: 100;*\/ \r\n        }\r\n         \r\n        \r\n        .controls {\r\n            position: absolute;\r\n            bottom: 100px;\r\n            left: 50%;\r\n            transform: translateX(-50%);\r\n            display: flex;\r\n            gap: 15px;\r\n            pointer-events: auto;\r\n        }\r\n        \r\n        .info-panel {\r\n            position: absolute;\r\n            top: 1px;\r\n            left: 50%;\r\n\t\t\tz-index:0;\r\n            transform: translateX(-50%);\r\n            text-align: center;\r\n            background: rgba(0,0,0,0.7);\r\n            padding: 20px 30px;\r\n            border-radius: 15px;\r\n            backdrop-filter: blur(10px);\r\n            border: 1px solid rgba(255,255,255,0.1);\r\n            pointer-events: auto;\r\n            min-width: 400px;\r\n            max-width: 100vw;\r\n        }\r\n        \r\n        .phrase-counter {\r\n            font-size: 1.2em;\r\n            font-weight: bold;\r\n            color: #4ecdc4;\r\n            margin-bottom: 10px;\r\n        }\r\n        \r\n        .current-phrase {\r\n            font-size: 1.3em;\r\n            color: white;\r\n            margin: 0;\r\n            line-height: 1.4;\r\n            text-shadow: 1px 1px 2px rgba(0,0,0,0.5);\r\n        }\r\n        \r\n        button {\r\n            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n            color: white;\r\n            border: none;\r\n            padding: 12px 20px;\r\n            border-radius: 25px;\r\n            cursor: pointer;\r\n            font-size: 1em;\r\n            font-weight: 500;\r\n            transition: all 0.3s ease;\r\n            box-shadow: 0 4px 15px rgba(0,0,0,0.3);\r\n            backdrop-filter: blur(10px);\r\n            border: 1px solid rgba(255,255,255,0.1);\r\n        }\r\n        \r\n        button:hover {\r\n            transform: translateY(-2px);\r\n            box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);\r\n        }\r\n        \r\n        button:active {\r\n            transform: translateY(0);\r\n        }\r\n                \r\n        .loading {\r\n            position: absolute;\r\n            top: 50%;\r\n            left: 50%;\r\n            transform: translate(-50%, -50%);\r\n            color: white;\r\n            font-size: 1.5em;\r\n            text-align: center;\r\n        }\r\n        \r\n        @media (max-width: 768px) {\r\n            .header h1 { font-size: 2em; }\r\n            .info-panel { min-width: 300px; }\r\n            .controls { bottom: 140px; }\r\n        }\r\n    <\/style>\r\n\r\n    <div id=\"containerTambour\">\r\n        <div class=\"loading\" id=\"loading\">\r\n            <div>\ud83c\udfa8 Chargement du tambour 3D&#8230;<\/div>\r\n        <\/div>\r\n        \r\n        <div class=\"ui-overlay\" id=\"ui\" style=\"display: none;\">           \r\n            <div class=\"controls\">\r\n                <button onclick=\"previousPhrase()\">\u2190 Pr\u00e9c\u00e9dent<\/button>\r\n                <button onclick=\"autoRotate()\">\ud83d\udd04 Auto<\/button>\r\n                <button onclick=\"resetRotation()\">\ud83c\udfaf Centrer<\/button>\r\n                <button onclick=\"nextPhrase()\">Suivant \u2192<\/button>\r\n            <\/div>\r\n            \r\n            <div class=\"info-panel\">\r\n                <div class=\"phrase-counter\" id=\"counter\">Phrase 1 \/ 8<\/div>\r\n                <div class=\"current-phrase\" id=\"currentPhrase\">Bienvenue dans le tambour 3D de phrases !<\/div>\r\n            <\/div>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <script>\r\n        \/\/ Configuration\r\n        const phrases = [\r\n            \"Bienvenue dans le tambour 3D de phrases !\",\r\n            \"La cr\u00e9ativit\u00e9 na\u00eet de la contrainte et meurt dans la libert\u00e9.\",\r\n            \"L'art de bien dire ce que l'on pense est diff\u00e9rent de la facult\u00e9 de penser.\",\r\n            \"Les mots sont les fen\u00eatres de l'\u00e2me.\",\r\n            \"Une phrase bien construite est un petit univers complet.\",\r\n            \"Le silence entre les mots est aussi important que les mots eux-m\u00eames.\",\r\n            \"Chaque phrase raconte une histoire, chaque histoire change une vie.\",\r\n            \"La beaut\u00e9 d'une phrase r\u00e9side dans sa simplicit\u00e9 et sa profondeur.\",\r\n            \"Bienvenue dans le tambour 3D de phrases !\",\r\n            \"La cr\u00e9ativit\u00e9 na\u00eet de la contrainte et meurt dans la libert\u00e9.\",\r\n            \"L'art de bien dire ce que l'on pense est diff\u00e9rent de la facult\u00e9 de penser.\",\r\n            \"Les mots sont les fen\u00eatres de l'\u00e2me.\",\r\n            \"Une phrase bien construite est un petit univers complet.\",\r\n            \"Le silence entre les mots est aussi important que les mots eux-m\u00eames.\",\r\n            \"Chaque phrase raconte une histoire, chaque histoire change une vie.\",\r\n            \"La beaut\u00e9 d'une phrase r\u00e9side dans sa simplicit\u00e9 et sa profondeur.\",\r\n\r\n        ];\r\n        \r\n        let scene, camera, renderer, drum, textSprites = [];\r\n        let currentAngle = 0;\r\n        let targetAngle = 0;\r\n        let currentPhraseIndex = 0;\r\n        let isAutoRotating = false;\r\n        let autoRotateSpeed = 0;\r\n        \r\n        const drumRadius = 2;\r\n        const drumHeight = 2;\r\n        const angleStep = (Math.PI * 2) \/ phrases.length;\r\n        \r\n        \/\/ Initialisation de Three.js\r\n        function initThreeJS() {\r\n            \/\/ Sc\u00e8ne\r\n            scene = new THREE.Scene();\r\n            \/\/scene.fog = new THREE.Fog(0x1a1a2e, 5, 15);\r\n            \r\n            \/\/ Cam\u00e9ra\r\n            \/\/camera = new THREE.PerspectiveCamera(75, window.innerWidth \/ window.innerHeight, 0.1, 1000);\r\n\t\t\t\/\/ camera = new THREE.PerspectiveCamera(75, 2, 0.1, 1000);\r\n\t\t\t\r\n\t\t\tcamera = new THREE.PerspectiveCamera(75, 1000\/800, 0.1, 1000);\r\n\t\t\t\/\/camera.position.set(0, 0, 8);\r\n            camera.position.z = 7;\r\n\t\t\tcamera.position.x = 0;\r\n\t\t\tcamera.position.y = -1;\r\n\t\t\t\r\n\t\t\t\r\n            \/\/camera.position.set(0, 0, 8);\r\n\t\t\t\r\n\t\t\t\/\/camera.position.x = 2;\r\n\t\t\t\/\/camera.position.y = -1.25;\r\n\t\t\t\/\/camera.position.z = 7;\r\n            \r\n            \/\/ Renderer\r\n            renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\r\n            \/\/renderer.setSize(window.innerWidth, window.innerHeight);\r\n\t\t\trenderer.setSize(1000, 800);\r\n            renderer.setClearColor(0x1a1a2e, 0);\r\n            renderer.shadowMap.enabled = true;\r\n            renderer.shadowMap.type = THREE.PCFSoftShadowMap;\r\n            document.getElementById('containerTambour').appendChild(renderer.domElement);\r\n            \r\n            \/\/ \u00c9clairage\r\n            setupLighting();\r\n            \r\n            \/\/ Cr\u00e9ation du tambour\r\n            createDrum();\r\n            \r\n            \/\/ Cr\u00e9ation des textes\r\n            createTextSprites();\r\n            \r\n            \/\/ Animation\r\n            animate();\r\n            \r\n            \/\/ Cacher le loading et montrer l'UI\r\n            document.getElementById('loading').style.display = 'none';\r\n            document.getElementById('ui').style.display = 'block';\r\n        }\r\n        \r\n        function setupLighting() {\r\n            \/\/ Lumi\u00e8re ambiante\r\n            const ambientLight = new THREE.AmbientLight(0x4a5568, 0.8);\r\n            scene.add(ambientLight);\r\n            \r\n            \/\/ Lumi\u00e8re directionnelle principale\r\n            const directionalLight = new THREE.DirectionalLight(0xffffff, 1);\r\n            directionalLight.position.set(5, 5, 5);\r\n            directionalLight.castShadow = true;\r\n            directionalLight.shadow.mapSize.width = 2048;\r\n            directionalLight.shadow.mapSize.height = 2048;\r\n            scene.add(directionalLight);\r\n            \r\n            \/\/ Lumi\u00e8re d'accent color\u00e9e\r\n            const spotLight1 = new THREE.SpotLight(0x4ecdc4, 0.8, 10, Math.PI \/ 4, 0.5);\r\n            spotLight1.position.set(-5, 3, 5);\r\n            scene.add(spotLight1);\r\n            \r\n            const spotLight2 = new THREE.SpotLight(0xff6b6b, 0.6, 10, Math.PI \/ 4, 0.5);\r\n            spotLight2.position.set(5, -3, 5);\r\n            scene.add(spotLight2);\r\n\r\n            const spotLight3 = new THREE.SpotLight(0xff6b6b, 0.9, 10, Math.PI \/ 4, 0.5);\r\n            spotLight3.position.set(5, -3, 2);\r\n            scene.add(spotLight3);\r\n\r\n        }\r\n        \r\n        function createDrum() {\r\n            \/\/ Corps principal du tambour (cylindre)\r\n            const drumGeometry = new THREE.CylinderGeometry(drumRadius, drumRadius, 2*drumHeight, 32);\r\n            const drumMaterial = new THREE.MeshPhongMaterial({\r\n                color: 0x2c3e50,\r\n                shininess: 100,\r\n                transparent: true,\r\n                opacity: 0.9\r\n            });\r\n            \r\n            drum = new THREE.Mesh(drumGeometry, drumMaterial);\r\n            drum.rotation.z = Math.PI \/ 2; \/\/ Rotation pour avoir un tambour horizontal\r\n            drum.castShadow = true;\r\n            drum.receiveShadow = true;\r\n            scene.add(drum);\r\n            \r\n            \/\/ Cercles des extr\u00e9mit\u00e9s du tambour\r\n            const ringGeometry = new THREE.RingGeometry(drumRadius - 0.1, drumRadius + 0.1, 32);\r\n            const ringMaterial = new THREE.MeshPhongMaterial({\r\n                color: 0x34495e,\r\n                side: THREE.DoubleSide\r\n            });\r\n            \r\n            const leftRing = new THREE.Mesh(ringGeometry, ringMaterial);\r\n            leftRing.position.set(-drumHeight, 0, 0);\r\n            leftRing.rotation.y = Math.PI \/ 2;\r\n            scene.add(leftRing);\r\n            \r\n            const rightRing = new THREE.Mesh(ringGeometry, ringMaterial);\r\n            rightRing.position.set(drumHeight, 0, 0);\r\n            rightRing.rotation.y = Math.PI \/ 2;\r\n            scene.add(rightRing);\r\n            \r\n            \/\/ Lignes d\u00e9coratives sur le tambour\r\n            \/*for (let i = 0; i < 8; i++) {\r\n                const angle = (i \/ 8) * Math.PI * 2;\r\n                const lineGeometry = new THREE.BoxGeometry(2*drumHeight + 0.2, 0.02, 0.02);\r\n                const lineMaterial = new THREE.MeshPhongMaterial({ color: \"yellow\" }); \/\/ 0x95a5a6 });\r\n                const line = new THREE.Mesh(lineGeometry, lineMaterial);\r\n                \r\n                const y = Math.cos(angle) * drumRadius;\r\n                const z = Math.sin(angle) * drumRadius;\r\n                line.position.set(0, y, z);\r\n                scene.add(line);\r\n            }*\/\r\n        }\r\n        \r\n        function createTextSprites() {\r\n            textSprites.forEach(sprite => scene.remove(sprite));\r\n            textSprites = [];\r\n            \r\n            phrases.forEach((phrase, i) => {\r\n                const index=i+4;\r\n\r\n                const canvas = document.createElement('canvas');\r\n                const context = canvas.getContext('2d');\r\n                \r\n                \/\/ Taille et style du texte\r\n                canvas.width = 1024;\r\n                canvas.height = 256;\r\n                context.font = 'Bold 32px Arial';\r\n                context.fillStyle = 'white';\r\n                context.opacity = 1.0;\r\n                context.textAlign = 'center';\r\n                context.textBaseline = 'middle';\r\n                \r\n                \/\/ Ombre du texte\r\n                context.shadowColor = 'rgba(0,0,0,0.8)';\r\n                context.shadowBlur = 4;\r\n                context.shadowOffsetX = 2;\r\n                context.shadowOffsetY = 2;\r\n                \r\n                \/\/ D\u00e9coupage du texte si trop long\r\n                const maxWidth = canvas.width - 40;\r\n                let displayText = phrase;\r\n                if (context.measureText(phrase).width > maxWidth) {\r\n                    displayText = phrase.substring(0, 50) + '...';\r\n\t\t            \/\/displayText = phrase;\r\n                }\r\n                \r\n                \/\/ Dessin du texte\r\n                context.fillText(displayText, canvas.width\/2 , canvas.height\/2 );\r\n                \r\n                \/\/ Cr\u00e9ation de la texture et du sprite\r\n                const texture = new THREE.CanvasTexture(canvas);\r\n                texture.needsUpdate = true;\r\n                \r\n                const spriteMaterial = new THREE.SpriteMaterial({ \r\n                    map: texture,\r\n                    transparent: true\r\n                });\r\n                const sprite = new THREE.Sprite(spriteMaterial);\r\n                \r\n                \/\/ Position sur le tambour\r\n                const angle = index * angleStep;\r\n                const x = 0;\r\n                const y = Math.cos(angle) * (drumRadius + 0.5);\r\n                const z = Math.sin(angle) * (drumRadius + 0.5);\r\n                \r\n                sprite.position.set(x, y, z);\r\n                sprite.scale.set(2, 1.5, 1);\r\n                sprite.userData = { originalAngle: angle, index: index };\r\n                \r\n                scene.add(sprite);\r\n                textSprites.push(sprite);\r\n            });\r\n        }\r\n        \r\n        function updateTextPositions() {\r\n            textSprites.forEach((sprite, index) => {\r\n                const angle = sprite.userData.originalAngle + currentAngle;\r\n                const x = 0;\r\n                const y = Math.cos(angle) * (drumRadius + 0.5);\r\n                const z = Math.sin(angle) * (drumRadius + 0.5);\r\n                \r\n                sprite.position.set(x, y, z);\r\n                \r\n                \/\/ Fade out des textes qui passent derri\u00e8re\r\n                const visibility = (Math.cos(angle) + 1) \/ 2;\r\n                sprite.material.opacity = Math.max(0.3, visibility);\r\n                \r\n                \/\/ Mise en \u00e9vidence du texte actuel\r\n                if (index === currentPhraseIndex) {\r\n                    sprite.material.opacity = Math.max(0.9, sprite.material.opacity);\r\n                    sprite.scale.set(3.5, 1.55, 1);\r\n                } else {\r\n                    sprite.scale.set(2.5, 0.75, 1.5);\r\n                }\r\n            });\r\n        }\r\n        \r\n        function animate() {\r\n            requestAnimationFrame(animate);\r\n            \r\n            \/\/ Rotation fluide vers l'angle cible\r\n            const diff = targetAngle - currentAngle;\r\n            if (Math.abs(diff) > 0.001) {\r\n                currentAngle += diff * 0.1;\r\n            }\r\n            \r\n            \/\/ Auto-rotation\r\n            if (isAutoRotating) {\r\n                autoRotateSpeed += 0.0002;\r\n                targetAngle += autoRotateSpeed;\r\n                \r\n                \/\/ V\u00e9rifier si on a atteint la phrase suivante\r\n                const newIndex = Math.round(-targetAngle \/ angleStep) % phrases.length;\r\n                if (newIndex !== currentPhraseIndex) {\r\n                    currentPhraseIndex = (newIndex + phrases.length) % phrases.length;\r\n                    updateUI();\r\n                }\r\n            }\r\n            \r\n            \/\/ Rotation du tambour\r\n            if (drum) {\r\n                drum.rotation.x = currentAngle;\r\n            }\r\n            \r\n            \/\/ Mise \u00e0 jour des positions des textes\r\n            updateTextPositions();\r\n            \r\n            renderer.render(scene, camera);\r\n        }\r\n        \r\n        function updateUI() {\r\n            document.getElementById('counter').textContent = \r\n                `Phrase ${currentPhraseIndex + 1} \/ ${phrases.length}`;\r\n            document.getElementById('currentPhrase').textContent = \r\n                phrases[currentPhraseIndex];\r\n        }\r\n        \r\n        function rotateTo(index) {\r\n            isAutoRotating = false;\r\n            autoRotateSpeed = 0;\r\n            targetAngle = -index * angleStep;\r\n            currentPhraseIndex = index;\r\n            updateUI();\r\n        }\r\n        \r\n        function nextPhrase() {\r\n            const newIndex = (currentPhraseIndex + 1) % phrases.length;\r\n            rotateTo(newIndex);\r\n        }\r\n        \r\n        function previousPhrase() {\r\n            const newIndex = (currentPhraseIndex - 1 + phrases.length) % phrases.length;\r\n            rotateTo(newIndex);\r\n        }\r\n        \r\n        function resetRotation() {\r\n            rotateTo(0);\r\n        }\r\n        \r\n        function autoRotate() {\r\n            isAutoRotating = !isAutoRotating;\r\n            if (!isAutoRotating) {\r\n                autoRotateSpeed = 0;\r\n            }\r\n        }\r\n        \r\n        \/\/ Gestion des \u00e9v\u00e9nements\r\n        function handleWheel(event) {\r\n            event.preventDefault();\r\n            const delta = event.deltaY > 0 ? 1 : -1;\r\n            const newIndex = (currentPhraseIndex + delta + phrases.length) % phrases.length;\r\n            rotateTo(newIndex);\r\n        }\r\n        \r\n        \/\/ Gestion tactile\r\n        let touchStartY = 0;\r\n        function handleTouchStart(event) {\r\n            touchStartY = event.touches[0].clientY;\r\n        }\r\n        \r\n        function handleTouchMove(event) {\r\n            event.preventDefault();\r\n            const touchY = event.touches[0].clientY;\r\n            const deltaY = touchStartY - touchY;\r\n            \r\n            if (Math.abs(deltaY) > 50) {\r\n                const direction = deltaY > 0 ? 1 : -1;\r\n                const newIndex = (currentPhraseIndex + direction + phrases.length) % phrases.length;\r\n                rotateTo(newIndex);\r\n                touchStartY = touchY;\r\n            }\r\n        }\r\n        \r\n        \/\/ Redimensionnement\r\n        function handleResize() {\r\n            camera.aspect = 1000\/800; \/\/ window.innerWidth \/ window.innerHeight;\r\n            camera.updateProjectionMatrix();\r\n            renderer.setSize(1000,800) ;\/\/ window.innerWidth, window.innerHeight);\r\n        }\r\n        \r\n        \/\/ \u00c9v\u00e9nements\r\n        window.addEventListener('wheel', handleWheel, { passive: false });\r\n        window.addEventListener('touchstart', handleTouchStart);\r\n        window.addEventListener('touchmove', handleTouchMove, { passive: false });\r\n        window.addEventListener('resize', handleResize);\r\n        \r\n        \/\/ Initialisation\r\n        initThreeJS();\r\n        updateUI();\r\n    <\/script>\r\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-10107","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages\/10107","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/spgoo.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=10107"}],"version-history":[{"count":1,"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages\/10107\/revisions"}],"predecessor-version":[{"id":10108,"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages\/10107\/revisions\/10108"}],"wp:attachment":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=10107"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}