diff --git a/blender/plugin.py b/blender/plugin.py index 1f843498..1fb15867 100644 --- a/blender/plugin.py +++ b/blender/plugin.py @@ -14,57 +14,123 @@ col["seen_objs"] = {} camera = bpy.context.scene.camera - -def my_osci_render_func(scene): - engine_info = {"objects": []} - new_objs = [] - - for obj in bpy.data.objects: - if obj.type == 'MESH': - object_info = {"name": obj.name} - if obj.name not in col["seen_objs"]: - col["seen_objs"][obj.name] = 1 - new_objs.append(obj.name) - - mesh = bmesh.new() - mesh.from_mesh(obj.data) - - object_info["vertices"] = [] - # If there are bugs, the vertices here might not match up with the vert.index in edges/faces - for vert in mesh.verts: - object_info["vertices"].append({ - "x": vert.co[0], - "y": vert.co[1], - "z": vert.co[2], - }) - - object_info["edges"] = [vert.index for edge in mesh.edges for vert in edge.verts] - object_info["faces"] = [[vert.index for vert in face.verts] for face in mesh.faces] - - camera_space = camera.matrix_world.inverted() @ obj.matrix_world - object_info["matrix"] = [camera_space[i][j] for i in range(4) for j in range(4)] - print(camera_space) - - engine_info["objects"].append(object_info) +sock = None - engine_info["focalLength"] = -0.1 * bpy.data.cameras[0].lens +class OBJECT_PT_osci_render_settings(bpy.types.Panel): + bl_idname = "OBJECT_PT_osci_render_settings" + bl_label = "osci-render settings" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - sock.connect((HOST, PORT)) - sock.sendall(json.dumps(engine_info, separators=(',', ':')).encode('utf-8')) - sock.close() - except OSError as exc: - # Remove all newly added objects if no connection was made - # so that the object data will be sent on next attempt - for obj_name in new_objs: - col["seen_objs"].pop(obj_name) + def draw_header(self, context): + layout = self.layout + + def draw(self, context): + global sock + if sock is None: + self.layout.operator("render.osci_render_connect", text="Connect to osci-render") + else: + self.layout.operator("render.osci_render_close", text="Close osci-render connection") +class osci_render_connect(bpy.types.Operator): + bl_label = "Connect to osci-render" + bl_idname = "render.osci_render_connect" + bl_description = "Connect to osci-render" -bpy.app.handlers.frame_change_pre.clear() -bpy.app.handlers.frame_change_pre.append(my_osci_render_func) + def execute(self, context): + global sock + if sock is None: + try: + col["seen_objs"] = {} + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((HOST, PORT)) + send_scene_to_osci_render(bpy.context.scene) + except OSError as exp: + sock.close() + sock = None -bpy.app.handlers.depsgraph_update_post.clear() -bpy.app.handlers.depsgraph_update_post.append(my_osci_render_func) \ No newline at end of file + return {"FINISHED"} + + +class osci_render_close(bpy.types.Operator): + bl_label = "Close osci-render connection" + bl_idname="render.osci_render_close" + + def execute(self, context): + global sock + if sock is not None: + sock.send("CLOSE\n".encode('utf-8')) + sock.close() + sock = None + + return {"FINISHED"} + + +def send_scene_to_osci_render(scene): + global sock + if sock is not None: + engine_info = {"objects": []} + new_objs = [] + + for obj in bpy.data.objects: + if obj.type == 'MESH': + object_info = {"name": obj.name} + if obj.name not in col["seen_objs"]: + col["seen_objs"][obj.name] = 1 + new_objs.append(obj.name) + + mesh = bmesh.new() + mesh.from_mesh(obj.data) + + object_info["vertices"] = [] + # If there are bugs, the vertices here might not match up with the vert.index in edges/faces + for vert in mesh.verts: + object_info["vertices"].append({ + "x": vert.co[0], + "y": vert.co[1], + "z": vert.co[2], + }) + + object_info["edges"] = [vert.index for edge in mesh.edges for vert in edge.verts] + object_info["faces"] = [[vert.index for vert in face.verts] for face in mesh.faces] + + camera_space = camera.matrix_world.inverted() @ obj.matrix_world + object_info["matrix"] = [camera_space[i][j] for i in range(4) for j in range(4)] + + engine_info["objects"].append(object_info) + + + engine_info["focalLength"] = -0.1 * bpy.data.cameras[0].lens + + try: + json_str = json.dumps(engine_info, separators=(',', ':')) + '\n' + sock.sendall(json_str.encode('utf-8')) + except OSError as exc: + # Remove all newly added objects if no connection was made + # so that the object data will be sent on next attempt + for obj_name in new_objs: + col["seen_objs"].pop(obj_name) + + +operations = [OBJECT_PT_osci_render_settings, osci_render_connect, osci_render_close] + + +def register(): + bpy.app.handlers.frame_change_pre.append(send_scene_to_osci_render) + bpy.app.handlers.depsgraph_update_post.append(send_scene_to_osci_render) + for operation in operations: + bpy.utils.register_class(operation) + + +def unregister(): + bpy.app.handlers.frame_change_pre.clear() + bpy.app.handlers.depsgraph_update_post.clear() + for operation in operations.reverse(): + bpy.utils.unregister_class(operation) + + +if __name__ == "__main__": + register() diff --git a/src/main/java/sh/ball/engine/CameraDrawKernel.java b/src/main/java/sh/ball/engine/CameraDrawKernel.java index 868981da..1fc71ea8 100644 --- a/src/main/java/sh/ball/engine/CameraDrawKernel.java +++ b/src/main/java/sh/ball/engine/CameraDrawKernel.java @@ -9,12 +9,12 @@ import sh.ball.shapes.Vector2; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.List; public class CameraDrawKernel extends Kernel { private WorldObject prevObject = null; + private List prevObjects = null; private float[] vertices; private float[] vertexResult; private float[] triangles; @@ -57,33 +57,38 @@ public class CameraDrawKernel extends Kernel { public List draw(ObjectSet objects, float focalLength) { this.focalLength = focalLength; usingObjectSet = 1; - List>> vertices = objects.objects.stream().map(WorldObject::getVertexPath).toList(); - this.vertexNums = vertices.stream().mapToInt( - l -> l.stream() - .map(List::size) - .reduce(0, Integer::sum) + l.size() - ).toArray(); - int numVertices = Arrays.stream(vertexNums).sum(); - this.vertices = new float[numVertices * 3]; - this.vertexResult = new float[numVertices * 2]; - this.matrices = new float[vertices.size() * 16]; + List objectList = objects.objects.stream().toList(); + if (!objectList.equals(prevObjects)) { + prevObjects = objectList; + List>> vertices = objectList.stream().map(WorldObject::getVertexPath).toList(); + this.vertexNums = vertices.stream().mapToInt( + l -> l.stream() + .map(List::size) + .reduce(0, Integer::sum) + l.size() + ).toArray(); + int numVertices = Arrays.stream(vertexNums).sum(); + this.vertices = new float[numVertices * 3]; + this.vertexResult = new float[numVertices * 2]; + this.matrices = new float[vertices.size() * 16]; + List triangles = objectList.stream().map(WorldObject::getTriangles).toList(); + int numTriangles = triangles.stream().map(arr -> arr.length).reduce(0, Integer::sum); + this.triangles = new float[numTriangles]; + int offset = 0; + for (float[] triangleArray : triangles) { + System.arraycopy(triangleArray, 0, this.triangles, offset, triangleArray.length); + offset += triangleArray.length; + } + int count = 0; + for (List> vertexList : vertices) { + count = initialiseVertices(count, vertexList); + } + } + int offset = 0; for (float[] matrix : objects.cameraSpaceMatrices) { System.arraycopy(matrix, 0, this.matrices, offset, matrix.length); offset += matrix.length; } - List triangles = objects.objects.stream().map(WorldObject::getTriangles).toList(); - int numTriangles = triangles.stream().map(arr -> arr.length).reduce(0, Integer::sum); - this.triangles = new float[numTriangles]; - offset = 0; - for (float[] triangleArray : triangles) { - System.arraycopy(triangleArray, 0, this.triangles, offset, triangleArray.length); - offset += triangleArray.length; - } - int count = 0; - for (List> vertexList : vertices) { - count = initialiseVertices(count, vertexList); - } this.cameraPosX = 0; this.cameraPosY = 0; diff --git a/src/main/java/sh/ball/engine/ObjectServer.java b/src/main/java/sh/ball/engine/ObjectServer.java index b1c37f1d..bd890e67 100644 --- a/src/main/java/sh/ball/engine/ObjectServer.java +++ b/src/main/java/sh/ball/engine/ObjectServer.java @@ -16,6 +16,13 @@ public class ObjectServer implements Runnable { private final Gson gson = new Gson(); private final Map objects = new HashMap<>(); private final ObjectSet objectSet = new ObjectSet(); + private final Runnable enableRendering; + private final Runnable disableRendering; + + public ObjectServer(Runnable enableRendering, Runnable disableRendering) { + this.enableRendering = enableRendering; + this.disableRendering = disableRendering; + } @Override public void run() { @@ -24,21 +31,28 @@ public class ObjectServer implements Runnable { while (true) { Socket socket = Server.accept(); + enableRendering.run(); BufferedReader clientReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); - String json = clientReader.readLine(); - EngineInfo info = gson.fromJson(json, EngineInfo.class); - - for (ObjectInfo obj : info.objects) { - if (!objects.containsKey(obj.name)) { - objects.put(obj.name, new WorldObject(obj.vertices, obj.edges, obj.faces)); + while (socket.isConnected()) { + String json = clientReader.readLine(); + if (json.equals("CLOSE")) { + socket.close(); + break; } + EngineInfo info = gson.fromJson(json, EngineInfo.class); + + for (ObjectInfo obj : info.objects) { + if (!objects.containsKey(obj.name)) { + objects.put(obj.name, new WorldObject(obj.vertices, obj.edges, obj.faces)); + } + } + + Set currentObjects = Arrays.stream(info.objects).map(obj -> obj.name).collect(Collectors.toSet()); + objects.entrySet().removeIf(obj -> !currentObjects.contains(obj.getKey())); + + objectSet.setObjects(objects.values(), Arrays.stream(info.objects).map(obj -> obj.matrix).toList(), info.focalLength); } - - Set currentObjects = Arrays.stream(info.objects).map(obj -> obj.name).collect(Collectors.toSet()); - objects.entrySet().removeIf(obj -> !currentObjects.contains(obj.getKey())); - socket.close(); - - objectSet.setObjects(objects.values(), Arrays.stream(info.objects).map(obj -> obj.matrix).toList(), info.focalLength); + disableRendering.run(); } } catch (IOException e) { e.printStackTrace(); diff --git a/src/main/java/sh/ball/gui/controller/MainController.java b/src/main/java/sh/ball/gui/controller/MainController.java index 6329289c..a17f1e16 100644 --- a/src/main/java/sh/ball/gui/controller/MainController.java +++ b/src/main/java/sh/ball/gui/controller/MainController.java @@ -49,6 +49,7 @@ import sh.ball.audio.engine.JavaAudioInput; import sh.ball.audio.midi.MidiListener; import sh.ball.audio.midi.MidiNote; import sh.ball.engine.ObjectServer; +import sh.ball.engine.ObjectSet; import sh.ball.gui.Gui; import sh.ball.parser.obj.ObjFrameSettings; import sh.ball.parser.obj.ObjParser; @@ -98,6 +99,7 @@ public class MainController implements Initializable, FrequencyListener, MidiLis private List>> frameSources = new ArrayList<>(); private FrameProducer> producer; private int currentFrameSource; + private boolean objectServerRendering = false; // javafx private final FileChooser osciFileChooser = new FileChooser(); @@ -454,13 +456,37 @@ public class MainController implements Initializable, FrequencyListener, MidiLis } } - server = new ObjectServer(); - openFiles.add(null); - frameSources.add(server.getObjectSet()); - frameSourcePaths.add("BLENDER"); + server = new ObjectServer(this::enableObjectServerRendering, this::disableObjectServerRendering); new Thread(server).start(); } + private void enableObjectServerRendering() { + Platform.runLater(() -> { + objectServerRendering = true; + ObjectSet set = server.getObjectSet(); + frameSources.forEach(FrameSource::disable); + set.enable(); + + producer = new FrameProducer<>(audioPlayer, set); + objController.setAudioProducer(producer); + + executor.submit(producer); + effectsController.restartEffects(); + + generalController.setFrameSourceName("Rendering from external input"); + generalController.updateFrameLabels(); + objTitledPane.setDisable(true); + }); + } + + private void disableObjectServerRendering() { + Platform.runLater(() -> { + server.getObjectSet().disable(); + objectServerRendering = false; + changeFrameSource(currentFrameSource); + }); + } + // used when a file is chosen so that the same folder is reopened when a // file chooser opens private void updateLastVisitedDirectory(File file) { @@ -577,7 +603,7 @@ public class MainController implements Initializable, FrequencyListener, MidiLis // increments and changes the frameSource after pressing 'j' public void nextFrameSource() { - if (frameSources.size() == 1) { + if (objectServerRendering || frameSources.size() == 1) { return; } int index = currentFrameSource + 1; @@ -589,7 +615,7 @@ public class MainController implements Initializable, FrequencyListener, MidiLis // decrements and changes the frameSource after pressing 'k' public void previousFrameSource() { - if (frameSources.size() == 1) { + if (objectServerRendering || frameSources.size() == 1) { return; } int index = currentFrameSource - 1;