Hi.
I'm evaluating jpct to use it internally in a personal project. My principal requirement is being able to show a volume in 3D given a set of "slices" of the volume in the form of polygons separated by a regular interval.
How easy/difficult it is to do this with jpct?
Thank you very much.
Regards,
Germain.
I'm not sure if i get what you mean. Do you have a screen shot or a drawing to illustrate it?
Hi. Thank you for your response.
This image, which I found via google, shows the kind of "slices" I'm talking about:
http://www.mlahanas.de/MOEA/HDRMOGA/Image134.gif
Regards,
Germán.
To clarify, the picture has slices for two distinct volumes. I couldn't find a better picture to show what I mean.
Thank you.
Germán.
And you only want to visualize such a situation or you do want to create the slices out of some geometry on the fly?
I have the polygons corresponding to each slice. I want to visualize the slices like in the picture posted but also I'm looking for a way to generate an estimation of the original volume from which the slices were taken.
Thank you.
Like this: http://www.thinkartificial.org/about/brain-visualization/ (http://www.thinkartificial.org/about/brain-visualization/) ???
Well, I want two different but somewhat complementary things. The first thing I want to display are some slices much like in the brain example you posted, but simpler because I only want to show the outline of each slice (my slices are polygons).
The second thing is more complicated. In the brain example, I would like to generate some suface that estimates the "skin" of the head from which the slices were taken. I would like to show a solid volume estimating the head from which the slices were taken. Note that in my case the slices are polygons.
Thank you for the time spent on this. This is one of the moments when I wish I had taken some more english classes, I can't express myself as I would like...
Thank you.
I think i got more or less...yes, you can do this with jPCT, but you have to compute the hull of your slices by yourself. There's no magic method in jPCT that does this. I'm not sure about the slices themselves...that "they are polygons" means that they already are somekind of 3D mesh or that they are nothing but a vectorized outline? In the latter case, you'll need some kind of tessellator (not sure if this is the right word for it...) that creates a triangle mesh from your outline. Or do you just want to render the outline?
Ok. I was looking for some magic method to generate the hull and don't have to do it by myself. The slices are a vectorized outline, yes, that's what I was trying to say. I only want to render the outline.
Do you know of some free library that can help me to generate the hull?
Thanks
As long as the slices all have the same number of points that define the outline, it should be pretty easy to create the hull object. Do they?
No. The slices are defined by a user by hand and can have arbitrary points.
I see. I don't know of any library that does this albeit i'm pretty sure that something has to exist for this task. However, i always like to write my own stuff, so this is my idea on this:
- iterate over the slices to find the one with the most points
- tessellate all other slices's vectors so that you end up with an identical number of points for each slice (even if some or all fall onto one point in space)
- find a starting point on each slice. I would start with a random one of the first slice and take the closest one from the next slice and so on
- take slice n and n+1, start at the starting point and create triangles that connect the points and create the hull
- continue with n+1 and n+2...until the last slice has been processed
IMHO, this should give you a good enough hull. But maybe i'm missing something...it sounds just too easy... ;)
Thank you :). I'll keep this on mind, I can't tell if its too simple or not
Can you give me a list of the principal api elements I would need to study in order to try this? (ie, to represent and render the vectorized outlines and the hull).
Thanks again.
Render the outlines with jPCT might feel a bit strange, because there's no line drawing method or something. The basic primitive in jPCT is the triangle. Everything you want to render has to be composed out of triangles. What you basically have to do is to create an Object3D from each vector of your outline giving it a real "body", i.e a kind of tube or prism. I would take the outline and create an Object3D from each vector by hand using the addTriangle()-method.
Ok.
Thank you very much.
Regards,
Germán.
Maybe this helps to get you started...a simple "slices viewer" of random slices:
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.input.Mouse;
import com.threed.jpct.Camera;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.IRenderer;
import com.threed.jpct.Matrix;
import com.threed.jpct.Object3D;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.World;
import com.threed.jpct.util.Light;
public class HullCreator {
private FrameBuffer buffer = null;
private World world = null;
private List<List<SimpleVector>> slices = new ArrayList<List<SimpleVector>>();
private List<Object3D> sliceObjs = new ArrayList<Object3D>();
private float distance = 100f;
public static void main(String[] args) {
HullCreator hc = new HullCreator();
hc.doIt();
}
private void doIt() {
init();
createSlices(5);
createSliceObjects();
render();
}
private void render() {
world.buildAllObjects();
Camera cam = world.getCamera();
while (!org.lwjgl.opengl.Display.isCloseRequested()) {
buffer.clear();
world.renderScene(buffer);
world.draw(buffer);
buffer.update();
buffer.displayGLOnly();
// Camera movement
int x = Mouse.getDX();
int y = Mouse.getDY();
int w = Mouse.getDWheel();
if (Mouse.isButtonDown(0)) {
SimpleVector line = new SimpleVector(x, 0, y);
Matrix m = line.normalize().getRotationMatrix();
m.rotateAxis(m.getXAxis(), (float) -Math.PI / 2f);
cam.moveCamera(Camera.CAMERA_MOVEIN, distance);
cam.rotateAxis(m.invert3x3().getXAxis(), line.length() / 200f);
cam.moveCamera(Camera.CAMERA_MOVEOUT, distance);
}
if (w != 0) {
float d = w / 200f;
distance -= d;
cam.moveCamera(Camera.CAMERA_MOVEIN, d);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
}
private void init() {
buffer = new FrameBuffer(800, 600, FrameBuffer.SAMPLINGMODE_HARDWARE_ONLY);
buffer.disableRenderer(IRenderer.RENDERER_SOFTWARE);
buffer.enableRenderer(IRenderer.RENDERER_OPENGL);
world = new World();
world.getCamera().moveCamera(Camera.CAMERA_MOVEOUT, 100);
world.getCamera().moveCamera(Camera.CAMERA_MOVEUP, 25);
world.setAmbientLight(100, 100, 100);
Light light = new Light(world);
light.setIntensity(200, 200, 255);
}
/**
* Create objects that visualize the slices
*
* @param world
*/
private void createSliceObjects() {
for (List<SimpleVector> data : slices) {
Object3D slice = new Object3D(0);
for (int i = 0; i < data.size(); i++) {
Object3D part = new Object3D(6);
SimpleVector p0 = data.get(i);
SimpleVector p1 = data.get((i + 1) % data.size());
SimpleVector a = p1.calcSub(p0);
Matrix rot = a.getRotationMatrix();
SimpleVector xd = rot.getXAxis();
SimpleVector yd = rot.getYAxis();
xd.scalarMul(0.5f);
yd.scalarMul(0.5f);
SimpleVector p0u = new SimpleVector(p0);
p0u.add(yd);
yd.scalarMul(-1);
SimpleVector p0r = new SimpleVector(p0);
p0r.add(yd);
p0r.add(xd);
xd.scalarMul(-1);
SimpleVector p0l = new SimpleVector(p0);
p0l.add(yd);
p0l.add(xd);
yd.scalarMul(-1);
xd.scalarMul(-1);
SimpleVector p1u = new SimpleVector(p1);
p1u.add(yd);
yd.scalarMul(-1);
SimpleVector p1r = new SimpleVector(p1);
p1r.add(yd);
p1r.add(xd);
xd.scalarMul(-1);
SimpleVector p1l = new SimpleVector(p1);
p1l.add(yd);
p1l.add(xd);
part.addTriangle(p0u, p1u, p0l);
part.addTriangle(p0l, p1u, p1l);
part.addTriangle(p0u, p0r, p1u);
part.addTriangle(p1u, p0r, p1r);
part.addTriangle(p0r, p0l, p1l);
part.addTriangle(p1l, p1r, p0r);
part.scale(1.05f);
part.rotateMesh();
part.clearRotation();
slice = Object3D.mergeObjects(slice, part);
}
slice.setCulling(false);
world.addObject(slice);
sliceObjs.add(slice);
}
}
/**
* Creates random slices
*
* @param cnt
*/
private void createSlices(int cnt) {
float yStart = 0;
for (int i = 0; i < cnt; i++) {
List<SimpleVector> data = new ArrayList<SimpleVector>();
int pointCnt = (int) (Math.random() * 10f + 5f);
float delta = (float) (2d * Math.PI) / (pointCnt+1);
SimpleVector midPoint = new SimpleVector(0, yStart, 0);
SimpleVector orbit = new SimpleVector(0, 0, 10);
float rot = 0;
for (int p = 0; p < pointCnt; p++) {
float r = (float) Math.random() / 5f;
if (rot + r < Math.PI * 2f) {
orbit.rotateY(r);
rot += r;
}
if (rot + delta < Math.PI * 2f) {
orbit.rotateY(delta);
rot += delta;
}
SimpleVector to = new SimpleVector(orbit);
float s = to.length();
s = s + (float) Math.random() * 10f;
to = to.normalize();
to.scalarMul(s);
SimpleVector res = new SimpleVector(midPoint);
res.add(to);
data.add(res);
}
slices.add(data);
yStart -= 10;
}
}
}
Edit: Corrected creation of the random values a little bit...
Excelent! This is more than I expected, you saved me also having to ask about how to interact with the mouse :)
Thank you.
Regards,
Germán
And the same thing with hull creation...works reasonable for the test data. It implements the algorithm mentioned above. Apart from the hull (can be made invisible by pressing the right mouse button), i've changed object creation for the slices outlines, because i've used some scaling before to fill the gaps between the vertices, which isn't a good idea. I'm using slightly longer vertices now.
BTW: This isn't the usual kind of support that i offer, but the topic somehow got me interested...i wanted to see if my naive idea would actual work... ;D
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.input.Mouse;
import com.threed.jpct.Camera;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.IRenderer;
import com.threed.jpct.Matrix;
import com.threed.jpct.Object3D;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.World;
import com.threed.jpct.util.Light;
public class HullCreator {
private FrameBuffer buffer = null;
private World world = null;
private List<List<SimpleVector>> slices = new ArrayList<List<SimpleVector>>();
private List<List<SimpleVector>> enhancedSlices = new ArrayList<List<SimpleVector>>();
private List<Object3D> sliceObjs = new ArrayList<Object3D>();
private float distance = 100f;
private Object3D hull = null;
public static void main(String[] args) {
HullCreator hc = new HullCreator();
hc.doIt();
}
private void doIt() {
init();
createSlices(5);
enhanceSlices();
createSliceObjects();
createHull();
render();
}
/**
* Creates a hull based on the enhanced slices
*/
private void createHull() {
hull = new Object3D(0);
for (int i = 0; i < enhancedSlices.size() - 1; i++) {
Object3D part = new Object3D(enhancedSlices.get(i).size() * 2);
List<SimpleVector> points = enhancedSlices.get(i);
List<SimpleVector> nextPoints = enhancedSlices.get(i + 1);
SimpleVector p0 = points.get(0);
float minDist = -1;
int npStart = 0;
int cnt = 0;
for (SimpleVector pn : nextPoints) {
float dist = pn.distance(p0);
if (dist < minDist) {
npStart = cnt;
minDist = dist;
}
cnt++;
}
for (int p = 0; p < points.size(); p++) {
p0 = points.get(p);
SimpleVector p1 = points.get((p + 1) % points.size());
SimpleVector p2 = nextPoints.get((p + npStart) % points.size());
SimpleVector p3 = nextPoints.get((p + 1 + npStart) % points.size());
part.addTriangle(p0, p3, p2);
part.addTriangle(p0, p1, p3);
}
hull = Object3D.mergeObjects(hull, part);
}
hull.setCulling(false);
world.addObject(hull);
}
private void render() {
world.buildAllObjects();
Camera cam = world.getCamera();
while (!org.lwjgl.opengl.Display.isCloseRequested()) {
if (Mouse.isButtonDown(1)) {
hull.setVisibility(false);
} else {
hull.setVisibility(true);
}
buffer.clear();
world.renderScene(buffer);
world.draw(buffer);
buffer.update();
buffer.displayGLOnly();
// Camera movement
int x = Mouse.getDX();
int y = Mouse.getDY();
int w = Mouse.getDWheel();
if (Mouse.isButtonDown(0)) {
SimpleVector line = new SimpleVector(x, 0, y);
Matrix m = line.normalize().getRotationMatrix();
m.rotateAxis(m.getXAxis(), (float) -Math.PI / 2f);
cam.moveCamera(Camera.CAMERA_MOVEIN, distance);
cam.rotateAxis(m.invert3x3().getXAxis(), line.length() / 200f);
cam.moveCamera(Camera.CAMERA_MOVEOUT, distance);
}
if (w != 0) {
float d = w / 200f;
distance -= d;
cam.moveCamera(Camera.CAMERA_MOVEIN, d);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
}
private void init() {
buffer = new FrameBuffer(800, 600, FrameBuffer.SAMPLINGMODE_HARDWARE_ONLY);
buffer.disableRenderer(IRenderer.RENDERER_SOFTWARE);
buffer.enableRenderer(IRenderer.RENDERER_OPENGL);
world = new World();
world.getCamera().moveCamera(Camera.CAMERA_MOVEOUT, 100);
world.getCamera().moveCamera(Camera.CAMERA_MOVEUP, 25);
world.setAmbientLight(100, 100, 100);
Light light = new Light(world);
light.setIntensity(0, 0, 255);
light.setPosition(world.getCamera().getPosition());
light = new Light(world);
light.setIntensity(255, 0, 0);
light.setPosition(new SimpleVector(0,-40,100));
}
/**
* Creates additional points. Rough algorithm...just a brain fart of
* mine...maybe it's faulty, but it seems to work reasonable well
* for the generated test data.
*/
private void enhanceSlices() {
boolean added = false;
do {
added = false;
boolean firstRun = enhancedSlices.size() == 0;
List<List<SimpleVector>> ds = slices;
if (!firstRun) {
ds = enhancedSlices;
}
int maxPoints = 0;
List<Float> lengths = new ArrayList<Float>();
for (List<SimpleVector> data : ds) {
if (data.size() > maxPoints) {
maxPoints = data.size();
}
float len = 0;
for (int i = 0; i < data.size(); i++) {
SimpleVector p0 = data.get(i);
SimpleVector p1 = data.get((i + 1) % data.size());
SimpleVector a = p1.calcSub(p0);
len += a.length();
}
lengths.add(Float.valueOf(len));
}
int cnt = 0;
for (List<SimpleVector> data : slices) {
List<SimpleVector> newData = null;
if (firstRun) {
newData = new ArrayList<SimpleVector>(data);
} else {
newData = enhancedSlices.get(cnt);
}
if (newData.size() < maxPoints) {
int toAdd = maxPoints - data.size();
int div = toAdd + 1;
float delta = lengths.get(cnt) / div;
float len = 0;
for (int i = 0; i < newData.size(); i++) {
SimpleVector p0 = newData.get(i);
SimpleVector p1 = newData.get((i + 1) % newData.size());
SimpleVector a = p1.calcSub(p0);
float alen = a.length();
len += alen;
if (len > delta) {
len = alen / 2;
a.scalarMul(0.5f);
SimpleVector c = new SimpleVector(p0);
c.add(a);
newData.add((i + 1) % newData.size(), c);
added = true;
i++;
}
if (newData.size() == maxPoints) {
break;
}
}
}
if (firstRun) {
enhancedSlices.add(newData);
}
cnt++;
}
} while (added);
}
/**
* Create objects that visualize the slices
*
* @param world
*/
private void createSliceObjects() {
for (List<SimpleVector> data : slices) {
Object3D slice = new Object3D(0);
for (int i = 0; i < data.size(); i++) {
Object3D part = new Object3D(6);
SimpleVector p0 = data.get(i);
SimpleVector p1 = data.get((i + 1) % data.size());
SimpleVector a = p1.calcSub(p0);
Matrix rot = a.getRotationMatrix();
SimpleVector xd = rot.getXAxis();
SimpleVector yd = rot.getYAxis();
SimpleVector zd = rot.getZAxis();
zd=zd.normalize();
zd.scalarMul(0.2f);
xd.scalarMul(0.5f);
yd.scalarMul(0.5f);
SimpleVector p0u = new SimpleVector(p0);
p0u.add(yd);
yd.scalarMul(-1);
SimpleVector p0r = new SimpleVector(p0);
p0r.add(yd);
p0r.add(xd);
xd.scalarMul(-1);
SimpleVector p0l = new SimpleVector(p0);
p0l.add(yd);
p0l.add(xd);
yd.scalarMul(-1);
xd.scalarMul(-1);
SimpleVector p1u = new SimpleVector(p1);
p1u.add(yd);
yd.scalarMul(-1);
SimpleVector p1r = new SimpleVector(p1);
p1r.add(yd);
p1r.add(xd);
xd.scalarMul(-1);
SimpleVector p1l = new SimpleVector(p1);
p1l.add(yd);
p1l.add(xd);
p1u.add(zd);
p1r.add(zd);
p1l.add(zd);
part.addTriangle(p0u, p1u, p0l);
part.addTriangle(p0l, p1u, p1l);
part.addTriangle(p0u, p0r, p1u);
part.addTriangle(p1u, p0r, p1r);
part.addTriangle(p0r, p0l, p1l);
part.addTriangle(p1l, p1r, p0r);
part.rotateMesh();
part.clearRotation();
slice = Object3D.mergeObjects(slice, part);
}
slice.setCulling(false);
world.addObject(slice);
sliceObjs.add(slice);
}
}
/**
* Creates random slices
*
* @param cnt
*/
private void createSlices(int cnt) {
float yStart = 0;
for (int i = 0; i < cnt; i++) {
List<SimpleVector> data = new ArrayList<SimpleVector>();
int pointCnt = (int) (Math.random() * 10f + 5f);
float delta = (float) (2d * Math.PI) / (pointCnt + 1);
SimpleVector midPoint = new SimpleVector(0, yStart, 0);
SimpleVector orbit = new SimpleVector(0, 0, 10);
float rot = 0;
for (int p = 0; p < pointCnt; p++) {
float r = (float) Math.random() / 5f;
if (rot + r < Math.PI * 2f) {
orbit.rotateY(r);
rot += r;
}
if (rot + delta < Math.PI * 2f) {
orbit.rotateY(delta);
rot += delta;
}
SimpleVector to = new SimpleVector(orbit);
float s = to.length();
s = s + (float) Math.random() * 10f;
to = to.normalize();
to.scalarMul(s);
SimpleVector res = new SimpleVector(midPoint);
res.add(to);
data.add(res);
}
slices.add(data);
yStart -= 10;
}
}
}
Disclaimer: The whole thing is pretty hacky. Especially the creation of additional points is questionable. It uses a kind of multi-pass-threshold-subdivision-thing of algorithm that most likely won't make it into any book about graphics programming... ;D
QuoteBTW: This isn't the usual kind of support that i offer, but the topic somehow got me interested...i wanted to see if my naive idea would actual work... ;D
Maybe the mix between a "strange english" and bad terminology contributed :)
Well, I think I have a
very good start for what I want to do. Thanks a lot!