Remove support for Java/desktop builds (#4263)
* Remove support for Java/desktop builds These builds are never tested nor used on our end. We cannot guarantee their proper support. It should also be possible for an app to handle this itself. * Remove Tungsten since it cannot be compiled anymore
This commit is contained in:
31
BUILDING.md
31
BUILDING.md
@@ -8,10 +8,6 @@ To build Filament, you must first install the following tools:
|
||||
- clang 7.0 (or more recent)
|
||||
- [ninja 1.10](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages) (or more recent)
|
||||
|
||||
To build the Java based components of the project you can optionally install (recommended):
|
||||
|
||||
- OpenJDK 1.8 (or more recent)
|
||||
|
||||
Additional dependencies may be required for your operating system. Please refer to the appropriate
|
||||
section below.
|
||||
|
||||
@@ -23,10 +19,8 @@ To build Filament for Android you must also install the following:
|
||||
|
||||
### Environment variables
|
||||
|
||||
Make sure the environment variable `ANDROID_HOME` points to the location of your Android SDK.
|
||||
|
||||
By default our build system will attempt to compile the Java bindings. To do so, the environment
|
||||
variable `JAVA_HOME` should point to the location of your JDK.
|
||||
To build Filament for Android, make sure the environment variable `ANDROID_HOME` points to the
|
||||
location of your Android SDK.
|
||||
|
||||
When building for WebGL, you'll also need to set `EMSDK`. See [WebAssembly](#webassembly).
|
||||
|
||||
@@ -65,23 +59,10 @@ To install the libraries and executables in `out/debug/` and `out/release/`, add
|
||||
You can force a clean build by adding the `-c` flag. The script offers more features described
|
||||
by executing `build.sh -h`.
|
||||
|
||||
### Disabling Java builds
|
||||
|
||||
By default our build system will attempt to compile the Java bindings. If you wish to skip this
|
||||
compilation step simply pass the `-j` flag to `build.sh`:
|
||||
|
||||
```
|
||||
$ ./build.sh -j release
|
||||
```
|
||||
|
||||
If you use CMake directly instead of the build script, pass `-DFILAMENT_ENABLE_JAVA=OFF`
|
||||
to CMake instead.
|
||||
|
||||
### Filament-specific CMake Options
|
||||
|
||||
The following CMake options are boolean options specific to Filament:
|
||||
|
||||
- `FILAMENT_ENABLE_JAVA`: Compile Java projects: requires a JDK and the JAVA_HOME env var
|
||||
- `FILAMENT_ENABLE_LTO`: Enable link-time optimizations if supported by the compiler
|
||||
- `FILAMENT_BUILD_FILAMAT`: Build filamat and JNI buildings
|
||||
- `FILAMENT_SUPPORTS_OPENGL`: Include the OpenGL backend
|
||||
@@ -163,14 +144,6 @@ make sure the command line tools are setup by running:
|
||||
$ xcode-select --install
|
||||
```
|
||||
|
||||
After installing Java 1.8 you must also ensure that your `JAVA_HOME` environment variable is
|
||||
properly set. If it doesn't already point to the appropriate JDK, you can simply add the following
|
||||
to your `.profile`:
|
||||
|
||||
```
|
||||
export JAVA_HOME="$(/usr/libexec/java_home)"
|
||||
```
|
||||
|
||||
Then run `cmake` and `ninja` to trigger a build:
|
||||
|
||||
```
|
||||
|
||||
@@ -11,8 +11,6 @@ project(TNT)
|
||||
# ==================================================================================================
|
||||
# Options
|
||||
# ==================================================================================================
|
||||
option(FILAMENT_ENABLE_JAVA "Compile Java projects, requires a JDK and the JAVA_HOME env var" ON)
|
||||
|
||||
option(FILAMENT_USE_EXTERNAL_GLES3 "Experimental: Compile Filament against OpenGL ES 3" OFF)
|
||||
|
||||
option(FILAMENT_USE_SWIFTSHADER "Compile Filament against SwiftShader" OFF)
|
||||
@@ -655,10 +653,6 @@ if (IS_HOST_PLATFORM)
|
||||
add_subdirectory(${LIBRARIES}/filamentapp)
|
||||
add_subdirectory(${LIBRARIES}/imageio)
|
||||
|
||||
add_subdirectory(${FILAMENT}/java/filamat)
|
||||
add_subdirectory(${FILAMENT}/java/filament)
|
||||
add_subdirectory(${FILAMENT}/java/gltfio)
|
||||
|
||||
add_subdirectory(${FILAMENT}/samples)
|
||||
|
||||
add_subdirectory(${EXTERNAL}/astcenc/tnt)
|
||||
|
||||
19
README.md
19
README.md
@@ -115,7 +115,7 @@ Here are a few screenshots of applications that use Filament in production:
|
||||
### APIs
|
||||
|
||||
- Native C++ API for Android, iOS, Linux, macOS and Windows
|
||||
- Java/JNI API for Android, Linux, macOS and Windows
|
||||
- Java/JNI API for Android
|
||||
- JavaScript API
|
||||
|
||||
### Backends
|
||||
@@ -265,20 +265,6 @@ For complete examples of Linux, macOS and Windows Filament applications, look at
|
||||
in the `samples/` directory. These samples are all based on `samples/app/` which contains the code
|
||||
that creates a native window with SDL2 and initializes the Filament engine, renderer and views.
|
||||
|
||||
### Java on Linux, macOS and Windows
|
||||
|
||||
After building Filament, you can use `filament-java.jar` and its companion `filament-jni` native
|
||||
library to use Filament in desktop Java applications.
|
||||
|
||||
You must always first initialize Filament by calling `Filament.init()`.
|
||||
|
||||
You can use Filament either with AWT or Swing, using respectively a `FilamentCanvas` or a
|
||||
`FilamentPanel`.
|
||||
|
||||
Following the steps above (how to use Filament from native code), create an `Engine` and a
|
||||
`Renderer`, but instead of calling `beginFrame` and `endFrame` on the renderer itself, call
|
||||
these methods on `FilamentCanvas` or `FilamentPanel`.
|
||||
|
||||
### Android
|
||||
|
||||
See `android/samples` for examples of how to use Filament on Android.
|
||||
@@ -330,9 +316,9 @@ and tools.
|
||||
- `docs`: Documentation
|
||||
- `math`: Mathematica notebooks used to explore BRDFs, equations, etc.
|
||||
- `filament`: Filament rendering engine (minimal dependencies)
|
||||
- `backend`: Rendering backends/drivers (Vulkan, Metal, OpenGL/ES)
|
||||
- `ide`: Configuration files for IDEs (CLion, etc.)
|
||||
- `ios`: Sample projects for iOS
|
||||
- `java`: Java bindings for Filament libraries
|
||||
- `libs`: Libraries
|
||||
- `bluegl`: OpenGL bindings for macOS, Linux and Windows
|
||||
- `bluevk`: Vulkan bindings for macOS, Linux, Windows and Android
|
||||
@@ -352,6 +338,7 @@ and tools.
|
||||
- `math`: Math library
|
||||
- `mathio`: Math types support for output streams
|
||||
- `utils`: Utility library (threads, memory, data structures, etc.)
|
||||
- `viewer`: glTF viewer library (requires gltfio)
|
||||
- `samples`: Sample desktop applications
|
||||
- `shaders`: Shaders used by `filamat` and `matc`
|
||||
- `third_party`: External libraries and assets
|
||||
|
||||
@@ -5,6 +5,8 @@ A new header is inserted each time a *tag* is created.
|
||||
|
||||
## v1.10.8 (currently main branch)
|
||||
|
||||
- java: Removed support for Java desktop targets (macOS, Linux, and Windows) [⚠️ **API Change**]
|
||||
|
||||
## v1.10.7
|
||||
|
||||
- engine: Spot-light position calculation moved to fragment shader.
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <jawt.h>
|
||||
|
||||
#if defined(__has_include)
|
||||
#if __has_include(<darwin/jawt_md.h>)
|
||||
#include <darwin/jawt_md.h>
|
||||
#else
|
||||
#include <jawt_md.h>
|
||||
#endif
|
||||
#else
|
||||
#include <darwin/jawt_md.h>
|
||||
#endif
|
||||
|
||||
#include <filament/Engine.h>
|
||||
#include "JAWTUtils.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "NotReleasedValue"
|
||||
extern "C" {
|
||||
void *getNativeWindow(JNIEnv *env, jclass klass, jobject surface) {
|
||||
void *win = nullptr;
|
||||
JAWT_DrawingSurface *ds = nullptr;
|
||||
JAWT_DrawingSurfaceInfo *dsi = nullptr;
|
||||
|
||||
if (!acquireDrawingSurface(env, surface, &ds, &dsi)) {
|
||||
return win;
|
||||
}
|
||||
NSObject<JAWT_SurfaceLayers>* jawldsip = (NSObject<JAWT_SurfaceLayers>*)dsi->platformInfo;
|
||||
// Use jawt_DrawingSurfaceInfo.bounds for frame dimension.
|
||||
NSView *view = [[NSView alloc] initWithFrame:
|
||||
NSMakeRect(dsi->bounds.x, dsi->bounds.y, dsi->bounds.width, dsi->bounds.height)];
|
||||
view.wantsLayer = true;
|
||||
[jawldsip setLayer:view.layer];
|
||||
|
||||
win = (void*) view;
|
||||
releaseDrawingSurface(ds, dsi);
|
||||
return win;
|
||||
}
|
||||
|
||||
jlong createNativeSurface(jint width, jint height) {
|
||||
NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width, height)];
|
||||
view.wantsLayer = true;
|
||||
return (jlong) view;
|
||||
}
|
||||
|
||||
void destroyNativeSurface(jlong surface) {
|
||||
NSView *view = reinterpret_cast<NSView*>(surface);
|
||||
[view release];
|
||||
}
|
||||
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "JAWTUtils.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
static std::vector<int> jawtVersions = {
|
||||
0x00010003,
|
||||
0x00010004,
|
||||
0x00010007,
|
||||
0x00010009,
|
||||
};
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wuninitialized"
|
||||
bool acquireDrawingSurface(JNIEnv* env, jobject surface,
|
||||
JAWT_DrawingSurface** ods, JAWT_DrawingSurfaceInfo** odsi) {
|
||||
|
||||
JAWT awt;
|
||||
JAWT_DrawingSurface* ds = nullptr;
|
||||
JAWT_DrawingSurfaceInfo* dsi = nullptr;
|
||||
|
||||
// Search for a valid AWT
|
||||
jboolean foundJawt = JNI_FALSE;
|
||||
for (int jawtVersion : jawtVersions) {
|
||||
awt.version = jawtVersion;
|
||||
#if defined(__APPLE__) && defined(__aarch64__)
|
||||
// FIXME: enable Apple Silicon build. The problem here is that we attempt to link an x86 jdk and that fails
|
||||
// so JAWT_GetAWT doesn't exist at link time.
|
||||
#warning "FIXME: JAWT_GetAWT() not supported"
|
||||
foundJawt = {};
|
||||
#else
|
||||
foundJawt = JAWT_GetAWT(env, &awt);
|
||||
#endif
|
||||
if (foundJawt == JNI_TRUE) {
|
||||
#ifndef NDEBUG
|
||||
printf("Found valid AWT v%08x.\n", jawtVersion);
|
||||
#endif
|
||||
break;
|
||||
} else {
|
||||
#ifndef NDEBUG
|
||||
printf("AWT v%08x not present.\n", jawtVersion);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
fflush(stdout);
|
||||
#endif
|
||||
|
||||
if (foundJawt == JNI_FALSE) {
|
||||
printf("AWT Not found\n");
|
||||
fflush(stdout);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the drawing surface
|
||||
ds = awt.GetDrawingSurface(env, surface);
|
||||
if (ds == nullptr) {
|
||||
#ifndef NDEBUG
|
||||
printf("NULL drawing surface\n");
|
||||
fflush(stdout);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lock the drawing
|
||||
jint lock = ds->Lock(ds);
|
||||
if ((lock & JAWT_LOCK_ERROR) != 0) {
|
||||
#ifndef NDEBUG
|
||||
printf("Error locking surface\n");
|
||||
fflush(stdout);
|
||||
#endif
|
||||
awt.FreeDrawingSurface(ds);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the drawing surface info
|
||||
dsi = ds->GetDrawingSurfaceInfo(ds);
|
||||
if (dsi == nullptr) {
|
||||
#ifndef NDEBUG
|
||||
printf("Error getting surface info\n");
|
||||
fflush(stdout);
|
||||
#endif
|
||||
|
||||
ds->Unlock(ds);
|
||||
awt.FreeDrawingSurface(ds);
|
||||
return false;
|
||||
}
|
||||
|
||||
*odsi = dsi;
|
||||
*ods = ds;
|
||||
|
||||
return true;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
void releaseDrawingSurface(JAWT_DrawingSurface* ds, JAWT_DrawingSurfaceInfo* dsi) {
|
||||
// Free the drawing surface info
|
||||
ds->FreeDrawingSurfaceInfo(dsi);
|
||||
// Unlock the drawing surface
|
||||
ds->Unlock(ds);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <jawt.h>
|
||||
|
||||
extern "C" {
|
||||
bool acquireDrawingSurface(JNIEnv* env, jobject surface, JAWT_DrawingSurface** ods,
|
||||
JAWT_DrawingSurfaceInfo** odsi);
|
||||
void releaseDrawingSurface(JAWT_DrawingSurface* ds, JAWT_DrawingSurfaceInfo* dsi);
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <jawt.h>
|
||||
#include <linux/jawt_md.h>
|
||||
|
||||
#include "JAWTUtils.h"
|
||||
#include<GL/glx.h>
|
||||
|
||||
extern "C" {
|
||||
void *getNativeWindow(JNIEnv* env, jclass, jobject surface) {
|
||||
void* win = nullptr;
|
||||
JAWT_DrawingSurface* ds = nullptr;
|
||||
JAWT_DrawingSurfaceInfo* dsi = nullptr;
|
||||
|
||||
if (!acquireDrawingSurface(env, surface, &ds, &dsi)) {
|
||||
return win;
|
||||
}
|
||||
JAWT_X11DrawingSurfaceInfo* dsi_x11 = (JAWT_X11DrawingSurfaceInfo*) dsi->platformInfo;
|
||||
|
||||
win = (void*) dsi_x11->drawable;
|
||||
releaseDrawingSurface(ds, dsi);
|
||||
return win;
|
||||
}
|
||||
|
||||
jlong createNativeSurface(jint width, jint height) {
|
||||
Display* display = XOpenDisplay(nullptr);
|
||||
int screen = DefaultScreen(display);
|
||||
Window window = 0;
|
||||
|
||||
#ifndef NDEBUG
|
||||
int major, minor;
|
||||
glXQueryVersion(display, &major, &minor);
|
||||
printf("Using GLX v%d.%d\n", major, minor); fflush(stdout);
|
||||
#endif
|
||||
|
||||
static int visualAttributess[] = {
|
||||
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
|
||||
GLX_RENDER_TYPE, GLX_RGBA_BIT,
|
||||
GLX_DOUBLEBUFFER, True,
|
||||
GLX_RED_SIZE, 8,
|
||||
GLX_GREEN_SIZE, 8,
|
||||
GLX_BLUE_SIZE, 8,
|
||||
GLX_DEPTH_SIZE, 24,
|
||||
None
|
||||
};
|
||||
int numConfigs = 0;
|
||||
GLXFBConfig* configs = glXChooseFBConfig( display, screen, visualAttributess, &numConfigs);
|
||||
if (numConfigs == 0) {
|
||||
printf("Unable to find a suitable Framebuffer Configuration.\n"); fflush(stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pbufferAttributes[] = {
|
||||
GLX_PBUFFER_WIDTH, width,
|
||||
GLX_PBUFFER_HEIGHT, height,
|
||||
None
|
||||
};
|
||||
window = glXCreatePbuffer( display, configs[0], pbufferAttributes );
|
||||
XFree(configs);
|
||||
// Make sure pbuffer creation has not been buffered in the event queue (we need it NOW).
|
||||
XFlush(display);
|
||||
|
||||
// Camouflage the pbuffer as a window which are both XID anyway.
|
||||
return (jlong) window;
|
||||
}
|
||||
|
||||
void destroyNativeSurface(jlong surface) {
|
||||
const char* displayName = nullptr;
|
||||
Display* display = XOpenDisplay(displayName);
|
||||
GLXPbuffer pBuffer = (GLXPbuffer)surface;
|
||||
glXDestroyPbuffer(display, pBuffer);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <jawt.h>
|
||||
#include <win32/jawt_md.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <utils/unwindows.h>
|
||||
|
||||
#include "JAWTUtils.h"
|
||||
#include <filament/Engine.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
void* getNativeWindow(JNIEnv* env, jclass, jobject surface) {
|
||||
JAWT_DrawingSurface* ds = nullptr;
|
||||
JAWT_DrawingSurfaceInfo* dsi = nullptr;
|
||||
|
||||
if (!acquireDrawingSurface(env, surface, &ds, &dsi)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JAWT_Win32DrawingSurfaceInfo* dsi_win32 = (JAWT_Win32DrawingSurfaceInfo*) dsi->platformInfo;
|
||||
HWND hWnd = dsi_win32->hwnd;
|
||||
|
||||
releaseDrawingSurface(ds, dsi);
|
||||
|
||||
return (void*) hWnd;
|
||||
}
|
||||
|
||||
|
||||
jlong createNativeSurface(jint width, jint height) {
|
||||
// We need to adjust the window size so the "client area" matches width and height requested.
|
||||
// Otherwise, the window itself will be of dimension width-height but the "client area" and the
|
||||
// resulting surface will be smaller than requested.
|
||||
RECT dimension = {0, 0, width, height};
|
||||
AdjustWindowRect(&dimension, WS_OVERLAPPEDWINDOW, FALSE);
|
||||
width = dimension.right - dimension.left;
|
||||
height = dimension.bottom - dimension.top;
|
||||
|
||||
HWND window = CreateWindowA("STATIC", "dummy", 0, 0, 0, width, height, NULL, NULL, NULL, NULL);
|
||||
SetWindowLong(window, GWL_STYLE, 0); //remove all window styles
|
||||
|
||||
return (jlong) window;
|
||||
}
|
||||
|
||||
void destroyNativeSurface(jlong surface) {
|
||||
HWND window = (HWND) surface;
|
||||
DestroyWindow(window);
|
||||
}
|
||||
|
||||
}
|
||||
14
build.sh
14
build.sh
@@ -27,8 +27,6 @@ function print_help {
|
||||
echo " Always invoke CMake before incremental builds."
|
||||
echo " -i"
|
||||
echo " Install build output"
|
||||
echo " -j"
|
||||
echo " Do not compile desktop Java projects"
|
||||
echo " -m"
|
||||
echo " Compile with make instead of ninja."
|
||||
echo " -p platform1,platform2,..."
|
||||
@@ -150,8 +148,6 @@ BUILD_ANDROID_SAMPLES=false
|
||||
|
||||
RUN_TESTS=false
|
||||
|
||||
FILAMENT_ENABLE_JAVA=ON
|
||||
|
||||
INSTALL_COMMAND=
|
||||
|
||||
VULKAN_ANDROID_OPTION="-DFILAMENT_SUPPORTS_VULKAN=ON"
|
||||
@@ -217,7 +213,6 @@ function build_desktop_target {
|
||||
-DIMPORT_EXECUTABLES_DIR=out \
|
||||
-DCMAKE_BUILD_TYPE="$1" \
|
||||
-DCMAKE_INSTALL_PREFIX="../${lc_target}/filament" \
|
||||
-DFILAMENT_ENABLE_JAVA="${FILAMENT_ENABLE_JAVA}" \
|
||||
${SWIFTSHADER_OPTION} \
|
||||
${MATDBG_OPTION} \
|
||||
${deployment_target} \
|
||||
@@ -694,12 +689,6 @@ function validate_build_command {
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
# Make sure we have Java
|
||||
local javac_binary=$(command -v javac)
|
||||
if [[ "${JAVA_HOME}" == "" ]] || [[ ! "${javac_binary}" ]]; then
|
||||
echo "Warning: JAVA_HOME is not set, skipping Java projects"
|
||||
FILAMENT_ENABLE_JAVA=OFF
|
||||
fi
|
||||
# If building a WebAssembly module, ensure we know where Emscripten lives.
|
||||
if [[ "${EMSDK}" == "" ]] && [[ "${ISSUE_WEBGL_BUILD}" == "true" ]]; then
|
||||
echo "Error: EMSDK is not set, exiting"
|
||||
@@ -774,9 +763,6 @@ while getopts ":hacCfijmp:q:uvslwtdk:" opt; do
|
||||
i)
|
||||
INSTALL_COMMAND=install
|
||||
;;
|
||||
j)
|
||||
FILAMENT_ENABLE_JAVA=OFF
|
||||
;;
|
||||
m)
|
||||
BUILD_GENERATOR="Unix Makefiles"
|
||||
BUILD_COMMAND="make"
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
project(filament-java)
|
||||
|
||||
if (NOT FILAMENT_ENABLE_JAVA)
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(Java)
|
||||
if (NOT Java_FOUND)
|
||||
message(WARNING "JDK not found, skipping Java projects")
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(JNI)
|
||||
if (NOT JNI_FOUND)
|
||||
message(WARNING "JNI not found, skipping Java projects")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED ENV{JAVA_HOME})
|
||||
message(WARNING "The JAVA_HOME environment variable must be set to compile Java projects")
|
||||
message(WARNING "Skipping Java projects")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# JNI bindings
|
||||
# ==================================================================================================
|
||||
set(TARGET filamat-jni)
|
||||
|
||||
include_directories(${JNI_INCLUDE_DIRS})
|
||||
|
||||
set(CXX_STANDARD "-std=c++17")
|
||||
if (WIN32)
|
||||
set(CXX_STANDARD "/std:c++17")
|
||||
endif()
|
||||
|
||||
set(FILAMAT_DIR ../../android/filamat-android)
|
||||
|
||||
set(JNI_SOURCE_FILES
|
||||
${FILAMAT_DIR}/src/main/cpp/MaterialBuilder.cpp
|
||||
)
|
||||
|
||||
add_library(${TARGET} SHARED ${JNI_SOURCE_FILES})
|
||||
target_link_libraries(${TARGET} filamat)
|
||||
|
||||
set(INSTALL_TYPE LIBRARY)
|
||||
if (WIN32 OR CYGWIN)
|
||||
set(INSTALL_TYPE RUNTIME)
|
||||
endif()
|
||||
install(TARGETS ${TARGET} ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
|
||||
set(LIB_SUFFIX ".so")
|
||||
if (APPLE)
|
||||
set(LIB_SUFFIX ".dylib")
|
||||
elseif (WIN32)
|
||||
set(LIB_SUFFIX ".dll")
|
||||
endif()
|
||||
install(CODE "execute_process(COMMAND ${CMAKE_STRIP} -x ${CMAKE_INSTALL_PREFIX}/lib/${DIST_DIR}/lib${TARGET}${LIB_SUFFIX})")
|
||||
|
||||
# ==================================================================================================
|
||||
# Java APIs
|
||||
# ==================================================================================================
|
||||
set(TARGET filamat-java)
|
||||
|
||||
include(UseJava)
|
||||
|
||||
set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.8" "-target" "1.8")
|
||||
|
||||
set(FILAMAT_JAVA_DIR ${FILAMAT_DIR}/src/main/java/)
|
||||
get_filename_component(FILAMAT_JAVA_DIR ${FILAMAT_JAVA_DIR} ABSOLUTE)
|
||||
|
||||
set(JAVA_SOURCE_FILES
|
||||
${FILAMAT_JAVA_DIR}/com/google/android/filament/filamat/MaterialBuilder.java
|
||||
${FILAMAT_JAVA_DIR}/com/google/android/filament/filamat/MaterialPackage.java
|
||||
)
|
||||
|
||||
add_jar(${TARGET}
|
||||
SOURCES ${JAVA_SOURCE_FILES}
|
||||
INCLUDE_JARS ../lib/annotation-1.0.0.jar)
|
||||
|
||||
install_jar(${TARGET} lib)
|
||||
@@ -1,165 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
project(filament-java)
|
||||
|
||||
if (NOT FILAMENT_ENABLE_JAVA)
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(Java)
|
||||
if (NOT Java_FOUND)
|
||||
message(WARNING "JDK not found, skipping Java projects")
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(JNI)
|
||||
if (NOT JNI_FOUND)
|
||||
message(WARNING "JNI not found, skipping Java projects")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED ENV{JAVA_HOME})
|
||||
message(WARNING "The JAVA_HOME environment variable must be set to compile Java projects")
|
||||
message(WARNING "Skipping Java projects")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# JNI bindings
|
||||
# ==================================================================================================
|
||||
set(TARGET filament-jni)
|
||||
|
||||
set(CXX_STANDARD "-std=c++17")
|
||||
if (WIN32)
|
||||
set(CXX_STANDARD "/std:c++17")
|
||||
endif()
|
||||
|
||||
set(ANDROID_DIR ../../android)
|
||||
set(FILAMENT_DIR ${ANDROID_DIR}/filament-android)
|
||||
set(COMMON_DIR ${ANDROID_DIR}/common)
|
||||
|
||||
include_directories(${JNI_INCLUDE_DIRS} ${ANDROID_DIR})
|
||||
|
||||
set(JNI_SOURCE_FILES
|
||||
${FILAMENT_DIR}/src/main/cpp/BufferObject.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/Camera.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/Colors.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/ColorGrading.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/VertexBuffer.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/Engine.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/EntityManager.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/Fence.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/IndexBuffer.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/IndirectLight.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/LightManager.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/Material.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/MaterialInstance.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/MathUtils.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/NativeSurface.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/RenderableManager.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/Renderer.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/RenderTarget.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/Scene.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/SkyBox.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/Stream.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/Texture.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/TextureSampler.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/TransformManager.cpp
|
||||
${FILAMENT_DIR}/src/main/cpp/View.cpp
|
||||
# Private utils
|
||||
${FILAMENT_DIR}/src/main/cpp/Filament.cpp
|
||||
# Desktop specific
|
||||
${FILAMENT_DIR}/src/main/cpp/nativewindow/JAWTUtils.cpp
|
||||
# Common utils
|
||||
${COMMON_DIR}/CallbackUtils.cpp
|
||||
${COMMON_DIR}/NioUtils.cpp
|
||||
)
|
||||
|
||||
if (LINUX)
|
||||
list(APPEND JNI_SOURCE_FILES ${FILAMENT_DIR}/src/main/cpp/nativewindow/Linux.cpp)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
list(APPEND JNI_SOURCE_FILES ${FILAMENT_DIR}/src/main/cpp/nativewindow/Win32.cpp)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
list(APPEND JNI_SOURCE_FILES ${FILAMENT_DIR}/src/main/cpp/nativewindow/Darwin.mm)
|
||||
endif()
|
||||
|
||||
add_library(${TARGET} SHARED ${JNI_SOURCE_FILES})
|
||||
target_link_libraries(${TARGET} filament ${JAVA_AWT_LIBRARY})
|
||||
|
||||
set(INSTALL_TYPE LIBRARY)
|
||||
if (WIN32 OR CYGWIN)
|
||||
set(INSTALL_TYPE RUNTIME)
|
||||
endif()
|
||||
install(TARGETS ${TARGET} ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
|
||||
set(LIB_SUFFIX ".so")
|
||||
if (APPLE)
|
||||
set(LIB_SUFFIX ".dylib")
|
||||
elseif (WIN32)
|
||||
set(LIB_SUFFIX ".dll")
|
||||
endif()
|
||||
install(CODE "execute_process(COMMAND ${CMAKE_STRIP} -x ${CMAKE_INSTALL_PREFIX}/lib/${DIST_DIR}/lib${TARGET}${LIB_SUFFIX})")
|
||||
|
||||
# ==================================================================================================
|
||||
# Java APIs
|
||||
# ==================================================================================================
|
||||
set(TARGET filament-java)
|
||||
|
||||
include(UseJava)
|
||||
|
||||
set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.8" "-target" "1.8")
|
||||
|
||||
set(FILAMENT_JAVA_DIR ${FILAMENT_DIR}/src/main/java/)
|
||||
get_filename_component(FILAMENT_JAVA_DIR ${FILAMENT_JAVA_DIR} ABSOLUTE)
|
||||
|
||||
set(JAVA_SOURCE_FILES
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Asserts.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Box.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/BufferObject.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Camera.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Colors.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/ColorGrading.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Engine.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Entity.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/EntityInstance.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/EntityManager.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Fence.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Filament.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/IndexBuffer.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/IndirectLight.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/LightManager.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Material.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/MaterialInstance.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/MathUtils.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/NativeSurface.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/NioUtils.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Platform.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/RenderableManager.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Renderer.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/RenderTarget.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Scene.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Skybox.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Stream.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/SwapChain.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Texture.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/TextureSampler.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/TransformManager.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/VertexBuffer.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/View.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/Viewport.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/proguard/UsedByNative.java
|
||||
${FILAMENT_JAVA_DIR}/com/google/android/filament/proguard/UsedByReflection.java
|
||||
# Desktop specific
|
||||
src/java/com/google/android/filament/DesktopPlatform.java
|
||||
src/java/com/google/android/filament/FilamentCanvas.java
|
||||
src/java/com/google/android/filament/FilamentPanel.java
|
||||
src/java/com/google/android/filament/FilamentTarget.java
|
||||
)
|
||||
|
||||
add_jar(${TARGET}
|
||||
SOURCES ${JAVA_SOURCE_FILES}
|
||||
INCLUDE_JARS ../lib/annotation-1.0.0.jar)
|
||||
|
||||
install_jar(${TARGET} lib)
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament;
|
||||
|
||||
import com.google.android.filament.Platform;
|
||||
|
||||
public class DesktopPlatform extends Platform {
|
||||
public DesktopPlatform() {
|
||||
}
|
||||
|
||||
@Override
|
||||
void log(String message) {
|
||||
System.out.println(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
void warn(String message) {
|
||||
System.out.println(message);
|
||||
}
|
||||
|
||||
boolean validateStreamSource(Object object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean validateSurface(Object object) {
|
||||
return object instanceof java.awt.Canvas;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean validateSharedContext(Object object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
long getSharedContextNativeHandle(Object sharedContext) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament;
|
||||
|
||||
import java.awt.Canvas;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import com.google.android.filament.Engine;
|
||||
import com.google.android.filament.SwapChain;
|
||||
import com.google.android.filament.View;
|
||||
import com.google.android.filament.Renderer;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class FilamentCanvas extends Canvas implements FilamentTarget {
|
||||
private SwapChain mSwapChain;
|
||||
private boolean mHasPainted;
|
||||
|
||||
public FilamentCanvas() {
|
||||
super();
|
||||
mHasPainted = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(@NonNull Graphics g) {
|
||||
// On windows, allowing paint more than once will lock the canvas filament renderer.
|
||||
// Not allowing it at all will trigger weird behavior where maximizing and returning
|
||||
// to previous window size prevent SwapBuffer from working properly. super.paint() must
|
||||
// be allowed exactly once.
|
||||
if (!mHasPainted) {
|
||||
super.paint(g);
|
||||
mHasPainted = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean beginFrame(@NonNull Engine engine, @NonNull Renderer renderer) {
|
||||
return renderer.beginFrame(getSwapChain(engine), 0L);
|
||||
}
|
||||
|
||||
public void endFrame(Renderer renderer) {
|
||||
renderer.endFrame();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private SwapChain getSwapChain(@NonNull Engine engine) {
|
||||
if (mSwapChain == null) {
|
||||
mSwapChain = engine.createSwapChain(this, 0);
|
||||
}
|
||||
return mSwapChain;
|
||||
}
|
||||
|
||||
public void destroy(@NonNull Engine engine) {
|
||||
if (mSwapChain == null) {
|
||||
return;
|
||||
}
|
||||
engine.destroySwapChain(mSwapChain);
|
||||
mSwapChain = null;
|
||||
}
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament;
|
||||
|
||||
import java.awt.*;
|
||||
import java.nio.IntBuffer;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
||||
/**
|
||||
* A Swing friendly component which can be used if an AWT based FilamentCanvas object results in
|
||||
* issues due to interactions with Swing lightweight pop-ups and tooltips. However keep in mind
|
||||
* it carries a much higher cost of operation.
|
||||
*/
|
||||
public class FilamentPanel extends JPanel implements FilamentTarget {
|
||||
|
||||
/**
|
||||
* A Slot contains a buffer for Filament to write and an Image for Swing to read.
|
||||
* It is ok for the buffer capacity to be > height * width. The image however MUST
|
||||
* be exactly of dimension height * width.
|
||||
*/
|
||||
class Slot {
|
||||
|
||||
IntBuffer buffer;
|
||||
BufferedImage image;
|
||||
|
||||
Slot(int width, int height) {
|
||||
buffer = ByteBuffer.allocateDirect(4 * width * height).asIntBuffer();
|
||||
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SlotPool is a circular buffer which provide "Slot"s where ExecutorService thread can write
|
||||
* pixels and Swing can read from. Its purpose it to reuse memory and avoid allocation. During
|
||||
* "normal" operation, SlotPool will generate no memory allocations. During a resize, it will
|
||||
* have to realloc for each frames if the surface gets bigger. If the new size of the frame is
|
||||
* smaller, only new BufferedImage are allocated.
|
||||
*/
|
||||
private final static int NUM_SLOTS = 4;
|
||||
|
||||
class SlotPool {
|
||||
private ConcurrentLinkedDeque<Slot> queue = new ConcurrentLinkedDeque<>();
|
||||
|
||||
public SlotPool() {
|
||||
for (int i = 0; i < NUM_SLOTS; i++) {
|
||||
Slot slot = new Slot(1, 1); // Zero sized BufferedImage are forbidden.
|
||||
queue.add(slot);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if no more slots are available. Otherwise, returns a slot adequate to
|
||||
* receive and process an image of dimension width * height * RGBA.
|
||||
*/
|
||||
public Slot getSlot(int width, int height) {
|
||||
Slot slot = queue.poll();
|
||||
|
||||
// Sorry we are out.
|
||||
if (slot == null) {
|
||||
return slot;
|
||||
}
|
||||
|
||||
// The slot can avoid an allocation and reuse the same BufferedImage if dimensions have
|
||||
// not changed.
|
||||
if (slot.image.getWidth() != width || slot.image.getHeight() != height) {
|
||||
slot.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
}
|
||||
|
||||
// The slot can avoid an allocation and reuse the same IntBuffer if it has the capacity.
|
||||
if (slot.buffer.capacity() < width * height) {
|
||||
slot.buffer = ByteBuffer.allocateDirect(4 * width * height).asIntBuffer();
|
||||
} else {
|
||||
// Reuse the buffer, just reset position
|
||||
slot.buffer.rewind();
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
public void putSlot(Slot slot) {
|
||||
queue.add(slot);
|
||||
}
|
||||
}
|
||||
|
||||
private final SlotPool mSlotPool = new SlotPool();
|
||||
|
||||
|
||||
private NativeSurface mNativeSurface;
|
||||
private SwapChain mSwapChain;
|
||||
private BufferedImage mImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
private final Object mImageLock = new Object();
|
||||
private int mHeight;
|
||||
private int mWidth;
|
||||
private Dimension mBackendDimension = new Dimension(0, 0);
|
||||
|
||||
private static final ExecutorService sExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
// Amount of padding added to surface dimension upon allocating them.
|
||||
private static final int SURFACE_PADDING = 128;
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g) {
|
||||
synchronized (mImageLock) {
|
||||
g.drawImage(mImage, 0, 0, Color.BLACK, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This must be called on Filament thread.
|
||||
* Prepare to renders to offscreen native window.
|
||||
*/
|
||||
public boolean beginFrame(@NonNull Engine engine, @NonNull Renderer renderer) {
|
||||
ensureSurface(engine);
|
||||
return renderer.beginFrame(mSwapChain, 0L);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This must be called on Filament thread.
|
||||
* Pixels are read from the Gl (located in VRAM) and copied to the JVM (in RAM).
|
||||
* Colors are converted from ABGR to RGBA. A BufferedImage is created (copy RAM to RAM
|
||||
* of all pixels). Finally the image is drawn to screen via Graphics.drawImage (one more trip
|
||||
* from RAM to VRAM).
|
||||
*/
|
||||
public void endFrame(@NonNull Renderer renderer) {
|
||||
// By the time readPixel callback is invoked, the dimension of the Panel may have changed.
|
||||
// Capture dimension so they match the current GL framebuffer.
|
||||
int capturedWidth = mWidth;
|
||||
int capturedHeight = mHeight;
|
||||
|
||||
// Get a slot where Filament can write its pixels and where we have an appropriately sized
|
||||
// BufferedImage.
|
||||
Slot slot = mSlotPool.getSlot(capturedWidth, capturedHeight);
|
||||
if (slot == null) {
|
||||
// It seems Filament is producing data faster than Swing can display it. Skip this frame
|
||||
// to allow Swing to catch up.
|
||||
renderer.endFrame();
|
||||
return;
|
||||
}
|
||||
|
||||
Texture.PixelBufferDescriptor pb = new Texture.PixelBufferDescriptor(slot.buffer,
|
||||
Texture.Format.RGBA, Texture.Type.UBYTE, 1, 0, 0, 0, sExecutor, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (mImageLock) {
|
||||
// Copy pixels read from the GL into the BufferedImage. Then convert from RGBA
|
||||
// to ARGB because that is what the BufferedImage expects.
|
||||
int[] data = ((DataBufferInt) slot.image.getRaster().getDataBuffer()).getData();
|
||||
slot.buffer.get(data, 0, capturedHeight * capturedWidth);
|
||||
for (int i = 0; i < capturedHeight * capturedWidth; i++) {
|
||||
int rgba = data[i];
|
||||
data[i] = (rgba >>> 8) | (rgba << 24);
|
||||
}
|
||||
mImage = slot.image;
|
||||
repaint();
|
||||
mSlotPool.putSlot(slot);
|
||||
}
|
||||
}
|
||||
});
|
||||
renderer.readPixels(0, 0, mWidth, mHeight, pb);
|
||||
renderer.endFrame();
|
||||
}
|
||||
|
||||
private void createSurfaces(@NonNull Engine engine, int width, int height) {
|
||||
mBackendDimension = new Dimension(width, height);
|
||||
mNativeSurface = new NativeSurface(width, height);
|
||||
mSwapChain = engine.createSwapChainFromNativeSurface(mNativeSurface, 0L);
|
||||
}
|
||||
|
||||
private void destroySurfaces(@NonNull Engine engine) {
|
||||
|
||||
if (mNativeSurface == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Previous frames using this surface may still be in the pipeline, so we must wait for
|
||||
// them to finish.
|
||||
engine.flushAndWait();
|
||||
|
||||
mNativeSurface.dispose();
|
||||
engine.destroySwapChain(mSwapChain);
|
||||
}
|
||||
|
||||
private void ensureSurface(@NonNull Engine engine) {
|
||||
// Capture the dimension at the time the surface was ensured.
|
||||
mWidth = getWidth();
|
||||
mHeight = getHeight();
|
||||
|
||||
if (mBackendDimension.width < mWidth || mBackendDimension.height < mHeight) {
|
||||
destroySurfaces(engine);
|
||||
|
||||
// A surface needs to be allocated. Allocate something a little bit bigger than
|
||||
// necessary in order to avoid reallocating too often if the Panel is resized.
|
||||
int widthToAllocate = mWidth + SURFACE_PADDING;
|
||||
int heightToAllocate = mHeight + SURFACE_PADDING;
|
||||
createSurfaces(engine, widthToAllocate, heightToAllocate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This must be called on Filament thread.
|
||||
*/
|
||||
public void destroy(@NonNull Engine engine) {
|
||||
destroySurfaces(engine);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public interface FilamentTarget {
|
||||
boolean beginFrame(Engine engine, Renderer renderer);
|
||||
void endFrame(@NonNull Renderer renderer);
|
||||
void destroy(@NonNull Engine engine);
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
project(filament-java)
|
||||
|
||||
if (NOT FILAMENT_ENABLE_JAVA)
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(Java)
|
||||
if (NOT Java_FOUND)
|
||||
message(WARNING "JDK not found, skipping Java projects")
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(JNI)
|
||||
if (NOT JNI_FOUND)
|
||||
message(WARNING "JNI not found, skipping Java projects")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED ENV{JAVA_HOME})
|
||||
message(WARNING "The JAVA_HOME environment variable must be set to compile Java projects")
|
||||
message(WARNING "Skipping Java projects")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# JNI bindings
|
||||
# ==================================================================================================
|
||||
set(TARGET gltfio-jni)
|
||||
|
||||
set(CXX_STANDARD "-std=c++17")
|
||||
if (WIN32)
|
||||
set(CXX_STANDARD "/std:c++17")
|
||||
endif()
|
||||
|
||||
set(ANDROID_DIR ../../android)
|
||||
set(GLTFIO_DIR ${ANDROID_DIR}/gltfio-android)
|
||||
set(COMMON_DIR ${ANDROID_DIR}/common)
|
||||
|
||||
include_directories(${JNI_INCLUDE_DIRS} ${ANDROID_DIR})
|
||||
|
||||
set(JNI_SOURCE_FILES
|
||||
${GLTFIO_DIR}/src/main/cpp/Animator.cpp
|
||||
${GLTFIO_DIR}/src/main/cpp/AssetLoader.cpp
|
||||
${GLTFIO_DIR}/src/main/cpp/FilamentAsset.cpp
|
||||
${GLTFIO_DIR}/src/main/cpp/FilamentInstance.cpp
|
||||
${GLTFIO_DIR}/src/main/cpp/MaterialKey.cpp
|
||||
${GLTFIO_DIR}/src/main/cpp/MaterialKey.h
|
||||
${GLTFIO_DIR}/src/main/cpp/UbershaderLoader.cpp
|
||||
${GLTFIO_DIR}/src/main/cpp/ResourceLoader.cpp
|
||||
${COMMON_DIR}/CallbackUtils.cpp
|
||||
${COMMON_DIR}/NioUtils.cpp
|
||||
)
|
||||
|
||||
add_library(${TARGET} SHARED ${JNI_SOURCE_FILES})
|
||||
target_link_libraries(${TARGET} gltfio_core gltfio_resources)
|
||||
|
||||
set(INSTALL_TYPE LIBRARY)
|
||||
if (WIN32 OR CYGWIN)
|
||||
set(INSTALL_TYPE RUNTIME)
|
||||
endif()
|
||||
install(TARGETS ${TARGET} ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
|
||||
set(LIB_SUFFIX ".so")
|
||||
if (APPLE)
|
||||
set(LIB_SUFFIX ".dylib")
|
||||
elseif (WIN32)
|
||||
set(LIB_SUFFIX ".dll")
|
||||
endif()
|
||||
install(CODE "execute_process(COMMAND ${CMAKE_STRIP} -x ${CMAKE_INSTALL_PREFIX}/lib/${DIST_DIR}/lib${TARGET}${LIB_SUFFIX})")
|
||||
|
||||
# ==================================================================================================
|
||||
# Java APIs
|
||||
# ==================================================================================================
|
||||
set(TARGET gltfio-java)
|
||||
|
||||
include(UseJava)
|
||||
|
||||
set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.8" "-target" "1.8")
|
||||
|
||||
set(GLTFIO_JAVA_DIR ${GLTFIO_DIR}/src/main/java/)
|
||||
get_filename_component(GLTFIO_JAVA_DIR ${GLTFIO_JAVA_DIR} ABSOLUTE)
|
||||
|
||||
set(JAVA_SOURCE_FILES
|
||||
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/Animator.java
|
||||
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/AssetLoader.java
|
||||
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/FilamentAsset.java
|
||||
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/FilamentInstance.java
|
||||
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/Gltfio.java
|
||||
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/MaterialProvider.java
|
||||
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/ResourceLoader.java
|
||||
)
|
||||
|
||||
add_jar(${TARGET}
|
||||
SOURCES ${JAVA_SOURCE_FILES}
|
||||
INCLUDE_JARS
|
||||
../lib/annotation-1.0.0.jar
|
||||
filament-java
|
||||
)
|
||||
|
||||
install_jar(${TARGET} lib)
|
||||
Binary file not shown.
10
tools/tungsten/.gitignore
vendored
10
tools/tungsten/.gitignore
vendored
@@ -1,10 +0,0 @@
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea
|
||||
.DS_Store
|
||||
build/
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
out/
|
||||
*.iml
|
||||
/core/resources/ibls
|
||||
@@ -1,92 +0,0 @@
|
||||
# Tungsten
|
||||
|
||||
Tungsten is a node based material editor for the Filament rendering engine.
|
||||
|
||||
__Note: Tungsten is still a work-in-progress and is not ready for public at-large consumption yet. Use at your own risk!__
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before building Tungsten, you'll need to first build Filament. See Filament's [README](../../README.md) for instructions. Be sure to run `make install` (or the equivalent for your chosen build system) to install Filament binaries to the `dist` folder at the root of Filament. Tungsten relies on `filament-java.jar`, `libfilament-jni`, and tools such as `matc` and `cmgen` in the appropriate directories under `dist`:
|
||||
|
||||
```
|
||||
Filament
|
||||
|-- dist
|
||||
| |-- bin
|
||||
| |-- matc
|
||||
| |-- cmgen
|
||||
| |-- lib
|
||||
| |-- x86_64
|
||||
| |-- libfilament-jni.*
|
||||
| filament-java.jar
|
||||
```
|
||||
|
||||
The location of this directory can be changed by updating the `filament_tools_dir` property inside of `gradle.properties`.
|
||||
|
||||
You'll also need Java 8 in order to use Tungsten. Tungsten is supported on Windows, Mac, and Linux.
|
||||
|
||||
## Getting Started
|
||||
|
||||
`cd` into the Tungsten directory. The standalone version of Tungsten can be run directly from [Gradle](https://gradle.org/):
|
||||
|
||||
```
|
||||
$ cd tools/tungsten
|
||||
$ ./gradlew :standalone:run
|
||||
```
|
||||
|
||||
## Running inside of Android Studio
|
||||
|
||||
To run inside of Android Studio, set the `ANDROID_STUDIO` environment variable to point to your install of Android Studio, for example:
|
||||
|
||||
```
|
||||
export ANDROID_STUDIO="/Applications/Android Studio.app" # Mac
|
||||
set ANDROID_STUDIO=C:\Program Files\Android\Android Studio # Windows
|
||||
```
|
||||
|
||||
Then run
|
||||
```
|
||||
$ ./gradlew :plugin:runIde
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
Tungsten comes in two flavors: a standalone app, and (in the future) an Android Studio plugin. The plugin is still in heavy development and not functional yet.
|
||||
|
||||
Both targets can be built using Gradle.
|
||||
|
||||
```
|
||||
$ ./gradlew build
|
||||
```
|
||||
|
||||
To build the standalone only:
|
||||
|
||||
```
|
||||
$ ./gradlew :standalone:build
|
||||
```
|
||||
|
||||
To build the plugin only:
|
||||
|
||||
```
|
||||
$ ./gradlew :plugin:build
|
||||
```
|
||||
|
||||
The Android Studio plugin will be built at `plugin/build/distributions/Tungsten-x.y.z.zip`.
|
||||
|
||||
## Running the tests
|
||||
|
||||
Tests can be run with
|
||||
|
||||
```
|
||||
$ ./gradlew test
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Please see Filament's [CONTRIBUTING](../../CONTRIBUTING.md) for details.
|
||||
|
||||
## Authors
|
||||
|
||||
* **Benjamin Doherty**
|
||||
|
||||
## License
|
||||
|
||||
Please see Filament's [LICENSE](../../LICENSE) for details.
|
||||
@@ -1,60 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
|
||||
}
|
||||
|
||||
apply from: '../../../android/build/filament-tasks.gradle'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ['src']
|
||||
}
|
||||
resources {
|
||||
srcDirs = ['resources']
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ['test']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.mockito:mockito-core:1.+'
|
||||
|
||||
implementation files("../../../dist/lib/filament-java.jar")
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.51"
|
||||
|
||||
implementation project(':kotlin-math')
|
||||
}
|
||||
|
||||
generateIbl {
|
||||
group 'Tungsten'
|
||||
description 'Generate a preview IBL'
|
||||
|
||||
inputFile = file("../../../third_party/environments/venetian_crossroads_2k.hdr")
|
||||
outputDir = file("resources/ibls")
|
||||
}
|
||||
|
||||
processResources.dependsOn generateIbl
|
||||
|
||||
test {
|
||||
testLogging {
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.filamesh;
|
||||
|
||||
import com.google.android.filament.IndexBuffer;
|
||||
import com.google.android.filament.VertexBuffer;
|
||||
|
||||
public class Filamesh {
|
||||
private final String mName;
|
||||
private final VertexBuffer mVertexBuffer;
|
||||
private final IndexBuffer mIndexBuffer;
|
||||
|
||||
Filamesh(String name, VertexBuffer vertexBuffer, IndexBuffer indexBuffer) {
|
||||
mName = name;
|
||||
mVertexBuffer = vertexBuffer;
|
||||
mIndexBuffer = indexBuffer;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public VertexBuffer getVertexBuffer() {
|
||||
return mVertexBuffer;
|
||||
}
|
||||
|
||||
public IndexBuffer getIndexBuffer() {
|
||||
return mIndexBuffer;
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.filamesh;
|
||||
|
||||
import com.google.android.filament.Engine;
|
||||
import com.google.android.filament.IndexBuffer;
|
||||
import com.google.android.filament.VertexBuffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
|
||||
public class FilameshLoader {
|
||||
|
||||
public static final String FILAMESH_FILE_IDENTIFIER = "FILAMESH";
|
||||
|
||||
private static class Float3 {
|
||||
float x, y, z;
|
||||
|
||||
public Float3(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
}
|
||||
|
||||
private static class FilameshHeader {
|
||||
int versionNumber;
|
||||
int numberOfParts;
|
||||
Float3 totalBoundingBoxCenter;
|
||||
Float3 totalBoundingBoxHalfExtent;
|
||||
int interleaved;
|
||||
int positionAttributeOffset;
|
||||
int positionAttributeStride;
|
||||
int tangentAttributeOffset;
|
||||
int tangentAttributeStride;
|
||||
int colorAttributeOffset;
|
||||
int colorAttributeStride;
|
||||
int uv0AttributeOffset;
|
||||
int uv0AttributeStride;
|
||||
int uv1AttributeOffset;
|
||||
int uv1AttributeStride;
|
||||
int totalVertices;
|
||||
int verticesSizeInBytes;
|
||||
int indices16Bit; // 0 if indices are stored as int, 1 if stored as uint16
|
||||
int totalIndices;
|
||||
int indicesSizeInBytes;
|
||||
};
|
||||
|
||||
private static boolean readMagicNumber(InputStream in) throws IOException {
|
||||
final byte[] temp = new byte[FILAMESH_FILE_IDENTIFIER.length()];
|
||||
in.read(temp);
|
||||
String tempS = new String(temp, "UTF-8");
|
||||
return tempS.equals(FILAMESH_FILE_IDENTIFIER);
|
||||
}
|
||||
|
||||
private static FilameshHeader readHeader(InputStream in) throws IOException {
|
||||
FilameshHeader header = new FilameshHeader();
|
||||
if (!readMagicNumber(in)) {
|
||||
System.err.print("Invalid filamesh file.");
|
||||
System.exit(1);
|
||||
}
|
||||
header.versionNumber = IOUtils.readIntLE(in);
|
||||
header.numberOfParts = IOUtils.readIntLE(in);
|
||||
header.totalBoundingBoxCenter =
|
||||
new Float3(IOUtils.readFloat32LE(in), IOUtils.readFloat32LE(in), IOUtils.readFloat32LE(in));
|
||||
header.totalBoundingBoxHalfExtent =
|
||||
new Float3(IOUtils.readFloat32LE(in), IOUtils.readFloat32LE(in), IOUtils.readFloat32LE(in));
|
||||
header.interleaved = IOUtils.readIntLE(in);
|
||||
header.positionAttributeOffset = IOUtils.readIntLE(in);
|
||||
header.positionAttributeStride = IOUtils.readIntLE(in);
|
||||
header.tangentAttributeOffset = IOUtils.readIntLE(in);
|
||||
header.tangentAttributeStride = IOUtils.readIntLE(in);
|
||||
header.colorAttributeOffset = IOUtils.readIntLE(in);
|
||||
header.colorAttributeStride = IOUtils.readIntLE(in);
|
||||
header.uv0AttributeOffset = IOUtils.readIntLE(in);
|
||||
header.uv0AttributeStride = IOUtils.readIntLE(in);
|
||||
header.uv1AttributeOffset = IOUtils.readIntLE(in);
|
||||
header.uv1AttributeStride = IOUtils.readIntLE(in);
|
||||
header.totalVertices = IOUtils.readIntLE(in);
|
||||
header.verticesSizeInBytes = IOUtils.readIntLE(in);
|
||||
header.indices16Bit = IOUtils.readIntLE(in);
|
||||
header.totalIndices = IOUtils.readIntLE(in);
|
||||
header.indicesSizeInBytes = IOUtils.readIntLE(in);
|
||||
return header;
|
||||
}
|
||||
|
||||
public static Filamesh load(String name, InputStream in, Engine engine) {
|
||||
try {
|
||||
FilameshHeader header = readHeader(in);
|
||||
|
||||
if (header.numberOfParts > 1) {
|
||||
System.out.println("Mesh " + name + " has " + header.numberOfParts + " parts.");
|
||||
System.out.println("Currently, only 1 part supported.");
|
||||
}
|
||||
|
||||
ReadableByteChannel channel = Channels.newChannel(in);
|
||||
|
||||
ByteBuffer vertexBuffer = ByteBuffer.allocateDirect(header.verticesSizeInBytes);
|
||||
vertexBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
channel.read(vertexBuffer);
|
||||
vertexBuffer.flip();
|
||||
|
||||
ByteBuffer indexBuffer = ByteBuffer.allocateDirect(header.indicesSizeInBytes);
|
||||
indexBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
channel.read(indexBuffer);
|
||||
indexBuffer.flip();
|
||||
|
||||
VertexBuffer vb = new VertexBuffer.Builder()
|
||||
.bufferCount(1)
|
||||
.vertexCount(header.totalVertices)
|
||||
.attribute(VertexBuffer.VertexAttribute.POSITION, 0, VertexBuffer.AttributeType.HALF4,
|
||||
header.positionAttributeOffset, header.positionAttributeStride)
|
||||
.attribute(VertexBuffer.VertexAttribute.TANGENTS, 0, VertexBuffer.AttributeType.SHORT4,
|
||||
header.tangentAttributeOffset, header.tangentAttributeStride)
|
||||
.attribute(VertexBuffer.VertexAttribute.COLOR, 0, VertexBuffer.AttributeType.UBYTE4,
|
||||
header.colorAttributeOffset, header.colorAttributeStride)
|
||||
.attribute(VertexBuffer.VertexAttribute.UV0, 0, VertexBuffer.AttributeType.HALF2,
|
||||
header.uv0AttributeOffset, header.uv0AttributeStride)
|
||||
.build(engine);
|
||||
vb.setBufferAt(engine, 0, vertexBuffer);
|
||||
|
||||
IndexBuffer ib = new IndexBuffer.Builder()
|
||||
.bufferType(IndexBuffer.Builder.IndexType.USHORT)
|
||||
.indexCount(header.totalIndices)
|
||||
.build(engine);
|
||||
ib.setBuffer(engine, indexBuffer);
|
||||
|
||||
return new Filamesh(name, vb, ib);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.err.println("Error loading mesh: " + name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.filamesh;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
class IOUtils {
|
||||
private IOUtils() {
|
||||
}
|
||||
|
||||
static void safelyClose(InputStream in) {
|
||||
try {
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
static int readUnsignedShortBE(InputStream in) throws IOException {
|
||||
return (in.read() & 0xff) << 8 | (in.read() & 0xff);
|
||||
}
|
||||
|
||||
static int readIntLE(InputStream in) throws IOException {
|
||||
return (in.read() & 0xff) |
|
||||
(in.read() & 0xff) << 8 |
|
||||
(in.read() & 0xff) << 16 |
|
||||
(in.read() & 0xff) << 24;
|
||||
}
|
||||
|
||||
static float readFloat32LE(InputStream in) throws IOException {
|
||||
byte[] bytes = new byte[4];
|
||||
in.read(bytes, 0, 4);
|
||||
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
|
||||
}
|
||||
|
||||
static long readUnsignedIntLE(InputStream in) throws IOException {
|
||||
return readIntLE(in) & 0xFFFFFFFFL;
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten;
|
||||
|
||||
import com.google.android.filament.Engine;
|
||||
import com.google.android.filament.FilamentPanel;
|
||||
import com.google.android.filament.Renderer;
|
||||
import com.google.android.filament.View;
|
||||
import com.google.android.filament.tungsten.texture.TextureCache;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class Filament {
|
||||
|
||||
public static class Viewer {
|
||||
public Renderer renderer;
|
||||
public FilamentPanel panel;
|
||||
public View view;
|
||||
|
||||
public void update(long deltaTimeMs) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static class Task {
|
||||
final Consumer<Engine> runnable;
|
||||
final Object wait;
|
||||
|
||||
public Task(Consumer<Engine> runnable, Object wait) {
|
||||
this.runnable = runnable;
|
||||
this.wait = wait;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Filament FILAMENT_HOLDER = new Filament();
|
||||
|
||||
private Thread mFilamentThread;
|
||||
private Engine mEngine;
|
||||
private final AtomicBoolean mRunFilament = new AtomicBoolean(true);
|
||||
private final BlockingQueue<Task> mJobQueue = new LinkedBlockingQueue<>();
|
||||
private final List<Viewer> mViewList = new LinkedList<>();
|
||||
private long mPreviousTime = 0;
|
||||
|
||||
private Filament() {
|
||||
|
||||
}
|
||||
|
||||
public static Filament getInstance() {
|
||||
return FILAMENT_HOLDER;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
startFilamentThread();
|
||||
}
|
||||
|
||||
public void runOnFilamentThread(Consumer<Engine> c) {
|
||||
mJobQueue.add(new Task(c, null));
|
||||
}
|
||||
|
||||
public void runImmediatelyOnFilamentThread(Consumer<Engine> c) throws InterruptedException {
|
||||
Object conditionObject = new Object();
|
||||
synchronized (conditionObject) {
|
||||
mJobQueue.add(new Task(c, conditionObject));
|
||||
conditionObject.wait();
|
||||
}
|
||||
}
|
||||
|
||||
public void addViewer(Viewer viewer) {
|
||||
runOnFilamentThread((Engine e) -> mViewList.add(viewer));
|
||||
}
|
||||
|
||||
public void removeViewer(Viewer viewer) {
|
||||
runOnFilamentThread((Engine e) -> mViewList.remove(viewer));
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
try {
|
||||
TextureCache.INSTANCE.shutdownAndDestroyTextures();
|
||||
runImmediatelyOnFilamentThread((Engine e) -> {
|
||||
// Before shutting down, drain the queue of any remaining jobs. Some jobs that deal
|
||||
// with cleanup might still remain and we want to ensure these all run.
|
||||
drainJobQueue();
|
||||
e.destroy();
|
||||
mRunFilament.set(false);
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void startFilamentThread() {
|
||||
mFilamentThread = new Thread(() -> {
|
||||
mEngine = Engine.create();
|
||||
|
||||
while (mRunFilament.get()) {
|
||||
// Run all of the outstanding jobs
|
||||
drainJobQueue();
|
||||
|
||||
renderViews();
|
||||
|
||||
try {
|
||||
Thread.sleep(16);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
mFilamentThread.setPriority(Thread.MAX_PRIORITY);
|
||||
mFilamentThread.start();
|
||||
}
|
||||
|
||||
public void assertIsFilamentThread() {
|
||||
assert Thread.currentThread().equals(mFilamentThread);
|
||||
}
|
||||
|
||||
private void drainJobQueue() {
|
||||
while (!mJobQueue.isEmpty()) {
|
||||
Task task = mJobQueue.poll();
|
||||
assert task != null;
|
||||
task.runnable.accept(mEngine);
|
||||
if (task.wait != null) {
|
||||
synchronized (task.wait) {
|
||||
task.wait.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderViews() {
|
||||
long now = System.currentTimeMillis();
|
||||
for (Viewer viewer : mViewList) {
|
||||
// We need to wait until Swing has had a chance to layout the panel before we can
|
||||
// attempt to render to it. Otherwise it won't have a valid swapchain.
|
||||
if (viewer.panel.getHeight() == 0 || viewer.panel.getWidth() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (!viewer.panel.beginFrame(mEngine, viewer.renderer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
viewer.update(now - mPreviousTime);
|
||||
|
||||
viewer.renderer.render(viewer.view);
|
||||
viewer.panel.endFrame(viewer.renderer);
|
||||
}
|
||||
mPreviousTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten;
|
||||
|
||||
public class MaterialCompilationException extends Exception {
|
||||
|
||||
public MaterialCompilationException(String message, Throwable t) {
|
||||
super(message, t);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
public class MaterialCompiler {
|
||||
|
||||
private final String mMatcPath;
|
||||
|
||||
public MaterialCompiler(String matcPath) {
|
||||
mMatcPath = matcPath;
|
||||
}
|
||||
|
||||
public Buffer compile(String source) throws MaterialCompilationException {
|
||||
try {
|
||||
File sourceFile = createTempFileForSource(source);
|
||||
File outputFile = createTempFileForOutput();
|
||||
executeCompileCommand(sourceFile, outputFile);
|
||||
return loadFileContent(outputFile.getAbsolutePath());
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new MaterialCompilationException("Error: " + e.getMessage() + ". Could not " +
|
||||
"compile material", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeCompileCommand(File sourceFile, File outputFile) throws
|
||||
IOException, InterruptedException {
|
||||
Process matcProcess = new ProcessBuilder(mMatcPath, "-o",
|
||||
outputFile.getAbsolutePath(), sourceFile.getAbsolutePath())
|
||||
.inheritIO()
|
||||
.start();
|
||||
matcProcess.waitFor();
|
||||
}
|
||||
|
||||
private static File createTempFileForOutput() throws IOException {
|
||||
File compiledOutput = File.createTempFile("filament-material-editor-output", ".bmat");
|
||||
compiledOutput.deleteOnExit();
|
||||
return compiledOutput;
|
||||
}
|
||||
|
||||
private static File createTempFileForSource(String source) throws IOException {
|
||||
File sourceFile = File.createTempFile("filament-material-editor-source", ".mat");
|
||||
sourceFile.deleteOnExit();
|
||||
PrintWriter sourceFileWriter = new PrintWriter(sourceFile);
|
||||
sourceFileWriter.println(source);
|
||||
sourceFileWriter.close();
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
private static Buffer loadFileContent(String path) throws IOException {
|
||||
try (RandomAccessFile file = new RandomAccessFile(path, "r");
|
||||
FileChannel fileChannel = file.getChannel()) {
|
||||
|
||||
int size = (int) file.length();
|
||||
|
||||
ByteBuffer buf = ByteBuffer.allocate(size);
|
||||
int bytesRead = fileChannel.read(buf);
|
||||
while (bytesRead != 0) {
|
||||
bytesRead = fileChannel.read(buf);
|
||||
}
|
||||
buf.rewind();
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten;
|
||||
|
||||
import com.google.android.filament.Engine;
|
||||
import com.google.android.filament.Material;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class MaterialManager {
|
||||
|
||||
private final MaterialCompiler mCompiler;
|
||||
|
||||
public MaterialManager(String matcPath) {
|
||||
mCompiler = new MaterialCompiler(matcPath);
|
||||
}
|
||||
|
||||
public CompletableFuture<Material> compileMaterial(String source) {
|
||||
CompletableFuture<Material> result = new CompletableFuture<>();
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
Buffer materialBlob = mCompiler.compile(source);
|
||||
// The Material load must happen on the Filament thread
|
||||
Filament.getInstance().runOnFilamentThread((Engine e) -> {
|
||||
Material m = uploadMaterialBlob(materialBlob, e);
|
||||
result.complete(m);
|
||||
});
|
||||
} catch (MaterialCompilationException e) {
|
||||
e.printStackTrace();
|
||||
result.cancel(true);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Material uploadMaterialBlob(Buffer materialBlob, Engine engine) {
|
||||
Material.Builder materialBuilder = new Material.Builder()
|
||||
.payload(materialBlob, materialBlob.capacity());
|
||||
return materialBuilder.build(engine);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten;
|
||||
|
||||
public final class MathUtils {
|
||||
|
||||
private MathUtils() { }
|
||||
|
||||
public static float[] createUniformScaleMatrix(float v) {
|
||||
return new float[]{v, 0, 0, 0,
|
||||
0, v, 0, 0,
|
||||
0, 0, v, 0,
|
||||
0, 0, 0, 1};
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class SwingHelper {
|
||||
|
||||
private static final RenderingHints HINTS = new RenderingHints(createRenderingHintsMap());
|
||||
|
||||
private static Map<RenderingHints.Key, Object> createRenderingHintsMap() {
|
||||
Map<RenderingHints.Key, Object> map = new HashMap<>();
|
||||
map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
map.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
|
||||
return map;
|
||||
}
|
||||
|
||||
private SwingHelper() { }
|
||||
|
||||
public static void setRenderingHints(Graphics2D g2d) {
|
||||
g2d.setRenderingHints(HINTS);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.compiler
|
||||
|
||||
import com.google.android.filament.tungsten.model.Node
|
||||
import com.google.android.filament.tungsten.model.Slot
|
||||
|
||||
data class CompiledGraph(
|
||||
// The Filament material definition that is fed into matc
|
||||
val materialDefinition: String,
|
||||
|
||||
// Maps a Node's property to the associated material parameter it controls
|
||||
val parameterMap: Map<Node.PropertyHandle, Parameter>,
|
||||
|
||||
// Maps a Slot to its corresponding Expression
|
||||
val expressionMap: Map<Slot, Expression>,
|
||||
|
||||
/**
|
||||
* Nodes can change during compilation. This maps from Nodes in the graph pre-compilation to
|
||||
* equivalent nodes post-compilation.
|
||||
*/
|
||||
val oldToNewNodeMap: Map<Node, Node>
|
||||
)
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.compiler
|
||||
|
||||
// TODO: support other data types- expression assumes a float vector
|
||||
|
||||
open class Expression(
|
||||
open val symbol: String,
|
||||
open val dimensions: Int
|
||||
) {
|
||||
val r get() = conform(1)
|
||||
val rg get() = conform(2)
|
||||
val rgb get() = conform(3)
|
||||
val rgba get() = conform(4)
|
||||
|
||||
override fun toString() = symbol
|
||||
|
||||
fun conform(components: Int): Expression {
|
||||
if (dimensions == components) return this
|
||||
return if (dimensions > components) {
|
||||
shorten(components)
|
||||
} else {
|
||||
lengthen(components)
|
||||
}
|
||||
}
|
||||
|
||||
open fun lengthen(components: Int): Expression {
|
||||
val componentsToAdd = components - dimensions
|
||||
val extraComponents = ", 0.0".repeat(componentsToAdd)
|
||||
return Expression(symbol = "float$components($symbol$extraComponents)",
|
||||
dimensions = components)
|
||||
}
|
||||
|
||||
open fun shorten(components: Int): Expression {
|
||||
val swizzle = when (components) {
|
||||
1 -> ".r"
|
||||
2 -> ".rg"
|
||||
3 -> ".rgb"
|
||||
else -> ""
|
||||
}
|
||||
return Expression(symbol = "$symbol$swizzle", dimensions = components)
|
||||
}
|
||||
}
|
||||
|
||||
fun floatTypeName(dimensions: Int) = if (dimensions > 1) "float$dimensions" else "float"
|
||||
|
||||
private fun createFloatLiteral(dimensions: Int, value: Float): String {
|
||||
val zeroes = "$value, ".repeat(maxOf(dimensions - 1, 0)) + "$value"
|
||||
val typeName = floatTypeName(dimensions)
|
||||
return "$typeName($zeroes)"
|
||||
}
|
||||
|
||||
class Literal(dimensions: Int, private val value: Float = 0.0f) : Expression(
|
||||
dimensions = dimensions, symbol = createFloatLiteral(dimensions, value)) {
|
||||
|
||||
override fun shorten(components: Int) = Literal(dimensions = components, value = value)
|
||||
|
||||
override fun lengthen(components: Int) = Literal(dimensions = components, value = value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Make two expressions the same dimensionality by trimming the higher dimension expression.
|
||||
*/
|
||||
fun trim(a: Expression, b: Expression): Pair<Expression, Expression> {
|
||||
if (a.dimensions == b.dimensions) return Pair(a, b)
|
||||
val dimensions = Math.min(a.dimensions, b.dimensions)
|
||||
return if (a.dimensions < b.dimensions) {
|
||||
Pair(a, b.shorten(dimensions))
|
||||
} else {
|
||||
Pair(a.shorten(dimensions), b)
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.compiler;
|
||||
|
||||
import com.google.android.filament.tungsten.model.Graph;
|
||||
import com.google.android.filament.tungsten.model.Node;
|
||||
import com.google.android.filament.tungsten.model.Slot;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* GraphCompiler takes a a graph of connected NodeModels and generates filament shader code.
|
||||
*/
|
||||
public final class GraphCompiler {
|
||||
|
||||
public enum CodeSection {
|
||||
AFTER_PREPARE_MATERIAL,
|
||||
BEFORE_PREPARE_MATERIAL
|
||||
}
|
||||
|
||||
// Material function source text before the "prepareMaterial" call
|
||||
private StringBuilder mMaterialFunctionPrologue = new StringBuilder();
|
||||
|
||||
// Material function source text after the "prepareMaterial" call
|
||||
private StringBuilder mMaterialFunctionBodyBuilder = new StringBuilder();
|
||||
|
||||
private CodeSection mCurrentCodeSection = CodeSection.AFTER_PREPARE_MATERIAL;
|
||||
|
||||
private final List<String> mRequiredAttributes = new ArrayList<>();
|
||||
private String mShadingModel = "unlit";
|
||||
private final List<Parameter> mParameters = new ArrayList<>();
|
||||
private final Map<String, Integer> mParameterNumberMap = new HashMap<>();
|
||||
private final Map<Node.PropertyHandle, Parameter> mPropertyParameterMap = new HashMap<>();
|
||||
private final LinkedHashMap<String, String> mGlobalFunctions = new LinkedHashMap<>();
|
||||
private final Map<String, Integer> mVariableNameMap = new HashMap<>();
|
||||
|
||||
private final @NotNull Graph mGraph;
|
||||
private final @NotNull Node mRootNode;
|
||||
private final Map<Slot, Expression> mCompiledVariableMap = new HashMap<>();
|
||||
private final Set<Node> mUncompiledNodes;
|
||||
private boolean mShouldAppendCode = true;
|
||||
private final Map<Node, Node> mOldToNewNodeMap = new HashMap<>();
|
||||
private final List<String> validShadingModels =
|
||||
Arrays.asList("lit", "unlit", "cloth", "subsurface");
|
||||
|
||||
public GraphCompiler(@NotNull Graph graph) {
|
||||
mGraph = graph;
|
||||
mRootNode = Objects.requireNonNull(mGraph.getRootNode());
|
||||
mUncompiledNodes = new HashSet<>(graph.getNodes());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CompiledGraph compileGraph() {
|
||||
compileNode(mRootNode);
|
||||
|
||||
// Compile the rest of the nodes in the graph that aren't necessarily connected to the
|
||||
// root node. These nodes should not contribute any code to the material definition.
|
||||
mShouldAppendCode = false;
|
||||
while (!mUncompiledNodes.isEmpty()) {
|
||||
Node next = mUncompiledNodes.iterator().next();
|
||||
compileNode(next);
|
||||
}
|
||||
|
||||
String fragmentSection = GraphFormatter.formatFragmentSection(mGlobalFunctions.values(),
|
||||
mMaterialFunctionPrologue.toString(), mMaterialFunctionBodyBuilder.toString());
|
||||
String materialDefinition =
|
||||
GraphFormatter.formatMaterialSection(mRequiredAttributes, mParameters,
|
||||
mShadingModel) + fragmentSection;
|
||||
return new CompiledGraph(materialDefinition, mPropertyParameterMap, mCompiledVariableMap,
|
||||
mOldToNewNodeMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the Node connected to InputSlot and retrieve its Expression entry.
|
||||
* @return null, if the InputSlot is not connected to any OutputSlot, otherwise an Expression.
|
||||
*/
|
||||
@Nullable
|
||||
public Expression compileAndRetrieveExpression(@NotNull Node.InputSlot slot) {
|
||||
Node.OutputSlot outputSlot = mGraph.getOutputSlotConnectedToInput(slot);
|
||||
if (outputSlot == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if we've already compiled
|
||||
Expression compiledExpression = mCompiledVariableMap.get(outputSlot);
|
||||
if (compiledExpression != null) {
|
||||
return compiledExpression;
|
||||
}
|
||||
|
||||
// Compile the connected node
|
||||
Node connectedNode = mGraph.getNodeForOutputSlot(outputSlot);
|
||||
if (connectedNode == null) {
|
||||
throw new RuntimeException("Output slot references node that does not exist in graph.");
|
||||
}
|
||||
compileNode(connectedNode);
|
||||
|
||||
// Verify that the connected node has set it's output variable
|
||||
compiledExpression = mCompiledVariableMap.get(outputSlot);
|
||||
if (compiledExpression == null) {
|
||||
throw new RuntimeException(
|
||||
"Output node did not set an output Expression on output slot: " + outputSlot);
|
||||
}
|
||||
|
||||
return compiledExpression;
|
||||
}
|
||||
|
||||
public void setExpressionForSlot(@NotNull Slot slot, @NotNull Expression expression) {
|
||||
mCompiledVariableMap.put(slot, expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a NodeModel subclass to denote that the given vertex attribute is required.
|
||||
* The attribute will be added to the material source's "requires" section.
|
||||
*
|
||||
* @param requirement The name of the vertex attribute, such as "uv0"
|
||||
*/
|
||||
public void requireAttribute(String requirement) {
|
||||
if (!mRequiredAttributes.contains(requirement)) {
|
||||
mRequiredAttributes.add(requirement);
|
||||
}
|
||||
}
|
||||
|
||||
public void setShadingModel(String shadingModel) {
|
||||
if (validShadingModels.contains(shadingModel)) {
|
||||
mShadingModel = shadingModel;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a node's compile function to add a new material parameter.
|
||||
* The parameter will be added to the material source's "parameters" section.
|
||||
*
|
||||
* @param type The type of parameter
|
||||
* @return A String with the unique name of the parameter that the node should use in code.
|
||||
*/
|
||||
@NotNull
|
||||
public Parameter addParameter(@NotNull String type, @NotNull String name) {
|
||||
String parameterName = allocateNewParameterName(name);
|
||||
Parameter parameter = new Parameter(type, parameterName);
|
||||
mParameters.add(parameter);
|
||||
return parameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a material parameter with a Node's property. After the graph is compiled, this
|
||||
* mapping between parameters and properties is returned so that adjustments to a property
|
||||
* can affect the appropriate material parameter.
|
||||
*/
|
||||
public void associateParameterWithProperty(@NotNull Parameter parameter,
|
||||
@NotNull Node.PropertyHandle property) {
|
||||
mPropertyParameterMap.put(property, parameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a NodeModel subclass to allocate a new temporary variable for its use.
|
||||
* @return The symbol name of the variable.
|
||||
*/
|
||||
public String getNewTemporaryVariableName(String name) {
|
||||
int iterationNumber = mVariableNameMap.getOrDefault(name, 0);
|
||||
String result = name + iterationNumber;
|
||||
mVariableNameMap.put(name, ++iterationNumber);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a NodeModel subclass to add code to the material function's body.
|
||||
* @param code code to be concatenated to the material function body.
|
||||
*/
|
||||
public void addCodeToMaterialFunctionBody(String code) {
|
||||
if (!mShouldAppendCode) {
|
||||
return;
|
||||
}
|
||||
if (mCurrentCodeSection == CodeSection.AFTER_PREPARE_MATERIAL) {
|
||||
mMaterialFunctionBodyBuilder.append(code);
|
||||
} else if (mCurrentCodeSection == CodeSection.BEFORE_PREPARE_MATERIAL) {
|
||||
mMaterialFunctionPrologue.append(code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state of this GraphCompiler to output code to a specific section of the material
|
||||
* function body: either before the call to prepareMaterial(), or after.
|
||||
* The state persists until setCurrentCodeSection is called again.
|
||||
*/
|
||||
public void setCurrentCodeSection(CodeSection section) {
|
||||
mCurrentCodeSection = section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for calling addCodeToMaterialFunctionBody with a format String and
|
||||
* arguments.
|
||||
*/
|
||||
public void addCodeToMaterialFunctionBody(String format, Object... args) {
|
||||
addCodeToMaterialFunctionBody(String.format(format, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a NodeModel to add a global function to the material code.
|
||||
* @param requestedName The name the Node wishes to use for the function.
|
||||
* @return The resulting symbol name for the global function. The NodeModel should use this name
|
||||
* when supplying the function definition and for references to the function in shader code.
|
||||
*/
|
||||
public String allocateGlobalFunction(String requestedName, String scope) {
|
||||
// Mangle the function name
|
||||
String mangledName = requestedName + "_" + scope;
|
||||
if (!mGlobalFunctions.containsKey(mangledName)) {
|
||||
mGlobalFunctions.put(mangledName, "");
|
||||
}
|
||||
return mangledName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a NodeModule to provide a definition for a previously allocated global function.
|
||||
* @param symbolName The name of the function symbol returned by allocateGlobalFunction.
|
||||
* @param definition The complete source text of the function, using symbolName.
|
||||
*/
|
||||
public void provideFunctionDefinition(String symbolName, String definition) {
|
||||
mGlobalFunctions.put(symbolName, definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for providing a function definition that uses a format String and
|
||||
* arguments.
|
||||
*/
|
||||
public void provideFunctionDefinition(String symbolName, String format, Object... args) {
|
||||
provideFunctionDefinition(symbolName, String.format(format, args));
|
||||
}
|
||||
|
||||
private void compileNode(Node node) {
|
||||
mUncompiledNodes.remove(node);
|
||||
Node newNode = node.getCompileFunction().invoke(node, this);
|
||||
|
||||
// We've received a new node while compiling, take note of it.
|
||||
if (newNode != node) {
|
||||
mOldToNewNodeMap.put(node, newNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an integer to a parameter name to ensure globally-unique names.
|
||||
*/
|
||||
private String allocateNewParameterName(String name) {
|
||||
Integer parameterNumber =
|
||||
mParameterNumberMap.computeIfPresent(name, (s, integer) -> integer + 1);
|
||||
if (parameterNumber == null) {
|
||||
parameterNumber = 0;
|
||||
mParameterNumberMap.putIfAbsent(name, parameterNumber);
|
||||
}
|
||||
return name + parameterNumber;
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.compiler;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
final class GraphFormatter {
|
||||
|
||||
// Amount of spaces global functions are indented by
|
||||
private static final int FUNCTION_INDENT_AMOUNT = 4;
|
||||
|
||||
// Amount of spaces that the material function body is indented by
|
||||
private static final int BODY_CODE_INDENT_AMOUNT = 8;
|
||||
|
||||
private GraphFormatter() { }
|
||||
|
||||
static String formatFragmentSection(Collection<String> globalFunctions,
|
||||
String materialFunctionBodyPrologue, String materialFunctionBody) {
|
||||
StringBuilder functions = new StringBuilder();
|
||||
for (String functionBody : globalFunctions) {
|
||||
functions.append(indent(functionBody, FUNCTION_INDENT_AMOUNT));
|
||||
functions.append("\n");
|
||||
}
|
||||
return "fragment {\n"
|
||||
+ functions.toString()
|
||||
+ " void material(inout MaterialInputs material) {\n"
|
||||
+ formatMaterialFunctionBody(materialFunctionBodyPrologue ,materialFunctionBody)
|
||||
+ " }\n"
|
||||
+ "}";
|
||||
}
|
||||
|
||||
static String formatMaterialSection(Collection<String> attributes,
|
||||
Collection<Parameter> parameters, String shadingModel) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String attribute : attributes) {
|
||||
builder.append(indent(attribute, BODY_CODE_INDENT_AMOUNT)).append("\n");
|
||||
}
|
||||
String attributeText = builder.toString();
|
||||
String parameterText = formatParameters(parameters);
|
||||
return "material {\n"
|
||||
+ " name : \"\",\n"
|
||||
+ parameterText
|
||||
+ " requires : [\n"
|
||||
+ attributeText
|
||||
+ " ],\n"
|
||||
+ " shadingModel : \"" + shadingModel + "\"\n"
|
||||
+ "}\n"
|
||||
+ "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Indents each line of a string by amount number of spaces.
|
||||
*/
|
||||
static String indent(String s, int amount) {
|
||||
if (s == null || s.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
String spaces = String.join("", Collections.nCopies(amount, " "));
|
||||
String t = s.replaceAll("^", spaces); // indent the first line
|
||||
return t.replaceAll("\n(?!$)", "\n" + spaces); // indent remaining non-empty lines
|
||||
}
|
||||
|
||||
private static String formatMaterialFunctionBody(String prologue, String epilogue) {
|
||||
return indent(prologue, BODY_CODE_INDENT_AMOUNT)
|
||||
+ indent("prepareMaterial(material);\n", BODY_CODE_INDENT_AMOUNT)
|
||||
+ indent(epilogue, BODY_CODE_INDENT_AMOUNT);
|
||||
}
|
||||
|
||||
private static String formatParameters(Collection<Parameter> parameters) {
|
||||
if (parameters.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder builder = new StringBuilder("parameters : [\n");
|
||||
int i = 0;
|
||||
for (Parameter parameter : parameters) {
|
||||
builder.append(formatParameterSource(parameter));
|
||||
if (i++ < parameters.size() - 1) {
|
||||
builder.append(",");
|
||||
}
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.append("],\n");
|
||||
return indent(builder.toString(), 4);
|
||||
}
|
||||
|
||||
private static String formatParameterSource(Parameter p) {
|
||||
return indent("{\n"
|
||||
+ " type : " + p.getType() + ",\n"
|
||||
+ " name : " + p.getName() + "\n"
|
||||
+ "}", 4);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.compiler
|
||||
|
||||
import com.google.android.filament.tungsten.model.Node
|
||||
import com.google.android.filament.tungsten.model.NodeId
|
||||
import com.google.android.filament.tungsten.model.createAddNode
|
||||
import com.google.android.filament.tungsten.model.createDivideNode
|
||||
import com.google.android.filament.tungsten.model.createFloat2ConstantNode
|
||||
import com.google.android.filament.tungsten.model.createFloat3ConstantNode
|
||||
import com.google.android.filament.tungsten.model.createFloat3ParameterNode
|
||||
import com.google.android.filament.tungsten.model.createFloatConstantNode
|
||||
import com.google.android.filament.tungsten.model.createMultiplyNode
|
||||
import com.google.android.filament.tungsten.model.createPannerNode
|
||||
import com.google.android.filament.tungsten.model.createShaderNode
|
||||
import com.google.android.filament.tungsten.model.createSubtractNode
|
||||
import com.google.android.filament.tungsten.model.createTexCoordNode
|
||||
import com.google.android.filament.tungsten.model.createTextureSampleNode
|
||||
import com.google.android.filament.tungsten.model.createTimeNode
|
||||
import com.google.android.filament.tungsten.model.serialization.INodeFactory
|
||||
|
||||
class NodeRegistry : INodeFactory {
|
||||
|
||||
/**
|
||||
* label is a human-readable name used for menus
|
||||
* typeIdentifier is a string that uniquely identifiers the NodeModel class for serialization
|
||||
*/
|
||||
private data class NodeEntry(
|
||||
val label: String,
|
||||
val typeIdentifier: String,
|
||||
val factoryFunction: (id: NodeId) -> Node
|
||||
)
|
||||
|
||||
private val mNodes: List<NodeEntry>
|
||||
|
||||
val nodeLabelsForMenu: List<String>
|
||||
|
||||
init {
|
||||
mNodes = listOf(
|
||||
NodeEntry("Add", "add", createAddNode),
|
||||
NodeEntry("Subtract", "subtract", createSubtractNode),
|
||||
NodeEntry("Multiply", "multiply", createMultiplyNode),
|
||||
NodeEntry("Divide", "divide", createDivideNode),
|
||||
NodeEntry("Constant float", "floatConstant", createFloatConstantNode),
|
||||
NodeEntry("Constant float2", "float2Constant", createFloat2ConstantNode),
|
||||
NodeEntry("Constant float3", "float3Constant", createFloat3ConstantNode),
|
||||
NodeEntry("Float3 parameter", "float3Parameter", createFloat3ParameterNode),
|
||||
NodeEntry("Texture sampler", "textureSample", createTextureSampleNode),
|
||||
NodeEntry("Time", "time", createTimeNode),
|
||||
NodeEntry("Texture coordinates", "texCoord", createTexCoordNode),
|
||||
NodeEntry("Texture panner", "panner", createPannerNode),
|
||||
NodeEntry("Shader", "shader", createShaderNode)
|
||||
)
|
||||
nodeLabelsForMenu = mNodes
|
||||
// The ShaderNode should not be visible in menus
|
||||
.filter { node -> node.factoryFunction != createShaderNode }
|
||||
.map { node -> node.label }
|
||||
}
|
||||
|
||||
fun createNodeForLabel(label: String, id: NodeId): Node? {
|
||||
val entry = mNodes.find { node -> node.label == label }
|
||||
return entry?.factoryFunction?.invoke(id)
|
||||
}
|
||||
|
||||
override fun createNodeForTypeIdentifier(typeIdentifier: String, id: NodeId): Node? {
|
||||
val entry = mNodes.find { node -> node.typeIdentifier == typeIdentifier }
|
||||
return entry?.factoryFunction?.invoke(id)
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.compiler
|
||||
|
||||
data class Parameter(val type: String, val name: String)
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.model
|
||||
|
||||
import com.google.android.filament.tungsten.compiler.Expression
|
||||
import com.google.android.filament.tungsten.compiler.GraphCompiler
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
data class Connection(
|
||||
val outputSlot: Node.OutputSlot,
|
||||
val inputSlot: Node.InputSlot
|
||||
)
|
||||
|
||||
abstract class Slot
|
||||
|
||||
typealias NodeId = Int
|
||||
typealias CompileFunction = (Node, GraphCompiler) -> Node
|
||||
|
||||
/**
|
||||
* An immutable Node in the graph.
|
||||
* To modify properties of the node, use the automatically-generated .copy function.
|
||||
*/
|
||||
data class Node(
|
||||
// A unique node id to identify this node in a graph
|
||||
val id: NodeId,
|
||||
val x: Float = 0.0f,
|
||||
val y: Float = 0.0f,
|
||||
val type: String,
|
||||
|
||||
// A node's compile function generates code by calling methods on a GraphCompiler. The function
|
||||
// can return a new node if compilation has changed properties of the node itself.
|
||||
val compileFunction: CompileFunction = { n, _ -> n },
|
||||
|
||||
// Input and output slots of a node are represented by a String name.
|
||||
val outputSlots: List<String> = emptyList(),
|
||||
val inputSlots: List<String> = emptyList(),
|
||||
|
||||
val properties: List<Property<*>> = emptyList()
|
||||
) {
|
||||
|
||||
data class InputSlot(val nodeId: NodeId, val name: String) : Slot()
|
||||
|
||||
data class OutputSlot(val nodeId: NodeId, val name: String) : Slot() {
|
||||
|
||||
/**
|
||||
* This allows some semantic sugar to construct a Connection:
|
||||
* outputSlot to inputSlot
|
||||
*/
|
||||
infix fun to(other: InputSlot): Connection {
|
||||
return Connection(this, other)
|
||||
}
|
||||
}
|
||||
|
||||
data class PropertyHandle(val nodeId: NodeId, val name: String)
|
||||
|
||||
fun getPropertyHandle(name: String) = PropertyHandle(id, name)
|
||||
|
||||
fun getInputSlot(name: String) = InputSlot(id, name)
|
||||
|
||||
fun getOutputSlot(name: String) = OutputSlot(id, name)
|
||||
|
||||
fun nodeBySettingInputSlots(newInputs: List<String>) =
|
||||
if (inputSlots === newInputs) this else copy(inputSlots = newInputs)
|
||||
}
|
||||
|
||||
class ConnectionMapper {
|
||||
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>):
|
||||
Map<Node.InputSlot, Node.OutputSlot> {
|
||||
val graph = thisRef as Graph
|
||||
return graph.connections.associate { c ->
|
||||
c.inputSlot to c.outputSlot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NodeMapper {
|
||||
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): Map<NodeId, Node> {
|
||||
val graph = thisRef as Graph
|
||||
return graph.nodes.associateBy { n -> n.id }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An immutable graph.
|
||||
* To modify, use the generated .copy function or one of the graphBy* convenience functions.
|
||||
*/
|
||||
data class Graph(
|
||||
val nodes: List<Node> = emptyList(),
|
||||
val rootNodeId: NodeId? = null,
|
||||
val selection: List<NodeId> = emptyList(),
|
||||
val connections: List<Connection> = emptyList(),
|
||||
|
||||
// Maps from slots to their corresponding Expressions.
|
||||
val expressionMap: Map<Slot, Expression> = emptyMap()
|
||||
) {
|
||||
|
||||
/**
|
||||
* connectionMap maps InputSlots to connected OutputSlots and aids graph compilation by
|
||||
* efficiently answering the question: "is this input connected to any output?"
|
||||
* It is lazily-computed by ConnectionMapper when needed.
|
||||
*/
|
||||
private val connectionMap: Map<Node.InputSlot, Node.OutputSlot> by ConnectionMapper()
|
||||
|
||||
/**
|
||||
* Lazily computes a map from NodeId to Node used for efficient Node lookup.
|
||||
*/
|
||||
private val nodeMap: Map<NodeId, Node> by NodeMapper()
|
||||
|
||||
/**
|
||||
* Returns the next available NodeId for this graph.
|
||||
*/
|
||||
fun getNewNodeId() = nodes.size
|
||||
|
||||
fun getRootNode() = nodeMap[rootNodeId]
|
||||
|
||||
fun getNodeWithId(id: NodeId) = nodeMap[id]
|
||||
|
||||
fun getNodeProperty(property: Node.PropertyHandle) =
|
||||
nodeMap[property.nodeId]?.properties?.find { p -> p.name == property.name }
|
||||
|
||||
fun getOutputSlotConnectedToInput(slot: Node.InputSlot) = connectionMap[slot]
|
||||
|
||||
fun isNodeSelected(node: Node) = selection.contains(node.id)
|
||||
|
||||
fun getNodeForOutputSlot(slot: Node.OutputSlot) = nodeMap[slot.nodeId]
|
||||
|
||||
fun getSelectedNodes() = nodes.filter { n -> selection.contains(n.id) }
|
||||
|
||||
/**
|
||||
* The following convenience methods return a new copy of the graph with certain attributes
|
||||
* modified.
|
||||
*/
|
||||
|
||||
fun graphByAddingNode(node: Node) = this.copy(nodes = nodes + node)
|
||||
|
||||
fun graphByChangingSelection(selection: List<NodeId>) = this.copy(selection = selection)
|
||||
|
||||
fun graphByAddingNodeAtLocation(node: Node, x: Float, y: Float) =
|
||||
this.copy(nodes = nodes + node.copy(x = x, y = y))
|
||||
|
||||
fun graphByReplacingNode(oldNode: Node, newNode: Node) =
|
||||
graphByReplacingNodes(mapOf(oldNode to newNode))
|
||||
|
||||
fun graphByChangingProperty(property: Node.PropertyHandle, value: Property<*>): Graph {
|
||||
val node = nodeMap[property.nodeId] ?: return this
|
||||
val newProperties = node.properties.map {
|
||||
p -> if (p.name == property.name) value else p
|
||||
}
|
||||
return graphByReplacingNode(node, node.copy(properties = newProperties))
|
||||
}
|
||||
|
||||
fun graphByMovingNode(node: Node, x: Float, y: Float) =
|
||||
graphByReplacingNode(node, node.copy(x = x, y = y))
|
||||
|
||||
fun graphByReplacingNodes(nodeMap: Map<Node, Node>): Graph {
|
||||
val newNodes = nodes.map { n -> nodeMap[n] ?: n }
|
||||
return this.copy(nodes = newNodes)
|
||||
}
|
||||
|
||||
fun graphByFormingConnection(connection: Connection) =
|
||||
this.copy(connections = connections + connection)
|
||||
|
||||
fun graphByRemovingConnection(connection: Connection) =
|
||||
this.copy(connections = connections - connection)
|
||||
|
||||
fun graphBySettingExpressionMap(expressionMap: Map<Slot, Expression>) =
|
||||
this.copy(expressionMap = expressionMap)
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.model
|
||||
|
||||
import com.google.android.filament.tungsten.compiler.Expression
|
||||
import com.google.android.filament.tungsten.compiler.Literal
|
||||
import com.google.android.filament.tungsten.compiler.floatTypeName
|
||||
import com.google.android.filament.tungsten.compiler.trim
|
||||
|
||||
data class ResolvedExpressions(
|
||||
val a: Expression,
|
||||
val b: Expression,
|
||||
val resultDimensionality: Int
|
||||
)
|
||||
|
||||
// A function that takes two expressions and prepares them for a math operation.
|
||||
typealias ExpressionResolver = (Expression?, Expression?) -> ResolvedExpressions
|
||||
|
||||
typealias Operation = (Expression, Expression) -> String
|
||||
|
||||
/**
|
||||
* Create a math node compile function with a given operation.
|
||||
* Assumes two input slots, "a" and "b", and a single output, "out".
|
||||
*/
|
||||
private fun createMathCompileFunction(
|
||||
expressionResolver: ExpressionResolver,
|
||||
operation: Operation,
|
||||
temporaryVariable: String
|
||||
): CompileFunction = { node, compiler ->
|
||||
val a = compiler.compileAndRetrieveExpression(node.getInputSlot("a"))
|
||||
val b = compiler.compileAndRetrieveExpression(node.getInputSlot("b"))
|
||||
|
||||
val (aExpr, bExpr, resultDimensionality) = expressionResolver(a, b)
|
||||
|
||||
compiler.setExpressionForSlot(node.getInputSlot("a"), aExpr)
|
||||
compiler.setExpressionForSlot(node.getInputSlot("b"), bExpr)
|
||||
|
||||
val temp = compiler.getNewTemporaryVariableName(temporaryVariable)
|
||||
compiler.addCodeToMaterialFunctionBody(
|
||||
"${floatTypeName(resultDimensionality)} $temp = ${operation(aExpr, bExpr)};\n")
|
||||
|
||||
val output = node.getOutputSlot("out")
|
||||
compiler.setExpressionForSlot(output, Expression(temp, resultDimensionality))
|
||||
|
||||
node
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims two expressions to be the same dimensionality, preferring the one with fewer dimensions.
|
||||
* If one of the inputs is a single float, neither input is modified, allowing for a scalar
|
||||
* operations.
|
||||
*/
|
||||
internal fun resolveInputExpressions(a: Expression?, b: Expression?): ResolvedExpressions {
|
||||
val aExpr = a ?: Literal(4)
|
||||
val bExpr = b ?: Literal(4)
|
||||
val (trimmedA, trimmedB) = if (aExpr.dimensions > 1 && bExpr.dimensions > 1) {
|
||||
trim(aExpr, bExpr)
|
||||
} else {
|
||||
Pair(aExpr, bExpr)
|
||||
}
|
||||
return ResolvedExpressions(trimmedA, trimmedB, maxOf(trimmedA.dimensions, trimmedB.dimensions))
|
||||
}
|
||||
|
||||
private val subtractNodeCompile = createMathCompileFunction(
|
||||
::resolveInputExpressions,
|
||||
{ a, b -> "$a - $b" },
|
||||
"subtract"
|
||||
)
|
||||
|
||||
private val addNodeCompile = createMathCompileFunction(
|
||||
::resolveInputExpressions,
|
||||
{ a, b -> "$a + $b" },
|
||||
"add"
|
||||
)
|
||||
|
||||
private val multiplyNodeCompile = createMathCompileFunction(
|
||||
::resolveInputExpressions,
|
||||
{ a, b -> "$a * $b" },
|
||||
"multiply"
|
||||
)
|
||||
|
||||
private val divideNodeCompile = createMathCompileFunction(
|
||||
::resolveInputExpressions,
|
||||
{ a, b -> "$a / $b" },
|
||||
"divide"
|
||||
)
|
||||
|
||||
val createAddNode = fun(id: NodeId) =
|
||||
Node(
|
||||
id = id,
|
||||
type = "add",
|
||||
compileFunction = addNodeCompile,
|
||||
inputSlots = listOf("a", "b"),
|
||||
outputSlots = listOf("out")
|
||||
)
|
||||
|
||||
val createSubtractNode = fun(id: NodeId) =
|
||||
Node(
|
||||
id = id,
|
||||
type = "subtract",
|
||||
compileFunction = subtractNodeCompile,
|
||||
inputSlots = listOf("a", "b"),
|
||||
outputSlots = listOf("out")
|
||||
)
|
||||
|
||||
val createMultiplyNode = fun(id: NodeId) =
|
||||
Node(
|
||||
id = id,
|
||||
type = "multiply",
|
||||
compileFunction = multiplyNodeCompile,
|
||||
inputSlots = listOf("a", "b"),
|
||||
outputSlots = listOf("out")
|
||||
)
|
||||
|
||||
val createDivideNode = fun(id: NodeId) =
|
||||
Node(
|
||||
id = id,
|
||||
type = "divide",
|
||||
compileFunction = divideNodeCompile,
|
||||
inputSlots = listOf("a", "b"),
|
||||
outputSlots = listOf("out")
|
||||
)
|
||||
@@ -1,349 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.model
|
||||
|
||||
import com.google.android.filament.tungsten.compiler.GraphCompiler
|
||||
import com.google.android.filament.tungsten.compiler.Expression
|
||||
import com.google.android.filament.tungsten.compiler.Literal
|
||||
import com.google.android.filament.tungsten.properties.ColorChooser
|
||||
import com.google.android.filament.tungsten.properties.FloatSlider
|
||||
import com.google.android.filament.tungsten.properties.MultipleChoice
|
||||
import com.google.android.filament.tungsten.properties.TextureFileChooser
|
||||
|
||||
private val UNLIT_INPUTS = listOf("baseColor", "emissive")
|
||||
private val LIT_INPUTS = listOf("normal", "baseColor", "metallic", "roughness", "reflectance",
|
||||
"clearCoat", "clearCoatRoughness", "anisotropy", "anisotropyDirection", "ambientOcclusion",
|
||||
"emissive")
|
||||
|
||||
private val inputSlotsForShadingModel = { shadingModel: String ->
|
||||
when (shadingModel) {
|
||||
"unlit" -> UNLIT_INPUTS
|
||||
"lit" -> LIT_INPUTS
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private val shaderNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
val shadingModel = (node.properties[0].value as StringValue).value
|
||||
compiler.setShadingModel(shadingModel)
|
||||
|
||||
val compileMaterialInput = { name: String, dimensions: Int ->
|
||||
// The "normal" input is special, and its code must go before the call to prepareMaterial().
|
||||
if (name == "normal") {
|
||||
compiler.setCurrentCodeSection(GraphCompiler.CodeSection.BEFORE_PREPARE_MATERIAL)
|
||||
}
|
||||
|
||||
val inputSlot = node.getInputSlot(name)
|
||||
val connectedExpression = compiler.compileAndRetrieveExpression(inputSlot)
|
||||
val isConnected = connectedExpression != null
|
||||
val expression = connectedExpression ?: Literal(dimensions)
|
||||
|
||||
// Conform the expression to match the dimensionality of the material input.
|
||||
val conformedExpression = expression.conform(dimensions)
|
||||
|
||||
if (isConnected) {
|
||||
compiler.addCodeToMaterialFunctionBody("material.$name = $conformedExpression;\n")
|
||||
}
|
||||
compiler.setExpressionForSlot(inputSlot, conformedExpression)
|
||||
|
||||
compiler.setCurrentCodeSection(GraphCompiler.CodeSection.AFTER_PREPARE_MATERIAL)
|
||||
}
|
||||
|
||||
// Compile inputs common to all shading models.
|
||||
val compileCommonInputs = {
|
||||
compileMaterialInput("baseColor", 4)
|
||||
compileMaterialInput("emissive", 4)
|
||||
}
|
||||
|
||||
// Compile inputs unique to the "lit" shading model.
|
||||
val compileLitInputs = {
|
||||
compileMaterialInput("metallic", 1)
|
||||
compileMaterialInput("roughness", 1)
|
||||
compileMaterialInput("reflectance", 1)
|
||||
compileMaterialInput("clearCoat", 1)
|
||||
compileMaterialInput("clearCoatRoughness", 1)
|
||||
compileMaterialInput("anisotropy", 1)
|
||||
compileMaterialInput("anisotropyDirection", 3)
|
||||
compileMaterialInput("ambientOcclusion", 1)
|
||||
}
|
||||
|
||||
if (shadingModel == "lit") {
|
||||
// "normal" must be compiled first. If it has any dependencies that are connected to other
|
||||
// inputs, we need to ensure their code comes before prepareMaterial()
|
||||
compileMaterialInput("normal", 3)
|
||||
}
|
||||
compileCommonInputs()
|
||||
when (shadingModel) {
|
||||
"lit" -> compileLitInputs()
|
||||
}
|
||||
|
||||
// If the input slots have changed, return a new node.
|
||||
return node.nodeBySettingInputSlots(inputSlotsForShadingModel(shadingModel))
|
||||
}
|
||||
|
||||
private val constantFloat3NodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
val outputSlot = node.getOutputSlot("result")
|
||||
|
||||
val x = (node.properties[0].value as FloatValue)
|
||||
val y = (node.properties[1].value as FloatValue)
|
||||
val z = (node.properties[2].value as FloatValue)
|
||||
|
||||
val outputVariable = compiler.getNewTemporaryVariableName("float3Constant")
|
||||
compiler.addCodeToMaterialFunctionBody(
|
||||
"float3 $outputVariable = float3(${x.v}, ${y.v}, ${z.v});\n")
|
||||
|
||||
compiler.setExpressionForSlot(outputSlot, Expression(outputVariable, 3))
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
private val float3ParameterNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
val parameter = compiler.addParameter("float3", "float3Parameter")
|
||||
compiler.associateParameterWithProperty(parameter, node.getPropertyHandle("value"))
|
||||
compiler.setExpressionForSlot(node.getOutputSlot("result"),
|
||||
Expression("materialParams.${parameter.name}", 3))
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
private val constantFloatNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
val value = (node.properties[0].value as FloatValue)
|
||||
|
||||
val outputVariable = compiler.getNewTemporaryVariableName("floatConstant")
|
||||
compiler.addCodeToMaterialFunctionBody("float $outputVariable = ${value.v};\n")
|
||||
|
||||
val outputSlot = node.getOutputSlot("out")
|
||||
compiler.setExpressionForSlot(outputSlot, Expression(outputVariable, 1))
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
private val constantFloat2NodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
val outputSlot = node.getOutputSlot("result")
|
||||
|
||||
val x = (node.properties[0].value as FloatValue)
|
||||
val y = (node.properties[1].value as FloatValue)
|
||||
|
||||
val outputVariable = compiler.getNewTemporaryVariableName("float2Constant")
|
||||
compiler.addCodeToMaterialFunctionBody(
|
||||
"float2 $outputVariable = float2(${x.v}, ${y.v});\n")
|
||||
|
||||
compiler.setExpressionForSlot(outputSlot, Expression(outputVariable, 2))
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* Process UV inputs, defaulting to getUV0() if nothing is connected.
|
||||
*/
|
||||
private fun resolveUvInput(input: Expression?, compiler: GraphCompiler): Expression {
|
||||
return if (input != null) {
|
||||
input.rg
|
||||
} else {
|
||||
compiler.requireAttribute("uv0")
|
||||
Expression("getUV0()", 2)
|
||||
}
|
||||
}
|
||||
|
||||
private val textureSampleCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
val parameter = compiler.addParameter("sampler2d", "texture")
|
||||
compiler.associateParameterWithProperty(parameter, node.getPropertyHandle("textureSource"))
|
||||
|
||||
// If nothing is connected to the UV input, default to getUV0()
|
||||
val uvInput = compiler.compileAndRetrieveExpression(node.getInputSlot("uv"))
|
||||
val uvs = resolveUvInput(uvInput, compiler)
|
||||
compiler.setExpressionForSlot(node.getInputSlot("uv"), uvs)
|
||||
|
||||
val outputVariable = compiler.getNewTemporaryVariableName("textureSample")
|
||||
compiler.addCodeToMaterialFunctionBody(
|
||||
"float4 $outputVariable = texture(materialParams_${parameter.name}, $uvs);\n", 4)
|
||||
compiler.setExpressionForSlot(node.getOutputSlot("out"),
|
||||
Expression(outputVariable, 4))
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
private val timeNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
compiler.setExpressionForSlot(node.getOutputSlot("out"), Expression("getTime()", 1))
|
||||
return node
|
||||
}
|
||||
|
||||
private val texCoordNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
compiler.requireAttribute("uv0")
|
||||
compiler.setExpressionForSlot(node.getOutputSlot("out"), Expression("getUV0()", 2))
|
||||
return node
|
||||
}
|
||||
|
||||
private val pannerNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
|
||||
val panFunction = compiler.allocateGlobalFunction("pan", "panner")
|
||||
compiler.provideFunctionDefinition(panFunction, """
|
||||
float2 $panFunction(float2 coords, float2 speed) {
|
||||
float2 invSpeed = 1.0 / speed;
|
||||
float2 offset = mod(float2(getTime(), getTime()), invSpeed) / invSpeed;
|
||||
float2 panned = coords + offset;
|
||||
return float2(fract(panned.x), fract(panned.y));
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
val uvInput = compiler.compileAndRetrieveExpression(node.getInputSlot("uv"))
|
||||
val uvs = resolveUvInput(uvInput, compiler)
|
||||
|
||||
val speedSlot = node.getInputSlot("speed")
|
||||
val speedInput = compiler.compileAndRetrieveExpression(node.getInputSlot("speed"))
|
||||
?: Literal(2, 1.0f)
|
||||
compiler.setExpressionForSlot(node.getInputSlot("uv"), uvs)
|
||||
compiler.setExpressionForSlot(speedSlot, speedInput)
|
||||
|
||||
val outputExpression = "$panFunction(${uvs}, $speedInput)"
|
||||
compiler.setExpressionForSlot(node.getOutputSlot("out"), Expression(outputExpression, 2))
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
val createTextureSampleNode = fun(id: NodeId) =
|
||||
Node(
|
||||
id = id,
|
||||
type = "textureSample",
|
||||
compileFunction = textureSampleCompile,
|
||||
inputSlots = listOf("uv"),
|
||||
outputSlots = listOf("out"),
|
||||
properties = listOf(Property(
|
||||
name = "textureSource",
|
||||
value = TextureFile(),
|
||||
type = PropertyType.MATERIAL_PARAMETER,
|
||||
editorFactory = ::TextureFileChooser
|
||||
))
|
||||
)
|
||||
|
||||
val createFloat3ConstantNode = fun(id: NodeId): Node {
|
||||
return Node(
|
||||
id = id,
|
||||
type = "float3Constant",
|
||||
compileFunction = constantFloat3NodeCompile,
|
||||
outputSlots = listOf("result"),
|
||||
properties = listOf(
|
||||
Property(
|
||||
name = "x",
|
||||
value = FloatValue(),
|
||||
editorFactory = ::FloatSlider
|
||||
),
|
||||
Property(
|
||||
name = "y",
|
||||
value = FloatValue(),
|
||||
editorFactory = ::FloatSlider
|
||||
),
|
||||
Property(
|
||||
name = "z",
|
||||
value = FloatValue(),
|
||||
editorFactory = ::FloatSlider
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val createFloat3ParameterNode = fun(id: NodeId): Node {
|
||||
return Node(
|
||||
id = id,
|
||||
type = "float3Parameter",
|
||||
compileFunction = float3ParameterNodeCompile,
|
||||
outputSlots = listOf("result"),
|
||||
properties = listOf(Property(
|
||||
name = "value",
|
||||
value = Float3(),
|
||||
type = PropertyType.MATERIAL_PARAMETER,
|
||||
editorFactory = ::ColorChooser))
|
||||
)
|
||||
}
|
||||
|
||||
val createFloatConstantNode = fun(id: NodeId) =
|
||||
Node(
|
||||
id = id,
|
||||
type = "floatConstant",
|
||||
compileFunction = constantFloatNodeCompile,
|
||||
outputSlots = listOf("out"),
|
||||
properties = listOf(Property(
|
||||
name = "value",
|
||||
value = FloatValue(),
|
||||
editorFactory = ::FloatSlider
|
||||
))
|
||||
)
|
||||
|
||||
val createFloat2ConstantNode = fun(id: NodeId): Node {
|
||||
return Node(
|
||||
id = id,
|
||||
type = "float2Constant",
|
||||
compileFunction = constantFloat2NodeCompile,
|
||||
outputSlots = listOf("result"),
|
||||
properties = listOf(
|
||||
Property(
|
||||
name = "x",
|
||||
value = FloatValue(),
|
||||
editorFactory = ::FloatSlider
|
||||
),
|
||||
Property(
|
||||
name = "y",
|
||||
value = FloatValue(),
|
||||
editorFactory = ::FloatSlider
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val createTimeNode = fun(id: NodeId): Node =
|
||||
Node(
|
||||
id = id,
|
||||
type = "time",
|
||||
compileFunction = timeNodeCompile,
|
||||
outputSlots = listOf("out")
|
||||
)
|
||||
|
||||
val createTexCoordNode = fun(id: NodeId): Node =
|
||||
Node(
|
||||
id = id,
|
||||
type = "texCoord",
|
||||
compileFunction = texCoordNodeCompile,
|
||||
outputSlots = listOf("out")
|
||||
)
|
||||
|
||||
val createPannerNode = fun(id: NodeId): Node =
|
||||
Node(
|
||||
id = id,
|
||||
type = "panner",
|
||||
compileFunction = pannerNodeCompile,
|
||||
inputSlots = listOf("uv", "speed"),
|
||||
outputSlots = listOf("out")
|
||||
)
|
||||
|
||||
val createShaderNode = fun(id: NodeId): Node {
|
||||
return Node(
|
||||
id = id,
|
||||
type = "shader",
|
||||
compileFunction = shaderNodeCompile,
|
||||
inputSlots = inputSlotsForShadingModel("unlit"),
|
||||
properties = listOf(Property(
|
||||
name = "materialModel",
|
||||
value = StringValue("unlit"),
|
||||
editorFactory = { MultipleChoice(it, listOf("lit", "unlit")) })))
|
||||
}
|
||||
|
||||
object GraphInitializer {
|
||||
|
||||
fun getInitialGraphState(): Graph {
|
||||
val shaderNode = createShaderNode(0)
|
||||
return Graph(nodes = listOf(shaderNode), rootNodeId = 0)
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.model
|
||||
|
||||
import com.google.android.filament.MaterialInstance
|
||||
import com.google.android.filament.TextureSampler
|
||||
import com.google.android.filament.tungsten.Filament
|
||||
import com.google.android.filament.tungsten.properties.PropertyEditor
|
||||
import com.google.android.filament.tungsten.texture.TextureCache
|
||||
import com.google.android.filament.tungsten.texture.TextureUtils
|
||||
import java.io.File
|
||||
|
||||
sealed class PropertyValue {
|
||||
|
||||
open fun applyToMaterialInstance(materialInstance: MaterialInstance, name: String) { }
|
||||
|
||||
/**
|
||||
* Serialize this value into a Kotlin List, Map, String, or Number
|
||||
*/
|
||||
abstract fun serialize(): Any?
|
||||
|
||||
/**
|
||||
* Deserialize into a new PropertyValue
|
||||
*/
|
||||
abstract fun deserialize(value: Any): PropertyValue
|
||||
}
|
||||
|
||||
data class FloatValue(val v: Float = 0.0f) : PropertyValue() {
|
||||
|
||||
override fun applyToMaterialInstance(materialInstance: MaterialInstance, name: String) {
|
||||
materialInstance.setParameter(name, v)
|
||||
}
|
||||
|
||||
override fun serialize() = v
|
||||
|
||||
override fun deserialize(value: Any): PropertyValue {
|
||||
if (value !is Number) return this
|
||||
return FloatValue(value.toFloat())
|
||||
}
|
||||
}
|
||||
|
||||
data class Float3(val x: Float = 0.0f, val y: Float = 0.0f, val z: Float = 0.0f) : PropertyValue() {
|
||||
|
||||
override fun applyToMaterialInstance(materialInstance: MaterialInstance, name: String) {
|
||||
materialInstance.setParameter(name, x, y, z)
|
||||
}
|
||||
|
||||
override fun serialize() = listOf(x, y, z)
|
||||
|
||||
override fun deserialize(value: Any): Float3 {
|
||||
if (value !is List<*> || value.size < 3) return this
|
||||
val (x, y, z) = value
|
||||
if (x !is Number || y !is Number || z !is Number) return this
|
||||
return Float3(x.toFloat(), y.toFloat(), z.toFloat())
|
||||
}
|
||||
}
|
||||
|
||||
data class StringValue(val value: String) : PropertyValue() {
|
||||
|
||||
override fun serialize() = value
|
||||
|
||||
override fun deserialize(value: Any): StringValue {
|
||||
if (value !is String) return this
|
||||
return StringValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
data class TextureFile(
|
||||
val file: File? = null,
|
||||
val colorSpace: TextureUtils.ColorSpaceStrategy =
|
||||
TextureUtils.ColorSpaceStrategy.USE_FILE_PROFILE
|
||||
) : PropertyValue() {
|
||||
|
||||
override fun serialize(): Any? {
|
||||
file ?: return null
|
||||
return mapOf(
|
||||
"path" to file.canonicalPath,
|
||||
"colorSpace" to colorSpace.name
|
||||
)
|
||||
}
|
||||
|
||||
private val textureFuture = if (file != null) {
|
||||
TextureCache.getTextureForFile(file, colorSpace)
|
||||
} else {
|
||||
TextureCache.getDefaultTexture()
|
||||
}
|
||||
|
||||
override fun deserialize(value: Any): PropertyValue {
|
||||
if (value !is Map<*, *>) return this
|
||||
|
||||
val path = value["path"] as? String
|
||||
val file = if (path != null) {
|
||||
File(path)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val colorSpaceString = value["colorSpace"] as? String
|
||||
val colorSpace = if (colorSpaceString != null) {
|
||||
TextureUtils.ColorSpaceStrategy.valueOf(colorSpaceString)
|
||||
} else {
|
||||
TextureUtils.ColorSpaceStrategy.USE_FILE_PROFILE
|
||||
}
|
||||
|
||||
return TextureFile(file, colorSpace)
|
||||
}
|
||||
|
||||
override fun applyToMaterialInstance(materialInstance: MaterialInstance, name: String) {
|
||||
textureFuture.thenApply { texture ->
|
||||
Filament.getInstance().assertIsFilamentThread()
|
||||
val sampler = TextureSampler()
|
||||
materialInstance.setParameter(name, texture, sampler)
|
||||
}.exceptionally { e ->
|
||||
println("Error loading texture: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class PropertyType {
|
||||
GRAPH_PROPERTY,
|
||||
MATERIAL_PARAMETER
|
||||
}
|
||||
|
||||
data class Property<T : PropertyValue>(
|
||||
val name: String,
|
||||
val value: T,
|
||||
val type: PropertyType = PropertyType.GRAPH_PROPERTY,
|
||||
|
||||
// Construct an appropriate PropertyEditor for this property.
|
||||
val editorFactory: (T) -> PropertyEditor
|
||||
) {
|
||||
|
||||
fun callEditorFactory(): PropertyEditor = this.editorFactory.invoke(value)
|
||||
}
|
||||
|
||||
fun <T : PropertyValue> copyPropertyWithValue(
|
||||
p: Property<T>,
|
||||
value: PropertyValue
|
||||
): Property<*> =
|
||||
Property(name = p.name, value = value as T, type = p.type, editorFactory = p.editorFactory)
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.model.serialization
|
||||
|
||||
import com.google.android.filament.tungsten.model.NodeId
|
||||
import java.awt.Point
|
||||
|
||||
data class NodeSchema(
|
||||
val type: String,
|
||||
val id: NodeId,
|
||||
val position: Point? = Point(0, 0),
|
||||
val properties: Map<String, Any>?
|
||||
)
|
||||
|
||||
data class SlotSchema(val id: NodeId, val name: String)
|
||||
|
||||
data class ConnectionSchema(val from: SlotSchema, val to: SlotSchema) {
|
||||
|
||||
constructor(fromId: NodeId, fromSlot: String, toId: NodeId, toSlot: String):
|
||||
this(SlotSchema(fromId, fromSlot), SlotSchema(toId, toSlot))
|
||||
}
|
||||
|
||||
data class GraphSchema(
|
||||
val nodes: List<NodeSchema>,
|
||||
val rootNode: NodeId?,
|
||||
val connections: List<ConnectionSchema>,
|
||||
val version: String,
|
||||
val editor: Map<String, Any> = emptyMap()
|
||||
)
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.model.serialization
|
||||
|
||||
object GraphFile {
|
||||
|
||||
fun addToolBlockToMaterialFile(materialFile: String, toolSectionContents: String): String {
|
||||
return "$materialFile\n\ntool {\n$toolSectionContents\n}"
|
||||
}
|
||||
|
||||
fun extractToolBlockFromMaterialFile(fileContents: String): String {
|
||||
val result = "tool\\s*\\{(.*)}"
|
||||
.toRegex(RegexOption.DOT_MATCHES_ALL)
|
||||
.find(fileContents)?.groupValues?.get(1)
|
||||
return result ?: ""
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.model.serialization
|
||||
|
||||
import com.google.android.filament.tungsten.model.Connection
|
||||
import com.google.android.filament.tungsten.model.Graph
|
||||
import com.google.android.filament.tungsten.model.Node
|
||||
import com.google.android.filament.tungsten.model.NodeId
|
||||
import com.google.android.filament.tungsten.model.copyPropertyWithValue
|
||||
import java.awt.Point
|
||||
|
||||
/**
|
||||
* An interface used by GraphSerializer that maps classes of NodeModels into String type identifiers
|
||||
* for serialization. Given a type identifier, it can instantiate a new NodeModel of the correct
|
||||
* type.
|
||||
*/
|
||||
interface INodeFactory {
|
||||
|
||||
fun createNodeForTypeIdentifier(typeIdentifier: String, id: NodeId): Node?
|
||||
}
|
||||
|
||||
const val GRAPH_VERSION = "0.1"
|
||||
|
||||
object GraphSerializer {
|
||||
|
||||
fun serialize(
|
||||
graph: Graph,
|
||||
editorData: Map<String, Any>,
|
||||
serializer: ISerializer = JsonSerializer()
|
||||
): String {
|
||||
val nodesToSerialize = graph.nodes.map { n ->
|
||||
val properties = n.properties.mapNotNull {
|
||||
val serialized = it.value.serialize()
|
||||
if (serialized == null) null else it.name to serialized
|
||||
}.toMap()
|
||||
NodeSchema(n.type, n.id, Point(n.x.toInt(), n.y.toInt()), properties)
|
||||
}
|
||||
|
||||
val connectionsToSerialize = graph.connections.map { c ->
|
||||
val fromId = c.outputSlot.nodeId
|
||||
val fromSlot = c.outputSlot.name
|
||||
val toId = c.inputSlot.nodeId
|
||||
val toSlot = c.inputSlot.name
|
||||
ConnectionSchema(fromId, fromSlot, toId, toSlot)
|
||||
}
|
||||
|
||||
return serializer.serialize(
|
||||
GraphSchema(nodesToSerialize, graph.rootNodeId, connectionsToSerialize,
|
||||
GRAPH_VERSION, editorData)
|
||||
)
|
||||
}
|
||||
|
||||
fun deserialize(
|
||||
data: String,
|
||||
nodeFactory: INodeFactory,
|
||||
deserializer: IDeserializer = JsonDeserializer()
|
||||
): Pair<Graph, Map<String, Any>> {
|
||||
val (nodes, rootNode, connections, _, editor) = deserializer.deserialize(data)
|
||||
|
||||
val nodesInGraph = nodes.mapNotNull { n ->
|
||||
val newNode = nodeFactory.createNodeForTypeIdentifier(n.type, n.id)
|
||||
val position = n.position ?: Point(0, 0)
|
||||
if (newNode == null) return@mapNotNull null
|
||||
// Allow properties specified in the serialized data to override default properties
|
||||
// of the node.
|
||||
val overridenProperties = newNode.properties.map { p ->
|
||||
val override = n.properties?.get(p.name) ?: p
|
||||
val newValue = p.value.deserialize(override)
|
||||
copyPropertyWithValue(p, newValue)
|
||||
}
|
||||
newNode.copy(
|
||||
x = position.x.toFloat(),
|
||||
y = position.y.toFloat(),
|
||||
properties = overridenProperties)
|
||||
}
|
||||
|
||||
val nodeMap = nodesInGraph.associateBy { n -> n.id }
|
||||
|
||||
val connectionsInGraph = connections.mapNotNull { c ->
|
||||
// Only add connections if they refer to valid nodes in the graph
|
||||
if (c.from.id in nodeMap && c.to.id in nodeMap)
|
||||
Connection(Node.OutputSlot(c.from.id, c.from.name),
|
||||
Node.InputSlot(c.to.id, c.to.name)) else null
|
||||
}
|
||||
|
||||
val graph = Graph(nodes = nodesInGraph, rootNodeId = rootNode,
|
||||
connections = connectionsInGraph)
|
||||
return Pair(graph, editor)
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.model.serialization
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonSerializer
|
||||
|
||||
interface ISerializer {
|
||||
|
||||
fun serialize(graph: GraphSchema): String
|
||||
}
|
||||
|
||||
interface IDeserializer {
|
||||
|
||||
fun deserialize(data: String): GraphSchema
|
||||
}
|
||||
|
||||
// Serialize Slot as an array: [5, "foobar"]
|
||||
val slotSerializer: JsonSerializer<SlotSchema> = JsonSerializer { src, _, _ ->
|
||||
val pairArray = JsonArray()
|
||||
pairArray.add(src.id)
|
||||
pairArray.add(src.name)
|
||||
pairArray
|
||||
}
|
||||
|
||||
// Deserialize Slot from an array: [5, "foobar"]
|
||||
val slotDeserializer: JsonDeserializer<SlotSchema> = JsonDeserializer { element, _, _ ->
|
||||
val o = element.asJsonArray
|
||||
val id = o.get(0).asInt
|
||||
val slot = o.get(1).asString
|
||||
SlotSchema(id, slot)
|
||||
}
|
||||
|
||||
class JsonSerializer : ISerializer {
|
||||
|
||||
override fun serialize(graph: GraphSchema): String {
|
||||
val gsonBuilder = GsonBuilder()
|
||||
gsonBuilder.registerTypeAdapter(SlotSchema::class.java, slotSerializer)
|
||||
val gson = gsonBuilder.create()
|
||||
return gson.toJson(graph)
|
||||
}
|
||||
}
|
||||
|
||||
class JsonDeserializer : IDeserializer {
|
||||
|
||||
override fun deserialize(data: String): GraphSchema {
|
||||
val gsonBuilder = GsonBuilder()
|
||||
gsonBuilder.registerTypeAdapter(SlotSchema::class.java, slotDeserializer)
|
||||
val gson = gsonBuilder.create()
|
||||
return gson.fromJson(data, GraphSchema::class.java)
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.properties
|
||||
|
||||
import com.google.android.filament.tungsten.model.Node
|
||||
import com.google.android.filament.tungsten.model.Property
|
||||
|
||||
interface IPropertiesPresenter {
|
||||
|
||||
fun propertyChanged(handle: Node.PropertyHandle, property: Property<*>)
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.properties
|
||||
|
||||
import com.google.android.filament.tungsten.model.Node
|
||||
import com.google.android.filament.tungsten.model.NodeId
|
||||
import com.google.android.filament.tungsten.model.copyPropertyWithValue
|
||||
import javax.swing.JPanel
|
||||
|
||||
class PropertiesPanel : JPanel() {
|
||||
|
||||
var presenter: IPropertiesPresenter? = null
|
||||
|
||||
private val editorCache: MutableMap<NodeId, List<PropertyEditor>> = mutableMapOf()
|
||||
|
||||
fun showNone() {
|
||||
removeAll()
|
||||
repaint()
|
||||
revalidate()
|
||||
}
|
||||
|
||||
fun showPropertiesForNode(node: Node) {
|
||||
val p = presenter ?: return
|
||||
// TODO: every time a node is selected, we recreate the property panel for its
|
||||
// properties. We could probably cache this somewhere and show / hide it to avoid
|
||||
// re-adding components each time.
|
||||
|
||||
removeAll()
|
||||
createEditorsForNode(node)
|
||||
val editors = editorCache[node.id] ?: return
|
||||
for ((property, editor) in node.properties.zip(editors)) {
|
||||
editor.setValue(property.value)
|
||||
editor.valueChanged = { newValue ->
|
||||
p.propertyChanged(node.getPropertyHandle(property.name),
|
||||
copyPropertyWithValue(property, newValue))
|
||||
}
|
||||
add(editor.component)
|
||||
}
|
||||
repaint()
|
||||
revalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* editorCache caches a list of property editors for each node in the graph. This is done so
|
||||
* property editors don't have to be re-allocated each time showPropertiesForNode is called.
|
||||
*/
|
||||
private fun createEditorsForNode(node: Node) {
|
||||
editorCache.computeIfAbsent(node.id) { _ ->
|
||||
node.properties.map { it.callEditorFactory() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.properties
|
||||
|
||||
import com.google.android.filament.tungsten.model.Float3
|
||||
import com.google.android.filament.tungsten.model.FloatValue
|
||||
import com.google.android.filament.tungsten.model.PropertyValue
|
||||
import com.google.android.filament.tungsten.model.StringValue
|
||||
import com.google.android.filament.tungsten.model.TextureFile
|
||||
import com.google.android.filament.tungsten.texture.TextureUtils
|
||||
import java.awt.Color
|
||||
import java.text.NumberFormat
|
||||
import javax.swing.JButton
|
||||
import javax.swing.JColorChooser
|
||||
import javax.swing.JComboBox
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.JFormattedTextField
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.JSlider
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
abstract class PropertyEditor {
|
||||
|
||||
abstract val component: JComponent
|
||||
var valueChanged: (newValue: PropertyValue) -> Unit = { }
|
||||
|
||||
abstract fun setValue(v: PropertyValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a JSlider without causing any changeListeners to trigger.
|
||||
*/
|
||||
fun JSlider.quietlySetValue(v: Int) {
|
||||
val changeListeners = this.changeListeners
|
||||
for (changeListener in changeListeners) {
|
||||
this.removeChangeListener(changeListener)
|
||||
}
|
||||
this.value = v
|
||||
for (changeListener in changeListeners) {
|
||||
this.addChangeListener(changeListener)
|
||||
}
|
||||
}
|
||||
|
||||
internal class FloatSlider(initialValue: FloatValue) : PropertyEditor() {
|
||||
|
||||
private val scaleFactor = 1000
|
||||
|
||||
override val component: JPanel = JPanel()
|
||||
private val slider: JSlider
|
||||
private val field: JFormattedTextField
|
||||
|
||||
private var currentValue: Float = initialValue.v
|
||||
|
||||
override fun setValue(v: PropertyValue) {
|
||||
val value = v as FloatValue
|
||||
currentValue = value.v
|
||||
field.value = currentValue
|
||||
slider.quietlySetValue((currentValue * scaleFactor).roundToInt())
|
||||
}
|
||||
|
||||
init {
|
||||
slider = JSlider(0, scaleFactor, (initialValue.v * scaleFactor).roundToInt())
|
||||
slider.addChangeListener {
|
||||
if (!slider.valueIsAdjusting) {
|
||||
currentValue = slider.value.toFloat() / scaleFactor
|
||||
updateValue()
|
||||
}
|
||||
}
|
||||
|
||||
val format = NumberFormat.getNumberInstance()
|
||||
format.maximumFractionDigits = 5
|
||||
field = JFormattedTextField(format)
|
||||
field.columns = 10
|
||||
field.value = initialValue.v
|
||||
|
||||
field.addActionListener {
|
||||
val newValue = field.value as? Number
|
||||
newValue?.let { v ->
|
||||
currentValue = v.toFloat()
|
||||
updateValue()
|
||||
}
|
||||
}
|
||||
|
||||
component.add(slider)
|
||||
component.add(field)
|
||||
}
|
||||
|
||||
private fun updateValue() {
|
||||
slider.quietlySetValue((currentValue * scaleFactor).roundToInt())
|
||||
field.value = currentValue
|
||||
valueChanged(FloatValue(currentValue))
|
||||
}
|
||||
}
|
||||
|
||||
internal class ColorChooser(value: Float3) : PropertyEditor() {
|
||||
|
||||
override val component: JColorChooser
|
||||
|
||||
override fun setValue(v: PropertyValue) {
|
||||
val value = v as Float3
|
||||
component.setColor((value.x * 255.0f).roundToInt(), (value.y * 255.0f).roundToInt(),
|
||||
(value.z * 255.0f).roundToInt())
|
||||
}
|
||||
|
||||
init {
|
||||
val initialColor = Color(value.x, value.y, value.z)
|
||||
component = JColorChooser(initialColor)
|
||||
component.selectionModel.addChangeListener {
|
||||
val newValue = value.copy(
|
||||
x = component.color.red / 255.0f,
|
||||
y = component.color.green / 255.0f,
|
||||
z = component.color.blue / 255.0f)
|
||||
valueChanged(newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class MultipleChoice(value: StringValue, choices: List<String>) : PropertyEditor() {
|
||||
|
||||
override val component = JComboBox<String>(choices.toTypedArray())
|
||||
|
||||
init {
|
||||
component.addActionListener {
|
||||
valueChanged(StringValue(component.selectedItem as String))
|
||||
}
|
||||
}
|
||||
|
||||
override fun setValue(v: PropertyValue) {
|
||||
val newValue = v as? StringValue ?: return
|
||||
component.selectedItem = newValue.value
|
||||
}
|
||||
}
|
||||
|
||||
internal class TextureFileChooser(initialValue: TextureFile) : PropertyEditor() {
|
||||
|
||||
private val colorSpaceToLabel = linkedMapOf(
|
||||
TextureUtils.ColorSpaceStrategy.FORCE_SRGB to "sRGB",
|
||||
TextureUtils.ColorSpaceStrategy.FORCE_LINEAR to "Linear",
|
||||
TextureUtils.ColorSpaceStrategy.USE_FILE_PROFILE to "Use embedded file profile"
|
||||
)
|
||||
|
||||
override val component: JPanel = JPanel()
|
||||
private val fileChooser = JFileChooser()
|
||||
private val colorSpaceChooser = JComboBox<String>(colorSpaceToLabel.values.toTypedArray())
|
||||
|
||||
private var textureFile = initialValue
|
||||
|
||||
override fun setValue(v: PropertyValue) {
|
||||
val newValue = v as? TextureFile ?: return
|
||||
textureFile = v
|
||||
colorSpaceChooser.selectedItem = colorSpaceToLabel[newValue.colorSpace]
|
||||
}
|
||||
|
||||
init {
|
||||
val button = JButton("Choose file...")
|
||||
|
||||
colorSpaceChooser.selectedItem = colorSpaceToLabel[initialValue.colorSpace]
|
||||
|
||||
component.add(button)
|
||||
component.add(colorSpaceChooser)
|
||||
|
||||
button.addActionListener {
|
||||
val result = fileChooser.showOpenDialog(component)
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
textureFile = textureFile.copy(file = fileChooser.selectedFile)
|
||||
valueChanged(textureFile)
|
||||
}
|
||||
}
|
||||
|
||||
colorSpaceChooser.addActionListener {
|
||||
val newColorSpace = when (colorSpaceChooser.selectedIndex) {
|
||||
0 -> TextureUtils.ColorSpaceStrategy.FORCE_SRGB
|
||||
1 -> TextureUtils.ColorSpaceStrategy.FORCE_LINEAR
|
||||
2 -> TextureUtils.ColorSpaceStrategy.USE_FILE_PROFILE
|
||||
else -> TextureUtils.ColorSpaceStrategy.USE_FILE_PROFILE
|
||||
}
|
||||
textureFile = textureFile.copy(colorSpace = newColorSpace)
|
||||
valueChanged(textureFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.texture
|
||||
|
||||
import com.google.android.filament.Texture
|
||||
import com.google.android.filament.tungsten.Filament
|
||||
import java.io.File
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
typealias FutureTexture = CompletableFuture<Texture>
|
||||
|
||||
object TextureCache {
|
||||
|
||||
private data class TextureCacheKey(
|
||||
val canonicalPath: String,
|
||||
val colorSpace: TextureUtils.ColorSpaceStrategy
|
||||
)
|
||||
|
||||
// Maps from canonical file path to Filament Texture
|
||||
private val cache = mutableMapOf<TextureCacheKey, FutureTexture>()
|
||||
private var defaultTexture: Texture? = null
|
||||
|
||||
// Only accessed from Filament thread
|
||||
private var allowNewTextures = true
|
||||
private val textures = mutableListOf<Texture>()
|
||||
|
||||
/**
|
||||
* Loads the image into a Filament texture and returns a future that will be completed when the
|
||||
* texture loads successfully or completed exceptionally if an error occurs.
|
||||
*/
|
||||
fun getTextureForFile(file: File, colorSpace: TextureUtils.ColorSpaceStrategy): FutureTexture {
|
||||
val key = TextureCacheKey(file.canonicalPath, colorSpace)
|
||||
cache.computeIfAbsent(key) {
|
||||
createTextureForImageSource(file, colorSpace)
|
||||
}
|
||||
return cache[key] ?: getDefaultTexture()
|
||||
}
|
||||
|
||||
fun getDefaultTexture(): FutureTexture {
|
||||
defaultTexture?.let { return CompletableFuture.completedFuture(it) }
|
||||
val futureTexture = FutureTexture()
|
||||
Filament.getInstance().runOnFilamentThread { engine ->
|
||||
val texture = TextureUtils.createDefaultTexture(engine)
|
||||
futureTexture.complete(texture)
|
||||
defaultTexture = texture
|
||||
textures.add(texture)
|
||||
}
|
||||
return futureTexture
|
||||
}
|
||||
|
||||
/*
|
||||
* Keep track of texture and delete when Filament shutdownAndDestroyTextures is called.
|
||||
*/
|
||||
fun addTextureForRemoval(texture: Texture) {
|
||||
Filament.getInstance().assertIsFilamentThread()
|
||||
textures.add(texture)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cached textures and disallow any additional texture caching.
|
||||
*/
|
||||
fun shutdownAndDestroyTextures() {
|
||||
Filament.getInstance().runOnFilamentThread { engine ->
|
||||
for (texture in textures) {
|
||||
engine.destroyTexture(texture)
|
||||
}
|
||||
allowNewTextures = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTextureForImageSource(
|
||||
imageSource: File,
|
||||
colorSpace: TextureUtils.ColorSpaceStrategy
|
||||
): FutureTexture {
|
||||
val futureTexture = FutureTexture()
|
||||
Filament.getInstance().runOnFilamentThread { engine ->
|
||||
if (!allowNewTextures) return@runOnFilamentThread
|
||||
val texture = TextureUtils.loadTextureFromFile(engine, imageSource, colorSpace)
|
||||
if (texture != null) {
|
||||
futureTexture.complete(texture)
|
||||
textures.add(texture)
|
||||
} else {
|
||||
futureTexture.completeExceptionally(RuntimeException("Unable to load texture."))
|
||||
}
|
||||
}
|
||||
return futureTexture
|
||||
}
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.texture
|
||||
|
||||
import com.google.android.filament.Engine
|
||||
import com.google.android.filament.Texture
|
||||
|
||||
import javax.imageio.ImageIO
|
||||
import java.awt.image.BufferedImage
|
||||
import java.awt.image.BufferedImage.TYPE_3BYTE_BGR
|
||||
import java.awt.image.BufferedImage.TYPE_4BYTE_ABGR
|
||||
import java.awt.image.BufferedImage.TYPE_4BYTE_ABGR_PRE
|
||||
import java.awt.image.BufferedImage.TYPE_INT_ARGB
|
||||
import java.awt.image.BufferedImage.TYPE_INT_ARGB_PRE
|
||||
import java.awt.image.BufferedImage.TYPE_INT_BGR
|
||||
import java.awt.image.DataBufferByte
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
private fun Texture.InternalFormat.toSrgb(): Texture.InternalFormat {
|
||||
return when (this) {
|
||||
Texture.InternalFormat.RGB8 -> Texture.InternalFormat.SRGB8
|
||||
Texture.InternalFormat.RGBA8 -> Texture.InternalFormat.SRGB8_A8
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
data class ImageInfo(val width: Int, val height: Int)
|
||||
|
||||
object TextureUtils {
|
||||
|
||||
enum class ColorSpaceStrategy {
|
||||
// Use the profile present in the image file, defaulting to sRGB if none exists.
|
||||
USE_FILE_PROFILE,
|
||||
|
||||
// Always assume an sRGB color space.
|
||||
FORCE_SRGB,
|
||||
|
||||
// Always assume a non-sRGB color space.
|
||||
FORCE_LINEAR
|
||||
}
|
||||
|
||||
fun createDefaultTexture(engine: Engine): Texture {
|
||||
val stream = javaClass.classLoader.getResourceAsStream("checkerboard.png")
|
||||
val texture = stream?.let { s ->
|
||||
ImageIO.read(s)?.let {
|
||||
img -> loadTextureFromImage(engine, img, ColorSpaceStrategy.FORCE_LINEAR)
|
||||
}
|
||||
}
|
||||
return texture ?: throw RuntimeException("Could not load default texture")
|
||||
}
|
||||
|
||||
fun loadTextureFromFile(engine: Engine, file: File, colorSpace: ColorSpaceStrategy): Texture? {
|
||||
val img = try {
|
||||
ImageIO.read(file) ?: return null
|
||||
} catch (e: IOException) {
|
||||
System.err.println("Could not read image ${file.canonicalPath}.")
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
|
||||
return loadTextureFromImage(engine, img, colorSpace)
|
||||
}
|
||||
|
||||
fun loadImageBufferFromStream(stream: InputStream): Pair<ByteBuffer, ImageInfo>? {
|
||||
val img = try {
|
||||
ImageIO.read(stream) ?: return null
|
||||
} catch (e: IOException) {
|
||||
System.err.println("Could not parse image from InputStream.")
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
return Pair(loadImageBuffer(img), ImageInfo(img.width, img.height))
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the number of components the image has and its color space, decide which
|
||||
* texture formats to use.
|
||||
*/
|
||||
private fun decideTextureFormat(
|
||||
img: BufferedImage,
|
||||
colorSpace: ColorSpaceStrategy
|
||||
): Pair<Texture.InternalFormat, Texture.Format> {
|
||||
val isSrgb = when (colorSpace) {
|
||||
ColorSpaceStrategy.FORCE_LINEAR -> false
|
||||
ColorSpaceStrategy.FORCE_SRGB -> true
|
||||
ColorSpaceStrategy.USE_FILE_PROFILE -> img.colorModel.colorSpace.isCS_sRGB
|
||||
}
|
||||
val (internalFormat, textureFormat) = when (img.colorModel.numComponents) {
|
||||
1 -> Texture.InternalFormat.R8 to Texture.Format.R
|
||||
2 -> Texture.InternalFormat.RG8 to Texture.Format.RG
|
||||
3 -> Texture.InternalFormat.RGB8 to Texture.Format.RGB
|
||||
4 -> Texture.InternalFormat.RGBA8 to Texture.Format.RGBA
|
||||
else -> Texture.InternalFormat.RGBA8 to Texture.Format.RGBA
|
||||
}
|
||||
return (if (isSrgb) internalFormat.toSrgb() else internalFormat) to textureFormat
|
||||
}
|
||||
|
||||
private fun loadImageBuffer(img: BufferedImage): ByteBuffer {
|
||||
val data = img.raster.dataBuffer as DataBufferByte
|
||||
flipComponentsIfNecessary(img)
|
||||
return ByteBuffer.wrap(data.data)
|
||||
}
|
||||
|
||||
private fun loadTextureFromImage(
|
||||
engine: Engine,
|
||||
img: BufferedImage,
|
||||
colorSpace: ColorSpaceStrategy
|
||||
): Texture? {
|
||||
val data = img.raster.dataBuffer as DataBufferByte
|
||||
|
||||
val (internalFormat, textureFormat) = decideTextureFormat(img, colorSpace)
|
||||
|
||||
flipComponentsIfNecessary(img)
|
||||
|
||||
val texture = Texture.Builder()
|
||||
.width(img.width)
|
||||
.height(img.height)
|
||||
.format(internalFormat)
|
||||
.sampler(Texture.Sampler.SAMPLER_2D)
|
||||
.build(engine)
|
||||
|
||||
val buf = ByteBuffer.wrap(data.data)
|
||||
|
||||
val desc = Texture.PixelBufferDescriptor(buf, textureFormat, Texture.Type.UBYTE)
|
||||
|
||||
texture.setImage(engine, Texture.BASE_LEVEL, desc)
|
||||
|
||||
return texture
|
||||
}
|
||||
|
||||
private fun flipComponentsIfNecessary(img: BufferedImage) {
|
||||
val data = img.raster.dataBuffer as DataBufferByte
|
||||
val pixels = data.data
|
||||
val components = img.colorModel.numComponents
|
||||
|
||||
// Only flip components if they are specified in non-RGBA order.
|
||||
val typesToFlip = listOf(
|
||||
TYPE_INT_ARGB,
|
||||
TYPE_INT_ARGB_PRE,
|
||||
TYPE_INT_BGR,
|
||||
TYPE_3BYTE_BGR,
|
||||
TYPE_4BYTE_ABGR,
|
||||
TYPE_4BYTE_ABGR_PRE
|
||||
)
|
||||
if (!typesToFlip.contains(img.type)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (components == 4) {
|
||||
// ABGR -> RGBA
|
||||
assert(pixels.size % 4 == 0)
|
||||
var i = 0
|
||||
while (i < pixels.size) {
|
||||
val A = pixels[i]
|
||||
val B = pixels[i + 1]
|
||||
val G = pixels[i + 2]
|
||||
val R = pixels[i + 3]
|
||||
pixels[i] = R
|
||||
pixels[i + 1] = G
|
||||
pixels[i + 2] = B
|
||||
pixels[i + 3] = A
|
||||
i += 4
|
||||
}
|
||||
}
|
||||
if (components == 3) {
|
||||
// BGR -> RGB
|
||||
assert(pixels.size % 3 == 0)
|
||||
var i = 0
|
||||
while (i < pixels.size) {
|
||||
val B = pixels[i]
|
||||
val G = pixels[i + 1]
|
||||
val R = pixels[i + 2]
|
||||
pixels[i] = R
|
||||
pixels[i + 1] = G
|
||||
pixels[i + 2] = B
|
||||
i += 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui
|
||||
|
||||
import java.awt.Color
|
||||
|
||||
object ColorScheme {
|
||||
|
||||
val background = Color(60, 63, 65)
|
||||
val connectionLine = Color.WHITE
|
||||
val slotLabel = Color.BLACK
|
||||
val nodeBackground = Color.LIGHT_GRAY
|
||||
val nodeSelected = Color.YELLOW
|
||||
val slotCircle = Color.GREEN
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui
|
||||
|
||||
import com.google.android.filament.tungsten.SwingHelper
|
||||
import com.google.android.filament.tungsten.model.Slot
|
||||
import java.awt.BasicStroke
|
||||
import java.awt.Color
|
||||
import java.awt.Graphics2D
|
||||
import java.awt.geom.GeneralPath
|
||||
import java.awt.geom.Point2D
|
||||
|
||||
/**
|
||||
* Endpoint provides either a start or end point to a ConnectionLine.
|
||||
*/
|
||||
typealias Endpoint = () -> Point2D.Double?
|
||||
|
||||
internal fun slotPoint(slot: Slot, graph: MaterialGraphComponent): Endpoint {
|
||||
return fun (): Point2D.Double? {
|
||||
val graphView = graph.graphView ?: return null
|
||||
val slotView = graph.getSlotCircleForSlot(slot) ?: return null
|
||||
val centerPoint = convertPoint(slotView, slotView.centerPoint, graphView)
|
||||
if (centerPoint != null) {
|
||||
return Point2D.Double(centerPoint.x, centerPoint.y)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
internal fun arbitraryPoint(x: Double, y: Double): Endpoint {
|
||||
return {
|
||||
Point2D.Double(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ConnectionLine is a line drawn between two slots, or if a connection is being formed, between a
|
||||
* slot and an arbitrary point.
|
||||
*/
|
||||
class ConnectionLine(from: Endpoint, to: Endpoint) {
|
||||
|
||||
var outputPoint = from
|
||||
var inputPoint = to
|
||||
|
||||
fun paint(g2d: Graphics2D) {
|
||||
SwingHelper.setRenderingHints(g2d)
|
||||
g2d.color = ColorScheme.connectionLine
|
||||
|
||||
val originPoint = outputPoint() ?: return
|
||||
val destinationPoint = inputPoint() ?: return
|
||||
|
||||
val deltaX = Math.abs(destinationPoint.getX() - originPoint.getX())
|
||||
|
||||
val p2 = Point2D.Double(originPoint.getX() + deltaX / 2.0, originPoint.getY())
|
||||
val p3 = Point2D.Double(destinationPoint.getX() - deltaX / 2, destinationPoint.getY())
|
||||
|
||||
drawBezierCurve(g2d, originPoint, p2, p3, destinationPoint)
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a 4 control point Bezier curve.
|
||||
*/
|
||||
fun drawBezierCurve(
|
||||
g2d: Graphics2D,
|
||||
p1: Point2D.Double,
|
||||
p2: Point2D.Double,
|
||||
p3: Point2D.Double,
|
||||
p4: Point2D.Double
|
||||
) {
|
||||
val path = GeneralPath()
|
||||
|
||||
path.reset()
|
||||
|
||||
path.moveTo(p1.x, p1.y)
|
||||
path.curveTo(p2.x, p2.y, p3.x, p3.y, p4.x, p4.y)
|
||||
|
||||
val actionStroke = BasicStroke(2f)
|
||||
|
||||
g2d.stroke = actionStroke
|
||||
g2d.color = Color.WHITE
|
||||
g2d.draw(path)
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.util.List;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
public class GraphPopup implements MouseListener, ActionListener {
|
||||
|
||||
private GraphPresenter mPresenter;
|
||||
private final JPopupMenu mMenu;
|
||||
private int mPopupXLocation;
|
||||
private int mPopupYLocation;
|
||||
|
||||
/**
|
||||
* Adds a Popup menu and calls actions in the presenter when items are selected.
|
||||
* @param component The component to add the popup menu to.
|
||||
*/
|
||||
public GraphPopup(JComponent component, List<String> items) {
|
||||
// Create a menu and for each node registered in NodeRegistry, create a corresponding item
|
||||
mMenu = new JPopupMenu();
|
||||
for (String nodeName : items) {
|
||||
JMenuItem menuItem = new JMenuItem(nodeName);
|
||||
mMenu.add(menuItem);
|
||||
menuItem.addActionListener(this);
|
||||
}
|
||||
|
||||
component.addMouseListener(this);
|
||||
}
|
||||
|
||||
public void setPresenter(GraphPresenter presenter) {
|
||||
mPresenter = presenter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
checkAndTriggerPopup(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
checkAndTriggerPopup(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
checkAndTriggerPopup(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
|
||||
}
|
||||
|
||||
private void checkAndTriggerPopup(MouseEvent e) {
|
||||
if (e.isPopupTrigger()) {
|
||||
mMenu.show(e.getComponent(), e.getX(), e.getY());
|
||||
mPopupXLocation = e.getX();
|
||||
mPopupYLocation = e.getY();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// The action command is the text displayed on the JMenuItem
|
||||
String nodeName = e.getActionCommand();
|
||||
mPresenter.popupMenuItemSelected(nodeName, mPopupXLocation, mPopupYLocation);
|
||||
}
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui;
|
||||
|
||||
import com.google.android.filament.Material;
|
||||
import com.google.android.filament.MaterialInstance;
|
||||
import com.google.android.filament.tungsten.Filament;
|
||||
import com.google.android.filament.tungsten.MaterialManager;
|
||||
import com.google.android.filament.tungsten.compiler.CompiledGraph;
|
||||
import com.google.android.filament.tungsten.compiler.GraphCompiler;
|
||||
import com.google.android.filament.tungsten.compiler.NodeRegistry;
|
||||
import com.google.android.filament.tungsten.compiler.Parameter;
|
||||
import com.google.android.filament.tungsten.model.Connection;
|
||||
import com.google.android.filament.tungsten.model.Graph;
|
||||
import com.google.android.filament.tungsten.model.GraphInitializer;
|
||||
import com.google.android.filament.tungsten.model.Node;
|
||||
import com.google.android.filament.tungsten.model.Property;
|
||||
import com.google.android.filament.tungsten.model.PropertyType;
|
||||
import com.google.android.filament.tungsten.model.serialization.GraphFile;
|
||||
import com.google.android.filament.tungsten.model.serialization.GraphSerializer;
|
||||
import com.google.android.filament.tungsten.model.serialization.JsonDeserializer;
|
||||
import com.google.android.filament.tungsten.model.serialization.JsonSerializer;
|
||||
import com.google.android.filament.tungsten.properties.IPropertiesPresenter;
|
||||
import com.google.android.filament.tungsten.properties.PropertiesPanel;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.swing.JTextArea;
|
||||
|
||||
import com.google.android.filament.tungsten.ui.preview.PreviewMeshPanel;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class GraphPresenter implements IPropertiesPresenter {
|
||||
|
||||
// Views
|
||||
private final MaterialGraphComponent mGraphView;
|
||||
private final PreviewMeshPanel mPreviewMeshPanel;
|
||||
private final JTextArea mMaterialSource;
|
||||
private final PropertiesPanel mPropertiesPanel;
|
||||
|
||||
// Dependencies
|
||||
private final NodeRegistry mNodeRegistry;
|
||||
private final MaterialManager mMaterialManager;
|
||||
private final TungstenFile mFile;
|
||||
|
||||
private CompiledGraph mCompiledGraph;
|
||||
|
||||
// Technically volatile is enough here b/c the Filament thread only ever reads
|
||||
private AtomicReference<Graph> mModel = new AtomicReference<>();
|
||||
|
||||
// Only accessed from Filament thread
|
||||
@Nullable private MaterialInstance mCurrentMaterialInstance = null;
|
||||
|
||||
public GraphPresenter(MaterialGraphComponent graphView, PreviewMeshPanel previewMeshPanel,
|
||||
JTextArea materialSource, PropertiesPanel propertiesPanel,
|
||||
MaterialManager materialManager, TungstenFile file) {
|
||||
mGraphView = graphView;
|
||||
mPreviewMeshPanel = previewMeshPanel;
|
||||
mMaterialSource = materialSource;
|
||||
mPropertiesPanel = propertiesPanel;
|
||||
mMaterialManager = materialManager;
|
||||
mNodeRegistry = new NodeRegistry();
|
||||
mFile = file;
|
||||
|
||||
loadModelFromFile(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action from the view that lets us know a connection between two nodes has been created.
|
||||
*/
|
||||
public void connectionCreated(Connection connection) {
|
||||
mModel.getAndUpdate(graph -> graph.graphByFormingConnection(connection));
|
||||
mGraphView.render(mModel.get());
|
||||
recompileGraph();
|
||||
}
|
||||
|
||||
/**
|
||||
* Action from the view that lets us know a connection between two nodes has been removed.
|
||||
*/
|
||||
public void connectionDisconnectedAtInput(Connection connection) {
|
||||
mModel.getAndUpdate(graph -> graph.graphByRemovingConnection(connection));
|
||||
mGraphView.render(mModel.get());
|
||||
recompileGraph();
|
||||
}
|
||||
|
||||
/**
|
||||
* Action from the view that lets us know the selection of nodes has changed.
|
||||
*/
|
||||
public void selectionChanged(List<Integer> selectedNodes) {
|
||||
mModel.getAndUpdate(graph -> graph.graphByChangingSelection(selectedNodes));
|
||||
mGraphView.render(mModel.get());
|
||||
if (selectedNodes.size() != 1) {
|
||||
mPropertiesPanel.showNone();
|
||||
return;
|
||||
}
|
||||
mPropertiesPanel.showPropertiesForNode(mModel.get().getNodeWithId(selectedNodes.get(0)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Action from the view that lets us know a node has been dragged to a new location.
|
||||
*/
|
||||
public void nodeMoved(Node node, int x, int y) {
|
||||
mModel.getAndUpdate(graph -> graph.graphByMovingNode(node, x, y));
|
||||
mGraphView.render(mModel.get());
|
||||
serializeAndSave();
|
||||
}
|
||||
|
||||
/**
|
||||
* Action from the view that lets us know a NodeProperty's value has changed.
|
||||
*/
|
||||
@Override
|
||||
public void propertyChanged(Node.PropertyHandle handle, Property property) {
|
||||
mModel.getAndUpdate(graph -> graph.graphByChangingProperty(handle, property));
|
||||
mGraphView.render(mModel.get());
|
||||
mPropertiesPanel.showPropertiesForNode(mModel.get().getSelectedNodes().get(0));
|
||||
|
||||
// Graph property changes trigger a full recompilation of the graph, while material
|
||||
// parameter changes trigger only a material parameter update on the current material
|
||||
// instance.
|
||||
if (property.getType() == PropertyType.GRAPH_PROPERTY) {
|
||||
recompileGraph();
|
||||
} else if (property.getType() == PropertyType.MATERIAL_PARAMETER) {
|
||||
serializeAndSave();
|
||||
Parameter parameter = mCompiledGraph.getParameterMap().get(handle);
|
||||
if (parameter == null) {
|
||||
return;
|
||||
}
|
||||
Filament.getInstance().runOnFilamentThread(engine -> {
|
||||
if (mCurrentMaterialInstance == null) {
|
||||
return;
|
||||
}
|
||||
// This check prevents us from setting a property on a newer material instance that
|
||||
// may no longer have that property.
|
||||
if (mCurrentMaterialInstance.getMaterial().hasParameter(parameter.getName())) {
|
||||
property.getValue().applyToMaterialInstance(mCurrentMaterialInstance,
|
||||
parameter.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action from the popup menu that lets us know an item has been clicked.
|
||||
*/
|
||||
public void popupMenuItemSelected(String name, int x, int y) {
|
||||
Node newNode = mNodeRegistry.createNodeForLabel(name, mModel.get().getNewNodeId());
|
||||
if (newNode == null) {
|
||||
return;
|
||||
}
|
||||
mModel.getAndUpdate(graph -> graph.graphByAddingNodeAtLocation(newNode, x, y));
|
||||
recompileGraph();
|
||||
mGraphView.render(mModel.get());
|
||||
}
|
||||
|
||||
private void loadModelFromFile(TungstenFile file) {
|
||||
file.read((success, contents) -> {
|
||||
if (!success) {
|
||||
// TODO: propagate this error to the user somehow in the UI
|
||||
System.out.println("Unable to read graph file.");
|
||||
return;
|
||||
}
|
||||
String toolBlock = GraphFile.INSTANCE.extractToolBlockFromMaterialFile(contents);
|
||||
if (toolBlock.isEmpty()) {
|
||||
mModel.set(GraphInitializer.INSTANCE.getInitialGraphState());
|
||||
recompileGraph();
|
||||
mGraphView.render(mModel.get());
|
||||
return;
|
||||
}
|
||||
mModel.set(GraphSerializer.INSTANCE.deserialize(
|
||||
toolBlock, mNodeRegistry, new JsonDeserializer()).getFirst());
|
||||
mGraphView.render(mModel.get());
|
||||
recompileGraph();
|
||||
});
|
||||
}
|
||||
|
||||
private void recompileGraph() {
|
||||
if (mModel.get().getRootNode() == null) {
|
||||
return;
|
||||
}
|
||||
GraphCompiler compiler = new GraphCompiler(mModel.get());
|
||||
mCompiledGraph = compiler.compileGraph();
|
||||
mMaterialSource.setText(mCompiledGraph.getMaterialDefinition());
|
||||
|
||||
CompletableFuture<Material> futureMaterial = mMaterialManager
|
||||
.compileMaterial(mCompiledGraph.getMaterialDefinition());
|
||||
|
||||
// Update the model with the newly compiled expression map and replace any modified nodes.
|
||||
mModel.getAndUpdate(
|
||||
graph -> graph
|
||||
.graphBySettingExpressionMap(mCompiledGraph.getExpressionMap())
|
||||
.graphByReplacingNodes(mCompiledGraph.getOldToNewNodeMap()));
|
||||
|
||||
mGraphView.render(mModel.get());
|
||||
|
||||
serializeAndSave();
|
||||
|
||||
final CompiledGraph compiledGraph = mCompiledGraph;
|
||||
futureMaterial.thenAccept(newMaterial -> {
|
||||
Filament.getInstance().assertIsFilamentThread();
|
||||
|
||||
mCurrentMaterialInstance = newMaterial.createInstance();
|
||||
mPreviewMeshPanel.updateMaterial(mCurrentMaterialInstance);
|
||||
|
||||
// For all parameters present in the compiled graph, update them based on the current
|
||||
// value in our model.
|
||||
for (Map.Entry<Node.PropertyHandle, Parameter> entry
|
||||
: compiledGraph.getParameterMap().entrySet()) {
|
||||
|
||||
Node.PropertyHandle handle = entry.getKey();
|
||||
Parameter parameter = entry.getValue();
|
||||
|
||||
Property nodeProperty = mModel.get().getNodeProperty(handle);
|
||||
if (nodeProperty == null) {
|
||||
continue;
|
||||
}
|
||||
if (mCurrentMaterialInstance.getMaterial().hasParameter(parameter.getName())) {
|
||||
nodeProperty.getValue()
|
||||
.applyToMaterialInstance(mCurrentMaterialInstance, parameter.getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void serializeAndSave() {
|
||||
if (mCompiledGraph == null) {
|
||||
return;
|
||||
}
|
||||
String serializedGraph = GraphSerializer.INSTANCE.serialize(mModel.get(),
|
||||
Collections.emptyMap(), new JsonSerializer());
|
||||
String materialDefinition =
|
||||
GraphFile.INSTANCE.addToolBlockToMaterialFile(
|
||||
mCompiledGraph.getMaterialDefinition(), serializedGraph);
|
||||
mFile.write(materialDefinition, success -> {
|
||||
if (!success) {
|
||||
// TODO: propagate this error to the user somehow in the UI
|
||||
System.out.println("Unable to write graph file.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui
|
||||
|
||||
import java.awt.Graphics2D
|
||||
import java.awt.geom.Point2D
|
||||
|
||||
private const val SLOT_CIRCLE_RADIUS = 10.0f
|
||||
private const val SLOT_TEXT_PAD = 5.0f
|
||||
private const val ARC_RADIUS = 10
|
||||
private const val BORDER_THICKNESS = 2
|
||||
private const val NODE_HEIGHT = 100.0f
|
||||
private const val NODE_WIDTH = 200.0f
|
||||
private const val SLOT_MARGIN = 10.0f
|
||||
private const val SLOT_HEIGHT = 20.0f
|
||||
|
||||
internal class SlotCircle(val parent: SlotView) : View() {
|
||||
|
||||
val centerPoint = Point2D.Float(SLOT_CIRCLE_RADIUS / 2, SLOT_CIRCLE_RADIUS / 2)
|
||||
|
||||
override fun render(g2d: Graphics2D) {
|
||||
g2d.color = ColorScheme.slotCircle
|
||||
g2d.fillOval(0, 0, SLOT_CIRCLE_RADIUS.toInt(), SLOT_CIRCLE_RADIUS.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
internal data class SlotView(
|
||||
val label: String,
|
||||
val isInput: Boolean,
|
||||
|
||||
val onConnectionDragStart: (SlotView) -> Unit,
|
||||
val onConnectionDragEnd: (SlotView) -> Unit
|
||||
) : View() {
|
||||
|
||||
val circle = SlotCircle(this)
|
||||
override val children = listOf(circle)
|
||||
|
||||
override val reactToMouseEvents = false
|
||||
|
||||
override fun layout() {
|
||||
if (isInput) {
|
||||
circle.x = 0.0f
|
||||
circle.y = 0.0f
|
||||
} else {
|
||||
circle.x = width - SLOT_CIRCLE_RADIUS
|
||||
circle.y = 0.0f
|
||||
}
|
||||
circle.width = SLOT_CIRCLE_RADIUS
|
||||
circle.height = SLOT_CIRCLE_RADIUS
|
||||
}
|
||||
|
||||
override fun render(g2d: Graphics2D) {
|
||||
g2d.color = ColorScheme.slotLabel
|
||||
if (isInput) {
|
||||
g2d.drawString(label, SLOT_CIRCLE_RADIUS + SLOT_TEXT_PAD, g2d.fontMetrics.height / 2.0f)
|
||||
} else {
|
||||
val textWidth = g2d.fontMetrics.stringWidth(label)
|
||||
g2d.drawString(label, (width - SLOT_CIRCLE_RADIUS - SLOT_TEXT_PAD) - textWidth,
|
||||
g2d.fontMetrics.height / 2.0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal data class NodeView(
|
||||
override var x: Float,
|
||||
override var y: Float,
|
||||
val inputSlots: List<SlotView>,
|
||||
val outputSlots: List<SlotView>,
|
||||
val isSelected: Boolean,
|
||||
val nodeDragStarted: (NodeView) -> Unit,
|
||||
val nodeDragStopped: (NodeView) -> Unit,
|
||||
val nodeClicked: () -> Unit
|
||||
) : View() {
|
||||
|
||||
override val children = inputSlots + outputSlots
|
||||
|
||||
override fun layout() {
|
||||
width = NODE_WIDTH
|
||||
height = maxOf((inputSlots.size + outputSlots.size) * SLOT_HEIGHT + 2 * SLOT_MARGIN,
|
||||
NODE_HEIGHT)
|
||||
inputSlots.forEachIndexed { index, slot ->
|
||||
slot.x = SLOT_MARGIN
|
||||
slot.y = SLOT_MARGIN + index * SLOT_HEIGHT
|
||||
slot.width = width - SLOT_MARGIN * 2
|
||||
slot.height = SLOT_HEIGHT
|
||||
}
|
||||
outputSlots.forEachIndexed { index, slot ->
|
||||
slot.x = SLOT_MARGIN
|
||||
slot.y = SLOT_MARGIN + (index + inputSlots.size) * SLOT_HEIGHT
|
||||
slot.width = width - SLOT_MARGIN * 2
|
||||
slot.height = SLOT_HEIGHT
|
||||
}
|
||||
}
|
||||
|
||||
override fun render(g2d: Graphics2D) {
|
||||
if (isSelected) {
|
||||
g2d.color = ColorScheme.nodeSelected
|
||||
g2d.fillRoundRect(0, 0, width.toInt(), height.toInt(), ARC_RADIUS, ARC_RADIUS)
|
||||
}
|
||||
g2d.color = ColorScheme.nodeBackground
|
||||
g2d.fillRoundRect(BORDER_THICKNESS, BORDER_THICKNESS,
|
||||
(width - 2 * BORDER_THICKNESS).toInt(),
|
||||
(height - 2 * BORDER_THICKNESS).toInt(), ARC_RADIUS, ARC_RADIUS)
|
||||
}
|
||||
}
|
||||
|
||||
internal data class GraphView(
|
||||
val nodes: List<NodeView>,
|
||||
override var width: Float,
|
||||
override var height: Float
|
||||
) : View() {
|
||||
|
||||
override val children = nodes
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
public interface GuiComponentFactory {
|
||||
|
||||
/**
|
||||
* Creates a Panel comprised of 3 components separated by platform-specific splitters.
|
||||
* _________
|
||||
* | | |
|
||||
* | | 2 |
|
||||
* | 1 |____|
|
||||
* | | |
|
||||
* | | 3 |
|
||||
* |____|____|
|
||||
*/
|
||||
JComponent createThreeComponentSplitLayout(JComponent first, JComponent second,
|
||||
JComponent third);
|
||||
}
|
||||
@@ -1,335 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui
|
||||
|
||||
import com.google.android.filament.tungsten.SwingHelper
|
||||
import com.google.android.filament.tungsten.model.Connection
|
||||
import com.google.android.filament.tungsten.model.Graph
|
||||
import com.google.android.filament.tungsten.model.Node
|
||||
import com.google.android.filament.tungsten.model.NodeId
|
||||
import com.google.android.filament.tungsten.model.Slot
|
||||
import java.awt.Graphics
|
||||
import java.awt.Graphics2D
|
||||
import java.awt.event.MouseEvent
|
||||
import java.util.ArrayList
|
||||
import java.util.HashMap
|
||||
import javax.swing.JComponent
|
||||
|
||||
class MaterialGraphComponent : JComponent(), TungstenMouseListener {
|
||||
|
||||
private val mEventManager = TungstenEventManager(this)
|
||||
private var mNodeBeingDragged: NodeId? = null
|
||||
private var mPreviousSelectionModel: List<Int> = ArrayList()
|
||||
private var mIsConnecting = false
|
||||
|
||||
// A connection is "in progress" when a user has started dragging from a slot
|
||||
private var mInProgressConnectionLine: ConnectionLine? = null
|
||||
|
||||
// The slot the user started dragging from when forming a connection
|
||||
private var mInProgressOriginSlot: Slot? = null
|
||||
|
||||
private val mConnectionLines = ArrayList<ConnectionLine>()
|
||||
private var mPresenter: GraphPresenter? = null
|
||||
|
||||
// Stores the current node views being shown
|
||||
private val mNodeViews = mutableListOf<NodeView>()
|
||||
|
||||
// Stores the current graph displayed in the component, or null if one hasn't been rendered yet
|
||||
private var mGraph: Graph? = null
|
||||
|
||||
internal var graphView: GraphView? = null
|
||||
|
||||
// Maps between the Nodes and the associated NodeViews that represent them
|
||||
private val nodeMap = HashMap<NodeId, NodeView>()
|
||||
|
||||
// Maps between the SlotModels we receive and the NodeSlot views
|
||||
private val mSlotMap = HashMap<Slot, SlotCircle>()
|
||||
|
||||
// Maps between the ConnectionModels we receive and our views
|
||||
private val mConnectionViewMap = HashMap<Connection, ConnectionLine>()
|
||||
|
||||
init {
|
||||
addMouseMotionListener(mEventManager)
|
||||
addMouseListener(mEventManager)
|
||||
}
|
||||
|
||||
fun setPresenter(presenter: GraphPresenter) {
|
||||
mPresenter = presenter
|
||||
}
|
||||
|
||||
private fun renderGraph(graph: Graph): GraphView {
|
||||
mSlotMap.clear()
|
||||
nodeMap.clear()
|
||||
|
||||
val renderSlot = { node: Node, slotName: String, isInput: Boolean ->
|
||||
val slotHandle = if (isInput) {
|
||||
node.getInputSlot(slotName)
|
||||
} else {
|
||||
node.getOutputSlot(slotName)
|
||||
}
|
||||
val expression = graph.expressionMap[slotHandle]
|
||||
val slotView = SlotView(
|
||||
label = if (expression != null)
|
||||
"$slotName (${expression.dimensions})" else slotName,
|
||||
isInput = isInput,
|
||||
onConnectionDragStart = { this.nodeSlotDragStarted(slotHandle) },
|
||||
onConnectionDragEnd = { this.nodeSlotDragEnded(slotHandle) }
|
||||
)
|
||||
mSlotMap[slotHandle] = slotView.circle
|
||||
slotView
|
||||
}
|
||||
|
||||
val renderNode = { node: Node ->
|
||||
val nodeView = NodeView(
|
||||
x = node.x,
|
||||
y = node.y,
|
||||
inputSlots = node.inputSlots.map { slotName ->
|
||||
renderSlot(node, slotName, true)
|
||||
},
|
||||
outputSlots = node.outputSlots.map { slotName ->
|
||||
renderSlot(node, slotName, false)
|
||||
},
|
||||
isSelected = graph.isNodeSelected(node),
|
||||
nodeDragStarted = { view ->
|
||||
mNodeBeingDragged = node.id
|
||||
notifySelectionIfChanged(listOf(node.id))
|
||||
},
|
||||
nodeDragStopped = { view ->
|
||||
// We've just finished dragging a node, so alert the presenter
|
||||
mPresenter?.nodeMoved(node, view.x.toInt(), view.y.toInt())
|
||||
mNodeBeingDragged = null
|
||||
},
|
||||
nodeClicked = {
|
||||
notifySelectionIfChanged(listOf(node.id))
|
||||
}
|
||||
)
|
||||
nodeMap[node.id] = nodeView
|
||||
nodeView
|
||||
}
|
||||
|
||||
return GraphView(
|
||||
nodes = graph.nodes.map { renderNode(it) },
|
||||
width = this.width.toFloat(),
|
||||
height = this.height.toFloat()
|
||||
)
|
||||
}
|
||||
|
||||
fun render(graph: Graph) {
|
||||
// Because graphs are immutable, if the graph we previously rendered is the same graph
|
||||
// object, we have no work to do.
|
||||
if (mGraph === graph) {
|
||||
return
|
||||
}
|
||||
|
||||
val graphView = renderGraph(graph)
|
||||
this.graphView = graphView
|
||||
|
||||
mNodeViews.clear()
|
||||
mNodeViews.addAll(graphView.nodes)
|
||||
|
||||
// Create a connection line for each connection in the graph.
|
||||
mConnectionLines.clear()
|
||||
mConnectionViewMap.clear()
|
||||
for (connection in graph.connections) {
|
||||
val outputSlot = connection.outputSlot
|
||||
val inputSlot = connection.inputSlot
|
||||
val line = ConnectionLine(slotPoint(outputSlot, this),
|
||||
slotPoint(inputSlot, this))
|
||||
mConnectionLines.add(line)
|
||||
mConnectionViewMap[connection] = line
|
||||
}
|
||||
|
||||
// Layout the rendered views so they have accurate bounds. This must be done here and not in
|
||||
// paintComponent, as paintComponent might not be called immediately.
|
||||
layoutHierarchy(graphView)
|
||||
|
||||
revalidate()
|
||||
repaint()
|
||||
|
||||
mGraph = graph
|
||||
}
|
||||
|
||||
override fun mousePressed(e: MouseEvent) {
|
||||
// Check if we've clicked on a MaterialNode.
|
||||
val node = graphView?.let { graphView ->
|
||||
val view = findViewAt(graphView, e.point)
|
||||
view as? NodeView?
|
||||
}
|
||||
if (node == null) {
|
||||
// If we didn't click on a MaterialNode, clear the selection
|
||||
deselectNodes()
|
||||
return
|
||||
}
|
||||
|
||||
node.nodeClicked()
|
||||
}
|
||||
|
||||
override fun mouseDragStarted(e: MouseEvent) {
|
||||
// If we've started dragging over a slot circle, we've started forming a new connection
|
||||
val graphView = graphView ?: return
|
||||
val view = findViewAt(graphView, e.point) ?: return
|
||||
|
||||
when (view) {
|
||||
is SlotCircle -> view.parent.onConnectionDragStart(view.parent)
|
||||
is NodeView -> view.nodeDragStarted(view)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseDragEnded(e: MouseEvent) {
|
||||
mNodeBeingDragged?.let { nodeId ->
|
||||
val nodeView = nodeMap[nodeId]
|
||||
if (nodeView != null) {
|
||||
nodeView.nodeDragStopped(nodeView)
|
||||
}
|
||||
}
|
||||
|
||||
if (!mIsConnecting) {
|
||||
return
|
||||
}
|
||||
mIsConnecting = false
|
||||
|
||||
repaint()
|
||||
|
||||
val graphView = graphView ?: return
|
||||
val view = findViewAt(graphView, e.point) ?: return
|
||||
|
||||
when (view) {
|
||||
is SlotCircle -> view.parent.onConnectionDragEnd(view.parent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseDragged(e: MouseEvent) {
|
||||
val nodeId = mNodeBeingDragged
|
||||
if (nodeId != null) {
|
||||
val nodeView = nodeMap[nodeId]
|
||||
if (nodeView != null) {
|
||||
nodeView.x += mEventManager.mouseDeltaX
|
||||
nodeView.y += mEventManager.mouseDeltaY
|
||||
repaint()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
nodeSlotDragged(e)
|
||||
}
|
||||
|
||||
override fun paintComponent(g: Graphics) {
|
||||
super.paintComponent(g)
|
||||
|
||||
val g2d = g as Graphics2D
|
||||
SwingHelper.setRenderingHints(g2d)
|
||||
|
||||
// Paint background
|
||||
g2d.color = ColorScheme.background
|
||||
g2d.fillRect(0, 0, width, height)
|
||||
|
||||
// If we're currently connecting two slots, draw the connection
|
||||
if (mIsConnecting) {
|
||||
mInProgressConnectionLine?.paint(g2d)
|
||||
}
|
||||
|
||||
mConnectionLines.forEach { connection -> connection.paint(g2d) }
|
||||
|
||||
// Render graph
|
||||
graphView?.let { graphView -> renderHierarchy(graphView, g2d) }
|
||||
}
|
||||
|
||||
internal fun getSlotCircleForSlot(slot: Slot) = mSlotMap[slot]
|
||||
|
||||
private fun nodeSlotDragStarted(slot: Slot) {
|
||||
// If the user starts dragging on an input slot that already has a connection, remove the
|
||||
// previous connection
|
||||
val graph = mGraph ?: return
|
||||
for (connection in graph.connections) {
|
||||
if (connection.inputSlot == slot) {
|
||||
// First, remove the connection view
|
||||
val connectionLine = mConnectionViewMap.remove(connection) ?: return
|
||||
mConnectionLines.remove(connectionLine)
|
||||
mInProgressConnectionLine = connectionLine
|
||||
|
||||
// Create an in-progress connection by disconnecting the destination
|
||||
mInProgressOriginSlot = connection.outputSlot
|
||||
mIsConnecting = true
|
||||
|
||||
mPresenter?.connectionDisconnectedAtInput(connection)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new connection line to represent this new "in progress" connection
|
||||
mInProgressConnectionLine = ConnectionLine(slotPoint(slot, this),
|
||||
slotPoint(slot, this))
|
||||
mInProgressOriginSlot = slot
|
||||
mIsConnecting = true
|
||||
repaint()
|
||||
}
|
||||
|
||||
private fun nodeSlotDragged(e: MouseEvent) {
|
||||
if (!mIsConnecting || mInProgressConnectionLine == null) {
|
||||
return
|
||||
}
|
||||
// Depending on what type of slot the user is dragging from, the mouse is updating the
|
||||
// input or output point
|
||||
if (mInProgressOriginSlot is Node.InputSlot) {
|
||||
mInProgressConnectionLine?.outputPoint = arbitraryPoint(e.x.toDouble(), e.y.toDouble())
|
||||
} else {
|
||||
mInProgressConnectionLine?.inputPoint = arbitraryPoint(e.x.toDouble(), e.y.toDouble())
|
||||
}
|
||||
repaint()
|
||||
}
|
||||
|
||||
private fun nodeSlotDragEnded(slot: Slot) {
|
||||
var output = mInProgressOriginSlot ?: return
|
||||
var input = slot
|
||||
|
||||
mIsConnecting = false
|
||||
|
||||
// If the user started dragging on an input slot, they're connecting the nodes "backwards"
|
||||
// from input to output, so swap output and input.
|
||||
if (mInProgressOriginSlot is Node.InputSlot) {
|
||||
val temp = output
|
||||
output = input
|
||||
input = temp
|
||||
}
|
||||
|
||||
val outputSlot = output as? Node.OutputSlot ?: return
|
||||
val inputSlot = input as? Node.InputSlot ?: return
|
||||
val newConnectionModel = Connection(outputSlot, inputSlot)
|
||||
|
||||
mPresenter?.connectionCreated(newConnectionModel)
|
||||
|
||||
repaint()
|
||||
}
|
||||
|
||||
private fun deselectNodes() {
|
||||
notifySelectionIfChanged(emptyList())
|
||||
}
|
||||
|
||||
/**
|
||||
* If selectionModel has changed, notify the presenter. Here we compare against the previous
|
||||
* selection in order to avoid notifying the presenter twice for the same selection (e.g. if the
|
||||
* user continuously clicks on the same node)
|
||||
*/
|
||||
private fun notifySelectionIfChanged(selectedNodes: List<Int>) {
|
||||
if (selectedNodes == mPreviousSelectionModel) {
|
||||
return
|
||||
}
|
||||
mPresenter?.selectionChanged(selectedNodes)
|
||||
mPreviousSelectionModel = selectedNodes
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
|
||||
/**
|
||||
* TungstenEventManager marshals events from both the MouseListener and MouseMotionListener
|
||||
* interfaces to a single TungstenMouseListener.
|
||||
*
|
||||
* Classes should instantiate a TungsteneEventManager and add it as a both a MouseListener and
|
||||
* MouseMotionListener.
|
||||
*/
|
||||
class TungstenEventManager implements MouseListener, MouseMotionListener {
|
||||
|
||||
private TungstenMouseListener mListener;
|
||||
private int mMousePreviousX;
|
||||
private int mMousePreviousY;
|
||||
private int mMouseDeltaX;
|
||||
private int mMouseDeltaY;
|
||||
private boolean mMouseIsDragging = false;
|
||||
|
||||
TungstenEventManager(TungstenMouseListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
int getMouseDeltaX() {
|
||||
return mMouseDeltaX;
|
||||
}
|
||||
|
||||
int getMouseDeltaY() {
|
||||
return mMouseDeltaY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
mListener.mouseClicked(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
mListener.mousePressed(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (mMouseIsDragging) {
|
||||
mMouseIsDragging = false;
|
||||
mListener.mouseDragEnded(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
mListener.mouseEntered(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
mListener.mouseExited(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
calculateMouseDelta(e);
|
||||
if (!mMouseIsDragging) {
|
||||
mMouseIsDragging = true;
|
||||
mListener.mouseDragStarted(e);
|
||||
}
|
||||
mListener.mouseDragged(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
calculateMouseDelta(e);
|
||||
mListener.mouseMoved(e);
|
||||
}
|
||||
|
||||
private void calculateMouseDelta(MouseEvent e) {
|
||||
mMouseDeltaX = e.getX() - mMousePreviousX;
|
||||
mMouseDeltaY = e.getY() - mMousePreviousY;
|
||||
mMousePreviousX = e.getX();
|
||||
mMousePreviousY = e.getY();
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui
|
||||
|
||||
import java.util.function.BiConsumer
|
||||
import java.util.function.Consumer
|
||||
|
||||
interface TungstenFile {
|
||||
|
||||
/**
|
||||
* Write new contents to the file, replacing any existing contents.
|
||||
* @param callback is called with true if the write is successful, false otherwise.
|
||||
*/
|
||||
fun write(contents: String, callback: Consumer<Boolean>)
|
||||
|
||||
/**
|
||||
* Read contents of a file asynchronously. result callback will be called with a boolean
|
||||
* representing success and if successful, the full contents of the file.
|
||||
*/
|
||||
fun read(result: BiConsumer<Boolean, String?>)
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
/**
|
||||
* TungstenMouseListener combines Swing's MouseListener and MouseMotionListener as well as adding
|
||||
* some helpful events, like mouseDragStarted and mouseDragEnded.
|
||||
*
|
||||
* An empty default implementation for each method is provided so classes that adhere are not
|
||||
* required to override every method.
|
||||
*/
|
||||
public interface TungstenMouseListener {
|
||||
|
||||
default void mouseDragged(MouseEvent e) {
|
||||
|
||||
}
|
||||
|
||||
default void mouseDragStarted(MouseEvent e) {
|
||||
|
||||
}
|
||||
|
||||
default void mouseDragEnded(MouseEvent e) {
|
||||
|
||||
}
|
||||
|
||||
default void mouseMoved(MouseEvent e) {
|
||||
|
||||
}
|
||||
|
||||
default void mouseClicked(MouseEvent e) {
|
||||
|
||||
}
|
||||
|
||||
default void mousePressed(MouseEvent e) {
|
||||
|
||||
}
|
||||
|
||||
default void mouseReleased(MouseEvent e) {
|
||||
|
||||
}
|
||||
|
||||
default void mouseEntered(MouseEvent e) {
|
||||
|
||||
}
|
||||
|
||||
default void mouseExited(MouseEvent e) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui;
|
||||
|
||||
import com.google.android.filament.tungsten.MaterialManager;
|
||||
import com.google.android.filament.tungsten.compiler.NodeRegistry;
|
||||
import com.google.android.filament.tungsten.properties.PropertiesPanel;
|
||||
import com.google.android.filament.tungsten.ui.preview.PreviewMeshPanel;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.JTextArea;
|
||||
|
||||
public class TungstenPanel extends JPanel {
|
||||
|
||||
private PreviewMeshPanel mPreviewMeshPanel;
|
||||
|
||||
public TungstenPanel(GuiComponentFactory componentFactory, MaterialManager materialManager,
|
||||
TungstenFile file) {
|
||||
|
||||
BorderLayout panelLayout = new BorderLayout();
|
||||
setLayout(panelLayout);
|
||||
|
||||
mPreviewMeshPanel = new PreviewMeshPanel();
|
||||
mPreviewMeshPanel.setMinimumSize(new Dimension(0, 300));
|
||||
|
||||
JTextArea materialSource = new JTextArea();
|
||||
materialSource.setEditable(false);
|
||||
|
||||
MaterialGraphComponent materialGraph = new MaterialGraphComponent();
|
||||
|
||||
PropertiesPanel propertiesPanel = new PropertiesPanel();
|
||||
|
||||
propertiesPanel.setMinimumSize(new Dimension(500, 200));
|
||||
|
||||
NodeRegistry registry = new NodeRegistry();
|
||||
GraphPopup popup = new GraphPopup(materialGraph, registry.getNodeLabelsForMenu());
|
||||
|
||||
JTabbedPane editor = new JTabbedPane();
|
||||
editor.addTab("Graph", materialGraph);
|
||||
editor.addTab("Source", materialSource);
|
||||
|
||||
GraphPresenter presenter = new GraphPresenter(materialGraph, mPreviewMeshPanel,
|
||||
materialSource, propertiesPanel, materialManager, file);
|
||||
materialGraph.setPresenter(presenter);
|
||||
propertiesPanel.setPresenter(presenter);
|
||||
popup.setPresenter(presenter);
|
||||
|
||||
add(componentFactory.createThreeComponentSplitLayout(editor, mPreviewMeshPanel,
|
||||
propertiesPanel));
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
mPreviewMeshPanel.destroy();
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui
|
||||
|
||||
import java.awt.Graphics2D
|
||||
import java.awt.geom.Point2D
|
||||
import java.awt.geom.Rectangle2D
|
||||
|
||||
abstract class View {
|
||||
|
||||
/**
|
||||
* x, y, width, and height are specified in this View's parent's coordinate system.
|
||||
*/
|
||||
|
||||
open var x: Float = 0.0f
|
||||
open var y: Float = 0.0f
|
||||
open var width: Float = 0.0f
|
||||
open var height: Float = 0.0f
|
||||
|
||||
/**
|
||||
* True, if the View should participate in calls to findViewAt.
|
||||
*/
|
||||
open val reactToMouseEvents: Boolean = true
|
||||
|
||||
/**
|
||||
* Bounds of the View with respect to its parent's coordinate system.
|
||||
*/
|
||||
val bounds: Rectangle2D.Float
|
||||
get() = Rectangle2D.Float(x, y, width, height)
|
||||
|
||||
open val children: List<View> = emptyList()
|
||||
|
||||
open fun layout() { }
|
||||
|
||||
open fun render(g2d: Graphics2D) { }
|
||||
}
|
||||
|
||||
fun layoutHierarchy(root: View) {
|
||||
root.layout()
|
||||
root.children.forEach { child -> layoutHierarchy(child) }
|
||||
}
|
||||
|
||||
fun renderHierarchy(root: View, g2d: Graphics2D) {
|
||||
g2d.translate(root.x.toDouble(), root.y.toDouble())
|
||||
root.render(g2d)
|
||||
root.children.forEach { child -> renderHierarchy(child, g2d) }
|
||||
g2d.translate(-root.x.toDouble(), -root.y.toDouble())
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the deepest nested view that contains point p. The point p should be in root's coordinate
|
||||
* system.
|
||||
* This assumes views at the same level of the view hierarchy do not overlap each other.
|
||||
*/
|
||||
fun findViewAt(root: View, p: Point2D): View? {
|
||||
val child = root.children.firstOrNull { v ->
|
||||
v.bounds.contains(p)
|
||||
} ?: return null
|
||||
p.setLocation(p.x - child.x, p.y - child.y)
|
||||
return findViewAt(child, p) ?: (if (child.reactToMouseEvents) child else null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from source's coordinates to destination's coordinates.
|
||||
* source view must be a descendant of destination view.
|
||||
*/
|
||||
fun convertPoint(source: View, point: Point2D, destination: View): Point2D? {
|
||||
if (source === destination) return point
|
||||
for (child in destination.children) {
|
||||
val result = convertPoint(source, Point2D.Float(point.x.toFloat() + child.x,
|
||||
point.y.toFloat() + child.y), child)
|
||||
if (result != null) return result
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui.preview
|
||||
|
||||
import com.curiouscreature.kotlin.math.Float2
|
||||
import com.curiouscreature.kotlin.math.Float3
|
||||
import com.curiouscreature.kotlin.math.Float4
|
||||
import com.curiouscreature.kotlin.math.length
|
||||
import com.curiouscreature.kotlin.math.normalize
|
||||
import com.curiouscreature.kotlin.math.rotation
|
||||
import com.curiouscreature.kotlin.math.translation
|
||||
import com.curiouscreature.kotlin.math.transpose
|
||||
import com.google.android.filament.Camera
|
||||
import kotlin.math.exp
|
||||
|
||||
private const val START_RADIUS = 3.0f
|
||||
|
||||
private fun rotateVector(rx: Float, ry: Float, v: Float3): Float3 {
|
||||
val matrix = rotation(Float3(rx, ry, 0.0f))
|
||||
return matrix.times(Float4(v)).xyz
|
||||
}
|
||||
|
||||
internal class CameraManipulator(private val camera: Camera) {
|
||||
|
||||
private var cameraTranslation = Float3(z = START_RADIUS)
|
||||
private var cameraRotation = Float3()
|
||||
private var centerOfInterest = -START_RADIUS
|
||||
|
||||
fun updateCameraTransform() {
|
||||
val view = translation(cameraTranslation) * rotation(cameraRotation)
|
||||
camera.setModelMatrix(transpose(view).toFloatArray())
|
||||
}
|
||||
|
||||
fun dolly(delta: Float, dollySpeed: Float) {
|
||||
val eye = cameraTranslation
|
||||
val v = rotateVector(cameraRotation.x, cameraRotation.y,
|
||||
Float3(0.0f, 0.0f, centerOfInterest))
|
||||
val view = eye + v
|
||||
|
||||
normalize(v)
|
||||
val dollyBy = (1.0f - exp(-dollySpeed * delta)) * centerOfInterest
|
||||
|
||||
val newEye = eye + (v * dollyBy)
|
||||
|
||||
cameraTranslation = newEye
|
||||
centerOfInterest = -length(newEye - view)
|
||||
}
|
||||
|
||||
fun rotate(delta: Float2, rotateSpeed: Float) {
|
||||
var rotX = cameraRotation.x
|
||||
var rotY = cameraRotation.y
|
||||
val eye = cameraTranslation
|
||||
|
||||
val view = eye + rotateVector(rotX, rotY, Float3(0.0f, 0.0f, centerOfInterest))
|
||||
|
||||
rotY += -delta.x * rotateSpeed
|
||||
rotX += -delta.y * rotateSpeed
|
||||
|
||||
cameraTranslation = view - rotateVector(rotX, rotY, Float3(0.0f, 0.0f, centerOfInterest))
|
||||
cameraRotation = Float3(rotX, rotY, cameraRotation.z)
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui.preview
|
||||
|
||||
import com.google.android.filament.Engine
|
||||
import com.google.android.filament.Skybox
|
||||
import com.google.android.filament.Texture
|
||||
import com.google.android.filament.tungsten.texture.TextureCache
|
||||
import com.google.android.filament.tungsten.texture.TextureUtils
|
||||
import java.io.InputStream
|
||||
import java.lang.RuntimeException
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.math.log2
|
||||
|
||||
private const val float = """([0-9-.]+)"""
|
||||
private val pattern = Regex("""\(\s*$float\s*,\s*$float\s*,\s*$float\s*\);""")
|
||||
|
||||
internal fun parseSphereHarmonics(harmonics: String): FloatArray {
|
||||
return harmonics.lines().fold(FloatArray(0)) { acc, line ->
|
||||
val match = pattern.find(line)
|
||||
val floats = match?.groups?.mapNotNull { group ->
|
||||
group?.value?.toFloatOrNull()
|
||||
}
|
||||
floats?.toFloatArray()?.let {
|
||||
acc + it
|
||||
} ?: acc
|
||||
}
|
||||
}
|
||||
|
||||
internal class Ibl(val engine: Engine, private val pathPrefix: String) {
|
||||
|
||||
val environmentMap: Texture
|
||||
val skyboxTexture: Texture
|
||||
val skybox: Skybox
|
||||
val irradiance: FloatArray
|
||||
|
||||
init {
|
||||
environmentMap = loadCubemapLevel(null, 0, "m0_")
|
||||
TextureCache.addTextureForRemoval(environmentMap)
|
||||
for (i in 1 until environmentMap.levels) {
|
||||
println("Loading level $i")
|
||||
loadCubemapLevel(environmentMap, i, "m${i}_")
|
||||
}
|
||||
|
||||
// Use non-prefixed images as skybox textures.
|
||||
skyboxTexture = loadCubemapLevel(null, 0, "")
|
||||
TextureCache.addTextureForRemoval(skyboxTexture)
|
||||
skybox = loadSkybox()
|
||||
|
||||
irradiance = loadSphereHarmonics()
|
||||
}
|
||||
|
||||
private fun loadSkybox(): Skybox {
|
||||
return Skybox.Builder()
|
||||
.environment(skyboxTexture)
|
||||
.showSun(true)
|
||||
.build(engine)
|
||||
}
|
||||
|
||||
private fun loadSphereHarmonics(): FloatArray {
|
||||
val path = "$pathPrefix/sh.txt"
|
||||
val stream: InputStream = javaClass.classLoader.getResourceAsStream(path)
|
||||
?: throw RuntimeException("Could not get stream for sphere harmonics at $path.")
|
||||
val contents = stream.bufferedReader().use { it.readText() }
|
||||
return parseSphereHarmonics(contents)
|
||||
}
|
||||
|
||||
private fun loadCubemapLevel(texture: Texture?, level: Int, facePrefix: String): Texture {
|
||||
require(texture != null || level == 0)
|
||||
|
||||
val cubemapFaces = listOf("px", "nx", "py", "ny", "pz", "nz")
|
||||
|
||||
val faceOffsets = IntArray(6)
|
||||
|
||||
val rawBuffers = cubemapFaces.map { face ->
|
||||
val path = "$pathPrefix/$facePrefix$face.rgb32f"
|
||||
val stream: InputStream = javaClass.classLoader.getResourceAsStream(path)
|
||||
?: throw RuntimeException("Could not get stream for cubemap face $path.")
|
||||
|
||||
val bufferAndInfo = TextureUtils.loadImageBufferFromStream(stream)
|
||||
?: throw RuntimeException("Could not load cubemap face $path.")
|
||||
|
||||
val (_, info) = bufferAndInfo
|
||||
if (info.width != info.height) {
|
||||
throw RuntimeException("Cubemap face $pathPrefix width != height")
|
||||
}
|
||||
|
||||
bufferAndInfo
|
||||
}
|
||||
|
||||
val firstFace = rawBuffers[0]
|
||||
val (_, firstFaceInfo) = firstFace
|
||||
val size = firstFaceInfo.width
|
||||
|
||||
// Assuming that size is a PO2
|
||||
val levels = if (facePrefix != "") {
|
||||
(log2(size.toFloat()) + 1).toInt()
|
||||
} else {
|
||||
1
|
||||
}
|
||||
|
||||
// Allocate a byte buffer large enough to hold all the faces
|
||||
val buffer = ByteBuffer.allocate(size * size * 4 * 6)
|
||||
for ((rawBuffer, _) in rawBuffers) {
|
||||
buffer.put(rawBuffer)
|
||||
}
|
||||
buffer.position(0)
|
||||
|
||||
val bufferDescriptor =
|
||||
Texture.PixelBufferDescriptor(buffer, Texture.Format.RGB, Texture.Type.UINT_10F_11F_11F_REV)
|
||||
|
||||
// If the texture hasn't been created yet, create it.
|
||||
val resultTexture = texture ?: Texture.Builder()
|
||||
.width(size)
|
||||
.height(size)
|
||||
.levels(levels)
|
||||
.format(Texture.InternalFormat.R11F_G11F_B10F)
|
||||
.sampler(Texture.Sampler.SAMPLER_CUBEMAP)
|
||||
.build(engine)
|
||||
|
||||
for (i in 0..5) {
|
||||
faceOffsets[i] = size * size * 4 * i
|
||||
}
|
||||
|
||||
resultTexture.setImage(engine, level, bufferDescriptor, faceOffsets)
|
||||
return resultTexture
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui.preview;
|
||||
|
||||
import com.google.android.filament.Engine;
|
||||
import com.google.android.filament.IndirectLight;
|
||||
import com.google.android.filament.Scene;
|
||||
|
||||
public final class LightHelpers {
|
||||
|
||||
private LightHelpers() { }
|
||||
|
||||
static IndirectLight addIndirectLight(Engine engine, Scene scene) {
|
||||
Ibl i = new Ibl(engine, "ibls/venetian_crossroads_2k");
|
||||
IndirectLight ibl =
|
||||
new IndirectLight.Builder()
|
||||
.irradiance(3, i.getIrradiance())
|
||||
.reflections(i.getEnvironmentMap())
|
||||
.intensity(30000.0f)
|
||||
.build(engine);
|
||||
scene.setIndirectLight(ibl);
|
||||
scene.setSkybox(i.getSkybox());
|
||||
return ibl;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui.preview;
|
||||
|
||||
import com.google.android.filament.Camera;
|
||||
|
||||
class PreviewCamera {
|
||||
|
||||
private static final double FOV = 65.0;
|
||||
private static final double NEAR_PLANE = 0.1;
|
||||
private static final double FAR_PLANE = 200.0;
|
||||
|
||||
private final Camera mCamera;
|
||||
|
||||
public PreviewCamera(Camera camera) {
|
||||
mCamera = camera;
|
||||
}
|
||||
|
||||
public void setProjection(int width, int height) {
|
||||
float displayRatio = (float) width / (float) height;
|
||||
Camera.Fov axis;
|
||||
if (width < height) {
|
||||
axis = Camera.Fov.VERTICAL;
|
||||
} else {
|
||||
axis = Camera.Fov.HORIZONTAL;
|
||||
}
|
||||
mCamera.setProjection(FOV, displayRatio, NEAR_PLANE, FAR_PLANE, axis);
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui.preview;
|
||||
|
||||
import com.google.android.filament.Box;
|
||||
import com.google.android.filament.Camera;
|
||||
import com.google.android.filament.Engine;
|
||||
import com.google.android.filament.EntityManager;
|
||||
import com.google.android.filament.FilamentPanel;
|
||||
import com.google.android.filament.IndexBuffer;
|
||||
import com.google.android.filament.IndirectLight;
|
||||
import com.google.android.filament.MaterialInstance;
|
||||
import com.google.android.filament.RenderableManager;
|
||||
import com.google.android.filament.Renderer;
|
||||
import com.google.android.filament.Scene;
|
||||
import com.google.android.filament.VertexBuffer;
|
||||
import com.google.android.filament.View;
|
||||
import com.google.android.filament.Viewport;
|
||||
import com.google.android.filament.filamesh.Filamesh;
|
||||
import com.google.android.filament.filamesh.FilameshLoader;
|
||||
import com.google.android.filament.tungsten.Filament;
|
||||
import com.google.android.filament.tungsten.MathUtils;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
public class PreviewMeshPanel extends JPanel {
|
||||
|
||||
// Filament resources
|
||||
private Renderer mRenderer;
|
||||
private Scene mScene;
|
||||
private Camera mCamera;
|
||||
private View mView;
|
||||
private FilamentPanel mFilamentPanel;
|
||||
private VertexBuffer mVertexBuffer;
|
||||
private IndexBuffer mIndexBuffer;
|
||||
private IndirectLight mIndirectLight;
|
||||
|
||||
private int mMeshRenderable;
|
||||
private int mMeshEntity;
|
||||
private int mMeshTransform;
|
||||
private int mSun;
|
||||
private int mPointLight;
|
||||
|
||||
private PreviewCamera mPreviewCamera;
|
||||
private Filament.Viewer mViewer;
|
||||
|
||||
public PreviewMeshPanel() {
|
||||
// On Windows D3D seems to mess with our OpenGLContext, this disable it.
|
||||
System.setProperty("sun.java2d.d3d", "false");
|
||||
|
||||
// Avoid flickering on X11 (Linux).
|
||||
System.setProperty("sun.awt.noerasebackground", "true");
|
||||
System.setProperty("sun.awt.erasebackgroundonresize", "false");
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
Filament.getInstance().runOnFilamentThread((Engine engine) -> {
|
||||
mScene = engine.createScene();
|
||||
mCamera = engine.createCamera(EntityManager.get().create());
|
||||
mView = engine.createView();
|
||||
mRenderer = engine.createRenderer();
|
||||
mPreviewCamera = new PreviewCamera(mCamera);
|
||||
|
||||
// Load the vertex and index buffers
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
InputStream previewMesh = classLoader.getResourceAsStream("sphere.mesh");
|
||||
Filamesh mesh = FilameshLoader.load("mesh", previewMesh, engine);
|
||||
mVertexBuffer = mesh.getVertexBuffer();
|
||||
mIndexBuffer = mesh.getIndexBuffer();
|
||||
|
||||
mCamera.setProjection(90.0, 1.3, 0.1, 200.0, Camera.Fov.HORIZONTAL);
|
||||
mCamera.lookAt(1.5f, 1.5f, 1.5f, 0, 0, 0, 0, 1, 0);
|
||||
|
||||
mIndirectLight = LightHelpers.addIndirectLight(engine, mScene);
|
||||
|
||||
loadMesh(engine, mScene, mVertexBuffer, mIndexBuffer);
|
||||
|
||||
int deviceScaleFactor = getDeviceScaleFactor();
|
||||
mView.setViewport(new Viewport(0, 0, 640 * deviceScaleFactor,
|
||||
480 * deviceScaleFactor));
|
||||
mView.setShadowsEnabled(false);
|
||||
mView.setCamera(mCamera);
|
||||
mView.setScene(mScene);
|
||||
mView.setClearColor(0f, 0f, 0f, 1.0f);
|
||||
|
||||
mFilamentPanel = new FilamentPanel();
|
||||
|
||||
mViewer = new TungstenViewer(mCamera, PreviewMeshPanel.this);
|
||||
assert mFilamentPanel != null;
|
||||
assert mView != null;
|
||||
assert mRenderer != null;
|
||||
mViewer.panel = mFilamentPanel;
|
||||
mViewer.view = mView;
|
||||
mViewer.renderer = mRenderer;
|
||||
|
||||
Filament.getInstance().addViewer(mViewer);
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
add(mFilamentPanel, BorderLayout.CENTER);
|
||||
revalidate();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void updateMaterial(MaterialInstance newMaterialInstance) {
|
||||
Filament.getInstance().runOnFilamentThread((Engine engine) -> {
|
||||
mMeshRenderable = engine.getRenderableManager().getInstance(mMeshEntity);
|
||||
engine.getRenderableManager().setMaterialInstanceAt(mMeshRenderable, 0,
|
||||
newMaterialInstance);
|
||||
});
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
Filament.getInstance().removeViewer(mViewer);
|
||||
Filament.getInstance().runOnFilamentThread((Engine engine) -> {
|
||||
engine.destroyRenderer(mRenderer);
|
||||
engine.destroyScene(mScene);
|
||||
engine.destroyCameraComponent(mCamera.getEntity());
|
||||
EntityManager.get().destroy(mCamera.getEntity());
|
||||
engine.destroyView(mView);
|
||||
mFilamentPanel.destroy(engine);
|
||||
engine.destroyVertexBuffer(mVertexBuffer);
|
||||
engine.destroyIndexBuffer(mIndexBuffer);
|
||||
engine.destroyIndirectLight(mIndirectLight);
|
||||
EntityManager.get().destroy(mMeshEntity);
|
||||
engine.getRenderableManager().destroy(mMeshEntity);
|
||||
engine.getTransformManager().destroy(mMeshTransform);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadMesh(Engine engine, Scene scene, VertexBuffer vb, IndexBuffer ib) {
|
||||
mMeshEntity = EntityManager.get().create();
|
||||
mMeshTransform = engine.getTransformManager().create(mMeshEntity, 0,
|
||||
MathUtils.createUniformScaleMatrix(3.0f));
|
||||
RenderableManager.Builder renderableBuilder = new RenderableManager.Builder(1);
|
||||
renderableBuilder
|
||||
.geometry(0, RenderableManager.PrimitiveType.TRIANGLES, vb, ib)
|
||||
.boundingBox(new Box(0, 0, 0, 1f, 1f, 1f))
|
||||
.build(engine, mMeshEntity);
|
||||
|
||||
scene.addEntity(mMeshEntity);
|
||||
}
|
||||
|
||||
private static int getDeviceScaleFactor() {
|
||||
final GraphicsDevice defaultScreenDevice = GraphicsEnvironment.getLocalGraphicsEnvironment()
|
||||
.getDefaultScreenDevice();
|
||||
int scaleFactor = 1;
|
||||
final Class<?> screenDeviceClass = defaultScreenDevice.getClass();
|
||||
if (screenDeviceClass.getCanonicalName().equals("sun.awt.CGraphicsDevice")) {
|
||||
try {
|
||||
final Method getScaleFactorMethod =
|
||||
screenDeviceClass.getDeclaredMethod("getScaleFactor");
|
||||
Object result = getScaleFactorMethod.invoke(defaultScreenDevice);
|
||||
return (int) result;
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
System.err.println("Unable to determine scale factor of screen, defaulting to 1");
|
||||
}
|
||||
}
|
||||
return scaleFactor;
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui.preview
|
||||
|
||||
import com.curiouscreature.kotlin.math.Float2
|
||||
import com.google.android.filament.Camera
|
||||
import com.google.android.filament.Viewport
|
||||
import com.google.android.filament.tungsten.Filament
|
||||
import java.awt.event.MouseEvent
|
||||
import java.awt.event.MouseListener
|
||||
import java.awt.event.MouseMotionListener
|
||||
|
||||
private const val SPEED_MULTIPLIER = 100.0f
|
||||
private const val DOLLY_MULTIPLIER = 5.0f
|
||||
|
||||
private fun MouseEvent.toFloat2() = Float2(this.x.toFloat(), this.y.toFloat())
|
||||
|
||||
internal class TungstenViewer(camera: Camera, val previewMeshPanel: PreviewMeshPanel)
|
||||
: Filament.Viewer() {
|
||||
|
||||
private val previewCamera = PreviewCamera(camera)
|
||||
private val manipulator = CameraManipulator(camera)
|
||||
|
||||
private var lastMouse: Float2? = null
|
||||
|
||||
init {
|
||||
previewMeshPanel.addMouseMotionListener(object : MouseMotionListener {
|
||||
override fun mouseMoved(e: MouseEvent?) { }
|
||||
|
||||
override fun mouseDragged(e: MouseEvent?) {
|
||||
e ?: return
|
||||
lastMouse?.let {
|
||||
val delta = e.toFloat2() - it
|
||||
val scaledDelta = delta / Float2(previewMeshPanel.width.toFloat(),
|
||||
previewMeshPanel.height.toFloat())
|
||||
manipulator.rotate(scaledDelta, SPEED_MULTIPLIER)
|
||||
}
|
||||
lastMouse = e.toFloat2()
|
||||
}
|
||||
})
|
||||
previewMeshPanel.addMouseListener(object : MouseListener {
|
||||
override fun mouseReleased(e: MouseEvent?) { }
|
||||
|
||||
override fun mouseEntered(e: MouseEvent?) { }
|
||||
|
||||
override fun mouseClicked(e: MouseEvent?) { }
|
||||
|
||||
override fun mouseExited(e: MouseEvent?) { }
|
||||
|
||||
override fun mousePressed(e: MouseEvent?) {
|
||||
e ?: return
|
||||
lastMouse = e.toFloat2()
|
||||
}
|
||||
})
|
||||
previewMeshPanel.addMouseWheelListener { e ->
|
||||
e?.let { mouseWheelEvent ->
|
||||
// Swing reports wheel events caused by a physical wheel (opposed to a trackpad)
|
||||
// with an inverted sign. Flip it so it feels more intuitive.
|
||||
val causedByWheel = mouseWheelEvent.wheelRotation != 0
|
||||
val multiplier = if (causedByWheel) 1 else -1
|
||||
manipulator.dolly(multiplier * mouseWheelEvent.preciseWheelRotation.toFloat() /
|
||||
previewMeshPanel.width, DOLLY_MULTIPLIER)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(deltaTimeMs: Long) {
|
||||
manipulator.updateCameraTransform()
|
||||
|
||||
val width = previewMeshPanel.width
|
||||
val height = previewMeshPanel.height
|
||||
view.setViewport(Viewport(0, 0, width, height))
|
||||
previewCamera.setProjection(width, height)
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.util;
|
||||
|
||||
public final class OperatingSystem {
|
||||
|
||||
private static final String OS_PROPERTY =
|
||||
System.getProperties().getProperty("os.name", "generic").toLowerCase();
|
||||
|
||||
private OperatingSystem() {
|
||||
|
||||
}
|
||||
|
||||
public static boolean isWindows() {
|
||||
return OS_PROPERTY.contains("win");
|
||||
}
|
||||
|
||||
public static boolean isMac() {
|
||||
return OS_PROPERTY.contains("mac") || OS_PROPERTY.contains("darwin");
|
||||
}
|
||||
|
||||
public static boolean isLinux() {
|
||||
return OS_PROPERTY.contains("nux");
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.compiler
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class ExpressionTest {
|
||||
|
||||
@Test
|
||||
fun `toString returns the symbol`() {
|
||||
assertEquals("float3(1.0, 2.0, 3.0)", Expression("float3(1.0, 2.0, 3.0)", 3).toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `swizzle to a lower dimension`() {
|
||||
assertEquals("exp.rg", "${Expression("exp", 4).rg}")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `swizzle to a higher dimension`() {
|
||||
assertEquals("float4(exp, 0.0, 0.0)", "${Expression("exp", 2).rgba}")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `swizzling a literal returns a new literal`() {
|
||||
assertEquals("float2(0.0, 0.0)", "${Literal(4).rg}")
|
||||
assertEquals("float3(0.0, 0.0, 0.0)", "${Literal(1).rgb}")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create a float literal of dimension 1`() {
|
||||
assertEquals("float(0.0)", "${Literal(1).r}")
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.compiler
|
||||
|
||||
import com.google.android.filament.tungsten.model.Graph
|
||||
import com.google.android.filament.tungsten.model.Node
|
||||
import com.google.android.filament.tungsten.model.createAddNode
|
||||
import com.google.android.filament.tungsten.model.createFloat3ConstantNode
|
||||
import com.google.android.filament.tungsten.model.createShaderNode
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Test
|
||||
|
||||
class GraphCompilerTest {
|
||||
|
||||
/**
|
||||
* For now, we just check that the graphs can compile without throwing any errors.
|
||||
* TODO: add more robust checks
|
||||
*/
|
||||
|
||||
@Test
|
||||
fun `Compile single node`() {
|
||||
val adderNode = createAddNode(0)
|
||||
val graph = Graph(nodes = listOf(adderNode), rootNodeId = adderNode.id)
|
||||
val compiler = GraphCompiler(graph)
|
||||
compiler.compileGraph()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Compile two nodes with connections`() {
|
||||
val constantNodeA = createFloat3ConstantNode(0)
|
||||
val constantNodeB = createFloat3ConstantNode(1)
|
||||
val adderNode = createAddNode(2)
|
||||
val shaderNode = createShaderNode(3)
|
||||
val graph = Graph(
|
||||
nodes = listOf(shaderNode, adderNode, constantNodeA, constantNodeB),
|
||||
rootNodeId = shaderNode.id,
|
||||
connections = listOf(
|
||||
adderNode.getOutputSlot("out") to shaderNode.getInputSlot("baseColor"),
|
||||
constantNodeA.getOutputSlot("result") to adderNode.getInputSlot("a"),
|
||||
constantNodeB.getOutputSlot("result") to adderNode.getInputSlot("b")
|
||||
)
|
||||
)
|
||||
|
||||
val compiler = GraphCompiler(graph)
|
||||
compiler.compileGraph()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple material parameters have unique names`() {
|
||||
val compiler = GraphCompiler(createMockGraph())
|
||||
val first = compiler.addParameter("float3", "parameter")
|
||||
val second = compiler.addParameter("float3", "parameter")
|
||||
assertNotEquals(first, second)
|
||||
}
|
||||
|
||||
private fun createMockGraph() = Graph(
|
||||
nodes = listOf(Node(id = 0, type = "node")),
|
||||
rootNodeId = 0)
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.model
|
||||
|
||||
import com.google.android.filament.tungsten.compiler.Literal
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class NodeTest {
|
||||
|
||||
@Test
|
||||
fun `resolveInputExpressions keeps two float4s the same length`() {
|
||||
val (a, b) = resolveInputExpressions(Literal(4), Literal(4))
|
||||
assertEquals(4, a.dimensions)
|
||||
assertEquals(4, b.dimensions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveInputExpressions keeps two floats the same length`() {
|
||||
val (a, b) = resolveInputExpressions(Literal(1), Literal(1))
|
||||
assertEquals(1, a.dimensions)
|
||||
assertEquals(1, b.dimensions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveInputExpressions trims both a float3 and a float2 to a float2`() {
|
||||
val (a, b) = resolveInputExpressions(Literal(3), Literal(2))
|
||||
assertEquals(2, a.dimensions)
|
||||
assertEquals(2, b.dimensions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveInputExpressions allows a float as input for scalar operations`() {
|
||||
val (a, b) = resolveInputExpressions(Literal(3), Literal(1))
|
||||
assertEquals(3, a.dimensions)
|
||||
assertEquals(1, b.dimensions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveInputExpressions defaults to float4 when a single input is not present`() {
|
||||
val (a, b) = resolveInputExpressions(null, Literal(1))
|
||||
assertEquals(4, a.dimensions)
|
||||
assertEquals(1, b.dimensions)
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.model.serialization
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class GraphFileTest {
|
||||
|
||||
@Test
|
||||
fun `Extract missing tool block`() {
|
||||
assertEquals("", GraphFile.extractToolBlockFromMaterialFile("""
|
||||
|material {
|
||||
| ...
|
||||
|}
|
||||
|
|
||||
|fragment {
|
||||
| ...
|
||||
|}
|
||||
""".trimMargin()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Extract empty tool block`() {
|
||||
assertEquals("", GraphFile.extractToolBlockFromMaterialFile("""
|
||||
|material {
|
||||
| ...
|
||||
|}
|
||||
|
|
||||
|fragment {
|
||||
| ...
|
||||
|}
|
||||
|
|
||||
|tool {}
|
||||
""".trimMargin()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Extract tool block with extra whitespace`() {
|
||||
assertEquals("\nfoobar\n", GraphFile.extractToolBlockFromMaterialFile("""
|
||||
|material {
|
||||
| ...
|
||||
|}
|
||||
|
|
||||
|fragment {
|
||||
| ...
|
||||
|}
|
||||
|
|
||||
| tool {
|
||||
|foobar
|
||||
|}
|
||||
""".trimMargin()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Extract tool block with JSON`() {
|
||||
assertEquals("\n {\"foo\":\"bar\",\"fizz\":[\"buzz\", 1]}\n",
|
||||
GraphFile.extractToolBlockFromMaterialFile("""
|
||||
|material {
|
||||
| ...
|
||||
|}
|
||||
|
|
||||
|fragment {
|
||||
| ...
|
||||
|}
|
||||
|
|
||||
|tool {
|
||||
| {"foo":"bar","fizz":["buzz", 1]}
|
||||
|}
|
||||
""".trimMargin()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Add tool block`() {
|
||||
assertEquals("""
|
||||
|content
|
||||
|
|
||||
|tool {
|
||||
|tool content
|
||||
|}
|
||||
""".trimMargin(), GraphFile.addToolBlockToMaterialFile("content", "tool content"))
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.model.serialization
|
||||
|
||||
import com.google.android.filament.tungsten.model.Float3
|
||||
import com.google.android.filament.tungsten.model.Graph
|
||||
import com.google.android.filament.tungsten.model.Node
|
||||
import com.google.android.filament.tungsten.model.NodeId
|
||||
import com.google.android.filament.tungsten.model.Property
|
||||
import com.google.android.filament.tungsten.model.copyPropertyWithValue
|
||||
import com.google.android.filament.tungsten.properties.ColorChooser
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* These test cases perform round-trip serialization. A Graph is serialized to a String, then
|
||||
* deserialized back to a Graph. The result is verified to equal the original.
|
||||
*/
|
||||
class GraphSerializerTest {
|
||||
|
||||
private val mockNodeFactory = object : INodeFactory {
|
||||
|
||||
override fun createNodeForTypeIdentifier(typeIdentifier: String, id: NodeId): Node? {
|
||||
return if (typeIdentifier == "node") createMockNode(id) else null
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Serialize empty model`() {
|
||||
val graph = Graph()
|
||||
val serialized = GraphSerializer.serialize(graph, emptyMap())
|
||||
val (deserialized, _) = GraphSerializer.deserialize(serialized, mockNodeFactory)
|
||||
assertEquals(graph, deserialized)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Serialize editor data`() {
|
||||
val serialized = GraphSerializer.serialize(Graph(), mapOf("foo" to "bar"))
|
||||
val (_, editorData) = GraphSerializer.deserialize(serialized, mockNodeFactory)
|
||||
assertEquals(mapOf("foo" to "bar"), editorData)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Serialize nodes and connections`() {
|
||||
val first = createMockNode(0)
|
||||
val second = first.copy(id = 1)
|
||||
val root = first.copy(id = 2)
|
||||
|
||||
val graph = Graph(
|
||||
nodes = listOf(first, second, root),
|
||||
rootNodeId = root.id,
|
||||
connections = listOf(
|
||||
first.getOutputSlot("output") to second.getInputSlot("input"),
|
||||
second.getOutputSlot("output") to root.getInputSlot("input")
|
||||
)
|
||||
)
|
||||
|
||||
val serialized = GraphSerializer.serialize(graph, emptyMap())
|
||||
val (deserialized, _) = GraphSerializer.deserialize(serialized, mockNodeFactory)
|
||||
|
||||
assertEquals(graph, deserialized)
|
||||
}
|
||||
|
||||
/**
|
||||
* If a node type identifier is not recognized, avoid adding the node or connection to the
|
||||
* graph.
|
||||
*/
|
||||
@Test
|
||||
fun `Serialize connection with unknown node type`() {
|
||||
val first = createMockNode(0)
|
||||
val root = createMockNode(1).copy(type = "unknown type")
|
||||
|
||||
val graph = Graph(
|
||||
nodes = listOf(first, root),
|
||||
rootNodeId = root.id,
|
||||
connections = listOf(first.getOutputSlot("output") to root.getInputSlot("input")))
|
||||
|
||||
val serialized = GraphSerializer.serialize(graph, emptyMap())
|
||||
val (deserialized, _) = GraphSerializer.deserialize(serialized, mockNodeFactory)
|
||||
|
||||
assertEquals(1, deserialized.nodes.size)
|
||||
assertEquals(graph.nodes[0], deserialized.nodes[0])
|
||||
assertEquals(0, deserialized.connections.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Serialize a property`() {
|
||||
val node = createMockNode(0).copy()
|
||||
|
||||
// Create a graph with a non-default property value
|
||||
val graph = Graph(
|
||||
nodes = listOf(node),
|
||||
rootNodeId = 0
|
||||
).graphByChangingProperty(node.getPropertyHandle("mockProperty"),
|
||||
copyPropertyWithValue(mockProperty, Float3(1.0f, 2.0f, 3.0f)))
|
||||
|
||||
val serialized = GraphSerializer.serialize(graph, emptyMap())
|
||||
val (deserialized, _) = GraphSerializer.deserialize(serialized, mockNodeFactory)
|
||||
|
||||
assertEquals(graph, deserialized)
|
||||
}
|
||||
|
||||
private val mockProperty = Property(
|
||||
name = "mockProperty",
|
||||
value = Float3(),
|
||||
editorFactory = ::ColorChooser
|
||||
)
|
||||
|
||||
private fun createMockNode(id: NodeId) = Node(
|
||||
id = id,
|
||||
type = "node",
|
||||
inputSlots = listOf("input"),
|
||||
outputSlots = listOf("output"),
|
||||
properties = listOf(mockProperty)
|
||||
)
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.ui.preview
|
||||
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Test
|
||||
|
||||
class IblTest {
|
||||
|
||||
@Test
|
||||
fun `parseSphereHarmonics parses single row`() {
|
||||
val result = parseSphereHarmonics(
|
||||
"(0.001, -0.003, -0.005); // L20, irradiance, pre-scaled base\n")
|
||||
val expected = floatArrayOf(0.001f, -0.003f, -0.005f)
|
||||
assertArrayEquals(expected, result, 0.0f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseSphereHarmonics parses multiple rows`() {
|
||||
val result = parseSphereHarmonics(
|
||||
"(-0.2, -0.24, -0.24); // L2-2, irradiance, pre-scaled base\n" +
|
||||
"( 0.05, 0.06, 0.06); // L2-1, irradiance, pre-scaled base\n")
|
||||
val expected = floatArrayOf(-0.2f, -0.24f, -0.24f, 0.05f, 0.06f, 0.06f)
|
||||
assertArrayEquals(expected, result, 0.0f)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
filament_tools_dir=../../../dist
|
||||
BIN
tools/tungsten/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
tools/tungsten/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
172
tools/tungsten/gradlew
vendored
172
tools/tungsten/gradlew
vendored
@@ -1,172 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
84
tools/tungsten/gradlew.bat
vendored
84
tools/tungsten/gradlew.bat
vendored
@@ -1,84 +0,0 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -1,73 +0,0 @@
|
||||
import java.nio.file.Paths
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://plugins.gradle.org/m2/'
|
||||
}
|
||||
maven {
|
||||
url 'http://dl.bintray.com/jetbrains/intellij-plugin-service'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'org.jetbrains.intellij' version '0.3.5'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
|
||||
// Bundle matc binary and Filament shared library
|
||||
def filament_path = file("../../../dist").absolutePath
|
||||
if (project.hasProperty("filament_dist_dir")) {
|
||||
filament_path = file("$filament_dist_dir").absolutePath
|
||||
}
|
||||
|
||||
// matc binary
|
||||
def matc = ['/bin/matc.exe', '/bin/matc']
|
||||
def matcFullPath = matc.collect { path -> Paths.get(filament_path, path) }
|
||||
|
||||
// Filament shared library
|
||||
def library = ["/lib/x86_64/filament-jni.dll", "/lib/x86_64/libfilament-jni.dylib",
|
||||
"/lib/x86_64/libfilament-jni.so"]
|
||||
def libraryFullPath = library.collect { path -> Paths.get(filament_path, path) }
|
||||
|
||||
// Ensure that at least one matc binary and Filament library is present
|
||||
if (!matcFullPath.any { path -> file(path).exists() }) {
|
||||
throw new StopActionException("No matc binary could be found in " + filament_path +
|
||||
"/bin. Ensure Filament has been built before building Tungsten.")
|
||||
}
|
||||
if (!libraryFullPath.any { path -> file(path).exists() }) {
|
||||
throw new StopActionException("No Filament shared library could be found in " + filament_path +
|
||||
"/lib/x86_64. Ensure Filament has been built before building Tungsten.")
|
||||
}
|
||||
|
||||
processResources {
|
||||
from( matcFullPath, libraryFullPath )
|
||||
}
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
}
|
||||
|
||||
intellij {
|
||||
version '2017.1'
|
||||
pluginName 'Tungsten'
|
||||
updateSinceUntilBuild false
|
||||
alternativeIdePath System.getenv('ANDROID_STUDIO')
|
||||
|
||||
dependencies {
|
||||
runtime project(':core')
|
||||
}
|
||||
}
|
||||
|
||||
group 'com.google'
|
||||
version '1.0.0'
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.plugin;
|
||||
|
||||
import com.google.android.filament.tungsten.ui.GuiComponentFactory;
|
||||
import com.intellij.ui.JBSplitter;
|
||||
import javax.swing.JComponent;
|
||||
|
||||
public class IntelliJComponentFactory implements GuiComponentFactory {
|
||||
|
||||
@Override
|
||||
public JComponent createThreeComponentSplitLayout(JComponent first, JComponent second,
|
||||
JComponent third) {
|
||||
JBSplitter verticalSplit = new JBSplitter(true);
|
||||
verticalSplit.setFirstComponent(second);
|
||||
verticalSplit.setSecondComponent(third);
|
||||
JBSplitter horizontalSplit = new JBSplitter(false);
|
||||
horizontalSplit.setFirstComponent(first);
|
||||
horizontalSplit.setSecondComponent(verticalSplit);
|
||||
return horizontalSplit;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.plugin
|
||||
|
||||
import com.google.android.filament.tungsten.ui.TungstenFile
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import java.io.IOException
|
||||
import java.util.function.BiConsumer
|
||||
import java.util.function.Consumer
|
||||
|
||||
class IntelliJFile(val file: VirtualFile) : TungstenFile {
|
||||
|
||||
override fun write(contents: String, callback: Consumer<Boolean>) {
|
||||
ApplicationManager.getApplication().runWriteAction {
|
||||
try {
|
||||
file.setBinaryContent(contents.toByteArray())
|
||||
callback.accept(true)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
callback.accept(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(result: BiConsumer<Boolean, String?>) {
|
||||
ApplicationManager.getApplication().runReadAction {
|
||||
try {
|
||||
val contents = String(file.contentsToByteArray())
|
||||
result.accept(true, contents)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
result.accept(false, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.plugin;
|
||||
|
||||
import com.google.android.filament.tungsten.MaterialManager;
|
||||
import com.google.android.filament.tungsten.ui.TungstenPanel;
|
||||
import com.intellij.codeHighlighting.BackgroundEditorHighlighter;
|
||||
import com.intellij.openapi.fileEditor.FileEditor;
|
||||
import com.intellij.openapi.fileEditor.FileEditorLocation;
|
||||
import com.intellij.openapi.fileEditor.FileEditorState;
|
||||
import com.intellij.openapi.util.UserDataHolderBase;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import javax.swing.JComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class MaterialFileEditor extends UserDataHolderBase implements FileEditor {
|
||||
|
||||
private final TungstenPanel mPanel;
|
||||
|
||||
public MaterialFileEditor(MaterialManager materialManager, VirtualFile file) {
|
||||
mPanel = new TungstenPanel(new IntelliJComponentFactory(), materialManager,
|
||||
new IntelliJFile(file));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return mPanel;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return mPanel;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Filament Material Editor";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setState(@NotNull FileEditorState state) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectNotify() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deselectNotify() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BackgroundEditorHighlighter getBackgroundHighlighter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public FileEditorLocation getCurrentLocation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
mPanel.destroy();
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.plugin;
|
||||
|
||||
import com.google.android.filament.tungsten.Filament;
|
||||
import com.google.android.filament.tungsten.MaterialManager;
|
||||
import com.google.android.filament.tungsten.util.OperatingSystem;
|
||||
import com.intellij.openapi.components.ApplicationComponent;
|
||||
import com.intellij.openapi.fileEditor.FileEditor;
|
||||
import com.intellij.openapi.fileEditor.FileEditorPolicy;
|
||||
import com.intellij.openapi.fileEditor.FileEditorProvider;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class MaterialFileProvider implements ApplicationComponent, FileEditorProvider {
|
||||
|
||||
private static final String COMPONENT_NAME = "MaterialFileProvider";
|
||||
private static final String EDITOR_TYPE_ID = "FilamentMaterial";
|
||||
|
||||
private String mMatcPath;
|
||||
private MaterialManager mMaterialManager;
|
||||
|
||||
@Override
|
||||
public void initComponent() {
|
||||
try {
|
||||
prepareFilament();
|
||||
Filament.getInstance().start();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disposeComponent() {
|
||||
Filament.getInstance().shutdown();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getComponentName() {
|
||||
return COMPONENT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
|
||||
String materialFileExtension = MaterialFileType.getInstance().getDefaultExtension();
|
||||
return materialFileExtension.equalsIgnoreCase(file.getExtension());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file) {
|
||||
return new MaterialFileEditor(mMaterialManager, file);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getEditorTypeId() {
|
||||
return EDITOR_TYPE_ID;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public FileEditorPolicy getPolicy() {
|
||||
return FileEditorPolicy.HIDE_DEFAULT_EDITOR;
|
||||
}
|
||||
|
||||
private void prepareFilament() throws IOException {
|
||||
ResourceLoader.loadDynamicLibrary("filament-jni");
|
||||
String matcFileName;
|
||||
if (OperatingSystem.isWindows()) {
|
||||
matcFileName = "matc.exe";
|
||||
} else {
|
||||
matcFileName = "matc";
|
||||
}
|
||||
File tempMatc = ResourceLoader.getResourceAsFile(matcFileName, "", true);
|
||||
mMatcPath = tempMatc.getAbsolutePath();
|
||||
mMaterialManager = new MaterialManager(mMatcPath);
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.plugin;
|
||||
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import javax.swing.Icon;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class MaterialFileType implements FileType {
|
||||
|
||||
private static final String EXTENSION = "mat";
|
||||
private static final String NAME = "Filament Material";
|
||||
private static final String DESCRIPTION = "A material for the Filament rendering engine";
|
||||
|
||||
private static MaterialFileType instance;
|
||||
|
||||
static MaterialFileType getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new MaterialFileType();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return DESCRIPTION;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDefaultExtension() {
|
||||
return EXTENSION;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinary() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getCharset(@NotNull VirtualFile file, @NotNull byte[] content) {
|
||||
return CharsetToolkit.UTF8;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.plugin;
|
||||
|
||||
import com.intellij.openapi.fileTypes.FileTypeConsumer;
|
||||
import com.intellij.openapi.fileTypes.FileTypeFactory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class MaterialFileTypeLoader extends FileTypeFactory {
|
||||
|
||||
@Override
|
||||
public void createFileTypes(@NotNull FileTypeConsumer consumer) {
|
||||
consumer.consume(MaterialFileType.getInstance(),
|
||||
MaterialFileType.getInstance().getDefaultExtension());
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import javax.annotation.Nonnull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Helpers to load resources packaged inside of a jar files.
|
||||
*/
|
||||
class ResourceLoader {
|
||||
|
||||
/**
|
||||
* Load a dynamic library from a resource residing on the classpath.
|
||||
* @param libName The portable library name, without any prefixes or extensions (i.e., library)
|
||||
* This will automatically be converted to a platform-specific name, like
|
||||
* liblibrary.dylib.
|
||||
*/
|
||||
static void loadDynamicLibrary(@Nonnull String libName) throws IOException {
|
||||
String filePath = "/" + System.mapLibraryName(libName);
|
||||
|
||||
InputStream input = ResourceLoader.class.getResourceAsStream(filePath);
|
||||
|
||||
if (input == null) {
|
||||
throw new FileNotFoundException("Could not find library " + filePath);
|
||||
}
|
||||
|
||||
File file = createTemporaryFile(libName, null);
|
||||
writeStreamToFile(input, file);
|
||||
System.load(file.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a File object from a resource residing on the classpath.
|
||||
*/
|
||||
@NotNull
|
||||
public static File getResourceAsFile(@Nonnull String name, @Nonnull String extension,
|
||||
boolean executable) throws IOException {
|
||||
String filePath = "/" + name + extension;
|
||||
|
||||
InputStream input = ResourceLoader.class.getResourceAsStream(filePath);
|
||||
|
||||
if (input == null) {
|
||||
throw new FileNotFoundException("Could not find resource file " + filePath);
|
||||
}
|
||||
|
||||
File file = createTemporaryFile(name, extension);
|
||||
if (!file.setExecutable(executable)) {
|
||||
throw new RuntimeException("Could not make file executable " + filePath);
|
||||
}
|
||||
writeStreamToFile(input, file);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static File createTemporaryFile(@Nonnull String name, @Nullable String extension)
|
||||
throws IOException {
|
||||
File file = File.createTempFile(name, extension);
|
||||
file.deleteOnExit();
|
||||
return file;
|
||||
}
|
||||
|
||||
private static void writeStreamToFile(@Nonnull InputStream input, @Nonnull File file)
|
||||
throws IOException {
|
||||
OutputStream out = new FileOutputStream(file);
|
||||
int read;
|
||||
byte[] bytes = new byte[4096];
|
||||
while ((read = input.read(bytes)) != -1) {
|
||||
out.write(bytes, 0, read);
|
||||
}
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<idea-plugin>
|
||||
<id>com.example.plugin</id>
|
||||
<name>Tungsten</name>
|
||||
<vendor email="filament@google.com" url="http://google.com">Google</vendor>
|
||||
|
||||
<description><![CDATA[
|
||||
Some description.
|
||||
]]></description>
|
||||
|
||||
<change-notes><![CDATA[
|
||||
Release notes here.
|
||||
]]>
|
||||
</change-notes>
|
||||
|
||||
<depends>com.intellij.modules.lang</depends>
|
||||
|
||||
<application-components>
|
||||
<component>
|
||||
<implementation-class>com.google.android.filament.tungsten.plugin.MaterialFileProvider</implementation-class>
|
||||
</component>
|
||||
</application-components>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<fileTypeFactory implementation="com.google.android.filament.tungsten.plugin.MaterialFileTypeLoader" />
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -1,5 +0,0 @@
|
||||
include 'core', 'standalone', 'plugin', 'kotlin-math'
|
||||
|
||||
project(':kotlin-math').projectDir = new File('third_party/kotlin-math')
|
||||
|
||||
rootProject.name = 'tungsten'
|
||||
@@ -1,28 +0,0 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
|
||||
}
|
||||
|
||||
mainClassName = 'com.google.android.filament.tungsten.Tungsten'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ['src']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run {
|
||||
systemProperty 'java.library.path', '../../../dist/lib/x86_64'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.51"
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten
|
||||
|
||||
import com.google.android.filament.tungsten.ui.TungstenFile
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.function.BiConsumer
|
||||
import java.util.function.Consumer
|
||||
|
||||
class StandaloneFile(private val filePath: String) : TungstenFile {
|
||||
|
||||
override fun write(contents: String, callback: Consumer<Boolean>) {
|
||||
try {
|
||||
File(filePath).writeText(contents)
|
||||
callback.accept(true)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
callback.accept(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(result: BiConsumer<Boolean, String?>) {
|
||||
try {
|
||||
result.accept(true, File(filePath).readText())
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
result.accept(false, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An InMemoryFile represents a file that has not been written to disk yet.
|
||||
* When the user opens the standalone version of Tungsten, they are editing this in-memory buffer.
|
||||
* When a file is first chosen to save to, the InMemoryFile can be "upgraded" to a StandaloneFile.
|
||||
*/
|
||||
class InMemoryFile(private var buffer: String = "") : TungstenFile {
|
||||
|
||||
override fun write(contents: String, callback: Consumer<Boolean>) {
|
||||
buffer = contents
|
||||
callback.accept(true)
|
||||
}
|
||||
|
||||
override fun read(result: BiConsumer<Boolean, String?>) {
|
||||
result.accept(true, buffer)
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten;
|
||||
|
||||
import com.google.android.filament.tungsten.ui.GuiComponentFactory;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JSplitPane;
|
||||
|
||||
public class SwingComponentFactory implements GuiComponentFactory {
|
||||
|
||||
@Override
|
||||
public JComponent createThreeComponentSplitLayout(JComponent first, JComponent second,
|
||||
JComponent third) {
|
||||
JSplitPane verticalSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, second, third);
|
||||
JSplitPane horizontalSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, first,
|
||||
verticalSplit);
|
||||
verticalSplit.setContinuousLayout(true);
|
||||
horizontalSplit.setContinuousLayout(true);
|
||||
return horizontalSplit;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.tungsten;
|
||||
|
||||
import com.google.android.filament.tungsten.ui.TungstenPanel;
|
||||
|
||||
import com.google.android.filament.tungsten.util.OperatingSystem;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Toolkit;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.WindowConstants;
|
||||
|
||||
public class Tungsten {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
String matcPath;
|
||||
if (OperatingSystem.isWindows()) {
|
||||
matcPath = "../../../dist/bin/matc.exe";
|
||||
} else {
|
||||
matcPath = "../../../dist/bin/matc";
|
||||
}
|
||||
MaterialManager materialManager = new MaterialManager(matcPath);
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.loadLibrary("filament-jni");
|
||||
Filament.getInstance().start();
|
||||
|
||||
TungstenPanel panel = createTungstenFrameAndPanel();
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
panel.destroy();
|
||||
Filament.getInstance().shutdown();
|
||||
}));
|
||||
}
|
||||
|
||||
private TungstenPanel createTungstenFrameAndPanel() {
|
||||
JFrame tungstenFrame = new JFrame();
|
||||
|
||||
TungstenPanel tungstenPanel = new TungstenPanel(new SwingComponentFactory(),
|
||||
materialManager, new InMemoryFile());
|
||||
tungstenFrame.setTitle("Tungsten");
|
||||
tungstenFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
tungstenFrame.add(tungstenPanel);
|
||||
|
||||
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
|
||||
tungstenFrame.setSize(screenSize);
|
||||
tungstenFrame.setLocationRelativeTo(null);
|
||||
|
||||
tungstenFrame.setVisible(true);
|
||||
|
||||
return tungstenPanel;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
.DS_Store
|
||||
.idea/shelf
|
||||
/dependencies
|
||||
/dist
|
||||
out/
|
||||
workspace.xml
|
||||
*.versionsBackup
|
||||
.gradle/
|
||||
build/
|
||||
!**/src/**/build
|
||||
!**/test/**/build
|
||||
*.iml
|
||||
.idea/libraries/
|
||||
.idea/modules
|
||||
.idea/modules.xml
|
||||
.idea/gradle.xml
|
||||
.idea/compiler.xml
|
||||
.idea/inspectionProfiles/profiles_settings.xml
|
||||
.idea/runConfigurations.xml
|
||||
.idea/.name
|
||||
local.properties
|
||||
*.ser
|
||||
201
tools/tungsten/third_party/kotlin-math/LICENSE
vendored
201
tools/tungsten/third_party/kotlin-math/LICENSE
vendored
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
176
tools/tungsten/third_party/kotlin-math/README.md
vendored
176
tools/tungsten/third_party/kotlin-math/README.md
vendored
@@ -1,176 +0,0 @@
|
||||
# kotlin-math
|
||||
Set of Kotlin APIs to make graphics math easier to write. These APIs are mostly
|
||||
modeled after GLSL (OpenGL Shading Language) to make porting code to and from
|
||||
shaders easier.
|
||||
|
||||
The various types offered by this library are only meant to be _value types_.
|
||||
Most APIs are therefore exposed as top-level functions and not as methods.
|
||||
For instance:
|
||||
|
||||
```kotlin
|
||||
val v = Float3(1.0f, 3.0f, 4.0f)
|
||||
val n = normalize(v)
|
||||
```
|
||||
|
||||
## Building the project
|
||||
|
||||
Simply run the following command to generate `build/libs/kotlin-math.jar`:
|
||||
|
||||
```bash
|
||||
$ ./gradlew assemble
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
Vector types:
|
||||
- Float2, vector of 2 floats
|
||||
- Float3, vector of 3 floats
|
||||
- Float4, vector of 4 floats
|
||||
- Bool2, vector of 2 booleans
|
||||
- Bool3, vector of 3 booleans
|
||||
- Bool4, vector of 4 booleans
|
||||
|
||||
Matrix types:
|
||||
- Mat3, 3x3 float matrix
|
||||
- Mat4, 4x4 float matrix
|
||||
|
||||
## Vector types
|
||||
|
||||
### Accessing components
|
||||
|
||||
Each vector type exposes its component as properties:
|
||||
|
||||
```kotlin
|
||||
val x = myVector.x
|
||||
val (x, y, z) = myVector
|
||||
```
|
||||
|
||||
A vector can also be treated as an array:
|
||||
|
||||
```kotlin
|
||||
val x = myVector[0]
|
||||
val x = myVector[VectorComponents.X]
|
||||
```
|
||||
|
||||
The traditional mathematical form with 1-based indexing can be used:
|
||||
|
||||
```kotlin
|
||||
val x = myVector(1)
|
||||
```
|
||||
|
||||
### Property aliases
|
||||
|
||||
To improve code readability, the vector types provide aliases for each property,
|
||||
allowing you to choose the most appropriate names:
|
||||
|
||||
```kotlin
|
||||
val (x, y, z) = myPosition
|
||||
val (r, g, b) = myColor
|
||||
val (s, t) = myTextureCoordinates
|
||||
```
|
||||
|
||||
### Swizzling
|
||||
|
||||
Vector types also provide different ways to swizzle their components, although
|
||||
in a more limited way than in GLSL. The most obvious use for swizzling is to
|
||||
extract sub-vectors:
|
||||
|
||||
```kotlin
|
||||
val position = Float3(…)
|
||||
val position2d = position.xy // extract a Float2
|
||||
|
||||
val colorWithAlpha = Float4(…)
|
||||
val rgbColor = colorWithAlpha.rgb // extract a Float3
|
||||
```
|
||||
|
||||
The get operators allows for more complex swizzling by enabling re-ordering and
|
||||
duplication of the components:
|
||||
|
||||
```kotlin
|
||||
val colorWithAlpha = Float4(…)
|
||||
val bgrColor = colorWithAlpha[
|
||||
VectorComponents.B,
|
||||
VectorComponents.G,
|
||||
VectorComponents.R
|
||||
] // re-ordered 3 components sub-vector
|
||||
```
|
||||
|
||||
### Comparing vector types
|
||||
|
||||
Vector comparisons follow GLSL rules:
|
||||
- `==` returns true if all components are equal
|
||||
- `!=` returns true if not all components are equal
|
||||
|
||||
In addition you can use component-wise relational operators that return a vector
|
||||
of boolean with the result of each component-wise comparison:
|
||||
- `lessThan`
|
||||
- `lessThanEqual`
|
||||
- `greaterThan`
|
||||
- `greaterThanEqual`
|
||||
- `equal`
|
||||
- `notEqual`
|
||||
|
||||
Example:
|
||||
|
||||
```kotlin
|
||||
if (all(lessThan(v1, v2))) {
|
||||
// …
|
||||
}
|
||||
```
|
||||
|
||||
You can also use the following infix operators if you prefer the operator
|
||||
syntax:
|
||||
- `lt`
|
||||
- `lte`
|
||||
- `gt`
|
||||
- `gte`
|
||||
- `eq`
|
||||
- `neq`
|
||||
|
||||
Example:
|
||||
|
||||
```kotlin
|
||||
if (any(v1 lte v2)) {
|
||||
// …
|
||||
}
|
||||
```
|
||||
|
||||
## Matrix types
|
||||
|
||||
Matrices are represented as a set of column vectors. For instance, a `Mat4` can
|
||||
be destructured into the right, up, forward and translation vectors:
|
||||
|
||||
```kotlin
|
||||
val (right, up, forward, translation) = myMat4
|
||||
```
|
||||
|
||||
Each vector can be accessed as a property or by its index:
|
||||
|
||||
```kotlin
|
||||
forward = myMat4.forward
|
||||
forward = myMat4.z
|
||||
forward = myMat4[2]
|
||||
forward = myMat4[MatrixColumns.Z]
|
||||
```
|
||||
|
||||
Matrix types also offer APIs to access each element individually by specifying
|
||||
the column then row:
|
||||
|
||||
```kotlin
|
||||
v = myMat4.z[1]
|
||||
v = myMat4[2, 1]
|
||||
v = myMat4[MatrixColumns.Z, 1]
|
||||
```
|
||||
|
||||
You can also use the invoke operator to access elements in row-major mode with
|
||||
1-based indices to follow the traditional mathematical notation:
|
||||
|
||||
```kotlin
|
||||
v = myMat4(2, 3) // equivalent to myMat4[2, 1]
|
||||
```
|
||||
|
||||
## Scalar APIs
|
||||
|
||||
The file `Scalar.kt` contains various helper methods to use common math operations
|
||||
with floats. It is intended to be used in combination with Kotlin 1.2's new float
|
||||
math methods.
|
||||
@@ -1,20 +0,0 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm" version "1.2.10"
|
||||
}
|
||||
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
|
||||
testImplementation 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.kotlin.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user