From 32a3508dd04a2b470299edfceee0135cab910178 Mon Sep 17 00:00:00 2001 From: Victor Valentim Date: Wed, 23 Oct 2024 19:10:23 -0300 Subject: [PATCH] Complete Beta --- README.md | 112 ++-- build.gradle.kts | 15 +- data/README | 7 + data/domemaster.glsl | 41 ++ data/equirectangular.glsl | 80 +++ examples/Basic/Basic.pde | 91 ++++ examples/HelloCircles/HelloCircles.pde | 24 - library.properties | 10 + mkdocs.yml | 4 +- release.properties | 50 +- .../com/myDomain/myLibrary/RandomCircles.java | 60 --- .../java/com/myDomain/myLibrary/SayHello.java | 36 -- .../zividomelive/CameraManager.java | 29 ++ .../zividomelive/CameraOrientation.java | 35 ++ .../zividomelive/ControlManager.java | 324 ++++++++++++ .../zividomelive/CubemapRenderer.java | 62 +++ .../zividomelive/CubemapViewRenderer.java | 75 +++ .../zividomelive/EquirectangularRenderer.java | 33 ++ .../zividomelive/FisheyeDomemaster.java | 68 +++ .../zividomelive/MouseControlledCamera.java | 85 +++ .../victorvalentim/zividomelive/Scene.java | 9 + .../zividomelive/StandardRenderer.java | 49 ++ .../zividomelive/zividomelive.java | 490 ++++++++++++++++++ 23 files changed, 1600 insertions(+), 189 deletions(-) create mode 100644 data/README create mode 100755 data/domemaster.glsl create mode 100755 data/equirectangular.glsl create mode 100644 examples/Basic/Basic.pde delete mode 100644 examples/HelloCircles/HelloCircles.pde create mode 100644 library.properties delete mode 100644 src/main/java/com/myDomain/myLibrary/RandomCircles.java delete mode 100644 src/main/java/com/myDomain/myLibrary/SayHello.java create mode 100644 src/main/java/com/victorvalentim/zividomelive/CameraManager.java create mode 100644 src/main/java/com/victorvalentim/zividomelive/CameraOrientation.java create mode 100644 src/main/java/com/victorvalentim/zividomelive/ControlManager.java create mode 100644 src/main/java/com/victorvalentim/zividomelive/CubemapRenderer.java create mode 100644 src/main/java/com/victorvalentim/zividomelive/CubemapViewRenderer.java create mode 100644 src/main/java/com/victorvalentim/zividomelive/EquirectangularRenderer.java create mode 100644 src/main/java/com/victorvalentim/zividomelive/FisheyeDomemaster.java create mode 100644 src/main/java/com/victorvalentim/zividomelive/MouseControlledCamera.java create mode 100644 src/main/java/com/victorvalentim/zividomelive/Scene.java create mode 100644 src/main/java/com/victorvalentim/zividomelive/StandardRenderer.java create mode 100644 src/main/java/com/victorvalentim/zividomelive/zividomelive.java diff --git a/README.md b/README.md index e797b13..fd3309e 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,72 @@ -# Processing Library Template -This is a template to help developers of Processing libraries to develop and release. - -Please read the [documentation website](https://processing.github.io/processing-library-template/) -for more information on how to use this template. - -Three important outputs are required to contribute a library to Processing, and this template provides -help and guidance on them. They are: -1. **The library's code** - This template will build your code into a jar file with Gradle. -2. **A website for the library** - We recommend using [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) - to create a static website for your library. It allows you to write content for your website - using markdown, and structure the site using a yml configuration file. We provide a skeleton - MkDocs website as part of this template. -3. **A url that serves the release artifacts** - We have already configured Gradle tasks to create the - release artifacts. If you host your code on Github, You can create a Github release that serves the - release artifacts. - - -References for developing libraries for Processing can be found on the following Github wiki pages: -- https://github.com/benfry/processing4/wiki/Library-Basics -- https://github.com/benfry/processing4/wiki/Library-Guidelines -- https://github.com/benfry/processing4/wiki/Library-Overview - -> [!Note] -> This template is based on Gradle. If you are looking for the old Ant-based template, see [processing/processing-library-template-ant](processing/processing-library-template-ant) - -## Contributors - -This template was created by Claudine Chen ([@mingness](https://github.com/mingness)) as part of the 2024 New Beginnings (pr05) Grant from the -[Processing Foundation](https://github.com/processing), to simplify the -workflows for libraries, tools, and modes, mentored by Stef Tervelde ([@Stefterv](https://github.com/stefterv)). - -It is based on and inspired by a number of Processing library templates, including: -- https://github.com/processing/processing-library-template-gradle -- https://github.com/enkatsu/processing-library-template-gradle -- https://github.com/hamoid/processing-library-template/ - -I wish to thank the developers of these repositories, who generously provided -guidance and time. This template has been developed in collaboration with -[@enkatsu](https://github.com/enkatsu). +## ziviDomeLive Library + +ziviDomeLive is a Processing library that facilitates the creation of immersive visual experiences for fulldome projections. It allows for interactive and sound-reactive scene manipulation, making it ideal for fulldome environments and other immersive installations. + +Features: +- Support for dome projection techniques (e.g., equirectangular and domemaster shaders). +- Interaction with 3D scenes in real time. +- Sound-reactive visual effects to enhance audience experience. +- Customizable scene managers for control over dome content. + +## Installation: + +Install with the Contribution Manager: +- You can install ziviDomeLive using the Processing Contribution Manager: + 1. Open Processing and navigate to Sketch → Import Library... → Add Library.... + 2. Search for ziviDomeLive and click Install. + +- If the library is not available in the Contribution Manager, follow the manual installation steps below. + +Manual Install: +1. Download ziviDomeLive from https://github.com/vicvalentim/zividomelive. +2. Unzip the downloaded file. +3. Copy the folder into the libraries directory of your Processing sketchbook. You can find the sketchbook location in Processing under Preferences. +4. The folder structure should be as follows: + + Processing + libraries + ziviDomeLive + examples + library + ziviDomeLive.jar + reference + src + +5. Restart Processing to use the library. + +## Usage: +To use ziviDomeLive in your Processing sketch, import the library at the beginning of your code: + +import ziviDomeLive.*; + +ziviDomeLive dome; + +void setup() { +size(800, 800, P3D); +dome = new ziviDomeLive(this); +dome.setup(); +} + +void draw() { + +dome.draw(); + +} + +## Examples: +Check out the examples folder for a variety of sample sketches that demonstrate how to use ziviDomeLive to create immersive, sound-reactive visual experiences. + +## Development: +If you want to contribute to the development of ziviDomeLive: +1. Fork the repository on GitHub: https://github.com/vicvalentim/zividomelive +2. Clone your fork to your local machine. +3. Make changes and test them locally. +4. Push your changes and create a pull request. + +## Author: +Developed by Victor Valentim. + +For more information, visit Victor Valentim's GitHub page: https://github.com/vicvalentim. + +## License: +ziviDomeLive is licensed under the Apache License, Version 2.0. See the LICENSE file for more details. diff --git a/build.gradle.kts b/build.gradle.kts index 79eafb3..95b22da 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,13 +27,13 @@ java { // Such as: // .jar will be the name of your build jar // .zip will be the name of your release file -val libName = "myLibrary" +val libName = "ziviDomeLive" // The group ID of your library, which uniquely identifies your project. // It's often written in reverse domain name notation. // For example, if your website is "myDomain.com", your group ID would be "com.myDomain". // Replace "com.myDomain" with your own domain or organization name. -group = "com.myDomain" +group = "com.victorvalentim.zividomelive" // The version of your library. It usually follows semantic versioning (semver), // which uses three numbers separated by dots: "MAJOR.MINOR.PATCH" (e.g., "1.0.0"). @@ -56,7 +56,7 @@ var sketchbookLocation = "" val userHome = System.getProperty("user.home") val currentOS = OperatingSystem.current() if(currentOS.isMacOsX) { - sketchbookLocation = "$userHome/Documents/Processing/sketchbook" + sketchbookLocation = "$userHome/Documents/Processing/" } else if(currentOS.isWindows) { sketchbookLocation = "$userHome/My Documents/Processing/sketchbook" } else { @@ -85,9 +85,10 @@ dependencies { // We are currently resolving from an unofficial, jitpack-enabled, processing4 repository. // Eventually, this will change to an official source. - // insert your external dependencies - implementation(group = "org.apache.commons", name = "commons-math3", version = "3.6.1") - // The provided example uses commons-math3. Replace or add as needed. + // Bibliotecas locais no sketchbook (ControlP5, Syphon, SpoutProcessing) + compileOnly(fileTree("$sketchbookLocation/libraries/controlP5/library")) + compileOnly(fileTree("$sketchbookLocation/libraries/Syphon/library")) + compileOnly(fileTree("$sketchbookLocation/libraries/spout/library")) // To add a dependency on a Processing library that is installed locally, // uncomment the line below, and replace with the location of that library @@ -262,4 +263,4 @@ tasks.register("deployToProcessingSketchbook") { ) into(installDirectory) } -} +} \ No newline at end of file diff --git a/data/README b/data/README new file mode 100644 index 0000000..24121ad --- /dev/null +++ b/data/README @@ -0,0 +1,7 @@ +the data folder: +If your Library is using files like images, sound files, +any data file, etc., put them into the data folder. +When coding your Library you can use processing's internal loading +functions like loadImage(), loadStrings(), etc. to load files +located inside the data folder into your Library. + diff --git a/data/domemaster.glsl b/data/domemaster.glsl new file mode 100755 index 0000000..eb426f9 --- /dev/null +++ b/data/domemaster.glsl @@ -0,0 +1,41 @@ +#version 410 core + +uniform sampler2D equirectangularMap; +uniform vec2 resolution; // Resolução da tela ou imagem de saída +uniform float fov; // Campo de visão em graus, permitindo até 360 graus +out vec4 fragColor; + +const float PI = 3.1415926535897932384626433832795; + +vec3 equirectangularToDir(vec2 uv) { + float theta = uv.y * PI; // de 0 a PI + float phi = uv.x * 2.0 * PI; // de 0 a 2*PI + return vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); +} + +void main() { + vec2 uv = (gl_FragCoord.xy / resolution) * 2.0 - 1.0; + uv.x = -uv.x; // Inverter a coordenada x para girar 180 graus no eixo horizontal + float phi = atan(uv.y, uv.x); + float l = length(uv); + + if (l > 1.0) { + discard; + } else { + float maxTheta = radians(min(fov, 360.0)) / 2.0; + float theta = l * maxTheta; + + vec2 sphericalUV = vec2((phi / (2.0 * PI)) + 0.5, theta / PI); + sphericalUV = clamp(sphericalUV, 0.0, 1.0); // Garante que UV fique dentro dos limites + + vec3 dir = equirectangularToDir(sphericalUV); + + float u = 0.5 + atan(dir.x, dir.z) / (2.0 * PI); + float v = 0.5 - asin(clamp(dir.y, -1.0, 1.0)) / PI; + u = fract(u); // Garante repetição sem exceder limites + v = clamp(v, 0.0, 1.0); // Evita acesso fora dos limites verticais + + vec4 color = texture(equirectangularMap, vec2(u, v)); + fragColor = color; + } +} diff --git a/data/equirectangular.glsl b/data/equirectangular.glsl new file mode 100755 index 0000000..fe39c61 --- /dev/null +++ b/data/equirectangular.glsl @@ -0,0 +1,80 @@ +#version 410 core + +const float PI = 3.1415926535897932384626433832795; + +uniform sampler2D posX, negX, posY, negY, posZ, negZ; +uniform vec2 resolution; + +out vec4 fragColor; + +void convert_xyz_to_cube_uv(float x, float y, float z, out int index, out vec2 uv) { + float absX = abs(x); + float absY = abs(y); + float absZ = abs(z); + + bool isXPositive = x > 0.0; + bool isYPositive = y > 0.0; + bool isZPositive = z > 0.0; + + float maxAxis, uc, vc; + + // Determine the major axis of projection + if (isXPositive && absX >= absY && absX >= absZ) { + maxAxis = absX; + uc = -z; + vc = y; + index = 0; // +X + } else if (!isXPositive && absX >= absY && absX >= absZ) { + maxAxis = absX; + uc = z; + vc = y; + index = 1; // -X + } else if (isYPositive && absY >= absX && absY >= absZ) { + maxAxis = absY; + uc = x; + vc = -z; + index = 2; // +Y + } else if (!isYPositive && absY >= absX && absY >= absZ) { + maxAxis = absY; + uc = x; + vc = z; + index = 3; // -Y + } else if (isZPositive && absZ >= absX && absZ >= absY) { + maxAxis = absZ; + uc = x; + vc = y; + index = 4; // +Z + } else { + maxAxis = absZ; + uc = -x; + vc = y; + index = 5; // -Z + } + + uv = 0.5 * (vec2(uc, vc) / maxAxis + 1.0); +} + +void main() { + vec2 uv = gl_FragCoord.xy / resolution; + float theta = uv.x * 2.0 * PI; // Horizontal angle + float phi = uv.y * PI; // Vertical angle + vec3 dir = vec3(sin(phi) * sin(theta), cos(phi), sin(phi) * cos(theta)); + + // Rotate 180 degrees around the vertical axis + dir.x = -dir.x; + dir.z = -dir.z; + + vec2 uvCube; + int index; + convert_xyz_to_cube_uv(dir.x, dir.y, dir.z, index, uvCube); + + switch(index) { + case 0: fragColor = texture(posX, uvCube); break; + case 1: fragColor = texture(negX, uvCube); break; + case 2: fragColor = texture(posY, uvCube); break; + case 3: fragColor = texture(negY, uvCube); break; + case 4: fragColor = texture(posZ, uvCube); break; + case 5: fragColor = texture(negZ, uvCube); break; + default: fragColor = vec4(0.0); break; + } +} diff --git a/examples/Basic/Basic.pde b/examples/Basic/Basic.pde new file mode 100644 index 0000000..243e9f8 --- /dev/null +++ b/examples/Basic/Basic.pde @@ -0,0 +1,91 @@ +import com.victorvalentim.zividomelive.*; +import controlP5.*; +import codeanticode.syphon.*; +import spout.*; + +// Instâncias principais +zividomelive ziviDome; // Instância da biblioteca zividomelive +Scene currentScene; // Cena atual que implementa a interface Scene + +void settings() { + size(1280, 720, P3D); // Define o tamanho da janela e o modo P3D +} + +void setup() { + // Inicializa a biblioteca zividomelive + ziviDome = new zividomelive(this); + ziviDome.setup(); // Configuração inicial da biblioteca + + // Inicializa a cena e a define na biblioteca + currentScene = new Scene1(ziviDome); + ziviDome.setScene(currentScene); + +} + +void draw() { + // Chama o método draw da biblioteca para processar renderizações adicionais + ziviDome.draw(); +} + +// Implementação da cena Scene1 que usa a interface Scene +class Scene1 implements Scene { + zividomelive parent; + + Scene1(zividomelive parent) { + this.parent = parent; + } + + @Override + public void setupScene() { + // Configurações específicas da cena, se necessário + println("Setup da Scene1 concluído."); + } + + @Override + public void sceneRender(PGraphics pg) { + pg.pushMatrix(); + pg.background(0, 0, 80, 0); + float radius = 700; // Distância do centro + int numPillars = 8; + float angleStep = TWO_PI / numPillars; + int[] colors = {#FF0000, #00FF00, #0000FF, #FFFF00, #FF00FF, #00FFFF, #FFFFFF, #FF8000}; + float time = millis() / 2000.0; // Tempo em segundos para animação + for (int i = 0; i < numPillars; i++) { + float angle = angleStep * i + time; // Adiciona a animação de rotação + float x = sin(angle) * radius; + float y = cos(angle) * radius; + pg.pushMatrix(); + pg.translate(x, y, 0); + pg.rotateX(time); // Adiciona a rotação no eixo X + pg.fill(colors[i]); + pg.box(200); // Altere os parâmetros conforme a necessidade para ajustar o tamanho + pg.popMatrix(); + } + pg.popMatrix(); + } + + @Override + public void keyPressed(char key) { + // Implementar lógica de resposta a teclas, se necessário + println("Tecla pressionada na Scene1: " + key); + } +} + +// Encaminha eventos de teclas para a biblioteca +void keyPressed() { + ziviDome.keyPressed(); + // Opcional: chamar keyPressed da cena atual + if (currentScene != null) { + currentScene.keyPressed(key); + } +} + +// Encaminha eventos de mouse para a biblioteca +void mouseEvent(processing.event.MouseEvent event) { + ziviDome.mouseEvent(event); +} + +// Encaminha eventos de controle para a biblioteca +void controlEvent(controlP5.ControlEvent theEvent) { + ziviDome.controlEvent(theEvent); +} diff --git a/examples/HelloCircles/HelloCircles.pde b/examples/HelloCircles/HelloCircles.pde deleted file mode 100644 index 99dbe3e..0000000 --- a/examples/HelloCircles/HelloCircles.pde +++ /dev/null @@ -1,24 +0,0 @@ -import com.myDomain.myLibrary.*; - -RandomCircles circles; -SayHello hello; - -void setup() { - size(400,400); - smooth(); - - circles = new RandomCircles(this); - hello = new SayHello(this); - - PFont font = createFont("Arial", 40); - textFont(font); -} - -void draw() { - background(0); - noStroke(); - fill(255,0,0); - circles.drawCircles(); - fill(255); - text(hello.text(), 40, 200); -} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..178d1e6 --- /dev/null +++ b/library.properties @@ -0,0 +1,10 @@ +authors=[Victor Valentim](https\://victorvalentim.com/) +categories=3D, Video & Vision +maxRevision=0 +minRevision=227 +name=ziviDomeLive +paragraph=This library allows for interactive scene manipulation, ideal for fulldome projections and immersive environments. +prettyVersion=1.0.0 +sentence=Facilitates the creation of immersive visual experiences for fulldome projections. +url=https\://github.com/vicvalentim/zividomelive +version=1 diff --git a/mkdocs.yml b/mkdocs.yml index e6aa782..081fb6e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ -site_name: Processing Library Template -site_url: "https://processing.github.io/processing-library-template/" +site_name: ziviDomeLive +site_url: "https://processing.github.io/ziviDomeLive/" theme: name: material diff --git a/release.properties b/release.properties index 53c95af..7f81bf2 100644 --- a/release.properties +++ b/release.properties @@ -2,7 +2,7 @@ # UTF-8 supported. # The name of your Library as you want it formatted. -name=A Template Example Library +name=ziviDomeLive # A version number that increments once with each release. This is used to # compare different versions of the same Library, and check if an update is @@ -11,43 +11,53 @@ name=A Template Example Library version=1 # List of authors. Links can be provided using the syntax [author name](url). -authors=[Your Name](https://myDomain.com) +authors=[Victor Valentim](https://victorvalentim.com/) # A web page for your Library, NOT a direct link to where to download it. -url=https://myDomain.com/myLibrary +url=https://github.com/vicvalentim/zividomelive # The category (or categories) of your Library, must be from the following list: -# "3D" "Animation" "Compilations" "Data" -# "Fabrication" "Geometry" "GUI" "Hardware" -# "I/O" "Language" "Math" "Simulation" +# "3D" "Animation" "Compilations" "Data" +# "Fabrication" "Geometry" "GUI" "Hardware" +# "I/O" "Language" "Math" "Simulation" # "Sound" "Utilities" "Typography" "Video & Vision" -# -# If a value other than those listed is used, your Library will listed as +# +# If a value other than those listed is used, your Library will listed as # "Other". Many categories must be comma-separated. -categories=Other +categories=3D, Video & Vision -# A short sentence (or fragment) to summarize the Library's function. This will -# be shown from inside the PDE when the Library is being installed. Avoid -# repeating the name of your Library here. Also, avoid saying anything redundant -# like mentioning that it's a Library. This should start with a capitalized +# A short sentence (or fragment) to summarize the Library's function. This will +# be shown from inside the PDE when the Library is being installed. Avoid +# repeating the name of your Library here. Also, avoid saying anything redundant +# like mentioning that it's a Library. This should start with a capitalized # letter, and end with a period. -sentence=This description shows in the Contribution Manager and website. +sentence=Facilitates the creation of immersive visual experiences for fulldome projections. # Additional information suitable for the Processing website. The value of # 'sentence' always will be prepended, so you should start by writing the # second sentence here. If your Library only works on certain operating systems, # mention it here. -paragraph= +paragraph=This library allows for interactive scene manipulation, ideal for fulldome projections and immersive environments. # Links in the 'sentence' and 'paragraph' attributes can be inserted using the -# same syntax as for authors. +# same syntax as for authors. # That is, [here is a link to Processing](http://processing.org/) # The min and max revision of Processing compatible with your Library. -# Note that these fields use the revision and not the version of Processing, -# parsable as an int. For example, the revision number for 4.2 is 1292. +# Note that these fields use the revision and not the version of Processing, +# parsable as an int. For example, the revision number for 4.2 is 1292. # You can find the revision numbers in the change log: https://raw.githubusercontent.com/processing/processing/master/build/shared/revisions.txt -# Only use maxRevision (or minRevision), when your Library is known to +# Only use maxRevision (or minRevision), when your Library is known to # break in a later (or earlier) release. Otherwise, use the default value 0. -minRevision=0 +minRevision=227 maxRevision=0 + +# The platforms and Processing version that the Library has been tested +# against. This information is only used in the generated webpage. +tested.platform=osx,windows,linux +tested.processingVersion=4.3.1 + +# Additional information for the generated webpage. +library.copyright=(c) 2024 Victor Valentim +library.dependencies=None +library.keywords=fulldome, projection, immersive, sound, visual, real-time diff --git a/src/main/java/com/myDomain/myLibrary/RandomCircles.java b/src/main/java/com/myDomain/myLibrary/RandomCircles.java deleted file mode 100644 index 79e2f9e..0000000 --- a/src/main/java/com/myDomain/myLibrary/RandomCircles.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.myDomain.myLibrary; - -import processing.core.*; -import org.apache.commons.math3.random.*; - -/** - * This is an example class within library myLibrary. - * Make sure you rename this class as well as the name of the example package 'com.myDomain.myLibrary' - * to your own library naming convention. - */ - -public class RandomCircles { - - // parent is a reference to the parent sketch, and to Processing commands - PApplet parent; - - /** - * a Constructor, usually called in the setup() method in your sketch to - * initialize and start the Library. - * - * @param theParent the parent PApplet - */ - public RandomCircles(PApplet theParent) { - this.parent = theParent; - } - - - /** - * drawCircles draws circles - * where their location is according to the Sobol Sequence, - * a statistical method found in Apache Commons Math. - * - * @param numCircles the number of circles to draw - */ - public void drawCircles(int numCircles) { - RandomVectorGenerator generator = new SobolSequenceGenerator(2); - for(int i=0; i orientations; + + CameraManager() { + initializeOrientations(); + } + + void initializeOrientations() { + // Initialize different camera orientations, storing them in a list + orientations = new ArrayList<>(); + orientations.add(new CameraOrientation(0, 0, 0, 1, 0, 0, 0, -1, 0)); + orientations.add(new CameraOrientation(0, 0, 0, -1, 0, 0, 0, -1, 0)); + orientations.add(new CameraOrientation(0, 0, 0, 0, 1, 0, 0, 0, 1)); + orientations.add(new CameraOrientation(0, 0, 0, 0, -1, 0, 0, 0, -1)); + orientations.add(new CameraOrientation(0, 0, 0, 0, 0, 1, 0, -1, 0)); + orientations.add(new CameraOrientation(0, 0, 0, 0, 0, -1, 0, -1, 0)); + } + + CameraOrientation getOrientation(int index) { + return orientations.get(index); + } +} + diff --git a/src/main/java/com/victorvalentim/zividomelive/CameraOrientation.java b/src/main/java/com/victorvalentim/zividomelive/CameraOrientation.java new file mode 100644 index 0000000..e71afb3 --- /dev/null +++ b/src/main/java/com/victorvalentim/zividomelive/CameraOrientation.java @@ -0,0 +1,35 @@ +package com.victorvalentim.zividomelive; + +public class CameraOrientation { + float eyeX, eyeY, eyeZ; + float centerX, centerY, centerZ; + float upX, upY, upZ; + + /** + * Constructor for CameraOrientation. + * + * @param eyeX The x-coordinate of the camera eye position. + * @param eyeY The y-coordinate of the camera eye position. + * @param eyeZ The z-coordinate of the camera eye position. + * @param centerX The x-coordinate of the point the camera is looking at. + * @param centerY The y-coordinate of the point the camera is looking at. + * @param centerZ The z-coordinate of the point the camera is looking at. + * @param upX The x-component of the camera's up direction. + * @param upY The y-component of the camera's up direction. + * @param upZ The z-component of the camera's up direction. + */ + CameraOrientation(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ) { + // Set the position of the camera, the point it is looking at, and the up direction + this.eyeX = eyeX; + this.eyeY = eyeY; + this.eyeZ = eyeZ; + this.centerX = centerX; + this.centerY = centerY; + this.centerZ = centerZ; + this.upX = upX; + this.upY = upY; + this.upZ = upZ; + } +} diff --git a/src/main/java/com/victorvalentim/zividomelive/ControlManager.java b/src/main/java/com/victorvalentim/zividomelive/ControlManager.java new file mode 100644 index 0000000..ca16116 --- /dev/null +++ b/src/main/java/com/victorvalentim/zividomelive/ControlManager.java @@ -0,0 +1,324 @@ +package com.victorvalentim.zividomelive; + +import controlP5.*; +import processing.core.*; +import processing.event.KeyEvent; +import java.util.regex.Pattern; + +public class ControlManager { + + ControlP5 cp5; + boolean numberboxActive = false; + int baseResolution; + zividomelive parent; + CheckBox previewCheckbox; + CheckBox outputCheckbox; + DropdownList resolutionDropdown; + DropdownList viewModeDropdown; + Textlabel fpsLabel; + PApplet p; // Referência para PApplet + + // Construtor que recebe o PApplet e o objeto zividomelive + ControlManager(PApplet p, zividomelive parent, int baseResolution) { + this.p = p; // Passando a instância de PApplet diretamente + this.parent = parent; + this.baseResolution = baseResolution; + cp5 = new ControlP5(p); // Inicializando ControlP5 com PApplet + + // Inicializa todos os elementos de controle + initializeControls(); + addNumberboxesAndSliders(); + addButtonsAndCheckboxes(); + addDropdownLists(); + + // Reseta os controles ao iniciar + resetControls(); + } + + // Inicializa os controles básicos como o rótulo de FPS + private void initializeControls() { + int yOffset = 10; + // Adiciona o rótulo de FPS no topo + fpsLabel = cp5.addTextlabel("fpsLabel") + .setPosition(10, yOffset) + .setSize(200, 20) + .setText("FPS: 0"); + } + + // Adiciona caixas de número e sliders para controlar parâmetros como pitch, yaw, roll, etc. + private void addNumberboxesAndSliders() { + int yOffset = 40; + int controlSpacing = 30; + + // Adiciona controle de pitch + addNumberbox("pitch", 10, yOffset, -PApplet.PI, PApplet.PI, parent.getPitch()); + addSlider("pitch", 10 + 60, yOffset, -PApplet.PI, PApplet.PI, parent.getPitch()); + + // Adiciona controle de yaw + addNumberbox("yaw", 10, yOffset + controlSpacing, -PApplet.PI, PApplet.PI, parent.getYaw()); + addSlider("yaw", 10 + 60, yOffset + controlSpacing, -PApplet.PI, PApplet.PI, parent.getYaw()); + + // Adiciona controle de roll + addNumberbox("roll", 10, yOffset + 2 * controlSpacing, -PApplet.PI, PApplet.PI, parent.getRoll()); + addSlider("roll", 10 + 60, yOffset + 2 * controlSpacing, -PApplet.PI, PApplet.PI, parent.getRoll()); + + // Adiciona controle de field of view (fov) + addNumberbox("fov", 10, yOffset + 3 * controlSpacing, 0, 360, parent.getFov()); + addSlider("fov", 10 + 60, yOffset + 3 * controlSpacing, 0, 360, parent.getFov()); + + // Adiciona controle de tamanho do fisheye + addNumberbox("size", 10, yOffset + 4 * controlSpacing, 0, 100, parent.getFishSize()); + addSlider("size", 10 + 60, yOffset + 4 * controlSpacing, 0, 100, parent.getFishSize()); + } + + // Adiciona botões e checkboxes para resetar os controles e controlar preview/output + private void addButtonsAndCheckboxes() { + int yOffset = 190; + int controlSpacing = 30; + + // Adiciona botão para resetar os controles + cp5.addButton("resetControls") + .setPosition(10, yOffset) + .setSize(200, 20) + .setLabel("Resetar Controles") + .onClick(event -> parent.resetControls()); + + // Adiciona checkbox para o preview + previewCheckbox = cp5.addCheckBox("previewCheckbox") + .setPosition(10, yOffset + controlSpacing) + .setSize(20, 20) + .addItem("Visualizar Domemaster", 0) + .setValue(parent.isShowPreview() ? 1 : 0); + + // Adiciona checkbox para habilitar o output + outputCheckbox = cp5.addCheckBox("outputCheckbox") + .setPosition(10, yOffset + 2 * controlSpacing) + .setSize(20, 20) + .addItem("Habilitar Saída", 0) + .setValue(parent.isEnableOutput() ? 1 : 0); + } + + // Adiciona listas dropdown para selecionar resolução e modo de visualização + private void addDropdownLists() { + int yOffset = 280; + int controlSpacing = 30; + + // Adiciona dropdown para seleção de modo de visualização + addViewModeDropdown(yOffset); + // Adiciona dropdown para seleção de resolução + addResolutionDropdown(yOffset + controlSpacing); + } + + // Adiciona um Numberbox para o controle de valores + private void addNumberbox(String name, float x, float y, float min, float max, float value) { + Numberbox numberbox = cp5.addNumberbox(name + "Value") + .setPosition(x, y) + .setSize(50, 20) + .setRange(min, max) + .setScrollSensitivity(0.1f) + .setValue(value) + .onChange(event -> { + if (!numberboxActive) { + numberboxActive = true; + setParentValue(name, event.getController().getValue()); + cp5.getController(name).setValue(event.getController().getValue()); + numberboxActive = false; + } + }); + numberbox.setLabelVisible(false); + numberbox.getCaptionLabel().setVisible(false); + makeEditable(numberbox); + } + + // Adiciona um Slider para controle contínuo de valores + private void addSlider(String name, float x, float y, float min, float max, float value) { + cp5.addSlider(name) + .setPosition(x, y) + .setSize(140, 20) + .setRange(min, max) + .setValue(value) + .onChange(event -> { + if (!numberboxActive) { + numberboxActive = true; + setParentValue(name, event.getController().getValue()); + cp5.getController(name + "Value").setValue(event.getController().getValue()); + numberboxActive = false; + } + }); + } + + // Adiciona dropdown para seleção de resolução + void addResolutionDropdown(float y) { + resolutionDropdown = cp5.addDropdownList("Resolution") + .setPosition(10, y) + .setSize(200, 200) + .setBarHeight(20) + .setItemHeight(20) + .close(); + + // Adiciona opções de resolução ao dropdown + String[] resolutionLabels = {"1024", "2048", "3072", "4096"}; + for (int i = 0; i < resolutionLabels.length; i++) { + resolutionDropdown.addItem("Resolução " + (i + 1) + "k " + resolutionLabels[i], i); + } + + // Define ação quando uma resolução for selecionada + resolutionDropdown.onChange(event -> { + int selectedIndex = (int) event.getController().getValue(); + int newResolution = 1024 * (selectedIndex + 1); + parent.resetGraphics(newResolution); + }); + + resolutionDropdown.onClick(event -> resolutionDropdown.bringToFront()); + } + + // Adiciona dropdown para seleção do modo de visualização + void addViewModeDropdown(float y) { + viewModeDropdown = cp5.addDropdownList("View Mode") + .setPosition(10, y) + .setSize(200, 200) + .setItemHeight(20) + .setBarHeight(20) + .close(); + + // Adiciona opções de modo de visualização ao dropdown + String[] viewModes = {"Fisheye Domemaster", "Equirectangular", "Cubemap Skybox", "Standard"}; + for (String viewMode : viewModes) { + viewModeDropdown.addItem(viewMode, viewModeDropdown.getItems().size()); + } + + // Define ação quando um modo de visualização for selecionado + viewModeDropdown.onChange(event -> { + int selectedIndex = (int) event.getController().getValue(); + parent.setCurrentView(zividomelive.ViewType.values()[selectedIndex]); + }); + + viewModeDropdown.onClick(event -> viewModeDropdown.bringToFront()); + } + + // Reseta todos os controles para seus valores padrões + void resetControls() { + String[] controlNames = {"pitch", "yaw", "roll", "fov", "size"}; + float[] defaultValues = {0.0f, 0.0f, 0.0f, 210.0f, 100.0f}; + + for (int i = 0; i < controlNames.length; i++) { + cp5.getController(controlNames[i]).setValue(defaultValues[i]); + } + } + + // Exibe o painel de controle + void show() { + cp5.show(); + } + + // Esconde o painel de controle + void hide() { + cp5.hide(); + } + + // Torna um Numberbox editável ao lidar com entrada de teclas + void makeEditable(Numberbox n) { + final NumberboxInput nin = new NumberboxInput(n, p); // Passando o PApplet diretamente + n.onClick(theEvent -> { + nin.setActive(true); + numberboxActive = true; + }).onLeave(theEvent -> { + nin.setActive(false); + numberboxActive = false; + nin.submit(); + }); + } + + // Atualiza o valor do objeto pai baseado no nome do controle + private void setParentValue(String name, float value) { + switch (name) { + case "pitch": + parent.setPitch(value); + break; + case "yaw": + parent.setYaw(value); + break; + case "roll": + parent.setRoll(value); + break; + case "fov": + parent.setFov(value); + break; + case "size": + parent.setFishSize(value); + parent.getFisheyeDomemaster().setSizePercentage(value); + break; + } + } + + // Classe interna para tornar o Numberbox editável + public class NumberboxInput { + String text = ""; + Numberbox n; + boolean active; + PApplet p; // Referência ao PApplet + + NumberboxInput(Numberbox theNumberbox, PApplet p) { + n = theNumberbox; + this.p = p; // Passando o PApplet diretamente + p.registerMethod("keyEvent", this); // Usando PApplet para registrar eventos + } + + public void keyEvent(KeyEvent k) { + if (k.getAction() == KeyEvent.PRESS && active) { + if (k.getKey() == '\n') { + submit(); + } else if (k.getKeyCode() == KeyEvent.ALT) { + text = text.isEmpty() ? "" : text.substring(0, text.length() - 1); + } else if (k.getKey() == '-' && text.isEmpty()) { + text += k.getKey(); + } else if (k.getKey() < 255) { + final String regex = "-?\\d*(\\.\\d{0,2})?"; + String s = text + k.getKey(); + if (Pattern.matches(regex, s)) { + text += k.getKey(); + } + } + n.getValueLabel().setText(this.text); + } + } + + public void setActive(boolean b) { + active = b; + if (active) { + n.getValueLabel().setText(""); + text = ""; + } + } + + public void submit() { + if (!text.isEmpty()) { + n.setValue(Float.parseFloat(text)); + setParentValue(n.getName().replace("Value", ""), n.getValue()); + cp5.getController(n.getName().replace("Value", "")).setValue(n.getValue()); + text = ""; + } else { + n.getValueLabel().setText("" + n.getValue()); + } + } + } + + // Lida com eventos de controle + public void handleEvent(ControlEvent theEvent) { + if (theEvent.isFrom(previewCheckbox)) { + parent.setShowPreview(!parent.isShowPreview()); + } else if (theEvent.isFrom(outputCheckbox)) { + parent.setEnableOutput(!parent.isEnableOutput()); + } + } + + // Retorna se a caixa de número está ativa + public boolean isNumberboxActive() { + return numberboxActive; + } + + // Método para atualizar o rótulo de FPS + public void updateFpsLabel(float frameRate) { + fpsLabel.setText("FPS: " + PApplet.nf(frameRate, 0, 1)); // Atualiza o rótulo de FPS + } +} diff --git a/src/main/java/com/victorvalentim/zividomelive/CubemapRenderer.java b/src/main/java/com/victorvalentim/zividomelive/CubemapRenderer.java new file mode 100644 index 0000000..a5faa2c --- /dev/null +++ b/src/main/java/com/victorvalentim/zividomelive/CubemapRenderer.java @@ -0,0 +1,62 @@ +package com.victorvalentim.zividomelive; + +import processing.core.*; + +public class CubemapRenderer { + private PGraphics[] cubemapFaces; + private int resolution; + private PApplet parent; + + CubemapRenderer(int initialResolution, PApplet parent) { + this.parent = parent; + this.resolution = initialResolution; + initializeCubemapFaces(); + } + + // Inicializa ou reinicializa as faces do cubemap com a resolução atual + private void initializeCubemapFaces() { + cubemapFaces = new PGraphics[6]; + for (int i = 0; i < 6; i++) { + cubemapFaces[i] = parent.createGraphics(resolution / 2, resolution / 2, PApplet.P3D); // Resolução ajustada + } + } + + // Atualiza a resolução do cubemap e reinicializa as faces + void updateResolution(int newResolution) { + this.resolution = newResolution; + initializeCubemapFaces(); + } + + // Configura a câmera para uma face específica do cubemap + private void configureCameraForFace(PGraphics pg, CameraOrientation orientation, float pitch, float yaw, float roll) { + PVector eye = new PVector(0, 0, 0); + pg.camera(eye.x, eye.y, eye.z, orientation.centerX, orientation.centerY, orientation.centerZ, orientation.upX, orientation.upY, orientation.upZ); + pg.perspective(PApplet.PI / 2, 1, 0.1f, 20000); + + // Aplica pitch, yaw e roll + pg.translate(pg.width / 2, pg.height / 2, 0); + pg.rotateX(pitch); + pg.rotateY(roll); + pg.rotateZ(yaw); + pg.translate(-pg.width / 2, -pg.height / 2, 0); + } + + // Captura o cubemap aplicando as transformações de câmera e renderizando a cena + void captureCubemap(float pitch, float yaw, float roll, CameraManager cameraManager, Scene currentScene) { + for (int i = 0; i < 6; i++) { + cubemapFaces[i].beginDraw(); + configureCameraForFace(cubemapFaces[i], cameraManager.getOrientation(i), pitch, yaw, roll); + + // Chama a renderização da cena diretamente a partir da interface Scene + if (currentScene != null) { + currentScene.sceneRender(cubemapFaces[i]); + } + cubemapFaces[i].endDraw(); + } + } + + // Retorna as faces do cubemap para outros renderizadores + PGraphics[] getCubemapFaces() { + return cubemapFaces; + } +} diff --git a/src/main/java/com/victorvalentim/zividomelive/CubemapViewRenderer.java b/src/main/java/com/victorvalentim/zividomelive/CubemapViewRenderer.java new file mode 100644 index 0000000..04c312d --- /dev/null +++ b/src/main/java/com/victorvalentim/zividomelive/CubemapViewRenderer.java @@ -0,0 +1,75 @@ +package com.victorvalentim.zividomelive; + +import processing.core.*; + +public class CubemapViewRenderer { + private int resolution; + private PGraphics cubemap; + private int[] faceRotations = {2, 2, 2, 2, 2, 2}; // Rotação em 90 graus: 0, 1, 2, 3 para 0, 90, 180, 270 graus + private boolean[] faceInversions = {true, true, true, true, true, true}; // Inversão horizontal + private PApplet parent; + + // Construtor + CubemapViewRenderer(PApplet parent, int resolution) { + this.parent = parent; + this.resolution = resolution; + initializeCubemap(); + } + + // Inicializa ou reinicializa o PGraphics do cubemap + private void initializeCubemap() { + cubemap = parent.createGraphics(resolution * 2, resolution * 3 / 2, PApplet.P2D); // Ajusta para um layout em "cruz" ou cubemap + } + + // Atualiza a resolução do cubemap + void updateResolution(int newResolution) { + this.resolution = newResolution; + initializeCubemap(); + } + + // Retorna o PGraphics do cubemap + PGraphics getCubemap() { + return cubemap; + } + + // Método para desenhar as faces do cubemap no gráfico + void drawCubemapToGraphics(PGraphics[] cubemapFaces) { + if (cubemapFaces == null || cubemapFaces.length != 6) { + System.out.println("Erro: cubemapFaces inválido."); + return; + } + + cubemap.beginDraw(); + cubemap.background(0,0); // Fundo do cubemap + + // Desenhar as faces do cubemap em cruz horizontal (4:3) + applyTransformations(cubemap, cubemapFaces[3], resolution / 2, 0, resolution / 2, resolution / 2, faceRotations[3], faceInversions[3]); // Top + applyTransformations(cubemap, cubemapFaces[1], 0, resolution / 2, resolution / 2, resolution / 2, faceRotations[0], faceInversions[0]); // Right + applyTransformations(cubemap, cubemapFaces[4], resolution / 2, resolution / 2, resolution / 2, resolution / 2, faceRotations[4], faceInversions[4]); // Front + applyTransformations(cubemap, cubemapFaces[0], resolution, resolution / 2, resolution / 2, resolution / 2, faceRotations[1], faceInversions[1]); // Left + applyTransformations(cubemap, cubemapFaces[5], resolution * 3 / 2, resolution / 2, resolution / 2, resolution / 2, faceRotations[5], faceInversions[5]); // Back + applyTransformations(cubemap, cubemapFaces[2], resolution / 2, resolution, resolution / 2, resolution / 2, faceRotations[2], faceInversions[2]); // Bottom + cubemap.endDraw(); + } + + // Aplica as transformações de rotação e inversão às faces do cubemap + void applyTransformations(PGraphics target, PGraphics face, float x, float y, float w, float h, int rotation, boolean invert) { + + target.pushMatrix(); + target.translate(x + w / 2, y + h / 2); + + for (int i = 0; i < rotation; i++) { + target.rotate(PApplet.HALF_PI); + } + + // Aplica a inversão se necessário + if (invert) { + target.scale(-1, 1); + } + + target.imageMode(PApplet.CENTER); + target.image(face, 0, 0, w, h); + target.popMatrix(); + + } +} diff --git a/src/main/java/com/victorvalentim/zividomelive/EquirectangularRenderer.java b/src/main/java/com/victorvalentim/zividomelive/EquirectangularRenderer.java new file mode 100644 index 0000000..9e368b8 --- /dev/null +++ b/src/main/java/com/victorvalentim/zividomelive/EquirectangularRenderer.java @@ -0,0 +1,33 @@ +package com.victorvalentim.zividomelive; + +import processing.core.*; +import processing.opengl.*; + +public class EquirectangularRenderer { + private PGraphics equirectangular; + private PShader equirectangularShader; + EquirectangularRenderer(int resolution, String shaderPath, PApplet parent) { + equirectangular = parent.createGraphics(resolution * 2, resolution, PApplet.P2D); + equirectangular.smooth(4); + equirectangularShader = parent.loadShader(shaderPath); + } + + void render(PGraphics[] faces) { + equirectangular.beginDraw(); + equirectangular.background(0, 0); + equirectangularShader.set("posX", faces[0]); + equirectangularShader.set("negX", faces[1]); + equirectangularShader.set("posY", faces[2]); + equirectangularShader.set("negY", faces[3]); + equirectangularShader.set("posZ", faces[4]); + equirectangularShader.set("negZ", faces[5]); + equirectangularShader.set("resolution", new float[]{equirectangular.width, equirectangular.height}); + equirectangular.shader(equirectangularShader); + equirectangular.rect(0, 0, equirectangular.width, equirectangular.height); + equirectangular.endDraw(); + } + + public PGraphics getEquirectangular() { + return equirectangular; + } +} diff --git a/src/main/java/com/victorvalentim/zividomelive/FisheyeDomemaster.java b/src/main/java/com/victorvalentim/zividomelive/FisheyeDomemaster.java new file mode 100644 index 0000000..9c0f9a8 --- /dev/null +++ b/src/main/java/com/victorvalentim/zividomelive/FisheyeDomemaster.java @@ -0,0 +1,68 @@ +package com.victorvalentim.zividomelive; + +import processing.core.*; +import processing.opengl.*; + +public class FisheyeDomemaster { + PGraphics domemaster, domemasterSize; + PShader domemasterShader; + int resolution; + float sizePercentage; // Variável para armazenar o tamanho em porcentagem + PApplet parent; // Reference to PApplet for shader use + + // Constructor with PApplet parent passed + FisheyeDomemaster(int resolution, String shaderPath, PApplet parent) { + this.resolution = resolution; + this.sizePercentage = 100.0f; // Inicializar com 100% + this.parent = parent; // Assign the parent PApplet + + domemaster = parent.createGraphics(resolution, resolution, PApplet.P2D); + domemaster.smooth(4); + domemasterSize = parent.createGraphics(resolution, resolution, PApplet.P2D); + domemasterSize.smooth(4); + domemasterShader = parent.loadShader(shaderPath); // Use parent to load shader + } + + // Method to set the field of view (fov) from the main class + void setFOV(float fov) { + domemasterShader.set("fov", fov); + } + + // Ajusta o percentual de tamanho + public void setSizePercentage(float percentage) { + sizePercentage = PApplet.constrain(percentage, 0, 100); // Garante que a porcentagem esteja entre 0 e 100 + } + + // Apply shader with the equirectangular map + void applyShader(PGraphics equirectangular, float fov) { + // Certifique-se de que o equirectangular não é nulo + if (equirectangular == null) { + System.out.println("Equirectangular PGraphics é nulo."); + return; + } + + // Atualiza o fov + setFOV(fov); + + // Renderiza o domemaster com o shader aplicado + domemaster.beginDraw(); + domemaster.background(0, 0); // Fundo transparente + domemasterShader.set("equirectangularMap", equirectangular); + domemasterShader.set("resolution", new float[]{domemaster.width, domemaster.height}); + domemaster.shader(domemasterShader); + domemaster.rect(0, 0, domemaster.width, domemaster.height); + domemaster.endDraw(); + + // Ajusta o tamanho do domemaster para o percentual configurado + float adjustedSize = resolution * (sizePercentage / 100.0f); + domemasterSize.beginDraw(); + domemasterSize.background(0, 0); // Fundo transparente + domemasterSize.image(domemaster, (domemasterSize.width - adjustedSize) / 2, (domemasterSize.height - adjustedSize) / 2, adjustedSize, adjustedSize); + domemasterSize.endDraw(); + } + + // Getter for domemaster graphics + public PGraphics getDomemasterGraphics() { + return domemasterSize; + } +} diff --git a/src/main/java/com/victorvalentim/zividomelive/MouseControlledCamera.java b/src/main/java/com/victorvalentim/zividomelive/MouseControlledCamera.java new file mode 100644 index 0000000..a43c3e4 --- /dev/null +++ b/src/main/java/com/victorvalentim/zividomelive/MouseControlledCamera.java @@ -0,0 +1,85 @@ +package com.victorvalentim.zividomelive; + +import processing.core.*; +import processing.event.MouseEvent; + +public class MouseControlledCamera { + PVector position; // Position of the camera + PVector center; // The point the camera is looking at + PVector up; // The up direction of the camera + float sensitivity = 0.005f; // Sensitivity for mouse movements + float zoomSensitivity = 10; // Sensitivity for zoom actions + float distance = 1500; // Initial distance from the center point + float angleX = PConstants.PI / 2; // Horizontal rotation angle + float angleY = 0; // Vertical rotation angle + boolean dragging = false; // Flag to indicate if the mouse is being dragged + float minDistance = 100; // Minimum zoom distance + float maxDistance = 5000; // Maximum zoom distance + + MouseControlledCamera() { + // Initialize the position, center, and up vector + position = new PVector(0, 0, distance); + center = new PVector(0, 0, 0); + up = new PVector(0, 1, 0); + } + + void apply(PGraphics pg) { + // Set the camera view for the given PGraphics object + pg.camera(position.x, position.y, position.z, center.x, center.y, center.z, up.x, up.y, up.z); + } + + void update(PApplet p) { + // Update the camera's position based on mouse input + if (isLeftMousePressed(p)) { + if (!dragging) { + dragging = true; + return; + } + // Calculate the change in angles based on mouse movement + float dx = (p.mouseX - p.pmouseX) * sensitivity; + float dy = (p.mouseY - p.pmouseY) * sensitivity; + angleX += dx; + angleY += dy; + } else { + dragging = false; + } + + // Update the camera's position based on the angles + float cosAngleY = PApplet.cos(angleY); + position.x = center.x + distance * PApplet.cos(angleX) * cosAngleY; + position.y = center.y + distance * PApplet.sin(angleY); + position.z = center.z + distance * PApplet.sin(angleX) * cosAngleY; + } + + void zoom(float delta) { + // Adjust the distance for zooming, constrained between min and max distances + distance -= delta * zoomSensitivity; + distance = PApplet.constrain(distance, minDistance, maxDistance); + } + + void mouseWheel(MouseEvent event) { + // Handle zooming using the mouse wheel + float e = event.getCount(); + zoom(e); + } + + void mouseDragged(PApplet p) { + // Update the camera angles when the mouse is dragged + if (isLeftMousePressed(p)) { + float dx = (p.mouseX - p.pmouseX) * sensitivity; + float dy = (p.mouseY - p.pmouseY) * sensitivity; + angleX += dx; + angleY += dy; + } + } + + void mouseReleased() { + // Reset dragging flag when the mouse is released + dragging = false; + } + + private boolean isLeftMousePressed(PApplet p) { + // Check if the left mouse button is pressed + return p.mousePressed && p.mouseButton == PConstants.LEFT; + } +} diff --git a/src/main/java/com/victorvalentim/zividomelive/Scene.java b/src/main/java/com/victorvalentim/zividomelive/Scene.java new file mode 100644 index 0000000..d72f408 --- /dev/null +++ b/src/main/java/com/victorvalentim/zividomelive/Scene.java @@ -0,0 +1,9 @@ +package com.victorvalentim.zividomelive; + +import processing.core.PGraphics; + +public interface Scene { + void setupScene(); // Método para configurar a cena + void sceneRender(PGraphics pg); // Método para desenhar a cena + void keyPressed(char key); // Método para tratar eventos de teclado +} diff --git a/src/main/java/com/victorvalentim/zividomelive/StandardRenderer.java b/src/main/java/com/victorvalentim/zividomelive/StandardRenderer.java new file mode 100644 index 0000000..a6c62a4 --- /dev/null +++ b/src/main/java/com/victorvalentim/zividomelive/StandardRenderer.java @@ -0,0 +1,49 @@ +package com.victorvalentim.zividomelive; + +import processing.core.*; + +public class StandardRenderer { + private PGraphics standardView; + private Scene currentScene; // Substituímos DrawScene por Scene + private MouseControlledCamera cam; + private PApplet parent; + + // Construtor da classe StandardRenderer + StandardRenderer(PApplet parent, int width, int height, Scene currentScene) { + this.parent = parent; + this.currentScene = currentScene; // Agora recebemos a cena diretamente + standardView = parent.createGraphics(width, height, PApplet.P3D); // Criação do PGraphics para a visualização padrão + setCam(new MouseControlledCamera()); // Inicializa a câmera + } + + // Método de renderização + void render() { + getCam().update(parent); // Atualiza a câmera antes de desenhar + + standardView.beginDraw(); // Inicia o desenho no PGraphics + standardView.background(0, 0); // Define o fundo da visualização + + // Aplica configurações da MouseControlledCamera ao PGraphics + getCam().apply(standardView); + + // Chama a função de renderização da cena usando a interface Scene + currentScene.sceneRender(standardView); + + standardView.endDraw(); // Finaliza o desenho no PGraphics + } + + // Retorna o PGraphics da visualização padrão + PGraphics getStandardView() { + return standardView; + } + + // Retorna a instância da câmera controlada pelo mouse + public MouseControlledCamera getCam() { + return cam; + } + + // Define uma nova instância da câmera controlada pelo mouse + public void setCam(MouseControlledCamera cam) { + this.cam = cam; + } +} diff --git a/src/main/java/com/victorvalentim/zividomelive/zividomelive.java b/src/main/java/com/victorvalentim/zividomelive/zividomelive.java new file mode 100644 index 0000000..42e0aef --- /dev/null +++ b/src/main/java/com/victorvalentim/zividomelive/zividomelive.java @@ -0,0 +1,490 @@ +package com.victorvalentim.zividomelive; + +import processing.core.*; +import processing.event.*; +import processing.opengl.*; +import controlP5.*; +import codeanticode.syphon.*; +import spout.*; + +public class zividomelive { + + private PApplet p; // Referência para a instância PApplet + private boolean initialized = false; // Flag para verificar a inicialização + + private Scene currentScene; // Interface para a cena personalizada + + // Variáveis globais principais + private float pitch = 0.0f, yaw = 0.0f, roll = 0.0f, fov = 210.0f, fishSize = 100.0f; + private int resolution = 1024; + private boolean showControlPanel = true; + private boolean showPreview = false; + private boolean enableOutput = false; + + // Gerenciadores e renderizadores + private ControlManager controlManager; + private CubemapRenderer cubemapRenderer; + private EquirectangularRenderer equirectangularRenderer; + private StandardRenderer standardRenderer; + private FisheyeDomemaster fisheyeDomemaster; + private CameraManager cameraManager; + private CubemapViewRenderer cubemapViewRenderer; + + private SyphonServer syphonServer; + private Spout spout; + + // Enum para tipos de visualização + public enum ViewType { + FISHEYE_DOMEMASTER, EQUIRECTANGULAR, CUBEMAP, STANDARD + } + + private ViewType currentView = ViewType.FISHEYE_DOMEMASTER; + private boolean pendingReset = false; + private int pendingResolution = resolution; + + // Construtor + public zividomelive(PApplet p) { + if (p == null) { + throw new IllegalArgumentException("A instância PApplet não pode ser nula."); + } + this.p = p; + welcome(); + } + + private void welcome() { + System.out.println("Biblioteca ziviDomeLive inicializada."); + } + + // Método para definir a cena atual + public void setScene(Scene scene) { + this.currentScene = scene; // Define a cena atual + currentScene.setupScene(); // Chama o setup da cena ao definir + } + + public void setup() { + if (p == null) { + throw new IllegalStateException("A instância PApplet não está configurada corretamente."); + } + + System.out.println("Iniciando setup..."); + + try { + p.frameRate(64); + System.out.println("Taxa de quadros definida para 64."); + } catch (Exception e) { + System.out.println("Erro ao definir a taxa de quadros: " + e.getMessage()); + } + + try { + printlnOpenGLInfo(); + } catch (Exception e) { + System.out.println("Erro ao imprimir informações do OpenGL: " + e.getMessage()); + } + + try { + setupHints(); + System.out.println("Dicas de textura configuradas."); + } catch (Exception e) { + System.out.println("Erro ao configurar dicas de textura: " + e.getMessage()); + } + + p.registerMethod("post", this); + + try { + setupSyphonOrSpout(); + System.out.println("Configuração do Syphon/Spout concluída."); + } catch (Exception e) { + System.out.println("Erro ao configurar Syphon/Spout: " + e.getMessage()); + } + + try { + registerMouseEvents(); + System.out.println("Eventos de mouse registrados."); + } catch (Exception e) { + System.out.println("Erro ao registrar eventos de mouse: " + e.getMessage()); + } + + System.out.println("Setup concluído."); + } + + void printlnOpenGLInfo() { + PApplet.println(PGraphicsOpenGL.OPENGL_VERSION); + PApplet.println(PGraphicsOpenGL.OPENGL_VENDOR); + PApplet.println(PGraphicsOpenGL.OPENGL_RENDERER); + } + + void setupHints() { + p.textureMode(PConstants.NORMAL); + p.textureWrap(PConstants.REPEAT); + p.hint(PConstants.DISABLE_OPENGL_ERRORS); + p.hint(PConstants.ENABLE_TEXTURE_MIPMAPS); + } + + public void post() { + if (!initialized) { + initializeManagers(); + initialized = true; + p.unregisterMethod("post", this); + } + } + + void initializeManagers() { + try { + System.out.println("Inicializando gerenciadores..."); + + cameraManager = new CameraManager(); + System.out.println("CameraManager inicializado."); + + initializeRenderers(); + controlManager = new ControlManager(p, this, resolution); + System.out.println("ControlManager inicializado."); + System.out.println("Gerenciadores inicializados com sucesso."); + } catch (Exception e) { + System.out.println("Erro ao inicializar gerenciadores: " + e.getMessage()); + e.printStackTrace(); + } + } + + void initializeRenderers() { + try { + System.out.println("Inicializando renderizadores..."); + + cubemapRenderer = new CubemapRenderer(resolution, p); + System.out.println("CubemapRenderer inicializado: " + (cubemapRenderer != null)); + + equirectangularRenderer = new EquirectangularRenderer(resolution, "equirectangular.glsl", p); + System.out.println("EquirectangularRenderer inicializado: " + (equirectangularRenderer != null)); + + standardRenderer = new StandardRenderer(p, p.width, p.height, currentScene); + System.out.println("StandardRenderer inicializado: " + (standardRenderer != null)); + + fisheyeDomemaster = new FisheyeDomemaster(resolution, "domemaster.glsl", p); + System.out.println("FisheyeDomemaster inicializado: " + (fisheyeDomemaster != null)); + + cubemapViewRenderer = new CubemapViewRenderer(p, resolution); + System.out.println("CubemapViewRenderer inicializado: " + (cubemapViewRenderer != null)); + + System.out.println("Renderizadores inicializados com sucesso."); + } catch (Exception e) { + System.out.println("Erro ao inicializar renderizadores: " + e.getMessage()); + e.printStackTrace(); + } + } + + void setupSyphonOrSpout() { + try { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("mac")) { + syphonServer = new SyphonServer(p, "ziviDomeLive Syphon"); + System.out.println("SyphonServer inicializado para macOS."); + } else if (os.contains("win")) { + spout = new Spout(p); + System.out.println("Spout inicializado para Windows."); + } + } catch (Exception e) { + System.out.println("Erro ao configurar Syphon/Spout: " + e.getMessage()); + } + } + + void registerMouseEvents() { + try { + p.registerMethod("mouseEvent", this); + System.out.println("Eventos de mouse registrados."); + } catch (Exception e) { + System.out.println("Erro ao registrar eventos de mouse: " + e.getMessage()); + } + } + + // Função principal de desenho + public void draw() { + // Verificação dos renderizadores e cena antes de iniciar a renderização + if (cubemapRenderer == null) { + System.out.println("Erro: CubemapRenderer não inicializado."); + return; + } + + if (equirectangularRenderer == null) { + System.out.println("Erro: EquirectangularRenderer não inicializado."); + return; + } + + if (fisheyeDomemaster == null) { + System.out.println("Erro: FisheyeDomemaster não inicializado."); + return; + } + + if (standardRenderer == null) { + System.out.println("Erro: StandardRenderer não inicializado."); + return; + } + + if (currentScene == null) { + System.out.println("Erro: currentScene não inicializado."); + return; + } + + // Depuração da etapa de reset gráfico + //System.out.println("Iniciando renderização..."); + + clearBackground(); // Limpar e configurar o fundo + handleGraphicsReset(); // Verificar se há necessidade de redefinir gráficos + + // Renderiza o cubemap + captureCubemap(); // Função para capturar cubemap + + // Depuração de qual visualização está sendo renderizada + //System.out.println("Modo de visualização atual: " + getCurrentView()); + + renderView(); + + // Desenhar o preview flutuante se estiver ativo + if (showPreview) { + drawFloatingPreview(); + } + + // Enviar saída para Syphon/Spout se habilitado + sendOutput(); // Função para enviar imagem para Syphon/Spout + + // Desenha painel de controle e visualizações, se necessário + drawControlPanel(); // Função para desenhar controles + } + + // Função para limpar o fundo da tela + private void clearBackground() { + //System.out.println("Limpando o fundo..."); + p.background(0, 0, 0, 0); // Define o fundo como preto com transparência + } + + // Função para verificar e aplicar resets gráficos + private void handleGraphicsReset() { + if (pendingReset) { + //System.out.println("Redefinindo gráficos com nova resolução: " + pendingResolution); + resolution = pendingResolution; + initializeRenderers(); // Reinicializa os renderizadores com a nova resolução + pendingReset = false; + } + } + + // Função para capturar o cubemap + private void captureCubemap() { + //System.out.println("Capturando cubemap..."); + if (cubemapRenderer != null) { + cubemapRenderer.captureCubemap(getPitch(), getYaw(), getRoll(), cameraManager, currentScene); + } else { + System.out.println("Erro: CubemapRenderer não inicializado."); + } + } + + void displayView(PGraphics pg) { + // Desenha a visualização na tela principal, ajustando o tamanho e centralizando + float aspectRatio = pg.width / (float) pg.height; + float displayWidth = p.width; + float displayHeight = p.width / aspectRatio; + + if (displayHeight > p.height) { + displayHeight = p.height; + displayWidth = p.height * aspectRatio; + } + + p.image(pg, (p.width - displayWidth) / 2, (p.height - displayHeight) / 2, displayWidth, displayHeight); + } + + private void updateRenderViews() { + // Renderiza sempre o equirectangular primeiro para garantir que esteja pronto para o fisheye + equirectangularRenderer.render(cubemapRenderer.getCubemapFaces()); + + // Aplica o shader Fisheye usando o equirectangular já renderizado + fisheyeDomemaster.applyShader(equirectangularRenderer.getEquirectangular(), getFov()); + + // Renderiza as outras vistas condicionalmente + switch (getCurrentView()) { + case CUBEMAP: + cubemapViewRenderer.drawCubemapToGraphics(cubemapRenderer.getCubemapFaces()); + break; + case STANDARD: + standardRenderer.render(); + break; + } + } + + private void displayCurrentView() { + // Exibir a visualização com base no modo atual + switch (getCurrentView()) { + case CUBEMAP: + displayView(cubemapViewRenderer.getCubemap()); + break; + case EQUIRECTANGULAR: + displayView(equirectangularRenderer.getEquirectangular()); + break; + case FISHEYE_DOMEMASTER: + displayView(fisheyeDomemaster.getDomemasterGraphics()); + break; + case STANDARD: + displayView(standardRenderer.getStandardView()); + break; + } + } + + private void renderView() { + updateRenderViews(); // Atualiza todas as renderizações necessárias + displayCurrentView(); // Exibe a vista conforme o modo selecionado + } + + // Função para enviar a saída via Syphon ou Spout + private void sendOutput() { + if (isEnableOutput()) { + if (syphonServer != null) { + syphonServer.sendImage(fisheyeDomemaster.getDomemasterGraphics()); + //System.out.println("Imagem enviada para Syphon."); + } else if (spout != null) { + spout.sendTexture(fisheyeDomemaster.getDomemasterGraphics()); + //System.out.println("Textura enviada para Spout."); + } + } + } + + // Função para desenhar o painel de controle + private void drawControlPanel() { + //System.out.println("Desenhando painel de controle..."); + p.hint(PConstants.DISABLE_DEPTH_TEST); + // Atualizar informações de controle e FPS + controlManager.updateFpsLabel(p.frameRate); + + if (showControlPanel) { + controlManager.show(); // Mostra os controles + } else { + controlManager.hide(); // Esconde os controles + } + p.hint(PConstants.ENABLE_DEPTH_TEST); + } + + public void drawFloatingPreview() { + // Definir o tamanho da pré-visualização flutuante + float previewWidth = 200f; + float previewHeight = 200f; + float x = p.width - previewWidth; // Usar 'p.width' se 'width' não for reconhecido diretamente + float y = p.height - previewHeight; // Usar 'p.height' se 'height' não for reconhecido diretamente + + // Obter o gráfico a ser mostrado, neste caso, o domemaster do fisheye + PGraphics previewGraphics = fisheyeDomemaster.getDomemasterGraphics(); + + // Desenhar a pré-visualização flutuante + p.image(previewGraphics, x, y, previewWidth, previewHeight); + } + + public void mouseEvent(MouseEvent event) { + if (event.getAction() == MouseEvent.WHEEL) { + standardRenderer.getCam().mouseWheel(event); + } + } + + public void keyPressed() { + if (!controlManager.isNumberboxActive()) { + if (p.key == 'h') { + showControlPanel = !showControlPanel; + System.out.println("Alternando visibilidade do painel de controle: " + showControlPanel); + } + if (p.key == 'm') { + setCurrentView(ViewType.values()[(getCurrentView().ordinal() + 1) % ViewType.values().length]); + System.out.println("Alternando visualização para: " + getCurrentView()); + } + } + } + + public void controlEvent(ControlEvent theEvent) { + if (controlManager != null) { + controlManager.handleEvent(theEvent); + } + } + + // Getters e setters para variáveis globais + + public float getFishSize() { + return fishSize; + } + + public void setFishSize(float fishSize) { + this.fishSize = fishSize; + } + + public float getFov() { + return fov; + } + + public void setFov(float fov) { + this.fov = fov; + } + + public float getPitch() { + return pitch; + } + + public void setPitch(float pitch) { + this.pitch = pitch; + } + + public float getYaw() { + return yaw; + } + + public void setYaw(float yaw) { + this.yaw = yaw; + } + + public float getRoll() { + return roll; + } + + public void setRoll(float roll) { + this.roll = roll; + } + + public ViewType getCurrentView() { + return currentView; + } + + public void setCurrentView(ViewType currentView) { + this.currentView = currentView; + } + + public boolean isEnableOutput() { + return enableOutput; + } + + public void setEnableOutput(boolean enableOutput) { + this.enableOutput = enableOutput; + } + + public boolean isShowPreview() { + return showPreview; + } + + public void setShowPreview(boolean showPreview) { + this.showPreview = showPreview; + } + + public void resetControls() { + controlManager.resetControls(); + } + + public void resetGraphics(int newResolution) { + pendingReset = true; + pendingResolution = newResolution; + } + + public FisheyeDomemaster getFisheyeDomemaster() { + return fisheyeDomemaster; + } + + public void setFisheyeDomemaster(FisheyeDomemaster fisheyeDomemaster) { + this.fisheyeDomemaster = fisheyeDomemaster; + } + + public PApplet getPApplet() { + return p; + } + + public boolean isInitialized() { + return initialized; + } +}