1 /*
   2  * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.sg.prism;
  27 
  28 import javafx.application.ConditionalFeature;
  29 import javafx.application.Platform;
  30 import javafx.scene.shape.CullFace;
  31 import javafx.scene.shape.DrawMode;
  32 import com.sun.javafx.geom.Vec3d;
  33 import com.sun.javafx.geom.transform.Affine3D;
  34 import com.sun.prism.Graphics;
  35 import com.sun.prism.Material;
  36 import com.sun.prism.MeshView;
  37 import com.sun.prism.ResourceFactory;
  38 
  39 /**
  40  * TODO: 3D - Need documentation
  41  */
  42 public abstract class NGShape3D extends NGNode {
  43     private NGPhongMaterial material;
  44     private DrawMode drawMode;
  45     private CullFace cullFace;
  46     private boolean materialDirty = false;
  47     private boolean drawModeDirty = false;
  48     NGTriangleMesh mesh;
  49     private MeshView meshView;
  50 
  51     public void setMaterial(NGPhongMaterial material) {
  52         this.material = material;
  53         materialDirty = true;
  54         visualsChanged();
  55     }
  56     public void setDrawMode(Object drawMode) {
  57         this.drawMode = (DrawMode) drawMode;
  58         drawModeDirty = true;
  59         visualsChanged();
  60     }
  61 
  62     public void setCullFace(Object cullFace) {
  63         this.cullFace = (CullFace) cullFace;
  64         visualsChanged();
  65     }
  66 
  67     void invalidate() {
  68         meshView = null;
  69         visualsChanged();
  70     }
  71 
  72     private void renderMeshView(Graphics g) {
  73 
  74         //validate state
  75         g.setup3DRendering();
  76 
  77         ResourceFactory rf = g.getResourceFactory();
  78 
  79         if (meshView == null && mesh != null) {
  80             meshView = rf.createMeshView(mesh.createMesh(rf));
  81             materialDirty = drawModeDirty = true;
  82         }
  83 
  84         if (meshView == null || !mesh.validate()) {
  85             return;
  86         }
  87 
  88         Material mtl =  material.createMaterial(rf);
  89         if (materialDirty) {
  90             meshView.setMaterial(mtl);
  91             materialDirty = false;
  92         }
  93 
  94         // NOTE: Always check determinant in case of mirror transform.
  95         int cullingMode = cullFace.ordinal();
  96         if (cullFace.ordinal() != MeshView.CULL_NONE
  97                 && g.getTransformNoClone().getDeterminant() < 0) {
  98             cullingMode = cullingMode == MeshView.CULL_BACK
  99                     ? MeshView.CULL_FRONT : MeshView.CULL_BACK;
 100         }
 101         meshView.setCullingMode(cullingMode);
 102 
 103         if (drawModeDirty) {
 104             meshView.setWireframe(drawMode == DrawMode.LINE);
 105             drawModeDirty = false;
 106         }
 107 
 108         // Setup lights
 109         int pointLightIdx = 0;
 110         if (g.getLights() == null || g.getLights()[0] == null) {
 111             // If no lights are in scene apply default light. Default light
 112             // is a single point white point light at camera eye position.
 113             meshView.setAmbientLight(0.0f, 0.0f, 0.0f);
 114             Vec3d cameraPos = g.getCameraNoClone().getPositionInWorld(null);
 115             meshView.setPointLight(pointLightIdx++,
 116                                    (float)cameraPos.x,
 117                                    (float)cameraPos.y,
 118                                    (float)cameraPos.z,
 119                                    1.0f, 1.0f, 1.0f, 1.0f);
 120         } else {
 121             float ambientRed = 0.0f;
 122             float ambientBlue = 0.0f;
 123             float ambientGreen = 0.0f;
 124 
 125             for (int i = 0; i < g.getLights().length; i++) {
 126                 NGLightBase lightBase = g.getLights()[i];
 127                 if (lightBase == null) {
 128                     // The array of lights can have nulls
 129                     break;
 130                 } else if (lightBase.affects(this)) {
 131                     float rL = lightBase.getColor().getRed();
 132                     float gL = lightBase.getColor().getGreen();
 133                     float bL = lightBase.getColor().getBlue();
 134                     /* TODO: 3D
 135                      * There is a limit on the number of lights that can affect
 136                      * a 3D shape. (Currently we simply select the first 3)
 137                      * Thus it is important to select the most relevant lights.
 138                      * 
 139                      * One such way would be to sort lights according to
 140                      * intensity, which becomes especially relevant when lights
 141                      * are attenuated. Only the most intense set of lights
 142                      * would be set.
 143                      * The approximate intesity a light will have on a given
 144                      * shape, could be defined by:
 145                      */
 146 //                    // Where d is distance from point light
 147 //                    float attenuationFactor = 1/(c + cL * d + cQ * d * d);
 148 //                    float intensity = rL * 0.299f + gL * 0.587f + bL * 0.114f;
 149 //                    intensity *= attenuationFactor;
 150                     if (lightBase instanceof NGPointLight) {
 151                         NGPointLight light = (NGPointLight)lightBase;
 152                         if (rL != 0.0f || gL != 0.0f || bL != 0.0f) {
 153                             Affine3D lightWT = light.getWorldTransform();
 154                             meshView.setPointLight(pointLightIdx++,
 155                                     (float)lightWT.getMxt(),
 156                                     (float)lightWT.getMyt(),
 157                                     (float)lightWT.getMzt(),
 158                                     rL, gL, bL, 1.0f);
 159                         }
 160                     } else if (lightBase instanceof NGAmbientLight) {
 161                         // Accumulate ambient lights
 162                         ambientRed   += rL;
 163                         ambientGreen += gL;
 164                         ambientBlue  += bL;
 165                     }
 166                 }
 167             }
 168             ambientRed = saturate(ambientRed);
 169             ambientGreen = saturate(ambientGreen);
 170             ambientBlue = saturate(ambientBlue);
 171             meshView.setAmbientLight(ambientRed, ambientGreen, ambientBlue);
 172         }
 173         // TODO: 3D Required for D3D implementation of lights, which is limited to 3
 174         while (pointLightIdx < 3) {
 175                 // Reset any previously set lights
 176                 meshView.setPointLight(pointLightIdx++, 0, 0, 0, 0, 0, 0, 0);
 177         }
 178 
 179         meshView.render(g);
 180     }
 181 
 182     // Clamp between [0, 1]
 183     private static float saturate(float value) {
 184         return value < 1.0f ? ((value < 0.0f) ? 0.0f : value) : 1.0f;
 185     }
 186 
 187     public void setMesh(NGTriangleMesh triangleMesh) {
 188         this.mesh = triangleMesh;
 189         meshView = null;
 190         visualsChanged();
 191     }
 192 
 193     @Override
 194     protected void renderContent(Graphics g) {
 195         if (!Platform.isSupported(ConditionalFeature.SCENE3D) ||
 196              material == null ||
 197              g instanceof com.sun.prism.PrinterGraphics)
 198         {
 199             return;
 200         }
 201         renderMeshView(g);
 202     }
 203 
 204     // This node requires 3D graphics state for rendering
 205     @Override
 206     boolean isShape3D() {
 207         return true;
 208     }
 209 
 210     @Override
 211     protected boolean hasOverlappingContents() {
 212         return false;
 213     }
 214 
 215     @Override
 216     public void release() {
 217         // TODO: 3D - Need to release native resources
 218         // material, mesh and meshview have native backing that need clean up.
 219     }
 220 }