2 * Copyright (c) 2003-2009 jMonkeyEngine
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 package com.jmex.effects.water;
35 import java.nio.FloatBuffer;
36 import java.nio.IntBuffer;
38 import com.jme.math.FastMath;
39 import com.jme.math.Matrix4f;
40 import com.jme.math.Quaternion;
41 import com.jme.math.Vector2f;
42 import com.jme.math.Vector3f;
43 import com.jme.renderer.AbstractCamera;
44 import com.jme.renderer.Camera;
45 import com.jme.renderer.Renderer;
46 import com.jme.scene.TexCoords;
47 import com.jme.scene.TriMesh;
48 import com.jme.util.Timer;
49 import com.jme.util.geom.BufferUtils;
50 import com.jmex.effects.ProjectedTextureUtil;
53 * <code>ProjectedGrid</code>
56 * @author Rikard Herlitz (MrCoder)
58 public class ProjectedGrid extends TriMesh {
59 private static final long serialVersionUID = 1L;
65 private static Vector3f calcVec1 = new Vector3f();
66 private static Vector3f calcVec2 = new Vector3f();
67 private static Vector3f calcVec3 = new Vector3f();
69 private FloatBuffer vertBuf;
70 private FloatBuffer normBuf;
71 private FloatBuffer texs;
72 private IntBuffer indexBuffer;
74 private float viewPortWidth = 0;
75 private float viewPortHeight = 0;
76 private float viewPortLeft = 0;
77 private float viewPortBottom = 0;
79 private Quaternion origin = new Quaternion();
80 private Quaternion direction = new Quaternion();
81 private Vector2f source = new Vector2f();
83 private Matrix4f modelViewMatrix = new Matrix4f();
84 private Matrix4f projectionMatrix = new Matrix4f();
85 private Matrix4f modelViewProjectionInverse = new Matrix4f();
86 private Quaternion intersectBottomLeft = new Quaternion();
87 private Quaternion intersectTopLeft = new Quaternion();
88 private Quaternion intersectTopRight = new Quaternion();
89 private Quaternion intersectBottomRight = new Quaternion();
91 private Matrix4f modelViewMatrix1 = new Matrix4f();
92 private Matrix4f projectionMatrix1 = new Matrix4f();
93 private Matrix4f modelViewProjection1 = new Matrix4f();
94 private Matrix4f modelViewProjectionInverse1 = new Matrix4f();
95 private Quaternion intersectBottomLeft1 = new Quaternion();
96 private Quaternion intersectTopLeft1 = new Quaternion();
97 private Quaternion intersectTopRight1 = new Quaternion();
98 private Quaternion intersectBottomRight1 = new Quaternion();
100 private Vector3f camloc = new Vector3f();
101 private Vector3f camdir = new Vector3f();
102 private Quaternion pointFinal = new Quaternion();
103 private Quaternion pointTop = new Quaternion();
104 private Quaternion pointBottom = new Quaternion();
105 private Vector3f realPoint = new Vector3f();
107 public boolean freezeProjector = false;
108 public boolean useReal = false;
109 private Vector3f projectorLoc = new Vector3f();
112 private float fovY = 45.0f;
114 private HeightGenerator heightGenerator;
115 private float textureScale;
117 private float[] vertBufArray;
118 private float[] normBufArray;
119 private float[] texBufArray;
121 public ProjectedGrid( String name, Camera cam, int sizeX, int sizeY, float texureScale, HeightGenerator heightGenerator ) {
125 this.textureScale = texureScale;
126 this.heightGenerator = heightGenerator;
129 if (cam.getFrustumNear() > 0.0f) {
130 fovY = FastMath.atan(cam.getFrustumTop() / cam.getFrustumNear())
131 * 2.0f / FastMath.DEG_TO_RAD;
134 timer = Timer.getTimer();
136 setVertexCount( sizeX * sizeY );
138 vertBufArray = new float[getVertexCount()*3];
139 normBufArray = new float[getVertexCount()*3];
140 texBufArray = new float[getVertexCount()*2];
143 buildTextureCoordinates();
147 public void switchFreeze() {
148 freezeProjector = !freezeProjector;
151 public void draw( Renderer r ) {
156 public void update() {
157 if( freezeProjector ) return;
159 float time = timer.getTimeInSeconds();
161 camloc.set( cam.getLocation() );
162 camdir.set( cam.getDirection() );
164 AbstractCamera camera = (AbstractCamera) cam;
166 viewPortWidth = camera.getWidth();
167 viewPortHeight = camera.getHeight();
168 viewPortLeft = camera.getViewPortLeft();
169 viewPortBottom = camera.getViewPortBottom();
170 modelViewMatrix.set( camera.getModelViewMatrix() );
171 projectionMatrix.set( camera.getProjectionMatrix() );
172 modelViewProjectionInverse.set( modelViewMatrix ).multLocal( projectionMatrix );
173 modelViewProjectionInverse.invertLocal();
175 source.set( 0.5f, 0.5f );
176 getWorldIntersection( source, modelViewProjectionInverse, pointFinal );
177 pointFinal.multLocal( 1.0f / pointFinal.w );
178 realPoint.set( pointFinal.x, pointFinal.y, pointFinal.z );
179 projectorLoc.set( cam.getLocation() );
180 realPoint.set( projectorLoc ).addLocal( cam.getDirection() );
182 Matrix4f rangeMatrix = null;
184 Vector3f fakeLoc = new Vector3f( projectorLoc );
185 Vector3f fakePoint = new Vector3f( realPoint );
186 fakeLoc.addLocal( 0, 1000, 0 );
188 rangeMatrix = getMinMax( fakeLoc, fakePoint, cam );
191 ProjectedTextureUtil.matrixLookAt( projectorLoc, realPoint, Vector3f.UNIT_Y, modelViewMatrix );
192 ProjectedTextureUtil.matrixProjection( fovY + 10.0f, viewPortWidth / viewPortHeight, cam.getFrustumNear(), cam.getFrustumFar(), projectionMatrix );
193 modelViewProjectionInverse.set( modelViewMatrix ).multLocal( projectionMatrix );
194 modelViewProjectionInverse.invertLocal();
196 if( useReal && rangeMatrix != null ) {
197 rangeMatrix.multLocal( modelViewProjectionInverse );
198 modelViewProjectionInverse.set( rangeMatrix );
202 getWorldIntersection( source, modelViewProjectionInverse, intersectBottomLeft );
204 getWorldIntersection( source, modelViewProjectionInverse, intersectTopLeft );
206 getWorldIntersection( source, modelViewProjectionInverse, intersectTopRight );
208 getWorldIntersection( source, modelViewProjectionInverse, intersectBottomRight );
211 float du = 1.0f / (float) (sizeX - 1);
212 float dv = 1.0f / (float) (sizeY - 1);
215 for( int y = 0; y < sizeY; y++ ) {
216 for( int x = 0; x < sizeX; x++ ) {
217 interpolate( intersectTopLeft, intersectTopRight, u, pointTop );
218 interpolate( intersectBottomLeft, intersectBottomRight, u, pointBottom );
219 interpolate( pointTop, pointBottom, v, pointFinal );
220 pointFinal.x /= pointFinal.w;
221 pointFinal.z /= pointFinal.w;
222 realPoint.set( pointFinal.x,
223 heightGenerator.getHeight( pointFinal.x, pointFinal.z, time ),
226 vertBufArray[index++] = realPoint.x;
227 vertBufArray[index++] = realPoint.y;
228 vertBufArray[index++] = realPoint.z;
235 vertBuf.put( vertBufArray );
238 for( int i = 0; i < getVertexCount(); i++ ) {
239 texBufArray[i*2] = vertBufArray[i*3] * textureScale;
240 texBufArray[i*2+1] = vertBufArray[i*3+2] * textureScale;
242 texs.put( texBufArray );
245 oppositePoint.set( 0, 0, 0 );
246 adjacentPoint.set( 0, 0, 0 );
247 rootPoint.set( 0, 0, 0 );
248 tempNorm.set( 0, 0, 0 );
249 int adj = 0, opp = 0, normalIndex = 0;
250 for( int row = 0; row < sizeY; row++ ) {
251 for( int col = 0; col < sizeX; col++ ) {
252 if( row == sizeY - 1 ) {
253 if( col == sizeX - 1 ) { // last row, last col
255 adj = normalIndex - sizeX;
256 opp = normalIndex - 1;
258 else { // last row, except for last col
260 adj = normalIndex + 1;
261 opp = normalIndex - sizeX;
265 if( col == sizeX - 1 ) { // last column except for last row
267 adj = normalIndex - 1;
268 opp = normalIndex + sizeX;
272 adj = normalIndex + sizeX;
273 opp = normalIndex + 1;
276 rootPoint.set(vertBufArray[normalIndex*3],vertBufArray[normalIndex*3+1],vertBufArray[normalIndex*3+2]);
277 adjacentPoint.set(vertBufArray[adj*3],vertBufArray[adj*3+1],vertBufArray[adj*3+2]);
278 oppositePoint.set(vertBufArray[opp*3],vertBufArray[opp*3+1],vertBufArray[opp*3+2]);
279 tempNorm.set( adjacentPoint ).subtractLocal( rootPoint )
280 .crossLocal( oppositePoint.subtractLocal( rootPoint ) )
283 normBufArray[normalIndex*3] = tempNorm.x;
284 normBufArray[normalIndex*3+1] = tempNorm.y;
285 normBufArray[normalIndex*3+2] = tempNorm.z;
290 normBuf.put( normBufArray );
293 private Matrix4f getMinMax( Vector3f fakeLoc, Vector3f fakePoint, Camera cam ) {
294 Matrix4f rangeMatrix;
295 ProjectedTextureUtil.matrixLookAt( fakeLoc, fakePoint, Vector3f.UNIT_Y, modelViewMatrix1 );
296 ProjectedTextureUtil.matrixProjection( fovY, viewPortWidth / viewPortHeight, cam.getFrustumNear(), cam.getFrustumFar(), projectionMatrix1 );
297 modelViewProjection1.set( modelViewMatrix1 ).multLocal( projectionMatrix1 );
298 modelViewProjectionInverse1.set( modelViewProjection1 ).invertLocal();
301 getWorldIntersection( source, modelViewProjectionInverse, intersectBottomLeft1 );
303 getWorldIntersection( source, modelViewProjectionInverse, intersectTopLeft1 );
305 getWorldIntersection( source, modelViewProjectionInverse, intersectTopRight1 );
307 getWorldIntersection( source, modelViewProjectionInverse, intersectBottomRight1 );
309 Vector3f tmp = new Vector3f();
310 tmp.set( intersectBottomLeft.x, intersectBottomLeft.y, intersectBottomLeft.z );
311 modelViewProjection1.mult( tmp, tmp );
312 intersectBottomLeft.x = tmp.x;
313 intersectBottomLeft.y = tmp.y;
314 intersectBottomLeft.z = tmp.z;
316 tmp.set( intersectTopLeft1.x, intersectTopLeft1.y, intersectTopLeft1.z );
317 modelViewProjection1.mult( tmp, tmp );
318 intersectTopLeft1.x = tmp.x;
319 intersectTopLeft1.y = tmp.y;
320 intersectTopLeft1.z = tmp.z;
322 tmp.set( intersectTopRight1.x, intersectTopRight1.y, intersectTopRight1.z );
323 modelViewProjection1.mult( tmp, tmp );
324 intersectTopRight1.x = tmp.x;
325 intersectTopRight1.y = tmp.y;
326 intersectTopRight1.z = tmp.z;
328 tmp.set( intersectBottomRight1.x, intersectBottomRight1.y, intersectBottomRight1.z );
329 modelViewProjection1.mult( tmp, tmp );
330 intersectBottomRight1.x = tmp.x;
331 intersectBottomRight1.y = tmp.y;
332 intersectBottomRight1.z = tmp.z;
334 // modelViewProjection1.mult( intersectBottomLeft1, intersectBottomLeft1 );
335 // modelViewProjection1.mult( intersectTopLeft1, intersectTopLeft1 );
336 // modelViewProjection1.mult( intersectTopRight1, intersectTopRight1 );
337 // modelViewProjection1.mult( intersectBottomRight1, intersectBottomRight1 );
339 float minX = Float.MAX_VALUE, minY = Float.MAX_VALUE, maxX = Float.MIN_VALUE, maxY = Float.MIN_VALUE;
340 if( intersectBottomLeft1.x < minX ) minX = intersectBottomLeft1.x;
341 if( intersectTopLeft1.x < minX ) minX = intersectTopLeft1.x;
342 if( intersectTopRight1.x < minX ) minX = intersectTopRight1.x;
343 if( intersectBottomRight1.x < minX ) minX = intersectBottomRight1.x;
344 if( intersectBottomLeft1.x > maxX ) maxX = intersectBottomLeft1.x;
345 if( intersectTopLeft1.x > maxX ) maxX = intersectTopLeft1.x;
346 if( intersectTopRight1.x > maxX ) maxX = intersectTopRight1.x;
347 if( intersectBottomRight1.x > maxX ) maxX = intersectBottomRight1.x;
349 if( intersectBottomLeft1.y < minY ) minY = intersectBottomLeft1.y;
350 if( intersectTopLeft1.y < minY ) minY = intersectTopLeft1.y;
351 if( intersectTopRight1.y < minY ) minY = intersectTopRight1.y;
352 if( intersectBottomRight1.y < minY ) minY = intersectBottomRight1.y;
353 if( intersectBottomLeft1.y > maxY ) maxY = intersectBottomLeft1.y;
354 if( intersectTopLeft1.y > maxY ) maxY = intersectTopLeft1.y;
355 if( intersectTopRight1.y > maxY ) maxY = intersectTopRight1.y;
356 if( intersectBottomRight1.y > maxY ) maxY = intersectBottomRight1.y;
357 rangeMatrix = new Matrix4f(
358 maxX - minX, 0, 0, minX,
359 0, maxY - minY, 0, minY,
363 rangeMatrix.transpose();
367 private void interpolate( Quaternion beginVec, Quaternion finalVec, float changeAmnt, Quaternion resultVec ) {
368 resultVec.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x;
369 // resultVec.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y;
370 resultVec.z = (1 - changeAmnt) * beginVec.z + changeAmnt * finalVec.z;
371 resultVec.w = (1 - changeAmnt) * beginVec.w + changeAmnt * finalVec.w;
374 private void interpolate( Vector3f beginVec, Vector3f finalVec, float changeAmnt, Vector3f resultVec ) {
375 resultVec.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x;
376 resultVec.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y;
377 resultVec.z = (1 - changeAmnt) * beginVec.z + changeAmnt * finalVec.z;
380 private void getWorldIntersection( Vector2f screenPosition, Matrix4f viewProjectionMatrix, Quaternion store ) {
381 origin.set( screenPosition.x * 2 - 1, screenPosition.y * 2 - 1, -1, 1 );
382 direction.set( screenPosition.x * 2 - 1, screenPosition.y * 2 - 1, 1, 1 );
384 viewProjectionMatrix.mult( origin, origin );
385 viewProjectionMatrix.mult( direction, direction );
387 if( cam.getLocation().y > 0 ) {
388 if( direction.y > 0 ) {
393 if( direction.y < 0 ) {
398 direction.subtractLocal( origin );
400 float t = -origin.y / direction.y;
402 direction.multLocal( t );
404 store.addLocal( direction );
407 private float homogenousIntersect(Quaternion a, Quaternion xa, Quaternion xb) {
408 // float tx = -xb.w*(dotXYZ(a.xyz,xa.xyz)+xa.w*a.w);
409 // float tw = dotXYZ(a,xa.w*xb.xyz-xb.w*xa.xyz);
414 private float dotXYZ(Quaternion a, Quaternion b) {
415 return a.x * b.x + a.y * b.y + a.z * b.z;
418 private void mulXYZ(Quaternion a, Quaternion b) {
422 * <code>setDetailTexture</code> copies the texture coordinates from the
423 * first texture channel to another channel specified by unit, mulitplying
424 * by the factor specified by repeat so that the texture in that channel
425 * will be repeated that many times across the block.
428 * channel to copy coords to
430 * number of times to repeat the texture across and down the
433 public void setDetailTexture( int unit, float repeat) {
434 copyTextureCoordinates(0, unit, repeat);
439 * <code>getSurfaceNormal</code> returns the normal of an arbitrary point
440 * on the terrain. The normal is linearly interpreted from the normals of
441 * the 4 nearest defined points. If the point provided is not within the
442 * bounds of the height map, null is returned.
444 * @param position the vector representing the location to find a normal at.
445 * @param store the Vector3f object to store the result in. If null, a new one
447 * @return the normal vector at the provided location.
449 public Vector3f getSurfaceNormal( Vector2f position, Vector3f store ) {
450 return getSurfaceNormal( position.x, position.y, store );
454 * <code>getSurfaceNormal</code> returns the normal of an arbitrary point
455 * on the terrain. The normal is linearly interpreted from the normals of
456 * the 4 nearest defined points. If the point provided is not within the
457 * bounds of the height map, null is returned.
459 * @param position the vector representing the location to find a normal at. Only
460 * the x and z values are used.
461 * @param store the Vector3f object to store the result in. If null, a new one
463 * @return the normal vector at the provided location.
465 public Vector3f getSurfaceNormal( Vector3f position, Vector3f store ) {
466 return getSurfaceNormal( position.x, position.z, store );
470 * <code>getSurfaceNormal</code> returns the normal of an arbitrary point
471 * on the terrain. The normal is linearly interpreted from the normals of
472 * the 4 nearest defined points. If the point provided is not within the
473 * bounds of the height map, null is returned.
475 * @param x the x coordinate to check.
476 * @param z the z coordinate to check.
477 * @param store the Vector3f object to store the result in. If null, a new one
479 * @return the normal unit vector at the provided location.
481 public Vector3f getSurfaceNormal( float x, float z, Vector3f store ) {
484 float col = FastMath.floor( x );
485 float row = FastMath.floor( z );
487 if( col < 0 || row < 0 || col >= sizeX - 1 || row >= sizeY - 1 ) {
490 float intOnX = x - col, intOnZ = z - row;
492 if( store == null ) store = new Vector3f();
494 Vector3f topLeft = store, topRight = calcVec1, bottomLeft = calcVec2, bottomRight = calcVec3;
496 int focalSpot = (int) (col + row * sizeX);
498 // find the heightmap point closest to this position (but will always
499 // be to the left ( < x) and above (< z) of the spot.
500 BufferUtils.populateFromBuffer( topLeft, normBuf, focalSpot );
502 // now find the next point to the right of topLeft's position...
503 BufferUtils.populateFromBuffer( topRight, normBuf, focalSpot + 1 );
505 // now find the next point below topLeft's position...
506 BufferUtils.populateFromBuffer( bottomLeft, normBuf, focalSpot + sizeX );
508 // now find the next point below and to the right of topLeft's
510 BufferUtils.populateFromBuffer( bottomRight, normBuf, focalSpot + sizeX
513 // Use linear interpolation to find the height.
514 topLeft.interpolate( topRight, intOnX );
515 bottomLeft.interpolate( bottomRight, intOnX );
516 topLeft.interpolate( bottomLeft, intOnZ );
517 return topLeft.normalizeLocal();
521 * <code>buildVertices</code> sets up the vertex and index arrays of the
524 private void buildVertices() {
525 vertBuf = BufferUtils.createVector3Buffer( vertBuf, getVertexCount() );
526 setVertexBuffer( vertBuf );
528 Vector3f point = new Vector3f();
529 for( int x = 0; x < sizeX; x++ ) {
530 for( int y = 0; y < sizeY; y++ ) {
531 point.set( x, 0, y );
532 BufferUtils.setInBuffer( point, vertBuf, (x + (y * sizeX)) );
537 int triangleQuantity = ((sizeX - 1) * (sizeY - 1)) * 2;
538 setTriangleQuantity( triangleQuantity );
539 indexBuffer = BufferUtils.createIntBuffer( triangleQuantity * 3 );
540 setIndexBuffer( indexBuffer );
542 //go through entire array up to the second to last column.
543 for( int i = 0; i < (sizeX * (sizeY - 1)); i++ ) {
544 //we want to skip the top row.
545 if( i % ((sizeX * (i / sizeX + 1)) - 1) == 0 && i != 0 ) {
546 // logger.info("skip row: "+i+" cause: "+((sizeY * (i / sizeX + 1)) - 1));
550 // logger.info("i: "+i);
552 //set the top left corner.
553 indexBuffer.put( i );
554 //set the bottom right corner.
555 indexBuffer.put( (1 + sizeX) + i );
556 //set the top right corner.
557 indexBuffer.put( 1 + i );
558 //set the top left corner
559 indexBuffer.put( i );
560 //set the bottom left corner
561 indexBuffer.put( sizeX + i );
562 //set the bottom right corner
563 indexBuffer.put( (1 + sizeX) + i );
568 * <code>buildTextureCoordinates</code> calculates the texture coordinates
571 private void buildTextureCoordinates() {
572 texs = BufferUtils.createVector2Buffer( getVertexCount() );
573 setTextureCoords( new TexCoords(texs), 0 );
577 for( int i = 0; i < getVertexCount(); i++ ) {
578 texs.put( vertBuf.get() * textureScale );
579 vertBuf.get(); // ignore vert y coord.
580 texs.put( vertBuf.get() * textureScale );
585 * <code>buildNormals</code> calculates the normals of each vertex that
586 * makes up the block of terrain.
588 Vector3f oppositePoint = new Vector3f();
589 Vector3f adjacentPoint = new Vector3f();
590 Vector3f rootPoint = new Vector3f();
591 Vector3f tempNorm = new Vector3f();
593 private void buildNormals() {
594 normBuf = BufferUtils.createVector3Buffer( normBuf, getVertexCount() );
595 setNormalBuffer( normBuf );
597 oppositePoint.set( 0, 0, 0 );
598 adjacentPoint.set( 0, 0, 0 );
599 rootPoint.set( 0, 0, 0 );
600 tempNorm.set( 0, 0, 0 );
601 int adj = 0, opp = 0, normalIndex = 0;
602 for( int row = 0; row < sizeY; row++ ) {
603 for( int col = 0; col < sizeX; col++ ) {
604 BufferUtils.populateFromBuffer( rootPoint, vertBuf, normalIndex );
605 if( row == sizeY - 1 ) {
606 if( col == sizeX - 1 ) { // last row, last col
608 adj = normalIndex - sizeX;
609 opp = normalIndex - 1;
611 else { // last row, except for last col
613 adj = normalIndex + 1;
614 opp = normalIndex - sizeX;
618 if( col == sizeY - 1 ) { // last column except for last row
620 adj = normalIndex - 1;
621 opp = normalIndex + sizeX;
625 adj = normalIndex + sizeX;
626 opp = normalIndex + 1;
629 BufferUtils.populateFromBuffer( adjacentPoint, vertBuf, adj );
630 BufferUtils.populateFromBuffer( oppositePoint, vertBuf, opp );
631 tempNorm.set( adjacentPoint ).subtractLocal( rootPoint )
632 .crossLocal( oppositePoint.subtractLocal( rootPoint ) )
634 BufferUtils.setInBuffer( tempNorm, normBuf, normalIndex );