www.jpct.net

jPCT - a 3d engine for Java => Support => Topic started by: gerferra on August 23, 2010, 03:19:37 pm

Title: Create custom volume from slices
Post by: gerferra on August 23, 2010, 03:19:37 pm
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.
Title: Re: Create custom volume from slices
Post by: EgonOlsen on August 23, 2010, 05:13:39 pm
I'm not sure if i get what you mean. Do you have a screen shot or a drawing to illustrate it?
Title: Re: Create custom volume from slices
Post by: gerferra on August 23, 2010, 05:46:51 pm
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.
Title: Re: Create custom volume from slices
Post by: gerferra on August 23, 2010, 05:49:51 pm
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.
Title: Re: Create custom volume from slices
Post by: EgonOlsen on August 23, 2010, 10:07:20 pm
And you only want to visualize such a situation or you do want to create the slices out of some geometry on the fly?
Title: Re: Create custom volume from slices
Post by: gerferra on August 23, 2010, 10:19:43 pm
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.

Title: Re: Create custom volume from slices
Post by: EgonOlsen on August 23, 2010, 10:32:21 pm
Like this: http://www.thinkartificial.org/about/brain-visualization/ (http://www.thinkartificial.org/about/brain-visualization/)    ???
Title: Re: Create custom volume from slices
Post by: gerferra on August 23, 2010, 11:05:31 pm

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.
Title: Re: Create custom volume from slices
Post by: EgonOlsen on August 23, 2010, 11:15:25 pm
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?
Title: Re: Create custom volume from slices
Post by: gerferra on August 23, 2010, 11:25:23 pm
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
Title: Re: Create custom volume from slices
Post by: EgonOlsen on August 23, 2010, 11:46:52 pm
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?
Title: Re: Create custom volume from slices
Post by: gerferra on August 24, 2010, 12:29:54 am
No. The slices are defined by a user by hand and can have arbitrary points.
Title: Re: Create custom volume from slices
Post by: EgonOlsen on August 24, 2010, 04:51:30 pm
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:


IMHO, this should give you a good enough hull. But maybe i'm missing something...it sounds just too easy... ;)
Title: Re: Create custom volume from slices
Post by: gerferra on August 24, 2010, 05:33:12 pm
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.
Title: Re: Create custom volume from slices
Post by: EgonOlsen on August 24, 2010, 06:22:25 pm
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.
Title: Re: Create custom volume from slices
Post by: gerferra on August 24, 2010, 06:28:58 pm
Ok.

Thank you very much.

Regards,
Germán.
Title: Re: Create custom volume from slices
Post by: EgonOlsen on August 24, 2010, 09:17:46 pm
Maybe this helps to get you started...a simple "slices viewer" of random slices:

Code: [Select]
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...
Title: Re: Create custom volume from slices
Post by: gerferra on August 24, 2010, 09:58:31 pm
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
Title: Re: Create custom volume from slices
Post by: EgonOlsen on August 24, 2010, 10:23:01 pm
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

Code: [Select]
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;
}
}

}

Title: Re: Create custom volume from slices
Post by: EgonOlsen on August 24, 2010, 10:28:00 pm
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
Title: Re: Create custom volume from slices
Post by: gerferra on August 24, 2010, 11:35:12 pm
Quote
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
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!