Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions docs/SimplexTriangleEditor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# SimplexTriangleEditor

The `SimplexTriangleEditor` is a Jetpack Compose UI component that provides an interactive equilateral triangle for selecting barycentric weights. It is particularly useful for blending three different properties, such as colors, textures, or shader parameters.

## Features

- **Visual Interaction**: Drag or tap anywhere inside the triangle to move the selector handle.
- **Barycentric Mapping**: Automatically calculates three normalized weights ($a + b + c = 1.0$) based on the handle's position.
- **Responsive Layout**: Dynamically adjusts its geometry based on the available canvas size.
- **Visual Cues**: Colors the vertices (Red, Green, Blue) to represent the weight influence of each corner.

## Usage

### Data Model

The widget uses a simple data class to hold the weights:

```kotlin
data class SimplexWeights(val a: Float, val b: Float, val c: Float)
```

### Basic Example

```kotlin
@Composable
fun WeightSelectionScreen() {
var weights by remember {
mutableStateOf(SimplexWeights(0.333f, 0.333f, 0.334f))
}

SimplexTriangleEditor(
modifier = Modifier.size(300.dp),
initialWeights = weights,
onWeightsChanged = { newWeights ->
weights = newWeights
// Update your logic or native shaders here
println("Weights: A=${newWeights.a}, B=${newWeights.b}, C=${newWeights.c}")
}
)
}
```

## Parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `modifier` | `Modifier` | Standard Compose modifier for layout and sizing. |
| `initialWeights` | `SimplexWeights` | The starting position of the selector handle. Defaults to center. |
| `onWeightsChanged` | `(SimplexWeights) -> Unit` | Callback triggered whenever the handle is moved or tapped. |

## Implementation Details

The widget implements **Inverse Kinematics** to map 2D pixel offsets from the screen into normalized barycentric coordinates. It ensures that even if a user taps slightly outside the triangle bounds, the resulting weights are clamped and re-normalized to maintain a valid state for rendering engines.

---

*Part of the LearnGLES tutorial series.*
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ in vec2 TexCoords;
uniform sampler2D image;

uniform bool horizontal;
uniform float weight[5] = float[] (0.2270270270, 0.1945945946, 0.1216216216, 0.0540540541, 0.0162162162);
const float weight[5] = float[] (0.2270270270, 0.1945945946, 0.1216216216, 0.0540540541, 0.0162162162);

void main()
{
vec2 tex_offset = 1.0 / textureSize(image, 0); // gets size of single texel
vec2 tex_offset = 1.0 / vec2(textureSize(image, 0)); // gets size of single texel
vec3 result = texture(image, TexCoords).rgb * weight[0];
if(horizontal)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ void main()
// attenuation
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
ambient *= attenuation;
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
specular *= attenuation;

vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ float ShadowCalculation(vec4 fragPosLightSpace)
// float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
// PCF
float shadow = 0.0;
vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));
for(int x = -1; x <= 1; ++x)
{
for(int y = -1; y <= 1; ++y)
Expand All @@ -55,9 +55,9 @@ void main()
{
vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb;
vec3 normal = normalize(fs_in.Normal);
vec3 lightColor = vec3(0.3);
vec3 lightColor = vec3(1.0);
// ambient
vec3 ambient = 0.3 * lightColor;
vec3 ambient = 0.15 * lightColor;
// diffuse
vec3 lightDir = normalize(lightPos - fs_in.FragPos);
float diff = max(dot(lightDir, normal), 0.0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ float radius = 0.5;
float bias = 0.025;

// tile noise texture over screen based on screen dimensions divided by noise size
const vec2 noiseScale = vec2(800.0/4.0, 600.0/4.0);
uniform vec2 noiseScale;

uniform mat4 projection;

Expand Down Expand Up @@ -51,7 +51,7 @@ void main()
float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth));
occlusion += (sampleDepth >= samplePos.z + bias ? 1.0 : 0.0) * rangeCheck;
}
occlusion = 1.0 - (occlusion / kernelSize);
occlusion = 1.0 - occlusion / float(kernelSize);

FragColor = occlusion;
}
2 changes: 2 additions & 0 deletions tutorial/src/main/cpp/TargetCamera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ void TargetCamera::update() {
glm::to_string(m_up).c_str());

m_viewMatrix = glm::lookAt(m_position, m_targetPosition, m_up);
m_front = glm::normalize(m_targetPosition - m_position);
m_right = glm::normalize(glm::cross(m_front, m_up));
viewDirty = false;
}
if (projectionDirty) {
Expand Down
144 changes: 89 additions & 55 deletions tutorial/src/main/cpp/lighting/BasicLightingScene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
#include "Camera.h"
#include "TargetCamera.h"
#include "Texture.h"
#include "glerror.h"

BasicLightingScene::BasicLightingScene() {
m_lightPos = glm::vec3(1.2f, 1.0f, 2.0f);
}

void BasicLightingScene::init() {
Expand All @@ -26,45 +28,51 @@ void BasicLightingScene::init() {
// ------------------------------------------------------------------
float vertices[] = {
// positions // normals // texture coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,

// front face
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,

// back face
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,

// left face
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,

0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
// right face
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,

// bottom face
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,

// top face
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
Expand All @@ -76,11 +84,11 @@ void BasicLightingScene::init() {
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glBindVertexArray(m_cubeVAO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) 0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) (3 * sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) (6 * sizeof(float)));
glEnableVertexAttribArray(2);

// second, configure the light's VAO (VBO stays the same; the vertices are the same for the light object which is also a 3D cube)
Expand All @@ -89,7 +97,7 @@ void BasicLightingScene::init() {

glBindBuffer(GL_ARRAY_BUFFER, m_VBO);
// note that we update the lamp's position attribute's stride to reflect the updated buffer data
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) 0);
glEnableVertexAttribArray(0);

// load textures
Expand All @@ -102,6 +110,8 @@ void BasicLightingScene::init() {
m_pLightingShader->setInt("material.diffuse", 0);
m_pLightingShader->setInt("material.specular", 1);
m_pLightingShader->setInt("material.emission", 2);

check_gl_error();
}

void BasicLightingScene::resize(int width, int height) {
Expand All @@ -116,6 +126,10 @@ void BasicLightingScene::draw() {
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// view/projection transformations
glm::mat4 projection = m_camera->getProjectionMatrix();
glm::mat4 view = m_camera->getViewMatrix();

// be sure to activate shader when setting uniforms/drawing objects
if(m_pLightingShader)
{
Expand All @@ -124,16 +138,14 @@ void BasicLightingScene::draw() {
m_pLightingShader->setVec3("viewPos", m_camera->getPosition());

// light properties
m_pLightingShader->setVec3("light.ambient", 0.2f, 0.2f, 0.2f);
m_pLightingShader->setVec3("light.diffuse", 0.5f, 0.5f, 0.5f);
m_pLightingShader->setVec3("light.specular", 1.0f, 1.0f, 1.0f);
m_pLightingShader->setVec3("light.ambient", ambientFactor, ambientFactor, ambientFactor);
m_pLightingShader->setVec3("light.diffuse", diffuseFactor, diffuseFactor, diffuseFactor);
m_pLightingShader->setVec3("light.specular", specularFactor, specularFactor, specularFactor);

// material properties
m_pLightingShader->setFloat("material.shininess", 64.0f);
m_pLightingShader->setFloat("material.shininess", 64.f * 64.f);


// view/projection transformations
glm::mat4 projection = m_camera->getProjectionMatrix();
glm::mat4 view = m_camera->getViewMatrix();
m_pLightingShader->setMat4("projection", projection);
m_pLightingShader->setMat4("view", view);

Expand All @@ -154,39 +166,38 @@ void BasicLightingScene::draw() {
// render the cube
glBindVertexArray(m_cubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
}

// also draw the lamp object
if(m_pLightCubeShader)
{
m_pLightCubeShader->use();
m_pLightCubeShader->setMat4("projection", projection);
m_pLightCubeShader->setMat4("view", view);
model = glm::mat4(1.0f);
model = glm::translate(model, m_lightPos);
model = glm::scale(model, glm::vec3(0.2f)); // a smaller cube
m_pLightCubeShader->setMat4("model", model);

glBindVertexArray(m_lightCubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
// also draw the lamp object
if (m_pLightCubeShader) {
m_pLightCubeShader->use();
m_pLightCubeShader->setMat4("projection", projection);
m_pLightCubeShader->setMat4("view", view);
glm::mat model = glm::mat4(1.0f);
model = glm::translate(model, m_lightPos);
model = glm::scale(model, glm::vec3(0.2f)); // a smaller cube
m_pLightCubeShader->setMat4("model", model);

glBindVertexArray(m_lightCubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
check_gl_error();
}

void BasicLightingScene::destroy() {
glDeleteVertexArrays(1, &m_cubeVAO);
glDeleteVertexArrays(1, &m_lightCubeVAO);
glDeleteBuffers(1, &m_VBO);
if (m_pLightingShader)
{
if (m_pLightingShader) {
delete m_pLightingShader;
m_pLightingShader = nullptr;
}
if (m_pLightCubeShader)
{
if (m_pLightCubeShader) {
delete m_pLightCubeShader;
m_pLightCubeShader = nullptr;
}
delete m_camera;
check_gl_error();
}

BasicLightingScene::~BasicLightingScene() {
Expand All @@ -201,16 +212,39 @@ std::map<std::string, std::any> BasicLightingScene::propertyEvent(std::map<std::
parseTargetCameraEvent(map);
}
}

if (auto it = map.find("ambientFactor"); it != map.end()) {
if (it->second.type() == typeid(float)) {
ambientFactor = std::any_cast<float>(it->second);
} else if (it->second.type() == typeid(double)) {
ambientFactor = (float)std::any_cast<double>(it->second);
}
}
if (auto it = map.find("diffuseFactor"); it != map.end()) {
if (it->second.type() == typeid(float)) {
diffuseFactor = std::any_cast<float>(it->second);
} else if (it->second.type() == typeid(double)) {
diffuseFactor = (float)std::any_cast<double>(it->second);
}
}
if (auto it = map.find("specularFactor"); it != map.end()) {
if (it->second.type() == typeid(float)) {
specularFactor = std::any_cast<float>(it->second);
} else if (it->second.type() == typeid(double)) {
specularFactor = (float)std::any_cast<double>(it->second);
}
}

return {};
}

void BasicLightingScene::parseTargetCameraEvent(std::map<std::string, std::any> &event) {
auto* targetCamera = dynamic_cast<TargetCamera*>(m_camera);
auto *targetCamera = dynamic_cast<TargetCamera *>(m_camera);
if (!targetCamera) return;

if (auto it = event.find("single_touching"); it != event.end()) {
if (it->second.type() == typeid(std::vector<float>)) {
const auto& val = std::any_cast<const std::vector<float>&>(it->second);
const auto &val = std::any_cast<const std::vector<float> &>(it->second);
if (val.size() >= 4) {
targetCamera->onSingleTouching(glm::vec2(val[0], val[1]), glm::vec2(val[2], val[3]));
}
Expand All @@ -219,10 +253,10 @@ void BasicLightingScene::parseTargetCameraEvent(std::map<std::string, std::any>

if (auto it = event.find("double_touching"); it != event.end()) {
if (it->second.type() == typeid(std::vector<float>)) {
const auto& val = std::any_cast<const std::vector<float>&>(it->second);
const auto &val = std::any_cast<const std::vector<float> &>(it->second);
if (val.size() >= 8) {
targetCamera->onDoubleTouching(glm::vec2(val[0], val[1]), glm::vec2(val[2], val[3]),
glm::vec2(val[4], val[5]), glm::vec2(val[6], val[7]));
glm::vec2(val[4], val[5]), glm::vec2(val[6], val[7]));
}
}
}
Expand Down
Loading